一文搞懂AOP

简介

  • 面向切面编程,是面向对象编程的重要组成部分,在不改变业务逻辑功能的基础上,对横切逻辑进行扩展

添加依赖

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.1.5.RELEASE</version>
    </dependency>

通知

  • @Before(前置通知):在业务方法执行之前调用
  • @After(后置通知):在方法之后执行
  • @AfterReturning(正常返回通知):在方法之后执行,只有在业务方法没有出现异常的时候才会执行
  • @AfterThrowing(异常通知) : 在方法之后执行,只有在业务方法出现异常的时候才会执行
  • @Around (环绕通知):在业务方法执行之前和之后执行

连接点

  • 业务层的所有方法,叫做连接点
  • 业务类中可以被增强的方法都叫做连接点

切点

  • 能切入切面逻辑的方法,叫做切点
  • 实际被增强的方法叫做切入点 ,其他的那些没有被增强的方法(连接点)不是切点

切面

  • 定义了增强方法的类就叫做切面

实现

  • 在配置类上开启切面,使用@EnableAspectJAutoProxy
@Configuration
@ComponentScan(value = {"cn.tedu.demo"})
@EnableAsync
@EnableAspectJAutoProxy
public class FirstConfig {}
  • 使用五个不同的通知完成切入:
/**
 * 切面,使用@Aspect标注
 */
@Component
@Aspect
public class CustomAspect {

    /**
     * 使用@PointCut定义切入点
     */
    @Pointcut(value = "execution(* cn.tedu.demo.aspect.AspectInvok.invok(..))")
    public void pointCut(){}

    /**
     * 前置通知,在指定方法之前执行
     * @param point JoinPoint对象,可以获取切点的各种属性,比如入参的参数等
     */
    @Before(value = "pointCut()")
    public void before(JoinPoint point){
        //获取入参的参数的值
        Object[] args = point.getArgs();
        System.out.println(Arrays.asList(args));
        //获取MethodSignature,其中可以获取切点的各种属性,比如方法返回类型,参数等等
        MethodSignature signature = (MethodSignature) point.getSignature();
        String[] parameterNames = signature.getParameterNames();
        System.out.println(Arrays.asList(parameterNames));
        System.out.println("在方法之前执行");
    }

    /**
     * 在切点之后执行
     * @param point JoinPoint对象
     */
    @After(value = "pointCut()",argNames = "point")
    public void after(JoinPoint point){
        System.out.println("在方法之后执行");
    }

    /**
     * 在方法前后都会执行
     * @param point
     */
    @Around(value = "pointCut()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("前置执行");
        //执行方法,可以获取返回值,否则方法将不会执行
        Object result = point.proceed(point.getArgs());
        System.out.println("后置执行,执行的结果=="+result);
    }

    /**
     * 正常返回通知,
     * @param point Joinpoint对象
     * @param result 方法执行返回的结果,需要和@AfterReturning注解中returning中的属性值相同,否则不能自动装配
     */
    @AfterReturning(value = "pointCut()",returning = "result")
    public void afterReturning(JoinPoint point,Object result){
        System.out.println("正常返回执行,执行的结果为:"+result);
    }

    /**
     * 异常返回执行,程序出现异常了才会执行
     * @param point
     * @param ex 切入点执行抛出的异常,需要和@AfterThrowing注解的throwing值相同,否则不能完成自动装配
     */
    @AfterThrowing(value = "pointCut()",throwing = "ex")
    public void afterThrowing(JoinPoint point,Exception ex){
        System.out.println("异常返回执行,执行的异常为:"+ex);
    }
}

注解的实现

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    String name() default "陈加兵";
    int age() default 22;
}
  • 定义一个切面,使用注解如下:
@Component
@Aspect
public class CustomAspect {

    /**
     * 使用@PointCut定义切入点
     */
    @Pointcut(value = "@annotation(cn.tedu.demo.aspect.MyAnnotation)")
    public void pointCut(){}

    /**
     * 前置通知,在指定方法之前执行
     * @param point JoinPoint对象,可以获取切点的各种属性,比如入参的参数等
     */
    @Before(value = "pointCut()")
    public void before(JoinPoint point){
        //获取MethodSignature,其中可以获取切点的各种属性,比如方法返回类型,参数等等
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //获取方法上指定的注解
        MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
        System.out.println("name="+myAnnotation.name());
        System.out.println("在方法之前执行");
    }
}

源码解析

@EnableAspectJAutoProxy

  • @EnableAspectJAutoProxy该注解中可以看出使用了@Import(AspectJAutoProxyRegistrar.class),因此实际作用的类就是AspectJAutoProxyRegistrar,因此我们必须跟进源码去看。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

AspectJAutoProxyRegistrar

  • 该类实现ImportBeanDefinitionRegistrar【向容器中注入Bean】。
  • 该类的主要作用就是向ioc容器中注入AnnotationAutoProxyCreator
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    /**
    * 向容器中注入AnnotationAutoProxyCreator
    */
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //调用方法注册AnnotationAutoProxyCreator
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        //获取@EnableAspectJAutoProxy注解中两个属性的值
        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        //判断注解属性的值
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }

}

AnnotationAutoProxyCreator【创建代理】

概念

  • advisor(增强器):实现Advisor接口,简单的说一个advisor存储的就是被五个通知的标注的方法的信息。
  • advisedBeans:Map<Object, Boolean>类型,key是Bean,value存储的是这个Bean是否需要被增强器(通知方法)增强

分析

  • @EnableAspectJAutoProxy注解的主要作用就是向容器中注入AnnotationAutoProxyCreator,ID为org.springframework.aop.config.internalAutoProxyCreator,因此切面的关键类就是这个,我们看看继承关系图,如下:

[图片上传失败...(image-e8798a-1562336107568)]

  • 从继承关系中可以看到该类及其父类实现了两个重要的接口,如下:
    • InstantiationAwareBeanPostProcessor:spring的后置处理器,在实例化前后,初始化前后执行
    • BeanFactoryAware:自动注入BeanFactory
  • 通过上面的分析可以知道,AOP的主要功能必定是在AnnotationAutoProxyCreator这个Bean的初始化和实例化前后执行逻辑,无非就是上面两个接口定义的方法,我们可以在对应的方法中打断点跟踪代码。

代码跟踪

  • 刷新容器种执行registerBeanPostProcessors(beanFactory)方法将全部的后置处理器注入到容器中,在其中执行执行后续的setBeanFactory的方法

    • 根据Bean的生命周期我们可以判断最新执行的一定是实现Aware方法的setBeanFactory方法【在invokeAwareMethods()方法中执行】,实际执行也是符合我们的猜想,第一个执行的方法就是org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#setBeanFactory
      • 创建AspectJAdvisorFactory:顾名思义,当然是从标注@Aspect的类中创建增强器【切面增强的方法】
      • 创建BeanFactoryAspectJAdvisorsBuilder:简单的说就是创建增强器的辅助类,用来构造增强器
    @Override
      public void setBeanFactory(BeanFactory beanFactory) {
            //调用父类的方法
          super.setBeanFactory(beanFactory);
          if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
              throw new IllegalArgumentException(
                      "AdvisorAutoProxyCreator requires a ConfigurableListableBeanFactory: " + beanFactory);
          }
            //实际执行的是initBeanFactory
          initBeanFactory((ConfigurableListableBeanFactory) beanFactory);
      }
    
    /*******************************initBeanFactory**************************/
      @Override
      protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
          super.initBeanFactory(beanFactory);
            //创建切面增强器工厂
          if (this.aspectJAdvisorFactory == null) {
              this.aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory);
          }
            //创建切面增强生成器
          this.aspectJAdvisorsBuilder =
                  new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory);
      }
    
  • 刷新容器方法中执行finishBeanFactoryInitialization(beanFactory)【实例化剩余的单例Bean】

    • getBean()--->doGetBean--->createBean,在createBean方法中执行Object bean = resolveBeforeInstantiation(beanName, mbdToUse);【在实例化之前给一个机会返回一个代理】

      • 内部执行bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);

        • 内部循环遍历后置处理器,判断类型为InstantiationAwareBeanPostProcessor,执行postProcessBeforeInstantiation()方法【在实例化之前调用,接口定义】。

          • 此时执行到AbstractAutoProxyCreator#postProcessBeforeInstantiation,源码如下:
            • 这个方法的主要作用就是查找切面类和已经有了TargetSource的类,这些类都是不需要增强的,设置adviseBean的value为false,后续就不需要创建代理对象了
          /**
          * 在实例化之前执行逻辑,如果返回不为null,后续的属性赋值等操作将不会执行
          *  主要的作用
          */
          public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
                  //获取cacheKey,主要是和FactoryBean对象区别
                  Object cacheKey = getCacheKey(beanClass, beanName);
                  //beanName为空,或者targetSourcedBeans不包含当前Bean
                  if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
                          //如果需要增强的Bean已经包含了当前Bean,后续流程不需要执行,返回null即可
                      if (this.advisedBeans.containsKey(cacheKey)) {
                          return null;
                      }
                      /**isInfrastructureClass(beanClass):【如果当前Bean是基础的类【实现了Advice、Pointcut、Advisor、AopInfrastructureBean】并且满足this.aspectJAdvisorFactory不为null&&当前类被@Aspect注解标注了】
                      *shouldSkip(beanClass, beanName):
                      *       ①获取所有的候选的增强器【List<Advisor> candidateAdvisors = findCandidateAdvisors();】
                      *       ②循环候选增强器,判断类型是否是AspectJPointcutAdvisor
                      */
                      if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                          //如果满足条件,那么表示该类是一个切面,不是需要增强的Bean,设置value为false
                          this.advisedBeans.put(cacheKey, Boolean.FALSE);
                          return null;
                      }
                  }
          
                  //获取自定义的TargetSource
                  TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
                      //如果存在,那么可以直接创建代理对象
                  if (targetSource != null) {
                      if (StringUtils.hasLength(beanName)) {
                          this.targetSourcedBeans.add(beanName);
                      }
                      Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
                      Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
                      this.proxyTypes.put(cacheKey, proxy.getClass());
                      return proxy;
                  }
          
                  return null;
              }
          
          • 代码继续执行,发现postProcessPropertiespostProcessBeforeInitializationpostProcessBeforeInitialization都是默认的实现,这里跳过。
          • 真正创建代理对象的是在postProcessAfterInitialization中,逻辑如下:
            • ①判断bean是否为null和earlyProxyReferences中是否存在当前的Bean
            • ②如果满足条件,调用wrapIfNecessary(bean, beanName, cacheKey)方法,封装Bean,其实就是创建代理对象
            • ③进入wrapIfNecessary方法
              • ①做一些判断,包括是否是targetSourcedBeans是否包含,是否是基础类,是否应该跳过,是否adviseBean中的value为false【不需要增强的Bean】,满足以上条件的都直接返回bean,不需要创建代理
              • ②调用方法获取能够作用于当前Bean的增强器(通知方法集合)封装在specificInterceptors,代码跟进,最重要的实现是在org.springframework.aop.support.AopUtils.canApply(org.springframework.aop.Advisor, java.lang.Class<?>, boolean)这个方法中,大致的思想就是
              • ③判断specificInterceptors是否为null,如果为空,表示当前Bean不需要增强,设置adviseBean的值为false,反之需要增强,此时就需要创建代理了,此时主要调用的是Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean))
            • ④ 进入createProxy方法
              • ① 创建ProxyFactory,主要作用就是创建代理的工厂,其中封装了Advisor,targetSource,getProxy()【创建代理】方法等
              • ② 判断是否应该直接代理目标类【if (!proxyFactory.isProxyTargetClass())】
              • ③ 调用evaluateProxyInterfaces(beanClass, proxyFactory);方法,主要的作用就是检查当前Bean实现的接口是否符合要求能够创建代理对象【不能是InitializingBeanDisposableBean,Closeable,AutoCloseable等接口】,如果满足直接设置到ProxyFactory中,否则设置proxyTargetClass=true【直接代理目标类targetSource】
              • ④ 调用Advisor[] advisors = buildAdvisors(beanName, specificInterceptors)方法,将公共的拦截器和适配的拦截器specificInterceptors一起返回
                • ① 根据指定的this.interceptorNames拦截器名称从容器中获取Bean,将其解析成commonInterceptors
                • ② 遍历commonInterceptors和specificInterceptors将其封装成Advisor数组返回
              • ⑤ 将advisor和targetSource添加到ProxyFactory中
              • ⑥ 调用proxyFactory.getProxy(getProxyClassLoader())方法,获取代理对象
                • ① 最终调用的是org.springframework.aop.framework.DefaultAopProxyFactory.createAopProxy的方法创建代理对象,是AopProxyFactory默认实现类中的方法,具体逻辑如下:
                  • 如果targetClass是接口或者继承Proxy使用JDK的动态代理,否则使用Cglib代理
            • ⑤ 将创建的代理对象proxy存放在proxyTypes中,返回proxy
            • ⑥ 至此代理对象创建完成

代理执行

  • 上面解释了代理对象的创建流程,下面就开始真正的执行和增强的流程了,需要了解到五种通知类型在什么时候执行,执行的顺序是什么?
  • 未完待续。。。。。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容