持续原创输出,点击上方蓝字关注我

目录

  • 前言
  • 什么是AOP?
  • AOP的相关概念(面试常客)
  • Spring Boot 如何整合AOP自定义一个注解?
  • 使用拦截器如何自定义注解?
  • 内部调用导致AOP注解失效
  • 总结

前言

注解相信大家都用过,尤其是Spring Boot 这个框架,比如@Controller

这篇文章就来介绍下Spring Boot 中如何自定义一个注解,顺带介绍一下Spring BootAOP如何整合。

什么是AOP?

AOP即是面向切面,是Spring的核心功能之一,主要的目的即是针对业务处理过程中的横向拓展,以达到低耦合的效果。

举个栗子,项目中有记录操作日志的需求、或者流程变更是记录变更履历,无非就是插表操作,很简单的一个save操作,都是一些记录日志或者其他辅助性的代码。一遍又一遍的重写和调用。不仅浪费了时间,又将项目变得更加的冗余,实在得不偿失。

此时AOP的就该出场了,能够在不改变原逻辑的基础上实现相关功能。

AOP的相关概念(面试常客)

要理解Spring Boot整合Aop的实现,就必须先对面向切面实现的一些Aop的概念有所了解,不然也是云里雾里。

「切面(Aspect)」:一个关注点的模块化。以注解@Aspect的形式放在类上方,声明一个切面。

「连接点(Joinpoint)」:在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候都可以是连接点。

「通知(Advice)」:通知增强,需要完成的工作叫做通知,就是你写的业务逻辑中需要比如事务、日志等先定义好,然后需要的地方再去用。增强包括如下五个方面:

  1. @Before:在切点之前执行
  2. @After:在切点方法之后执行
  3. @AfterReturning:切点方法返回后执行
  4. @AfterThrowing:切点方法抛异常执行
  5. @Around:属于环绕增强,能控制切点执行前,执行后,用这个注解后,程序抛异常,会影响@AfterThrowing这个注解。

「切点(Pointcut)」:其实就是筛选出的连接点,匹配连接点的断言,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点做为切点。

「引入(Introduction)」:在不改变一个现有类代码的情况下,为该类添加属性和方法,可以在无需修改现有类的前提下,让它们具有新的行为和状态。其实就是把切面(也就是新方法属性:通知定义的)用到目标类中去。

「目标对象(Target Object)」:被一个或者多个切面所通知的对象。也被称做被通知(adviced)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

「AOP代理(AOP Proxy)」AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

「织入(Weaving)」:把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

Spring Boot 如何整合AOP自定义一个注解?

在实际开发中对于横向公共的逻辑需要抽取出来,这时候就需要使用AOP,比如日志的记录、权限的验证等等,这些功能都可以用注解轻松的完成。

下面介绍如何在Spring Boot使用AOP定义一个注解。

添加依赖starter

AOP整合Spring Boot有一个starter,只需要添加依赖即可,如下:

<!--springboot集成Aop-->
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
  </dependency>

开启AOP

在配置类上标注@EnableAspectJAutoProxy注解即可开启AOP,这个注解有什么用呢,源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {}

最重要的是如下一行代码:

@Import(AspectJAutoProxyRegistrar.class)

@Import这个注解很熟悉了吧,快速注入一个类,这里是注入一个AnnotationAwareAspectJAutoProxyCreator

自定义一个注解

就以日志处理为例子,定义一个日志处理的注解,如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
    String value() default "";
}

定义一个切面

一个切面的满足条件如下:

  1. 类上标注了@Aspect注解
  2. 注入到IOC容器中,比如@Component注解

定义的日志切面如下:

@Component
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SysLogAspect {
}

@Order指定了切面执行的优先级,假如有多个切面,肯定是要有先后的执行顺序,这样才能保证逻辑性。

定义切点表达式

这里需要拦截的肯定是@SysLog这个注解,只要方法上标注了该注解都将会被拦截,表达式如下:

@Pointcut("@annotation(com.example.annotation_demo.annotation.SysLog)")
public void pointCut() {}

添加通知方法

既然是日志记录,肯定是在方法执行前,执行后都需要记录,因此需要定义一个环绕通知,如下:

  @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //逻辑开始时间
        long beginTime = System.currentTimeMillis();

        //执行方法
        Object result = point.proceed();

        //todo,保存日志,自己完善
        saveLog(point,beginTime);

        return result;
    }

测试

以上配置完成后即可使用,只需要在需要的方法上标注@SysLog注解即可,如下:

@SysLog
@PostMapping("/add")
public String add(){
  return "";
}

使用拦截器如何自定义注解?

使用AOP自定义的注解在每个方法上都会被拦截验证,首先效率上就不高。

然而拦截器是在每个Controller方法执行之前进行拦截,其他的方法都不会生效,比如service方法。

比如权限的验证、防止瞬间重复点击等等需求就适合使用拦截器自定义的注解。

自定义一个注解

就以防止瞬间重复点击的例子来创建一个注解,如下:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    /**
     * 默认失效时间5秒
     */
    long seconds() default 5;
}

自定义拦截器

需要在请求执行之前完成验证,逻辑很简单,就是判断方法上有没有标注@RepeatSubmit注解,代码如下:

/**
 * description:重复提交注解的拦截器
 */
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod){
            //只拦截标注了@RepeatSubmit该注解
            HandlerMethod handlerMethod=(HandlerMethod)handler;
            //获取controller方法上标注的注解
            RepeatSubmit repeatSubmit = AnnotationUtils.findAnnotation(handlerMethod.getMethod(),RepeatSubmit.class);
            //没有限制重复提交,直接跳过
            if (Objects.isNull(repeatSubmit))
                return true;
            //todo 一个值,标志这个请求的唯一性,比如IP+userId+uri+请求参数
            String flag="";
            //存在即返回false,不存在即返回true
            Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(flag, "", repeatSubmit.seconds(), TimeUnit.SECONDS);
            if (ifAbsent!=null&&!ifAbsent)
                //todo: 此处抛出异常,需要在全局异常解析器中捕获
                throw new RepeatSubmitException();
        }
        return true;
    }
}

注入的拦截器

将上述自定义的拦截器注入到Sprign Boot中,这里不再演示了,前面教程有介绍过,请看:Spring Boot 第六弹,拦截器如何配置,看这儿~

测试

在需要拦截方法上添加@RepeatSubmit注解即可,如下:

    @RepeatSubmit
    @GetMapping("/add")
    public String add(){
        return "";
    }

内部调用导致AOP注解失效

这个问题在事务中也是经常被忽略的问题,网上很多人说是AOPBug,其实在我看来这真不是一个BUG,并且也是有办法解决的。

先来看一下失效的案例,如下:

public class ArticleServiceImpl{
  @SysLog
  public void A(){
    ......
  }
  
  
  public void B(){
    this.A();
  }
}

在上述的代码中,如果执行方法B,则@SysLog注解将会失效。

失效的原因

AOP使用的是动态代理的机制,它会给类生成一个代理类,事务的相关操作都在代理类上完成。内部方式使用this调用方式时,使用的是实例调用,并没有通过代理类调用方法,所以会导致事务失效。

解决方法

其实解决方法有很多,下面将会一一介绍。

1. 引入自身的Bean

在类内部通过@Autowired将本身bean引入,然后通过调用自身bean,从而实现使用AOP代理操作。代码如下:

public class ArticleServiceImpl{
  /**
  * 注入自身的Bean
  */
  @Autowired
  private ArticleService articleService;
  
  @SysLog
  public void A(){
    ......
  }
  
  public void B(){
    articleService.A();
  }
}

2. 通过ApplicationContext引入bean

通过ApplicationContext获取bean,通过bean调用内部方法,就使用了bean的代理类。

需要先创建一个ApplicationContext的工具类获取ApplicationContext,然后才能调用getBean()方法,代码如下:

public class ArticleServiceImpl{
  
  @SysLog
  public void A(){
    ......
  }
  
  public void B(){
    ApplicationContextUtils.getApplicationContext().getBean(ArticleService.class).A();
  }
}

3. 通过AopContext获取当前类的代理类

此种方法需要设置@EnableAspectJAutoProxy中的exposeProxytrue

使用AopContext获取当前的代理对象,代码如下:

public class ArticleServiceImpl{
  
  @SysLog
  public void A(){
    ......
  }
  
  public void B(){
    ((ArticleService)AopContext.currentProxy()).A();
  }
}

总结

这篇文章介绍了AOP的相关概念、AOP实现自定义注解以及拦截器实现自定义注解,都是日常开发中必备的知识点,希望这篇文章对各位有所帮助。

源码已经上传,回复关键词AOP注解获取。

最后,别忘了点赞哦!!!

另外作者的第一本PDF书籍已经整理好了,由浅入深的详细介绍了Mybatis基础以及底层源码,有需要的朋友回复关键词「Mybatis进阶」即可获取,目录如下:

AOP 与 注解的那些事儿~的更多相关文章

  1. 总结切面编程AOP的注解式开发和XML式开发

    有段日子没有总结东西了,因为最近确实有点忙,一直在忙于hadoop集群的搭建,磕磕碰碰现在勉强算是能呼吸了,因为这都是在自己的PC上,资源确实有点紧张(搭建过程后期奉上),今天难得大家都有空(哈哈哈~ ...

  2. 利用spring AOP 和注解实现方法中查cache-我们到底能走多远系列(46)

    主题:这份代码是开发中常见的代码,查询数据库某个主表的数据,为了提高性能,做一次缓存,每次调用时先拿缓存数据,有则直接返回,没有才向数据库查数据,降低数据库压力. public Merchant lo ...

  3. spring aop 使用注解方式总结

    spring aop的注解方式:和xml的配置方式略有区别,详细如下: 1.首先还是建立需要的切面类:切面类里面定义好切点配置,以及所有的需要实现的通知方法. /** * */ package com ...

  4. 利用Spring AOP自定义注解解决日志和签名校验

    转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...

  5. Spring 代理对象,cglib,jdk的问题思考,AOP 配置注解拦截 的一些问题.为什么不要注解在接口,以及抽象方法.

    可以被继承 首先注解在类上是可以被继承的 在注解上用@Inherited /** * Created by laizhenwei on 17:49 2017-10-14 */ @Target({Ele ...

  6. ssm+redis整合(通过aop自定义注解方式)

    此方案借助aop自定义注解来创建redis缓存机制. 1.创建自定义注解类 package com.tp.soft.common.util; import java.lang.annotation.E ...

  7. spring AOP自定义注解方式实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

  8. Spring的第四天AOP之注解版

    Spring的第四天AOP之注解版 ssm框架 spring  在上一篇博客中,介绍了Spring的AOP的xml版本的使用,在这篇博客中,我将介绍一下,注解版的使用. 常用注解 注解 通知 @Aft ...

  9. [10] AOP的注解配置

    1.关于配置文件 首先在因为要使用到扫描功能,所以xml的头文件中除了引入bean和aop之外,还要引入context才行: <?xml version="1.0" enco ...

随机推荐

  1. ASP课程实例1——简易的手机号抽奖

    本程序用到了最基本的vbscript函数. 请大家注意它们的用法并熟悉asp网页的基本结构. inputbox,mid() ,replace(),rnd(),fix(),document.write ...

  2. Python爬虫框架--Scrapy安装以及简单实用

    scrapy框架 框架 ​ -具有很多功能且具有很强通用性的一个项目模板 环境安装: Linux: ​        pip3 install scrapy ​ ​ ​  Windows: ​     ...

  3. 扫描仪扫描文件处理-ABBYY自动矫正图像歪斜

    修改界面语言: 设置为不识别图片文字(因为我们只需要把图片歪斜校正): 保存无损彩色格式:

  4. centos8平台使用lsof

    一,lsof的用途 lsof,List Open Files 列出当前系统打开文件的工具. 在linux环境下,任何事物都以文件的形式存在, 所以lsof通过文件不仅仅可以访问常规数据,还可以访问网络 ...

  5. centos8平台给sudo配置日志

    一,sudo日志的用途: 我们可以记录下来用户账号在哪个时间进行过sudo 这样不需要再从secure日志中查找用户的sudo记录 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://w ...

  6. <转>二十问全链路压测干货汇总(上)

    本文转载自:微信公众号-数列科技<二十问全链路压测干货汇总(上)> 最近几年全链路压测无疑成为了一个热门话题,在各个技术峰会上都可以看到它的身影. 一些大型的互联网公司,比如阿里巴巴.京东 ...

  7. 性能测试之JVM的故障排查-死锁

    死锁原因 Java发生死锁的根本原因是:在申请锁时发生了交叉闭环申请.即线程在获得了锁A并且没有释放的情况下去申请锁B,这时,另一个线程已经获得了锁B,在释放锁B之前又要先获得锁A,因此闭环发生,陷入 ...

  8. 分布式文件存储数据库 MongoDB

    MongoDB 简介 Mongo 并非芒果(Mango)的意思,而是源于 Humongous(巨大的:庞大的)一词. MongoDB 是一个基于分布式文件存储的 NoSQL 数据库.由 C++ 语言编 ...

  9. 拿了十几个offer,怎样做选择?

    本文已经收录至我的GitHub,欢迎大家踊跃star 和 issues. https://github.com/midou-tech/articles 最近收到好几个读者的咨询,关于如何选offer的 ...

  10. Linux系统Yum仓库制作

    在使用Linux系统的时候,通常需要安装许多软件,Linux系统通常安装软件有源码包安装(文件格式:.tar.gz 或.tar.bz2:安装过程:解压.环境检查.编译和安装).Rpm包安装(文件格式: ...