Java核心知识体系4:AOP原理和切面应用

news/2024/4/26 13:38:42/文章来源:https://www.cnblogs.com/wzh2010/p/15886644.html

1 概述

我们所说的Aop(即面向切面编程),即面向接口,也面向方法,在基于IOC的基础上实现。
Aop最大的特点是对指定的方法进行拦截并增强,这种增强的方式不需要业务代码进行调整,无需侵入到业务代码中,使业务与非业务处理逻辑分离。
以Spring举例,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。
aop的实现主要包括了两个部分:

  • 匹配符合条件的方法(Pointcut)
  • 对匹配的方法增强(JDK代理、cglib代理)
    spring针对xml配置和配置自动代理的Advisor有很大的处理差别,在IOC中主要是基于XML配置分析的,在AOP的源码解读中,则主要从自动代理的方式解析,分析完注解的方式,再分析基于xml的方式。

2 案例分析

下面是spring aop的用法 也是用于源码分析的案例
切面类:TracesRecordAdvisor

@Aspect
@Component
public class TracesRecordAdvisor {@Pointcut("execution(* spring.action.expend.aop.services.*.*(..))")public void expression() {}@Before("expression()")public void beforePrint(){System.out.println("进入服务,在服务执行之前,记录日志....");}@AfterReturning("expression()")public void afterPrint(){System.out.println("退出服务,在服务执行结束之后,记录日志.....");}
}

xml配置: aop的注解启用只需要在xml中配置这段代码即可,这个是作为入口

<aop:aspectj-autoproxy/>

服务类:PayServiceImpl 使用jdk代理 所以要有一个接口

@Service
public class PayServiceImpl implements PayService {public void payMoneyMenthod() {System.out.println("正在执行付款...");}
}

测试方法:

    @Testpublic void springAopTestService() {ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-aop.xml");PayService payService= (PayService) applicationContext.getBean("payServiceImpl");payService.payMoneyMenthod();}

执行结果:

进入服务,在服务执行之前,记录日志....
正在执行付款...
退出服务,在服务执行结束之后,记录日志.....

从上面的执行结果看,payMoneyMenthod 方法的确是被增强了。

3 BeanFactoryPostProcessor

读spring源码的时候,可以首先看下BeanFactoryPostProcessor和BeanPostProcess,这两个接口都是在spring通过配置文件或者xml获取bean声明,生成BeanDefinition后,允许我们再对生成的BeanDefinition,进行入口包装和增强。
我们看看BeanFactoryPostProcessor的定义

public interface BeanFactoryPostProcessor {void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}

方法postProcessBeanFactory的参数为ConfigurableListableBeanFactory,我们之前讨论过beanFactory用来获取bean的,而ConfigurableListableBeanFactory继承接口SingletonBeanRegistry和BeanFactroy,所以可以访问到已经生成过的BeanDefinitions集合,如果某个类实现该接口,spring会注册这个类,然后执行这个类的postProcessBeanFactory方法,以便我们对BeanDefinition进行扩展。
接下来的代码表示Spring是如何注册BeanFactoryPostProcessor并执行postProcessBeanFactory的。

    @Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {prepareRefresh();//核心方法1ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();prepareBeanFactory(beanFactory);try {postProcessBeanFactory(beanFactory);//核心方法2 执行BeanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactory);//核心方法 3 注册BeanPostProcessorregisterBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (BeansException ex) {............throw ex;}finally {............resetCommonCaches();}}}

核心方法1obtainFreshBeanFactory就是前两篇所说的生成BeanDefinition的入口,invokeBeanFactoryPostProcessors核心方法2就是执行BeanFactoryPostProcessor接口的方法。

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
}

通过方法getBeanFactoryPostProcessors获取注册BeanFactoryPostProcessor,然后来看看如何添加一个处理器

@Override
public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor) {this.beanFactoryPostProcessors.add(beanFactoryPostProcessor);
}

对于方法invokeBeanFactoryPostProcessors不再往下看了,里面的方法大致先对BeanFactoryPostProcessor进行排序,排序的标准是是否实现了PriorityOrdered,然后根据设置的order大小指定执行顺序,生成一个排序集合和一个普通的集合,最后执行invokeBeanFactoryPostProcessors

private static void invokeBeanFactoryPostProcessors(Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {for (BeanFactoryPostProcessor postProcessor : postProcessors) {//执行到自定义的BeanFactoryPostProcessorpostProcessor.postProcessBeanFactory(beanFactory);}
}

这个方法就会循环先前注册的BeanFactoryPostProcessor集合,然后执行postProcessBeanFactory。

4 BeanPostProcess 解读

与BeanFactoryPostProcessor相比,BeanPostProcess就重要得多了,因为Spring的注解、AOP等都是通过这个接口的方法拦截执行的,它贯穿了Bean创建过程的整个生命周期,在IOC阶段,Spring只注册BeanPostProcess,执行则放到了Bean的实例化创建阶段。
首先看下BeanPostProcessor的接口定义

public interface BeanPostProcessor {//在bean创建 属性赋值之后  Aware接口执行之后执行Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;//在init-method afterPropertiesSet 执行之后执行Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}

在bean的声明周期中,下面的序列是bean创建后要执行的接口和方法顺序:

  • 实例化(autowireConstructor或者instantiateBean)
  • 属性初始化(populateBean)
  • Aware接口(如果你的 bean 有进行实现)
  • BeanPostProcess.postProcessBeforeInitialization
  • PostConstructInitializingBean.afterPropertiesSet
  • BeanPostProcess.postProcessAfterInitialization

其中通过注解引入依赖的方式就是在AutowiredAnnotationBeanPostProcessor这个类中实现的,而接下来要分析的Spring Aop也是从这里开始的,这个类叫AnnotationAwareAspectJAutoProxyCreator,

5 NameSpaceHanlder 解读

在Spring中,任何的技术都是在IOC的基础上进行的,Aop也不例外,程序会首先读取xml配置文件,然后对读取到的标签先查找命名空间,然后找对应的NameSpaceHandler,最终调用parse方法解析标签。
aop标签的解析,使用纯注解的方式aop:aspectj-autoproxy和使用aop:config的配置解析不太一样,具体表现在生成PointCut和生成Before、After、Around等切面类时,使用aop:config的方式会为这些注解生成一个BeanDefinition,而这个BeanDefinition的构造函数是由3个BeanDefinition组成,表明这个类是合成类,即synthetic这个属性为true。然后跟解析普通的bean一样,生成这些实例对象,后面的过程就跟是用纯注解的方式相同了,接下来的分析是基于纯注解分析的,也就是解析从解析aop:aspectj-autoproxy这个标签开始。
前面的xml文件的标签解析是通过parseDefaultElement方法解析默认的标签的,而我们在配置文件里面配置了启动自动代理的方式<aop:aspectj-autoproxy/>,当Spring读取到这个标签,则会走parseCustomElement(root)这个方法了,这个方法的源码不再解析,主要完成的功能如下:

  • 获取element的nameSpaceUri,根据nameSpaceUri找到NameSpaceHanlder
  • 调用NameSpaceHanlder的parse方法解析element

下面是NameSpaceHanlder接口的定义

public interface NamespaceHandler {void init();BeanDefinition parse(Element element, ParserContext parserContext);BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext       parserContext);}

这里面的init方法是我们初始化操作的,这里可以完成对指定的标签设置解析器,然后再parse方法里面找到指定标签的解析器,然后调用该解析器的parse方法解析标签,后面会重点看这两个方法。
再来看下Spring如何加载NameSpaceHanlder的,Spring首先会取查找项目空间下目录META-INF/的所有spring.handlers文件,这个文件是在Spring依赖的jar下面,在核心jar包都会由这个文件,aop的jar包路径下文件内容为:spring.handlers

http://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

发现这里面存储的是一个key,value,key是aop的nameSpaceUri,value是AopNamespaceHandler,从这个类名上就能发现该类实现了NamespaceHandler,肯定也就实现了init和parse方法,所以解析<aop:aspectj-autoproxy/>的任务就由AopNamespaceHandler的parse完成。
查看AopNamespaceHandler的init方法

    @Overridepublic void init() {// In 2.0 XSD as well as in 2.1 XSD.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());registerBeanDefinitionParser("aspectj-autoproxy", new                       AspectJAutoProxyBeanDefinitionParser());registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());// Only in 2.0 XSD: moved to context namespace as of 2.1registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());}

上面的代码就很清晰了,<aop:config> 标签由ConfigBeanDefinitionParser处理,<aop:aspectj-autoproxy/> 则由AspectJAutoProxyBeanDefinitionParser这个类处理,这两种处理其实对应了自动代理和通过xml配置的处理方式,然后会调用AspectJAutoProxyBeanDefinitionParser的parse方法

@Overridepublic BeanDefinition parse(Element element, ParserContext parserContext) {AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);extendBeanDefinition(element, parserContext);return null;}

这个方法其实就是为了注册一个AnnotationAwareAspectJAutoProxyCreator类,然后AOP的所有处理逻辑都会交给这个类处理,由于这个类的实现了BeanPostProcessor,所以这个类的入口就是BeanPostProcessor接口的两个方法:

  • postProcessBeforeInitialization
  • postProcessAfterInitialization

6 Spring Aop 源码分析

我们前面分析了,当spring读取xml文件遇到<aop:aspectj-autoproxy/>会找到AopNamespaceHandler这个处理类,然后这个类又将这个标签委托给了AspectJAutoProxyBeanDefinitionParser类,最终调用这个类得parse方法,parse方法未做分析,其实这个方法的目的很简单,就是注册AnnotationAwareAspectJAutoProxyCreator这个类,这个类实现了BeanPostProcessor和InstantiationAwareBeanPostProcessor接口,最终在实例化bean对象也就是执行BeanFactory.getBean(beanName)的过程中,会调用这两个接口的方法(执行顺序如下):
InstantiationAwareBeanPostProcessor先执行:

postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
postProcessAfterInstantiation(Object bean, String beanName)

BeanPostProcessor再执行:

postProcessBeforeInitialization(Object bean, String beanName)
Object postProcessAfterInitialization(Object bean, String beanName)

AOP的实现基本上是在这两个方法中进行的,所以就从这里来看Spring是如何实现AOP的,Spring的AOP代理目前支持方法的增强,看源码目前好像也支持了属性的增强了。
读取源码前首先来分析一下方法增强的原理,有助于我们读取源码时紧紧抓住主线。首先第一个问题,如果我们想对一个类的方法进行增强,我们应该怎么做呢?
这种业务需求可以通过代理实现,在方法执行前,拦截这个方法,并且加入要执行增强的逻辑,最后再执行目标方法。下面是Spring用的两种代理方式:

JDK代理:我们可以通Proxy类获取一个目标类的代理对象,但JDK代理要求被代理的类必须实现接口,所以是基于接口的代理。

cglib代理:如果目标类没有接口,使用cglib代理,是由asm封装的,直接操作类得字节码,效率也很高。

由于在生产业务中,我们不可能对所有的类都执行增强,所以还需要一个选择器,将符合条件的bean进行增强,Spring使用了PointCut接口,通过该接口的getMethodMatcher方法获取一个方法匹配器,然后通过matches方法匹配到目标类对象的目标方法执行增强操作。mathcer匹配规则就是通过Spring 配置的expression表达式了。

所以在分析源码的时,要围绕这两方面进行:

  • 匹配切点方法(构建切入点表达式类和切面类)
  • 创建代理对象

这两方面在Spring的实现里非常复杂,尤其是第一步匹配切点方法过程,这个过程中,Spring会将@Aspect注解类的@Before,@After,@Around、@Pointcut等注解都封装成待执行的切面方法类,然后通过方法匹配器匹配到的要增强的方法前后执行切面方法类,达到方法增强的目的。
第二阶段,创建代理对象默认是通过JDK代理实现配置,<aop:aspectj-autoproxy proxy-target-class="true">这样配置可以指定使用cglib代理。

7 注解切面代理类分析

上面分析了真正实现AOP功能的是AnnotationAwareAspectJAutoProxyCreator,由于这个类实现了BeanPostProcessor和InstantiationAwareBeanPostProcessor,所以在创建一个bean的时候,会进入到这两个接口的方法,这两个接口包含了四个方法,方法执行顺序上面已经分析过了,来看看这个类的类图:
image
类图上比较重要的接口就是右上角实现的两个接口,在bean创建的生命周期过程中,会校验当前容器中是否注册了实现了这两个接口的类,如果有则调用接口的方法,前面的分析中在解析aop:aspectj-autoproxy/时,将这个类注册到了容器中,而且上面也罗列了这两个接口中四个方法的调用顺序,在这个类中完成主要功能的2个方法及其执行顺序:

  • InstantiationAwareBeanPostProcessor先执行:
postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
  • BeanPostProcessor再执行:
Object postProcessAfterInitialization(Object bean, String beanName)

postProcessBeforeInstantiation方法主要是找出注解了Advice的类,并将Advice的类使用了@Before,@After,@Around、@Pointcut,@AfterThrowing等注解的方法封装成一个一个类放入到缓存中供匹配到的类生成代理用。postProcessAfterInitialization主要是匹配符合条件的目标类对象,然后生成代理的过程,接下来就按顺序分析这两个方法完成的功能。

8 Aspect注解类分析

    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {//构建一个缓存keyObject cacheKey = getCacheKey(beanClass, beanName);if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {//如果当前beanClass的缓存key 存在于Class为Advise的缓存中,表示当前的beanClass是Adivse类//而且不需要生成代理。if (this.advisedBeans.containsKey(cacheKey)) {return null;}//核心校验:1 当前类是否是AOP的基础类 2、当前类是否应该跳过不生成代理if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return null;}}//这部分主要是用于实现了TargetSource接口的bean,然后从getTarget中获取对象 创建代理if (beanName != null) {TargetSource targetSource = getCustomTargetSource(beanClass, beanName);if (targetSource != null) {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;}

这个方法主要是先为beanClass生成一个缓存的key,这个beanClass如果是FactoryBean,则按照工厂类的命名规则命名,否则用beanName命名,然后用刚才生成的key判断beanClass是否已经存在于Advice的缓存集合中,如果已经存在则代表该类是切面类而且已经被处理过了,后续处理不会为该类生成代理,如果没有没处理过,则会调用下面的方法校验该类是否是AOP的基础类 ,总之这个方法作用就是将AOP相关操作的切面类和基础类放入到缓存中,当为bean生成代理的时候,忽略advice缓存中的AOP切面类和基础类,下面是具体校验过程:
AnnotationAwareAspectJAutoProxyCreator重写了该方法

@Override
protected boolean isInfrastructureClass(Class<?> beanClass) {
//调用父类的isInfrastructureClass判断是否是aop基础类
//校验当前类是否使用@Aspect注解
return (super.isInfrastructureClass(beanClass) || this.aspectJAdvisorFactory.isAspect(beanClass));
}

父类的isInfrastructureClass方法

    protected boolean isInfrastructureClass(Class<?> beanClass) {boolean retVal = Advice.class.isAssignableFrom(beanClass) ||Advisor.class.isAssignableFrom(beanClass) ||AopInfrastructureBean.class.isAssignableFrom(beanClass);if (retVal && logger.isTraceEnabled()) {logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");}return retVal;}

里面isAssignableFrom表示当前类是否允许被设置为beanClass类对象,可以以此判断beanClass是否是Advice类,所以这个方法的校验目的就是判断当前正在创建目标类是否是AOP的基础类,即该类是否是Advice,Advisor或者实现了AopInfrastructureBean接口。该方法调用父类的isInfrastructureClass判断是否是aop基础类,然后再校验当前类是否使用@Aspect注解,目的只有一个,如果是Advice切面相关的类不做任何处理,直接放入advice缓存即可。
然后再来看shouldSkip(beanClass, beanName):

    @Overrideprotected boolean shouldSkip(Class<?> beanClass, String beanName) {//查找当前已经生成的所有Advisor切面类  不展开分析List<Advisor> candidateAdvisors = findCandidateAdvisors();for (Advisor advisor : candidateAdvisors) {if (advisor instanceof AspectJPointcutAdvisor) {if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {return true;}}}return super.shouldSkip(beanClass, beanName);}

这个方法主要是校验当前正在创建bean的beanName是否属于已经创建好的切面类缓存中,如果是则加入到advices缓存中,不再处理。其中findCandidateAdvisors()会查找当前容器中生成的所有实现了Advisor的类,Spring会将@Before,@After,@Around等生成一个继承了Advisor类对象存储到缓存中供后续使用,这一部分时Spring AOP前半段的核心内容,后续都会围绕着如何将切面类的注解生成Adisor类探索。
AnnotationAwareAspectJAutoProxyCreator重写了findCandidateAdvisors方法,所以会执行到该方法:

@Override
protected List<Advisor> findCandidateAdvisors() {//通过父类的方法查找所有容器中的Advisor类,也就是基于xml配置的<aop:before/>生成的List<Advisor> advisors = super.findCandidateAdvisors();//查找通过注解的方式生成Advisor类advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());return advisors;
}

这个方法会首先调用父类的findCandidateAdvisors方法用于获取通过xml文件配置生成的Advisor,也就是通过aop:before,aop:after等生成的,然后调用通过注解方式即@Before,@After,@Around、@Pointcut,@AfterThrowing生成的advisor,可以说,这两个方法分别处理了基于xml配置文件的方式和基于注解的配置方式,因为所有的分析都是基于AnnotationAwareAspectJAutoProxyCreator这个类进行的,所以在这个地方会先获取配置文件的,再生成基于注解类的Advisor,这样就将基于xml配置的和基于注解的配置都会解析到。
看下 this.aspectJAdvisorsBuilder.buildAspectJAdvisors()

    public List<Advisor> buildAspectJAdvisors() {List<String> aspectNames = null;synchronized (this) {aspectNames = this.aspectBeanNames;if (aspectNames == null) {List<Advisor> advisors = new LinkedList<Advisor>();aspectNames = new LinkedList<String>();//从beanDefinitions中获取所有的beanNameString[] beanNames =BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);for (String beanName : beanNames) {//如果beanName不符合配置的 <aop:include name="***"/>//忽略这个bean上所有的切面方法if (!isEligibleBean(beanName)) {continue;}Class<?> beanType = this.beanFactory.getType(beanName);if (beanType == null) {continue;}//如果当前beanType是一个切面类 则将该切面类相关信息封装起来if (this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {// 将切面信息放入到分装到MetadataAwareAspectInstanceFactory 生成一个AspectMetadataMetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);// 获取容器中所有Advisor类 需要进入这个方法详细分析List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {//单例加入缓存this.advisorsCache.put(beanName, classAdvisors);} else {//非单例  将工厂加入缓存this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);} else {// 非单例  将生成Advisor的工厂类加入到缓存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;}}..............}

这个方法主要的任务其实就是获取类得类型为Aspect的切面类,然后获取切面类方法的所有注解并将注解转换成Advisor类返回,主要步骤为:

  • 获取容器中所有的BeanDefinition的beanName
  • 根据beanName,或者beanClass,匹配符合规则的Aspect切面类,通过aop:include配置的规则
  • 获取Aspect切面类的所有切面方法封装成Advisor对象返回。
  • 将获取到的所有Advisor放入到缓存中。
    这个方法代码虽然很多,但是核心的是this.advisorFactory.getAdvisors(factory),即第三个步骤,这个方法将会获取到切面类的所有切面方法,并封装成Advisor,getAdvisors是一个接口,ReflectiveAspectJAdvisorFactory实现了这个接口,下面代码是其实现逻辑:
@Overridepublic List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {//获取切面类ClassClass<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();//获取切面类的beanNameString aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();validate(aspectClass);//进一步对AspectMetadata封装 里面包含了切面类的信息MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);List<Advisor> advisors = new LinkedList<Advisor>();//获取切面类中没有使用Pointcut注解的方法for (Method method : getAdvisorMethods(aspectClass)) {//检查该方法是否是切面方法, 如果是成Advisor类返回Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);if (advisor != null) {advisors.add(advisor);}}//如果没有切面方法 设置一个空的if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);advisors.add(0, instantiationAdvisor);}//处理属性字段 Spring支持到了属性的增强for (Field field : aspectClass.getDeclaredFields()) {Advisor advisor = getDeclareParentsAdvisor(field);if (advisor != null) {advisors.add(advisor);}}return advisors;}

这个方法首先已经将切面类信息封装到AspectMetadata的类再次封装到MetadataAwareAspectInstanceFactory,然后获取切面类的所有没有使用Pointcut注解的方法,调用getAdvisor获取这个方法使用的切面注解,生成对应的Advisor类。 至于PointCut的处理则是再后面的getAdvisor中处理的。

9 获取切面类的Advisor

获取Advisor类的方法为getAdvisor,首先来看下这个方法的参数:

//切面类的切面方法  这里可能就是 beforePrint()
Method  candidateAdviceMethod 
//获取AspectMetadata的实例工厂(可以获取切面的类所有信息)
MetadataAwareAspectInstanceFactory aspectInstanceFactory
//切面的排序
int declarationOrderInAspect
//切面类的beanName 这里是tracesRecordAdvisorString aspectName

上面的参数中可以获取到切面类和切面方法,这样就可以获得一个Advisor对象,然后还需要一个切入点表达式PointCut用来匹配符合条件的方法,拦截到目标方法后,就可以执行Adivsor增强方法了。 来看看创建Advisor的过程,这里假设Method是TracesRecordAdvisor类的beforePrint方法,也就是我们测试案例中创建使用了@Before注解的切面方法:

@Override
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,int declarationOrderInAspect, String aspectName) {validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());//获取pointCut,这里实际上获得的是 expression()这个方法对应了pointCut的内容AspectJExpressionPointcut expressionPointcut = getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());if (expressionPointcut == null) {return null;}//创建advisorreturn new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

看看getPointCut方法如何获取到exression过程需要嵌套很多步骤,这里不展开了,简单看下如何将查找到的值设置到表达式中的:

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {//AspectJAnnotation<?> aspectJAnnotation =AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;}AspectJExpressionPointcut ajexp =new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);//将上面生成的AspectJAnnotation 解析出的expression方法放入到表达式中//ajexp.setExpression(aspectJAnnotation.getPointcutExpression());return ajexp;
}

这里需要关注下上面的findAspectJAnnotationOnMethod方法:

protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {//看到了我们熟悉的切面方法注解,这里的beforePrint使用@Before注解Class<?>[] classesToLookFor = new Class<?>[] {Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};for (Class<?> c : classesToLookFor) {AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);if (foundAnnotation != null) {return foundAnnotation;}}return null;
}

这个方法就是查找切面方法是否使用了Before, Around, After,AfterReturning, AfterThrowing,Pointcut注解,如果使用了,则返回一个AspectJAnnotation对象,里面有一个annotation的泛型对象,这个泛型对象就是被设置为这些注解的值,而且还会获得这些注解里面配置的pointcut表达式内容,如果是引用的表达式方法,则将方法参数设置到pointcutExpression这个属性中。
解析完切面方法的注解后现在再回过头来看看如何创建一个advisor实例:

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {this.declaredPointcut = declaredPointcut;this.aspectJAdviceMethod = aspectJAdviceMethod;this.aspectJAdvisorFactory = aspectJAdvisorFactory;this.aspectInstanceFactory = aspectInstanceFactory;this.declarationOrder = declarationOrder;this.aspectName = aspectName;//切面类是否是懒加载if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {// Static part of the pointcut is a lazy type.Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);// Make it dynamic: must mutate from pre-instantiation to post-instantiation state.// If it's not a dynamic pointcut, it may be optimized out// by the Spring AOP infrastructure after the first evaluation.this.pointcut = new PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);this.lazy = true;}else {this.pointcut = this.declaredPointcut;this.lazy = false;//最终会执行到这里获取一个advicethis.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);}
}

10 为切面方法创建Advice

上面方法的最后一句instantiateAdvice(this.declaredPointcut)会创建一个advice,具体是调用getAdvice方法获取:

    @Overridepublic Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {//获取切面类对象,这里是TracesRecordAdvisorClass<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();validate(candidateAspectClass);//核心点1:获取切面注解,这里得方法是 beforePrint 使用了@Before注解AspectJAnnotation<?> aspectJAnnotation =AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;}....................AbstractAspectJAdvice springAdvice;//核心点2:根据注解转换后的 将注解生成不同的Advice类。switch (aspectJAnnotation.getAnnotationType()) {case AtBefore:springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfter:springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfterReturning:springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterReturningAnnotation.returning())) {springAdvice.setReturningName(afterReturningAnnotation.returning());}break;case AtAfterThrowing:springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {springAdvice.setThrowingName(afterThrowingAnnotation.throwing());}break;case AtAround:springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtPointcut://这里对PointCut不做处理if (logger.isDebugEnabled()) {logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");}return null;default:throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);}// 将切面类信息配置到SpringAdvice中springAdvice.setAspectName(aspectName);springAdvice.setDeclarationOrder(declarationOrder);String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);if (argNames != null) {springAdvice.setArgumentNamesFromStringArray(argNames);}springAdvice.calculateArgumentBindings();return springAdvice;}

首先来看看核心点1,上面其实已经看过了, 但是上面的方法作用仅仅是为了获取注解上的exression表达式的,这里再调用一遍就是为注解生成Advice类的,目的就是获取切面注解与AspectJAnnotation的映射类。

protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {//看到了我们熟悉的切面方法注解,这里的beforePrint使用@Before注解Class<?>[] classesToLookFor = new Class<?>[] {Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};for (Class<?> c : classesToLookFor) {AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);if (foundAnnotation != null) {return foundAnnotation;}}return null;
}

这个方法就是查找切面方法是否实现了Before, Around, After,AfterReturning, AfterThrowing,Pointcut注解,如果实现了,则返回一个AspectJAnnotation对象,里面有一个annotation的泛型对象,这个泛型对象就是被设置为这些注解的值。最终这些对象会被转换成下面的对象存入AspectJAnnotation中:

static {//会将注解转换成后面的AspectJAnnotationType枚举的类。 annotationTypes.put(Pointcut.class,AspectJAnnotationType.AtPointcut);annotationTypes.put(After.class,AspectJAnnotationType.AtAfter);annotationTypes.put(AfterReturning.class,AspectJAnnotationType.AtAfterReturning);annotationTypes.put(AfterThrowing.class,AspectJAnnotationType.AtAfterThrowing);annotationTypes.put(Around.class,AspectJAnnotationType.AtAround);annotationTypes.put(Before.class,AspectJAnnotationType.AtBefore);
}

通过核心点1,Spring已经将注解@Before对应转换为AtBefore,@After转换成AtAfter,以此类推,都会一一映射到了核心点2的switch的条件类了,在核心点2中,会为对应的切面注解类生成Advice类。 所有的注解切面类具体实现都是由AbstractAspectJAdvice这个抽象类实现的,这个类的构造函数有三个参数:

//切面方法 这里可能是beforePrint
Method aspectJAroundAdviceMethod
//切入点表达式匹配器 这里指封装了exression的匹配器
AspectJExpressionPointcut pointcut
//切面类  这里指TracesRecordAdvisor
AspectInstanceFactory aif

下面是Spring为对应注解生成对应的Advice类
|--|--|
|注解类|Advice 顾问方法|
|AtBefore|AspectJMethodBeforeAdvice|
|AtAfter|AspectJAfterAdvice|
|AtAfterReturning|AspectJAfterReturningAdvice|
|AtAfterThrowing|AspectJAfterThrowingAdvice|
|AtAround|AspectJAroundAdvice|

各个注解会在不同的实际执行自身增强方法,这个部分只是生成Advice类,然会放入到缓存中,等真正生成代理时就会调用这些方法。这个在创建代理的时候需要具体拆开说,至此,Spring将使用了@Aspect注解的切面类的切面方法,都转换成了对应的Adivsor类,这个类包含了切面方法,封装后的切点匹配器PointCut以及生成切面类的实例对象,通过这个类就可以匹配到符合条件的目标类的目标方法,然后执行增强操作了。
由切面注解生成的Advice类,最终会放入到一个缓存中,当生成目标bean的时候,会将所有所以能够匹配到目标bean的advice放入到集合中,由一个实现了MethodInvocation的类统一管理调用过程,这个类后面会详细说到,这里简单分析下AspectJAfterAdvice的invoke方法,看看它的调用过程

	@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {try {//调用是实现了MethodInvocation方法的类 这个其实是个链式调用return mi.proceed();}finally {//最终执行后置增强方法invokeAdviceMethod(getJoinPointMatch(), null, null);}}

上面的invoke方法需要一个MethodInvocation的参数,上面的Advice类除了AspectJMethodBeforeAdvice之外,都实现了这个接口,所以可以实现链式调用,这个逻辑会在创建代理的具体讲解,这里只是简单分析下,这些advice的invoke方法规定了切面方法于要增强方法的执行时机。

11 初探AOP代理

上面一部分操作主要是处理使用了@Aspect注解的切面类,然后将切面类的所有切面方法根据使用的注解生成对应的Advisor的过程,这个Advisor包含了切面方法,切入点匹配器和切面类,也就是准好了要增强的逻辑,接下来就是要将这些逻辑注入到合适的位置进行增强,这部分的操作就是由老生常谈的代理实现的了。

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);//如果要创建的类不是提前暴露的代理 则进入下面的方法if (!this.earlyProxyReferences.contains(cacheKey)) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}

创建代理前,需要先校验bean是否需要创建代理

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {//如果bean是通过TargetSource接口获取 则直接返回if (beanName != null && this.targetSourcedBeans.contains(beanName)) {return bean;}//如果bean是切面类 直接返回if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}//如果bean是Aspect 而且允许跳过创建代理, 加入advise缓存 返回if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}//如果前面生成的advisor缓存中存在能够匹配到目标类方法的Advisor 则创建代理Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);//创建代理Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

方法很简单,主要的关注点在getAdvicesAndAdvisorsForBean和createProxy上,第一个是获取能够匹配目标类方法的Advisor集合,如果这个集合不为空,则代表该类需要被增强,需要生成代理,如果匹配不到,则表示该类并不需要被增强,无需创建代理。至于createProxy就很明显了,就是创建代理,这个方法里面决定了使用jdk代理还是cglib代理,并且用到了前面生成的Advisor实现增强功能。 这部分内容会放到下一篇文章中专门分析。

12 总结

简单总结一下,Spring AOP在初始阶段完成的主要任务:
读取配置文件阶段:

  • 读取xml文件遇到 <aop:aspectj-autoproxy/>标签时,找到命名空间处理器AopNamespaceHandler,然后找到处理该标签的类AspectJAutoProxyBeanDefinitionParser
  • 通过AspectJAutoProxyBeanDefinitionParser的parse方法,将AspectJAwareAdvisorAutoProxyCreator注册到容器的声明周期中。
    创建bean阶段:
  • 执行AspectJAwareAdvisorAutoProxyCreator的postProcessBeforeInstantiation校验目标类是否是Aspect类和AOP基础类以及是否需要跳过不需要执行代理的类
  • 获取beanDefinitions中所有使用了Aspect注解的类,然后将切面方法根据使用的注解生成Advisor类放入到缓存(关键)
  • 调用AspectJAwareAdvisorAutoProxyCreator的postProcessAfterInitialization的方法,对需要增强的类创建代理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_2382.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

npm warn config global `--global`, `--local` are deprecated. use `--location=global` instead.

报错信息: npm warn config global `--global`, `--local` are deprecated. use `--location=global` instead. 报错截图: 如何弃用 npm WARN 配置全局 –global, –local。使用“–location=global”代替“错误发生? 当我尝试使用-g的全局命令时,只是尝试安装使用npm ins…

【面试题】循环打印红绿灯

循环打印红绿灯 点击打开视频讲解更加详细 红灯3秒后变成绿灯 绿灯5秒后变成黄灯 黄灯2秒后变成红灯案例: <template><div id="app"><div>循环打印红绿灯</div><div>红灯3秒后变成绿灯</div><div>绿灯5秒后变成黄灯</…

Python custom modify the __add__ method All In One

Python custom modify the __add__ method All In OnePython 改写 `__add__` 类方法Python custom modify the add method All In OnePython 改写 __add__ 类方法"""# class Juice: # def __init__(self, name, capacity): # self.name = name # …

高亮显示指定内容

问题:海量数据中,高亮显示下表第一行的内容。 解决:开始》条件格式》突出显示单元格规则》小于="id5" 原博客各种作……所以换阵地了,不过每篇都搬过来,实在有点累,想看就自己看吧:http://blog.sina.com.cn/pureiceshadow

项目压测数据

压测流程首先启动 locust 压测脚本 然后启动bus查分模拟脚本 收集数据 压测结束,清理数据采集的数据为:请求相关数据,如响应时间,请求总数据量 资源相关,请求时pod的数量以及实时cpu,内存消耗 请求数量数量,总请求数量,时间分布 apm请求记录,查询请求具体耗时 数据库信…

认识mtv

MTV设计模式 那么 Django 的 MTV 又是怎么回事呢?下面讲解 Django 的设计模式。Django 借鉴了经典的 MVC 模式,它也将交互的过程分为了 3 个层次,也就是 MTV 设计模式;Model:数据存储层,处理所有数据相关的业务,和数据库进行交互,并提供数据的增删改查; Template:模板…

《机器学习的数学修炼》

目录:第六章 线性回归: 1.1三种方法实现:import numpy as np import pandas as pd from scipy import statsdf = pd.read_csv("DBS_SingDollar.csv") # X = df[df.columns[0]] # y = df[df.columns[1]] X = df["DBS"] Y = df["SGD"] slope,in…

变量

变量声明 在ES6以前我们通常通过var来声明变量。首先要进行变量声明,然后再进行使用 var num = 123;//声明变量num,并且赋值为123var声明多个变量 var a = 10, b = 20, c; console.log(delete c, delete b); // false false console.log(a, b, c); // 10 20 undefined// 通过…

内存颗粒, rank, chip, bank, row, column, page

【百度百科】中国港台地区把内存芯片叫做“内存颗粒”,其它芯片叫做“晶片”。百度翻译把“内存颗粒”译为"memory particle",用Bing国际版搜memory particle结果很少。 https://golerugged.com/article/284.htmlThe full name is Quad-Level Cell, a four-layer s…

蔚来杯2022牛客暑期多校训练营4 N-Particle Arts

问题描述 In a confined NIO space, there are nnn NIO particles, the iii-th of which has aia_iai​ joule energy. The NIO particles are very special as they keep colliding with each other randomly. When one particle carrying energy aaa joule collides with ano…

【SpringBoot】定时任务

SpringBoot实现定时任务 SpringBoot创建定时任务,目前主要有以下三种实现方式:基于注解(@Scheduled): 基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响; 基于接口(SchedulingConfigurer): 用于实现从数据库获取指定时间来…

蔚来杯2022牛客暑期多校训练营2 G-Link with Monotonic Subsequence

问题描述 First, lets review some definitions. Feel free to skip this part if you are familiar with them. A sequence aaa is an increasing (decreasing) subsequence of a sequence bbb if aaa can be obtained from bbb by deletion of several (possibly, zero or al…

基于NFS实现pod数据持久化

一、nfs-server服务端:挂载一块新磁盘1.1、格式化并挂载parted /dev/vdb mklable xfs parted /dev/vdb primay 0% 100% mkfs.xfs /dev/vdb1 echo "/dev/vdb1 /nfs_share xfs defaults 0 0" >> /etc/fstab mount -a 1.2、安装nfs服务apt install nfs-kernel-s…

Mybatis的缓存

1. Mybatis的一级缓存 Mybatis的一级缓存是默认开启的,你只要搭建一个Mybatis框架,就可以直接使用一级缓存。 一级缓存是SqlSession级别的,通过SqlSession查询的数据会被缓存,下次使用同一个SqlSession查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问,减轻数…

2022年多校冲刺NOIP联训测试13 51nod2023省选联训 第三场

A 隔离 二分答案,简单\(check\)一下即可code #include<cstring> #include<algorithm> #include<cstdio> #include<queue> #include<vector> #include<set> #include<map> #include<iostream> #include<random>using na…

低风险稳健策略:BTC套利策略

更多精彩内容,欢迎关注公众号:数量技术宅,也可添加技术宅个人微信号:sljsz01,与我交流。 币安零手续费带来的机会 从7月8日的20点开始,币安推出了BTC现货交易零手续费的优惠活动,不论是Maker还是Taker都不收取手续费。此次活动包括了交易量最大的BTC/USDT和BTC/BUSD。BT…

适用于ps的Raw格式图像插件

Adobe Camera Raw14 中文版是适用于ps的Raw格式图像插件,安装上Camera Raw插件能在PS中打开编辑RAW格式文件,可以说是专业摄影师必备工具。目前Adobe Camera Raw中文版已经支持大部分主流相机,可以让用户在PS中处理各种形态的RAW文件。 软件下载地址 Camera Raw 软件是作为一…

AJAX概念和AJAX实现_原生JS方式

AJAX概念:概念:ASynchronous JavaScript And XML 异步的JavaScript和XML AJAX是一种在无需重新加载整个网页的情况下能够更新部分网页的技术。通过在后台于服务器进行少量数据叫唤,Ajax可以使网页实现异步更新,这意味着可以在不重新加载整个网页的情况下,对网页的某部分进…

RadioGroup+RadioButton进行美化,以替换之前的选择状态

效果图: 1、进行样式定义 radio_sel_state:<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_checked="false"android:drawab…

Hadoop常见的文件格式及压缩算法

前言该文章中将会整理一些大数据中常见的文件格式及压缩算法的理论知识,作为后期实践的理论指导。理论+实践才会更方便用这些文件格式和压缩算法。 目前hadoop中常见的文件格式有textfile、sequencefile、avro、rcfile、orcfile、parquet等,上述六种文件格式又可以划分为行…