Spring-Aop源码解析(中)

Spring-Aop源码解析(上)上文讲解了到底什么是Aop,以及围绕方法该如何去找对应的增强点,包括整个Advisor链路的执行顺序,本文来对上文中存在的一些关键点进行一个深入挖掘

Advice:要增强的逻辑,就是我们执行额外逻辑的那坨代码,比如要在test方法之前横向插入一段逻辑,那么我们就需要实现BeforeAdvice接口,来重写里面的before方法。

JointPoint:具体要增强的哪个点,比如我要对UserService下的test方法进行增强,那么test方法就是JointPoint

PointCut:切入点,我要对哪一些点进行增强, 这些点就被我们称为PointCut,就是一类JoinPoint

Advisor:PointCut+Advice,我要对哪一类进行一个额外的逻辑增强,Advisor可以理解为一种行为

下面的代码可以一目了然:

设置一个Advisor,并且在Advisor中可以设置PointCut和Advice,PointCut中又分为ClassFilter过滤和MethodMatcher过滤,MethodMatcher中的runtime属性设置成true的时候会对方法中的参数进行一个额外的逻辑校验。Advice就是我们自己要实现的增强逻辑,所以这段代码翻译成中文就是,我要对UserService下的test方法,并且test方法中只有一个参数的这个方法进行前置逻辑的增强。那么在Spring中是如何实现这段逻辑的

java 复制代码
		proxyFactory.addAdvisor(new PointcutAdvisor() {
			@Override
			public Pointcut getPointcut() {
				return new Pointcut() {
					@Override
					public ClassFilter getClassFilter() {
					return 	new ClassFilter() {
							@Override
							public boolean matches(Class<?> clazz) {
								//只对UserSer的类进行增强
								return UserService.class.equals(clazz);
							}
						};
					}
					@Override
					public MethodMatcher getMethodMatcher() {
						return new MethodMatcher() {
							@Override
							public boolean matches(Method method, Class<?> targetClass) {
								//只对UserService下的test方法进行增强
								if(method.equals("test")){
									return true;
								}
								return false;
							}

							@Override
							public boolean isRuntime() {
								//如果这边设置成true,那么下面那个matches才会生效
								return true;
							}

							@Override
							public boolean matches(Method method, Class<?> targetClass, Object... args) {
								//根据方法中的参数进行额外逻辑的判断
								if(args.length == 1){
									return true;
								}
								return false;
							}
						};
					}
				};
			}

			@Override
			public Advice getAdvice() {
				//实现了BeforeAdvice的一个类
				return new MyBeforeAdvice();
			}

			@Override
			public boolean isPerInstance() {
				return false;
			}
		});

Spring-Aop源码解析(上)篇中,我们遗留了一个方法getCallbacks的方法,这里面就是Spring处理这些Advisor的实现逻辑,getCallBacks中核心逻辑如下

java 复制代码
	private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
		
		//获取被代理对象的所有方法
		Method[] methods = rootClass.getMethods();
		Callback[] fixedCallbacks = new Callback[methods.length];
		for (int x = 0; x < methods.length; x++) {
			Method method = methods[x];
			//将方法和被代理类传入,进行Advisor的过滤,到底哪些Advisor是被我需要的
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
			//针对于我要增强的方法和类得到的代理链(Advice链),生成一个Interceptor,然后缓存
			fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
					chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
			this.fixedInterceptorMap.put(method, x);
		}
	}

继续追踪源码:AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice->DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice,这块代码的逻辑就完美呈现了PointCut到底是如何去匹配的,如果匹配,就将该Advisor转换成成MethodInterceptor(这里面会借用适配器去强行转换),所以最后我们返回的就是针对于匹配到的被代理对象的一串List<MethodInterceptor>,上文中的疑惑(DynamicMethodMatcher到底是啥玩意,包括Advisor是如何整合成MethodInterceptor的)也在本文中得到了解析

java 复制代码
	public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
			Advised config, Method method, @Nullable Class<?> targetClass) {
		// 从ProxyFactory中拿到所设置的Advice(添加时被封装成了DefaultPointcutAdvisor)
		// 添加的时候会控制顺序
        //这个Advisor是怎么塞进去的会在后续章节详细讲解,主要是在Bean的生命周期初始化后那边塞得
		Advisor[] advisors = config.getAdvisors();
		List<Object> interceptorList = new ArrayList<>(advisors.length);
		Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
		for (Advisor advisor : advisors) {
			if (advisor instanceof PointcutAdvisor) {
				PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
				// 先匹配类
				if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
					// 再匹配方法
					MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
					boolean match = mm.matches(method, actualClass);
					if (match) {
						// 如果匹配则将Advisor封装成为Interceptor,当前Advisor中的Advice可能即是MethodBeforeAdvice,也是ThrowsAdvice
						MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
						if (mm.isRuntime()) {
							//如果是runTIme属性被设置成true,那么会将它封装成Dynamic类型,就是针对方法额外做了一个参数的匹配
							for (MethodInterceptor interceptor : interceptors) {
								interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
							}
						}
						else {
							interceptorList.addAll(Arrays.asList(interceptors));
						}
					}
				// 最终,interceptorList中存储的是当前正在执行的Method所匹配的MethodInterceptor,可能动态的,也可能是非动态的,
				// 找到Method所匹配的MethodInterceptor后,就会开始调用这些MethodInterceptor,如果是动态的,会额外进行方法参数的匹配
				}
			}}
	}

    //Advisor是如何转换成MethodInterceptor的
	@Override
	public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
		List<MethodInterceptor> interceptors = new ArrayList<>(3);
		Advice advice = advisor.getAdvice();
        //如果默认是MethodInterceptor,那么就直接转
		if (advice instanceof MethodInterceptor) {
			interceptors.add((MethodInterceptor) advice);
		}
        //借助于适配器,将其转换成MethodInterceptor
		// 将Advice适配成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]);
	}

总结:

Advisor中可以自行的设置PointCut和Advice,在方法执行的时候(method.invoke())PointCut则会对被代理对象的类和方法进行匹配,如果匹配成功,那么就会将该Advisor打包成一个MethodInterceptor链(因为可能有很多增强逻辑,针对于同一个类或者方法),然后返回。匹配逻辑则是先匹配类,在匹配方法,如果runTime属性设置成true,那么会再去匹配方法中的参数是否匹配,然后针对于参数这种情况会返回一个DynamicMethodMatcher(上文中有讲解到这是在哪用的)

将Advisor转换成MethodInterceptor的适配器如下,Spring-Aop并没有提供给我们AfterAdvice的适配器,但是Aspectj里面有就是@After注解(AspectJAfterAdvice),你要的话可以自己使用,他是实现了MethodInterceptor接口的,所以在之前就可以直接强转了,不用这些适配器

java 复制代码
	public DefaultAdvisorAdapterRegistry() {
		registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
		registerAdvisorAdapter(new AfterReturningAdviceAdapter());
		registerAdvisorAdapter(new ThrowsAdviceAdapter());
	}
相关推荐
devlei3 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
pshdhx_albert4 小时前
AI agent实现打字机效果
java·http·ai编程
沉鱼.445 小时前
第十二届题目
java·前端·算法
努力的小郑5 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
赫瑞5 小时前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
Victor3566 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3566 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁6 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp6 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
周末也要写八哥7 小时前
多进程和多线程的特点和区别
java·开发语言·jvm