spring aop 源码分析(二) 代理方法的执行过程分析
在上一篇aop源码分析时,我们已经分析了一个bean被代理的详细过程,参考:https://www.cnblogs.com/yangxiaohui227/p/13266014.html
本次主要是分析目标方法的执行过程: 测试代码在git : https://gitee.com/yangxioahui/aopdemo.git
主要代码如下:
public interface Calc {
Integer add(int num1,int num2);
}
@Component
public class MyMath implements Calc {
public Integer add(int num1,int num2){
return num1+num2;
}
}
@Aspect
@Component
public class MyAspectJ {
@Pointcut(" execution(* com.yang.xiao.hui.aop.MyMath.*(..))")
private void pointcut(){}
@Before(value = "pointcut()")
public void before(JoinPoint joinPoint){
System.out.println("before。。。。。");
}
@After(value = "pointcut()")
public void after(JoinPoint joinPoint){
System.out.println("after。。。。");
}
@AfterReturning(value = "pointcut()")
public void afterReturning(){
System.out.println("afterReturning。。。");
}
@AfterThrowing(value = "pointcut()")
public void afterThrowing(JoinPoint joinPoint){
System.out.println("afterThrowing。。。。");
}
@Around(value ="pointcut()")
public void around(ProceedingJoinPoint joinPoint){
System.out.println("around-before。。。。");
try {
Object proceed = joinPoint.proceed();
System.out.println("around-after_return。。。");
} catch (Throwable throwable) {
System.out.println("around-throw。。。"+throwable.getMessage());
}
System.out.println("around-after。。。");
}
}
@Configuration
@ComponentScan("com.yang.xiao.hui.aop")
@EnableAspectJAutoProxy()
public class App
{
public static void main( String[] args )
{
ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);
Calc myMath = (Calc)ctx.getBean("myMath");
myMath.add(3,5);
System.out.println(ctx.getBean("myMath").getClass());
}
}
测试: MyMath 类的add 方法执行过程,debug 调试:


上一篇源码分析,我们已经分析过,最终会被JdkDynamicAopProxy 的invoke方法调用,下面分析下该方法:
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; //里面封装了被代理对象,也就是MyMath
Object target = null; //被代理对象 try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { //处理equals方法
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { //处理Hashcode 方法
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {//显然,我们的方法定义所在的类是Calc
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
//this.advised.opaque 这个值是说代理类能否强转为Advised,flase是可以强转,在代理对象创建源码时分析过了,代理对象确实实现了Advised接口
//method.getDeclaringClass().isInterface() 我们的目标方法定义所在类是Calc,确实是一个接口,但该接口没有实现Advised,所以下面方法不会进入
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); //这里是直接反射调用方法: method.invoke(target, args);
} Object retVal; if (this.advised.exposeProxy) { //@EnableAspectJAutoProxy 注解的一个属性,为true时,就会将代理对象
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);//将当前的代理对象存到ThreadLocal中
setProxyContext = true;
} // Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget(); //获取被代理类的实例,在这里是MyMath的实例
Class<?> targetClass = (target != null ? target.getClass() : null); //在这里是com.yang.xiao.hui.aop.MyMath // Get the interception chain for this method. 将advisors封装成MethodInterceptor,在代理对象源码的博客介绍过,@before等注解标注的方法会被封装成advisor,所以这里取出来,封装一下,下面会详细分析该方法
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); //如果没有拦截器,获取实际参数,反射调用目标方法就行了
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); ////这里是直接反射调用方法: method.invoke(target, args);
}
else {
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //反射创建一个方法执行器,这里后续会分析,用到了责任链模式
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();//开始调用责任链
} // Massage return value if necessary.
Class<?> returnType = method.getReturnType(); //获取方法返回值类型
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { //特殊情况,责任链调用的返回值就是被代理对象(target),而方法的返回类型要求的是代理对象Proxy,这样就将代理对象返回去就可以了
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { //如果方法的返回类型不是void,并且是基本类型,那么,如果执行调用后,拿到的返回值是空,就抛异常
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;//返回得到的结果
}
finally {
if (target != null && !targetSource.isStatic()) { //如果目标代理对象不为空,同时每次调用targetSource.getTarget()返回的对象不同,也就是非单例的情况下需要释放目标对象
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy); //方法调用完毕,将ThreadLocal中的代理对象设置回之前的那个
}
}
}
//通过上面的分析,目标方法在执行时,主要分两步,取得拦截链,调用拦截链:
先分析: List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 其中AdvisedSupport: advised拥有被代理对象和advisors,在代理对象创建源码分析时,有分析过

下面分析获取chain的过程:

@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class<?> targetClass) { // This is somewhat tricky... We have to process introductions first,
// but we need to preserve order in the ultimate list.
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors(); // config 就是AdvisedSupport,它拥有advisors,在代理对象创建过程中分析过
List<Object> interceptorList = new ArrayList<>(advisors.length); //要将advisor转成方法拦截器,才能起到拦截的作用
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());//被代理类
Boolean hasIntroductions = null; for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) { //根据advisor的类型不一样,转成MethodInterceptor 逻辑也不一样
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
//config.isPreFiltered()都是返回true,代表advisors是否已经是过滤过的了
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {//主要通过切入点表达式再次校验目标类是否需要被切入
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();// 如果根据切入点表达式判断了类需要被切入,那之后就要判断方法是否需要切入,因为一个类有很多方法,并不是所有方法都需要被切入
boolean match;
if (mm instanceof IntroductionAwareMethodMatcher) { //根据方法匹配器的类型,使用不同发匹配方式
if (hasIntroductions == null) {
hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
}
match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
}
else {
match = mm.matches(method, actualClass);
}
if (match) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor); //如果advisor 匹配上了,那就要封装成方法拦截器
if (mm.isRuntime()) { /mm的类型主要有2种,一种是动态方法匹配器,永远返回true,一种是静态方法匹配器,永远返回false
// Creating a new object instance in the getInterceptors() method
// isn't a problem as we normally cache created chains.
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm)); //这里做了一个简单的封装
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors)); //将所有符合要求的interceptors 存起来
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) { //逻辑跟之前分析的差不多
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
} return interceptorList;
}
上面的方法主要是根据advisor的类型,使用不同的方法匹配器,看看目标的方法是否可以使用该advisor,是的话,就封装成MethodInterceptor,看看封装的逻辑:registry.getInterceptors(advisor);
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList<>(3);
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
for (AdvisorAdapter adapter : this.adapters) { //三种适配器: MethodBeforeAdviceAdapter/ AfterReturningAdviceAdapter/ThrowsAdviceAdapter 看看哪个匹配上,就用哪一种适配器来封装成拦截器
if (adapter.supportsAdvice(advice))
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
}
//advisor封装成interceptor的一个例子:

拦截器链获取分析完毕了,那么,我们接下来分析下,责任链的执行:


之后我们看看:invocation.proceed();
public Object proceed() throws Throwable {
//刚开始currentInterceptorIndex=-1
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { //如果最后一个拦截器执行完毕后,就直接反射调用目标方法
return invokeJoinpoint(); //method.invoke(target, args);
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //获取其中一个拦截器
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { //判断拦截器是不是动态匹配方法的
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) { //动态拦截器,执行时才去匹配
return dm.interceptor.invoke(this); //匹配得上,就调用拦截方法
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();//匹配失败,就调用下一个
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); //如果是静态的匹配的,就直接调用目标方法
}
}
debug调试调用过程:
先看看我们的链条都有哪些:

总共有六个拦截器:
























最后总结:

springboot对aop的自动装配机制:我们在springboot项目中,没有自己给启动类贴上@EnableAspectJAutoProxy 注解,aop一样生效,原理是有个配置类:

默认值如下:

我们可以在yml中配置相应的属性:

spring aop 源码分析(二) 代理方法的执行过程分析的更多相关文章
- Spring AOP 源码分析 - 创建代理对象
1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...
- Spring AOP 源码分析 - 拦截器链的执行过程
1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...
- 5.2 Spring5源码--Spring AOP源码分析二
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- 5.2 spring5源码--spring AOP源码分析二--切面的配置方式
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- Spring AOP 源码分析 - 筛选合适的通知器
1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...
- Spring AOP 源码分析系列文章导读
1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...
- Spring AOP源码分析(三):基于JDK动态代理和CGLIB创建代理对象的实现原理
AOP代理对象的创建 AOP相关的代理对象的创建主要在applyBeanPostProcessorsBeforeInstantiation方法实现: protected Object applyBea ...
- spring AOP源码分析(三)
在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...
- spring aop 源码分析(三) @Scope注解创建代理对象
一.源码环境的搭建: @Component @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON,proxyMode = ScopedP ...
随机推荐
- shell 三剑客之 sed
sed 在shell 编程里也很常用,功能强大! 同grep一样,sed提供两种方式: 方式一:stdout | sed [option] "pattern command" 从文 ...
- SpringBoot2搭建基础架构——开源软件诞生4
技术框架篇--第4篇 用日志记录“开源软件”的诞生 赤龙ERP开源地址: 点亮星标,感谢支持,加微信与开发者交流 kzca2000 码云:https://gitee.com/redragon/redr ...
- 03.AOF持久化机制配置与工作流程
一.AOF持久化的配置 配置文件redis.conf,AOF持久化默认是关闭的,默认是打开RDB持久化 appendonly yes 二.工作流程: 打开AOF持久化机制之后,redis每次接 ...
- 攻防世界——web新手练习区解题记录<1>(1-4题)
web新手练习区一至四题 第一题view_source: 题目说右键不管用了,我们先获取在线场景来看一看,我们看到这样一个网页,并且右键确实点了没什么反应,而用到右键一般就是查看网页源码 用快捷键(F ...
- mysql 8.0.11安装教程
安装环境:win7 1. 下载安装包 下载地址:https://dev.mysql.com/downloads/file/?id=476233 2. 解压zip包 3. 初始化my.ini 创建my. ...
- P3311 [SDOI2014]数数 AC自动机+数位DP
题意 给定一个正整数N和n个模式串,问不大于N的数字中有多少个不包含任意模式串,输出对\(1e^9+7\)取模后的答案. 解题思路 把所有模式串都加入AC自动机,然后跑数位DP就好了.需要注意的是,这 ...
- ASP导出数据到excel遇到的一些问题
一直用动易平台的ASP做新闻发布网站,直到现在才接触导出数据到Excel的问题,目的在于公司要统计各部门的投稿量,要做这么个东西,实现起来是挺简单的,但是第一次做,还是费了一些功夫的,特此记录一下 主 ...
- 剑指 Offer 54. 二叉搜索树的第k大节点
题目描述 给定一棵二叉搜索树,请找出其中第k大的节点. 示例1: 输入: root = [3,1,4,null,2], k = 1 3 / \ 1 4 \ 2 输出: 4 示例2: 输入: root ...
- 用于测试 SqlAnalyzer1.01 的21个测试用例
感慨:当年看着 https://www.cnblogs.com/heyang78/p/11451814.html 一文望洋兴叹,如今我也自己做出来了! 21. 原文=select name, cnt ...
- selenium+python对表格数据的操作
一.直接获取整个表格数据,包含表头 def table_info(self): tr_data=[] table_data=[] css='id=>useradmin'#根据表格id找到表格 s ...