SpringAOP的源码解析
一、SpringAOP的概念
一、AOP的基本概念
1、连接点(Joinpoint):可以被增强的方法。
2、切点(Pointcut):实际被增强的方法。
3、通知(Advice)(增强):
3.1.实际增强的逻辑部分叫做通知
3.2.通知类型包括
- 前置通知(执行方法前执行,通常用作参数日志输出、权限校验等)
- 后置通知(逻辑代码执行完,准备执行return的代码时通知,通常用作执行结果日志输出、结果加密等)
- 环绕通知(是前置通知和后置通知的综合,方法执行前和方法执行后都要执行,通常用作方法性能统计、接口耗时、统一加密、解密等)
- 异常通知(相当于try{}catch ()中catch执行的部分,程序抛出异常时执行,通常用作告警处理、事务回滚等)
- 最终通知(相当于try{}catch (Exception e){}finally { }中的finally执行的部分,通常用在关闭资源、清理缓存等业务逻辑中)
4、切面(Aspect):把通知(增强)应用到切入点的过程。
二、Spring 框架一般都是基于 AspectJ 实现 AOP 操作
(1)AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,进行 AOP 操作
三、基于 AspectJ 实现 AOP 操作
(1)基于 xml 配置文件实现
(2)基于注解方式实现(使用)
二、SpringAOP的使用
1.通过maven方式引用jar包
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.17</version>
    </dependency>
2.创建被代理接口和实现类。代码如下:
package com.ybe.aop;
public interface  Calculate {
    /**
     * 除法
     * @param numA
     * @param numB
     * @return
     */
    int div(int numA,int numB);
}
package com.ybe.aop.impl;
import com.ybe.aop.Calculate;
public class CalculateImpl implements Calculate {
    @Override
    public int div(int numA, int numB) {
        System.out.println("执行目标方法:div");
        return numA / numB;
    }
}
3.使用@Aspect注解创建切面类(Aspect),代码如下:
package com.ybe.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class LogAspectj {
    @Pointcut("execution(* com.ybe.aop.impl.CalculateImpl.*(..))")
    private void pointCut(){
    }
    @Before(value = "pointCut()")
    public void methodBefore(JoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }
    @After(value = "pointCut()")
    public void methodAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<后置通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }
    @AfterReturning(value = "pointCut()",returning = "result")
    public void methodReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<返回通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }
    @AfterThrowing(value = "pointCut()")
    public void methodAfterThrowing(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<异常通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }
}
4.创建Config 配置类,使用注解@EnableAspectJAutoProxy开启aop功能,代码如下:
package com.ybe.aop.config;
import com.ybe.aop.Calculate;
import com.ybe.aop.impl.CalculateImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.ybe.aop")
@EnableAspectJAutoProxy
public class Config {
    @Bean
    public Calculate calculate(){
        return new CalculateImpl();
    }
}
5.Main的代码
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
 Calculate proxyFactoryBean = context.getBean("calculate", Calculate.class);
 context.div(1,1);
6.运行结果如下:

三、SpringAOP的源码分析
SpringAop实现利用了SpringIoc容器。在SpringIOC容器的生命周期过程中整合了SpringAOP的功能。大概过程:通过 @Import注册 实现了ImportBeanDefinitionRegistrar 接口的 AspectJAutoProxyRegistrar 类。在该类中添加实现了 InstantiationAwareBeanPostProcessor 接口的 AnnotationAwareAspectJAutoProxyCreator 类。在创建AnnoteationConfigApplicationContext的构造函数中会调用refresh()方法。refresh方法会进行 AspectJAutoProxyRegistrar 的调用,并且生成
AnnotationAwareAspectJAutoProxyCreator 的Bean对象。在第一次调用 CreateBean 的时候,进行Advisors的创建。在创建完 Bean后会调用AnnotationAwareAspectJAutoProxyCreator的 postProcessAfterInitialization方法。从 Advisors 中查找是否匹配当前正在创建的Bean。如果能匹配,则创建相关的动态代理对象。
完整源码分析分三部分:SpringAOP的初始化、创建动态代理、代理方法调用过程。
一、SpringAOP的初始化。
主要逻辑是找到所有标注了 @Aspect 的类,并且解析类中所有的通知方法并添加到 BeanFactoryAspectJAdvisorsBuilder.advisorsCache 缓存中。
整体代码流程图如下:

说明:
- 创建 AnnotationConfigApplicationContext() 容器。 
- 在invokeBeanFactoryPostProcessors()中,会调用 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry() 。在此方法中,会找到 @EnableAspectJAutoProxy 的 @Import 属性传入的 AspectJAutoProxyRegistrar.class 类。并且执行该类的registerBeanDefinitions() 方法,创建类型为 AnnotationAwareAspectJAutoProxyCreator 、名称为org.springframework.aop. 
 config.internalAutoProxyCreator的 RootBeanDefinition注册到BeanDefinitionRegistry中。
- 在 registerBeanPostProcessors() 中会根据上面一步生成的 RootBeanDefinition对象创建 AnnotationAwareAspectJAutoProxyCreator 的实例。 
- 在 finishBeanFactoryInitialization() 中第一次执行到 AbstractAutowireCapableBeanFactory.createBean() 时,会执行一段这样的代码,如下 - try {
 // 让 BeanPostProcessors 有机会返回一个代理而不是目标 bean 实例
 Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
 if (bean != null) {
 return bean;
 }
 }
 - @Nullable
 protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
 Object bean = null;
 if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
 // Make sure bean class is actually resolved at this point.
 if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
 Class<?> targetType = determineTargetType(beanName, mbd);
 if (targetType != null) {
 bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
 if (bean != null) {
 bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
 }
 }
 }
 mbd.beforeInstantiationResolved = (bean != null);
 }
 return bean;
 }
 - @Nullable
 protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
 for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
 Object result = bp.postProcessBeforeInstantiation(beanClass, beanName);
 if (result != null) {
 return result;
 }
 }
 return null;
 }
 - 以上代码会执行 AnnotationAwareAspectJAutoProxyCreator 的 postProcessBeforeInstantiation() 方法。在该方法中会 执行 shouldSkip() 方法。代码如下: - @Override
 protected boolean shouldSkip(Class<?> beanClass, String beanName) {
 // TODO: Consider optimization by caching the list of the aspect names
 // 找到所有候选的 Advisors
 List<Advisor> candidateAdvisors = findCandidateAdvisors();
 for (Advisor advisor : candidateAdvisors) {
 if (advisor instanceof AspectJPointcutAdvisor &&
 ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
 return true;
 }
 }
 return super.shouldSkip(beanClass, beanName);
 }
 - 在 findCandidateAdvisors 中具体会生成所有的 Advisors。 - @Override
 protected List<Advisor> findCandidateAdvisors() {
 // 找到所有的 实现了 Advisor.class 接口的类,并且生成候选的 Advisors.
 List<Advisor> advisors = super.findCandidateAdvisors();
 // 创建所有的带了 @Aspect 特性的切面类 .
 if (this.aspectJAdvisorsBuilder != null) {
 advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
 }
 return advisors;
 }
 - aspectJAdvisorsBuilder.buildAspectJAdvisors() 是核心。方法里面的逻辑如下: - 1.获取容器里所有的beanNames.
 2.遍历 beanNames,根据beanName获取对应的beanType对象。
 3.判断beanType是否有@Aspect注解。
 4.如果有,调用getAdvisorMethods()通过反射获取该类型所有的 advisor 的 method 元数据。
 5.遍历 methods 调用 getAdvisor() 获取 Advisor 对象(InstantiationModelAwarePointcutAdvisorImpl)
 6.添加到 this.advisorsCache 中。
 
 postProcessBeforeInstantiation方法会缓存所有的advisor,方法的最后返回 null。至此整个 SpringAOP的初始化完成。
二、创建动态代理
 在创建Bean的生命周期的 initializeBean 方法中,会执行 AnnotationAwareAspectJAutoProxyCreator的 postProcessAfterInitialization方法。该方法会拿缓存BeanFactoryAspectJAdvisorsBuilder.advisorsCache 中所有advisor的pointCut去匹配正在创建的实例Bean的所有方法。如果 advisor 和 Bean 的某一个方法能匹配上,则把该advisor添加到 advisor的候选集合中。直到找出匹配Bean的所有Adsivors。最后根据Adsivor的候选集合和Bean类型创建动态代理对象ProxyFactory。
整体代码流程图如下:

说明:
1.List排序后的顺序为:
ExposeInvocationInterceptor
Around
Before
After
AfterReturning
AfterThrowing
2.动态代理的创建
创建动态代理有两种方法,一种是 JDK ,一种是 CGLib 。
1.如果目标类有实现接口的话,则是使用JDK的方式生成代理对象。
2.配置了使用Cglib进行动态代理或者目标类没有实现接口,那么使用Cglib的方式创建代理对象。
三、动态代理调用
以 JdkDynamicAopProxy 为例,在调用方法的时候会直接调用 JdkDynamicAopProxy.invoke()方法,里面的大概逻辑如下:
1.获取被代理的实现类;
2.找出所有匹配被调用方法的 advisor,并且转成具体的通知拦截器 MethodInterceptor,返回通知拦截器链。转换代码如下:
List<MethodInterceptor> interceptors = new ArrayList<>(3);
// 从Advisor中获取 Advice
Advice advice = advisor.getAdvice();
// 如果 advice 本身就实现了  MethodInterceptor 接口 ,则直接进行转换
if (advice instanceof MethodInterceptor) {
    interceptors.add((MethodInterceptor) advice);
}
// AfterReturningAdviceInterceptor MethodBeforeAdviceInterceptor  ThrowsAdviceInterceptor
// 这三种是通过适配器的方式进行转换 MethodInterceptor类型
for (AdvisorAdapter adapter : this.adapters) {
    if (adapter.supportsAdvice(advice)) {
        interceptors.add(adapter.getInterceptor(advisor));
    }
}
if (interceptors.isEmpty()) {
    throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
3.创建 ReflectiveMethodInvocation 对象(该对象中包括了 代理对象、被代理对象、执行的方法、方法参数、被代理对象的类型、通知拦截器链),执行该对象的proceed()方法,该方法中会进行通知拦截器链的递归调用,具体调用流程如下图。ReflectiveMethodInvocation 对象在通知拦截器链调用中作用很关键,有衔接各个拦截器的作用。
代码流程如下图:

说明:
1.在proceed方法中,会先判断当前拦截器链的索引,如果索引等于最后一个那么则执行被代理类的方法。
2.如果不是,那么先获取该通知拦截器并且执行该拦截器的 proceed 方法(方法接受 ReflectiveMethodInvocation 对象实例),每个通知拦截器中都会调用 ReflectiveMethodInvocation 对象实例 的proceed 方法。在这里会形成递归调用。
3.通知拦截器的排序请看下图:

4.五个通知拦截器的代码解释请看上面的代码流程图。
SpringAOP的源码解析的更多相关文章
- 基于注解的SpringAOP源码解析(三)
		注意,读完本篇文章需要很长很长时间 在之前的2篇文章:AOP源码分析(一)AOP源码分析(二) 中,我们搭建了SpringAOP源码分析的环境,介绍了@EnableAspectJAutoProxy注解 ... 
- SpringAOP+源码解析,切就完事了
		本文是对近期学习知识的一个总结,附带源码注释及流程图,如有不足之处,还望评论区批评指正. 目录 一.AOP.SpringAOP.AspectJ的区别 二.AOP关键术语 三.通知的五种类型 四.切入点 ... 
- Spring AOP的实现及源码解析
		在介绍AOP之前,想必很多人都听说AOP是基于动态代理和反射来实现的,那么在看AOP之前,你需要弄懂什么是动态代理和反射及它们又是如何实现的. 想了解JDK的动态代理及反射的实现和源码分析,请参见下面 ... 
- Spring系列(五):Spring AOP源码解析
		一.@EnableAspectJAutoProxy注解 在主配置类中添加@EnableAspectJAutoProxy注解,开启aop支持,那么@EnableAspectJAutoProxy到底做了什 ... 
- Spring源码解析系列汇总
		相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ... 
- Spring事务源码解析(二)获取增强
		在上一篇文章@EnableTransactionManagement注解解析中,我们搭建了源码阅读的环境,以及解析了开启Spring事务功能的注解@EnableTransactionManagemen ... 
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
		本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ... 
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
		[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ... 
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
		上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ... 
随机推荐
- 如何绑定msix中断 cpu亲和性
			echo X > /proc/irq/中断号/smp_affinity /proc/irq目录下面会为每个注册的irq创建一个以irq编号为名字的子目录,每个子目录下分别有以下条目:1.smp_ ... 
- 计算机网络:套接字(Socket)| Python socket实现服务器端与客户端通信,使用TCP socket阿里云ECS服务器与本机通信
			所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象.一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制.从所处的地位来讲,套接字上联应 ... 
- bzoj3545/bzoj3551 [ONTAK2010]Peaks/Peaks加强版
			bzoj3545/bzoj3551 [ONTAK2010]Peaks/Peaks加强版 传送门:bzoj bzoj wdnmd为什么加强版不是权限题原题却是啊 3545: [ONTAK2010]Pe ... 
- IDEA 配置Tomcat乱码解决方法
			问题:页面没有乱码,但是通过http请求的js文件会乱码,原因是由于 CharacterEncodingFilter 只会处理Servlet请求,不会处理静态文件的响应编码,所以这里需要进一步的配置. ... 
- chubby 是什么,和 zookeeper 比你怎么看?
			chubby 是 google 的,完全实现 paxos 算法,不开源.zookeeper 是 chubby的开源实现,使用 zab 协议,paxos 算法的变种. 
- Executors 类是什么?
			Executors 为 Executor,ExecutorService,ScheduledExecutorService, ThreadFactory 和 Callable 类提供了一些工具方法. ... 
- @Controller 注解?
			该注解表明该类扮演控制器的角色,Spring 不需要你继承任何其他控制器基类或 引用 Servlet API. 
- C++ pair的基本用法总结
			1,pair的应用 pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存.另一个应用是,当一个函数需要返回2个数据的时候, ... 
- Architecture Principles
			Architecture Principles - Completed Components Name Statement Rationale Implications TOGAF Principle ... 
- Vuet.js规则详解,它是你不知道的强大功能?
			Vuet.js是什么? Vuet.js是给Vue.js提供状态管理的一个工具,与vuex不同,它是一种崇尚规则定制的状态管理模式.事先将状态更新的规则写好,然后将规则注入到组件中,然后状态按照预订的规 ... 
