zoukankan      html  css  js  c++  java
  • 基于个人理解的springAOP部分源码分析,内含较多源码,慎入

    本文源码较多,讲述一些个人对spring中AOP编程的一个源码分析理解,只代表个人理解,希望能和大家进行交流,有什么错误也渴求指点!!!接下来进入正题

    AOP的实现,我认为简单的说就是利用代理模式,对目标方法所在的类进行封装代理。请求目标方法时,是直接请求代理对象,再根据用户指定的通知(切点),在代理对象中进行操作,到了该使用目标方法的时候,调用代理对象中包装的真正目标方法完成,以实现面向切面编程,以下对两个问题进行一个分析:

    • 代理对象什么时候被创建
    • 切面类我们定义的切点信息是怎么加载的
      • 找到在何处进行扫描
      • 探究如何进行扫描

    代理对象是什么时候被创建的?

    这里是通过name来获取bean的情况,注意使用type的方式来获取bean进行调试,就会和本文有所出入
    现在从Main方法出发

    //1.创建IOC容器
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    //2.从IOC中获取bean实例
    ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculatorImpl");
    
    • 我们进入getBean的代码查看,发现bean是直接在一个名为singletonObjects的concurrentHashMap(ioc容器)中取出来的,取出时就已经是一个proxy对象了,可以证明,是在初始化实例的时候就创建了

    • 我们知道spring会在程序启动的时候,初始化ioc容器,而对于我们指定的切面类,在初始化实例时,就将找到我们指定的类,对其进行创建代理,代理对象创建后,放置到ioc容器中

    • 具体的流程是在初始化到我们指定的对象时,他会先创建出一个未代理的实例
      该实例会经过一个applyBeanPostProcessorsAfterInitialization方法,7个spring的后置处理器进行遍历,如果该类符合某个后置处理器的条件,则会被后置处理器加载,而我们aop的类会被(AnnotationAwareAspectJAutoProxyCreator)后置处理器处理,如下为7个后置处理器

    • 进入到该后置处理器我们会发现,他会通过判断这个类是否注释了切面编程的标记,如果注释了则进行处理,也就是我们的注解起了作用,这里是经过了AnnotationAwareAspectJAutoProxyCreator后置处理器的处理,以下是AnnotationAwareAspectJAutoProxyCreator处理器的applyBeanPostProcessorsAfterInitialization初始化方法

      //applyBeanPostProcessorsAfterInitialization方法的源码
      public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
          Object result = existingBean;
      
          Object current;
          for(Iterator var4 = this.getBeanPostProcessors().iterator(); var4.hasNext(); result = current) {
              BeanPostProcessor processor = (BeanPostProcessor)var4.next();
              //processor=AnnotationAwareAspectJAutoProxyCreator时,会进入postProcessAfterInitialization方法
              current = processor.postProcessAfterInitialization(result, beanName);
              if (current == null) {
                  return result;
              }
          }
          return result;
      }
      
      //进入AnnotationAwareAspectJAutoProxyCreator后置处理器的操作,通过调试我们看到,在wrapIfNecessary方法中进行了代理
      public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
          if (bean != null) {
              Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
              //判断是否需要代理
              if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                  //代理
                  return this.wrapIfNecessary(bean, beanName, cacheKey);
              }
      }
      
      //该方法是真正对proxy进行代理的方法
      protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
          if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
              return bean;
          } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
              return bean;
          } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
              //获取切点信息
              Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
              if (specificInterceptors != DO_NOT_PROXY) {
                  this.advisedBeans.put(cacheKey, Boolean.TRUE);
                  //创建了代理对象
                  Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                  this.proxyTypes.put(cacheKey, proxy.getClass());
                  //返回
                  return proxy;
              } else {
                  this.advisedBeans.put(cacheKey, Boolean.FALSE);
                  return bean;
              }
          } else {
              this.advisedBeans.put(cacheKey, Boolean.FALSE);
              return bean;
          }
      }
      
    • 后置处理器的处理方式,是在一个(postProcessAfterInitialization)方法中进行的处理,通过判断指定是使用jdbc的动态代理创建proxy还是通过cglib,处理后返回到变量中,完成后添加到ioc容器中,完成初始化,之后调用时都是已经代理过的实例,就可以进行切面编程了

    //最终是调用了DefaultAopProxyFactory类的createAopProxy来实现代理
    //可以看到这里根据一些配置条件来判断我们要创建的代理是jdk动态代理还是cglib
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            //创建返回
            return new JdkDynamicAopProxy(config);
        } else {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            } else {
                //创建返回
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
            }
        }
    }
    

    也就是说,spring在初始化容器的时候,会为@Aspect注解的类进行一个代理操作,而其中主要起作用的是7个后置处理器中的AnnotationAwareAspectJAutoProxyCreator处理器对其进行了代理

    切点信息是如何获取、封装,然后存储在List中的

    先找到他在何处进行的扫描

    以上的创建代理过程中,我们知道了如下代码是获得了切点信息的一个语句

    //该方法中调用的时返回值为List<Advisor>的方法,在返回的时候将其转化为Object[]类型,所以以下代码的返回值就编程List<Advisor>,也说明了Advisor就是封装了pointcut信息的对象
    Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
    

    进入其中,往里追代码,追到以下位置

    protected List<Advisor> findCandidateAdvisors() {
        List<Advisor> advisors = super.findCandidateAdvisors();
        if (this.aspectJAdvisorsBuilder != null) {
            //我们可以看到添加Advisor于buildAspectJAdvisors()有关
            advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
        }
    

    继续往里追,我们看到在buildAspectJAdvisors方法中有这么一句代码

      List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName);
    

    观察advisorsCache,可以知道,这是一个map集合,显然他以我们@AspectJ注解的类名为key,切点注解的信息集合为值

    我们发现只是从advisorsCache这个map中取出来的,也就是在这之前advisorsCache就已经赋值了,于是我们应该追溯advisorsCache对象的初始化,这里利用一个小技巧,当我们要追溯一个map或者其他集合的构建时,可以通过查找关键字即advisorsCache.put这样的方式来追溯到位置,于是我们发现同样在buildAspectJAdvisors方法中我们能看到此处有对advisorsCache的初始化,代码分析如下

    //BeanFactoryAspectJAdvisorsBuilder类中的
    public List<Advisor> buildAspectJAdvisors() {
        //aspectBeanNames拥有我们注释了AspectJ注解的类名
        List<String> aspectNames = this.aspectBeanNames;
        //我们发现第一获取时,aspectNames为空,会进入一个线程安全的代码块进行扫描,猜测这是一个双重检验实现懒加载,需要的时候才去扫描注解
        if (aspectNames == null) {
            synchronized(this) {
                aspectNames = this.aspectBeanNames;
                if (aspectNames == null) {
                    List<Advisor> advisors = new ArrayList();
                    List<String> aspectNames = new ArrayList();
                    //调用了工具来得到所有的beanNames
                    String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
                    String[] var18 = beanNames;
                    int var19 = beanNames.length;
                    //遍历所有的bean
                    for(int var7 = 0; var7 < var19; ++var7) {
                        String beanName = var18[var7];
                        //isEligibleBean -- 具备条件
                //protected boolean isEligibleBean(String beanName) {
                // return AnnotationAwareAspectJAutoProxyCreator.this.isEligibleAspectBean(beanName);
           	    // }
                        if (this.isEligibleBean(beanName)) {
                            Class<?> beanType = this.beanFactory.getType(beanName);
                            //this.advisorFactory.isAspect(beanType)校验是否有这个注解
                            //    public boolean isAspect(Class<?> clazz) {
            			   //return this.hasAspectAnnotation(clazz) && !this.compiledByAjc(clazz);
        				  //}
                            if (beanType != null && this.advisorFactory.isAspect(beanType)) {
                                //将类名存储
                                aspectNames.add(beanName);
                                //AspectMetadata应该是判断该beanName对应的类有没有一个注解@Aspect,主要是在AspectMetadata的AjType对象的getPerClause()中进行一个判断,如果是true,则new PerClauseImpl(PerClauseKind.SINGLETON)) 返回,则可以通过以下的if判断
                                AspectMetadata amd = new AspectMetadata(beanType, beanName);
                               if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                    //MetadataAwareAspectInstanceFactory就是把beanFactory和当前得到的beanName封装在一起
                                   MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                                    //此处得到Advisor结果集,Advisor主要是封装了每个方法对应的连接点
                                    List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                                    if (this.beanFactory.isSingleton(beanName)) {
                                        //满足条件之后添加到advisorsCache中,
                                        this.advisorsCache.put(beanName, classAdvisors);
                                    } else {
                                        this.aspectFactoryCache.put(beanName, factory);
                                    }
                                   //同时添加到advisors中,满足第一次调用输出
                                    advisors.addAll(classAdvisors);
                                } else {
                                    if (this.beanFactory.isSingleton(beanName)) {
                                        throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton");
                                    }
    
                                    MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                                    this.aspectFactoryCache.put(beanName, factory);
                                    advisors.addAll(this.advisorFactory.getAdvisors(factory));
                                }}}}
                    this.aspectBeanNames = aspectNames;
                    //返回
                    return advisors;
                }
         }
         if (aspectNames.isEmpty()) {
            return Collections.emptyList();
             //如果不为空
        } else {
             //创建一个封装了切点信息的advisor对象集合
            List<Advisor> advisors = new ArrayList();
            Iterator var3 = aspectNames.iterator();
    
            while(var3.hasNext()) {
                String aspectName = (String)var3.next();
                //只要不为空,在advisorsCache就能拿到aspectName对应的List<Advisor>,也就是advisorsCache这个map中已经存好了我们的pointcut,只是将他赋给advisors然后返回而已,也即是我们上一步寻找切点信息来源时的代码
                List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName);
                if (cachedAdvisors != null) {
                    advisors.addAll(cachedAdvisors);
                } else {
                    MetadataAwareAspectInstanceFactory factory = (MetadataAwareAspectInstanceFactory)this.aspectFactoryCache.get(aspectName);
                    advisors.addAll(this.advisorFactory.getAdvisors(factory));
                }
            }
            return advisors;
        }
    }
    

    也就是说在buildAspectJAdvisors()处,spring是使用了synchronized代码块以及双重校验来保证关于切点信息扫描的代码块在任何情况下都只执行一次,也就是线程安全的,而且在此处是一个懒加载的效果,第一次调用就会进入代码块执行扫描并返回结果,第二次就直接从cachedAdvisors获取了,完成的是扫描所有的bean,会对我们@Aspect注解进行识别,解析,将类中每个切点的类型和切点的目标和方法封装在一起,得到多个切点对象,然后返回

    以上为我认为的spring扫描切点信息的源码位置

    接下来看看他是怎么扫描的

    为了方便以下代码的阅读,我先将封装结构贴在这里,以下为Advisor对切点信息的封装结构:一个Advisor封装的是一个切点,一个bean有多个Advisor

    接下来进入代码分析,由上可以知道Advisor对象是封装pointcut信息的,那么我们就追进getAdvisors()方法来看看

    //获取Advisor结果集,摘取部分代码我们来看看
    public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
        ……………………
    	//var6为该bean的所有方法,循环得到方法上的注解信息
        while(var6.hasNext()) {
                Method method = (Method)var6.next();
            	//传入方法得对象
                Advisor advisor = this.getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
                if (advisor != null) {
                    advisors.add(advisor);
         }
            ……………………
     }
    
    public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {
        	//猜测这是一个判断,检查的或许和Aspect有关
           this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
        	//可以看出来,getPointcut方法就是封装切点信息的,进入这个方法
           AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
            return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
        }
    
    
    	private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
        //findAspectJAnnotationOnMethod得到方法上的注解信息,也就是我们指定的连接点的处理方法,在这个方法中将aspectJ支持的切面通知类型逐个遍历,看看method中的注解是哪一个,并提取出他的信息,内部代码为:
        //     A result = AnnotationUtils.findAnnotation(method, toLookFor);
        //    return result != null ? new AbstractAspectJAdvisorFactory.AspectJAnnotation(result) : null;
        //		result为 method.getDeclaredAnnotation(annotationType)的返回值,即result封装的是我们在类上的A切点注解,将其作为参数构建出AspectJAnnotation对象并返回,也就是说AspectJAnnotation对象中就包含了切点注解的信息
        //		而进入AspectJAnnotation对象的构造器中,我们发觉调用this.pointcutExpression = this.resolveExpression(annotation);完成对excution表达式的解析,内部代码比较容易看懂,这里不做记录
        AspectJAnnotation<?> aspectJAnnotation = 
            AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
        if (aspectJAnnotation == null) {
            return null;
        } else {
            AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
            //在这里设置了pointcut信息,execution表达式在创建aspectJAnnotation的时候已经赋值了
            ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
            if (this.beanFactory != null) {
                ajexp.setBeanFactory(this.beanFactory);
            }
            //返回包含了一个切点的AspectJExpressionPointcut对象
            return ajexp;
        }
    }
    

    受本人水平有限,关于扫描只能分析到这里,之后会继续更新,有错误希望大家可以评论指点,谢谢大佬~~

  • 相关阅读:
    jvm 优化
    SqlServer体系结构
    sqlserver2012 在视图中建索引
    win10 桌面设置为远程桌面
    ORACLE 查询某表中的某个字段的类型,是否为空,是否有默认值等
    activemq读取剩余消息队列中消息的数量
    Oracl 一条sql语句 批量添加、修改数据
    ClickOnce一项Winform部署
    C#语言中的修饰符
    关于MySQL集群的一些看法
  • 原文地址:https://www.cnblogs.com/JIATCODE/p/13398819.html
Copyright © 2011-2022 走看看