曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了

三国梦回   2020-03-05 我要评论
# 写在前面的话 相关背景及资源: [曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享](https://www.cnblogs.com/grey-wolf/p/12044199.html) [曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解](https://www.cnblogs.com/grey-wolf/p/12051957.html ) [曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下](https://www.cnblogs.com/grey-wolf/p/12070377.html) [曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?](https://www.cnblogs.com/grey-wolf/p/12078673.html) [曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean](https://www.cnblogs.com/grey-wolf/p/12093929.html) [曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的](https://www.cnblogs.com/grey-wolf/p/12114604.html ) [曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)](https://www.cnblogs.com/grey-wolf/p/12151809.html) [曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)](https://www.cnblogs.com/grey-wolf/p/12158935.html) [曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)](https://www.cnblogs.com/grey-wolf/p/12189842.html) [曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)](https://www.cnblogs.com/grey-wolf/p/12199334.html) [曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)](https://www.cnblogs.com/grey-wolf/p/12203743.html) [曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)](https://www.cnblogs.com/grey-wolf/p/12214408.html) [曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)](https://www.cnblogs.com/grey-wolf/p/12228958.html) [曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成](https://www.cnblogs.com/grey-wolf/p/12283544.html) [曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)](https://www.cnblogs.com/grey-wolf/p/12288391.html) [曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)](https://www.cnblogs.com/grey-wolf/p/12314954.html) [曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)](https://www.cnblogs.com/grey-wolf/p/12317612.html) [曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)](https://www.cnblogs.com/grey-wolf/p/12322587.html) [曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)](https://www.cnblogs.com/grey-wolf/p/12359963.html) [曹工说Spring Boot源码(20)-- 码网恢恢,疏而不漏,如何记录Spring RedisTemplate每次操作日志](https://www.cnblogs.com/grey-wolf/p/12375656.html) [曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了](https://www.cnblogs.com/grey-wolf/p/12384356.html) [工程代码地址](https://gitee.com/ckl111/spring-boot-first-version-learn ) [思维导图地址](https://www.processon.com/view/link/5deeefdee4b0e2c298aa5596) 工程结构图: ![](https://img2018.cnblogs.com/blog/519126/201912/519126-20191215144930717-1919774390.png) # 概要 本讲,主要讲讲,spring aop和aspectJ到底啥关系,如果说spring aop依赖aspectJ,那么,到底是哪儿依赖它了? 得讲证据啊,对不对? 其实,我可以先说下结论。spring aop是基于代理的,有接口的时候,就是基于jdk 动态代理,jdk动态代理是只能对方法进行代理的,因为在Proxy.newInstance创建代理时,传入的第三个参数为java.lang.reflect.InvocationHandler,该接口只有一个方法: ```java public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; ``` 这里面的method,就是被调用的方法,所以,jdk动态代理,是只能对方法进行代理。 而aspectJ就要强大多了,可以对field、constructor的访问进行拦截;而且,spring aop的采用运行期间去生成目标对象的代理对象来实现,导致其只能在运行期工作。 而我们知道,AspectJ是可以在编译期通过特殊的编译期,就把切面逻辑,织入到class中,而且可以嵌入切面逻辑到任意地方,比如constructor、静态初始化块、field的set/get等; 另外,AspectJ也支持LTW,前面几讲我们讲过这个东西,即在jvm加载class的时候,去修改class字节码。 AspectJ也无意去搞运行期织入,Spring aop也无意去搞编译期和类加载期织入说了半天,spring aop看起来和AspectJ没半点交集啊,但是,他们真的毫无关系吗? 我打开了ide里,spring-aop-5.1.9.RELEASE的pom文件,里面清楚看到了 ```xml 4.0.0 org.springframework spring-aop 5.1.9.RELEASE Spring AOP ... org.springframework spring-beans 5.1.9.RELEASE compile org.springframework spring-core 5.1.9.RELEASE compile com.jamonapi jamon 2.81 compile true org.apache.commons commons-pool2 2.6.0 compile true // 就是这里 org.aspectj aspectjweaver 1.9.4 compile true ``` 所以,大家看到,spring aop依赖了aspectjweaver。到底为什么依赖它,就是我们本节的主题。 在此之前,我们先简单了解下AspectJ。 # AspectJ如何比较切点是否匹配目标Class 假设我有如下类: ```java package foo; public interface Perform { public void sing(); } ``` 然后,我们再用AspectJ的方式来定义一个切点: ```java execution(public * *.Perform.sing(..)) ``` 大家一看,肯定知道,这个切点是可以匹配这个Perform类的sing方法的,但是,如果让你用程序实现呢?你怎么做? 我听说Spring最早的时候,是不依赖AspectJ的,自己写正则来完成上面的判断是否匹配切点的逻辑,但后来,不知道为啥,就变成了AspectJ了。 如果我们要用AspectJ来判断,有几步? ## 引入依赖 maven的pom里,只需要引入如下依赖: ```xml org.aspectj aspectjweaver 1.8.2 ``` ## 定义切点解析器 ```java private static final Set SUPPORTED_PRIMITIVES = new HashSet(); static { SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS); SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET); } #下面这个方法,就是来获取切点解析器的,cl是一个classloader类型的实例 /** * Initialize the underlying AspectJ pointcut parser. */ private static PointcutParser initializePointcutParser(ClassLoader cl) { PointcutParser parser = PointcutParser .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution( SUPPORTED_PRIMITIVES, cl); return parser; } ``` 大家可以看到,要获得PointcutParser的实例,只需要调用其一个静态方法,这个静态方法虽然很长,但还是很好读的,读完基本知道方法啥意思了:获取一个利用指定classloader、支持指定的原语集合的切点解析器。 ###参数1:SUPPORTED_PRIMITIVES 我们定义了一个集合,集合里塞了一堆集合,这些集合是什么呢?我简单摘抄了几个: ```java 位于org.aspectj.weaver.tools.PointcutPrimitive类: public static final PointcutPrimitive CALL = new PointcutPrimitive("call",1); public static final PointcutPrimitive EXECUTION = new PointcutPrimitive("execution",2); public static final PointcutPrimitive GET = new PointcutPrimitive("get",3); public static final PointcutPrimitive SET = new PointcutPrimitive("set",4); public static final PointcutPrimitive INITIALIZATION = new PointcutPrimitive("initialization",5); ``` 其实,这些就是代表了切点中的一些语法原语,SUPPORTED_PRIMITIVES这个集合,就是加了一堆原语,从SUPPORTED_PRIMITIVES的名字可以看出,就是说:我支持解析哪些切点。 ###参数2:ClassLoader cl 大家知道,切点表达式里是如下结构:public/private 返回值 包名.类名.方法名(参数...);这里面的类名部分,如果明确指定了,是需要去加载这个class的。这个cl就是用于加载切点中的类型部分。 原注释如下: > ``` > * When resolving types in pointcut expressions, the given classloader is used to find types. > ``` 这里有个比较有意思的部分,在生成的PointcutParser实例中,是怎么保存这个classloader的呢? ```java private WeakClassLoaderReference classLoaderReference; /** * Set the classloader that this parser should use for type resolution. * * @param aLoader */ protected void setClassLoader(ClassLoader aLoader) { this.classLoaderReference = new WeakClassLoaderReference(aLoader); world = new ReflectionWorld(this.classLoaderReference.getClassLoader()); } ``` 可以看到,进来的classloader,作为构造器参数,new了一个WeakClassLoaderReference实例。 ```java public class WeakClassLoaderReference{ protected final int hashcode; //1. 重点关注处 private final WeakReference loaderRef; public WeakClassLoaderReference(ClassLoader loader) { loaderRef = new WeakReference(loader); if(loader == null){ // Bug: 363962 // Check that ClassLoader is not null, for instance when loaded from BootStrapClassLoader hashcode = System.identityHashCode(this); }else{ hashcode = loader.hashCode() * 37; } } public ClassLoader getClassLoader() { ClassLoader instance = (ClassLoader) loaderRef.get(); // Assert instance!=null return instance; } } ``` 上面的讲解点1,大家看到,使用了弱引用来保存,我说下原因,主要是为了避免在应用上层已经销毁了该classloader加载的所有实例、所有Class,准备回收该classloader的时候,却因为PointcutParser长期持有该classloader的引用,导致没法垃圾回收。 ## 使用切点解析器,解析切点表达式 ```java /** * Build the underlying AspectJ pointcut expression. */ private static PointcutExpression buildPointcutExpression(ClassLoader classLoader, String expression) { PointcutParser parser = initializePointcutParser(classLoader); // 讲解点1 return parser.parsePointcutExpression(expression); } ``` 讲解点1,就是目前所在位置。我们拿到切点表达式后,利用`parser.parsePointcutExpression(expression)`解析,返回的对象为PointcutExpression类型。 ## 测试 ```java public static void main(String[] args) throws NoSuchMethodException { boolean b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Performer.class); System.out.println(b); b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Main.class); System.out.println(b); b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Perform.class); System.out.println(b); } /** * 测试class匹配 * @param expression * @param clazzToBeTest * @return */ public static boolean testClassMatchExpression(String expression, Class<?> clazzToBeTest) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); PointcutExpression pointcutExpression = buildPointcutExpression(classLoader, expression); boolean b = pointcutExpression.couldMatchJoinPointsInType(clazzToBeTest); return b; } ``` 输出如下: > true Performer实现了Perform接口,所有匹配 > false Main类,当然不能匹配 > true 完全匹配 说完了class匹配,下面我们看看怎么实现方法匹配。 # AspectJ如何比较切点是否匹配目标方法 方法匹配的代码也很简单,如下: ```java public static void main(String[] args) throws NoSuchMethodException { boolean b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Performer.class); System.out.println(b); b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Main.class); System.out.println(b); b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Perform.class); System.out.println(b); Method sing = Perform.class.getMethod("sing"); b = testMethodMatchExpression("execution(public * *.*.sing(..))",sing); System.out.println(b); } /** * 测试方法匹配 * @param expression * @return */ public static boolean testMethodMatchExpression(String expression, Method targetMethod) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); PointcutExpression pointcutExpression = buildPointcutExpression(classLoader, expression); ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(targetMethod); if (shadowMatch.alwaysMatches()) { return true; } else if (shadowMatch.neverMatches()) { return false; } else if (shadowMatch.maybeMatches()) { System.out.println("可能匹配"); } return false; } ``` 主要是这个方法: ` ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(targetMethod);` 返回的shadowMatch类型实例,这个是个接口,专门用来表示:切点匹配后的结果。其注释如下: > /** * The result of asking a PointcutExpression to match at a shadow (method execution, * handler, constructor call, and so on). * */ 其有如下几个方法: ```java public interface ShadowMatch { /** * True iff the pointcut expression will match any join point at this * shadow (for example, any call to the given method). */ boolean alwaysMatches(); /** * True if the pointcut expression may match some join points at this * shadow (for example, some calls to the given method may match, depending * on the type of the caller). *

If alwaysMatches is true, then maybeMatches is always true.

*/ boolean maybeMatches(); /** * True iff the pointcut expression can never match any join point at this * shadow (for example, the pointcut will never match a call to the given * method). */ boolean neverMatches(); ... } ``` 这个接口就是告诉你,匹配了切点后,你可以找它拿结果,结果可能是:总是匹配;总是不匹配;可能匹配。 什么情况下,会返回可能匹配,我目前还没试验出来。 我跟过AspectJ的代码,发现解析处主要在以下方法: org.aspectj.weaver.patterns.SignaturePattern#matchesExactlyMethod 有兴趣的小伙伴可以看下,方法很长,以下只是一部分。 ```java private FuzzyBoolean matchesExactlyMethod(JoinPointSignature aMethod, World world, boolean subjectMatch) { if (parametersCannotMatch(aMethod)) { // System.err.println("Parameter types pattern " + parameterTypes + " pcount: " + aMethod.getParameterTypes().length); return FuzzyBoolean.NO; } // OPTIMIZE only for exact match do the pattern match now? Otherwise defer it until other fast checks complete? if (!name.matches(aMethod.getName())) { return FuzzyBoolean.NO; } // Check the throws pattern if (subjectMatch && !throwsPattern.matches(aMethod.getExceptions(), world)) { return FuzzyBoolean.NO; } // '*' trivially matches everything, no need to check further if (!declaringType.isStar()) { if (!declaringType.matchesStatically(aMethod.getDeclaringType().resolve(world))) { return FuzzyBoolean.MAYBE; } } ... } ``` 这两部分,代码就讲到这里了。我的demo源码在: # Spring aop如何依赖AspectJ 前面为什么要讲AspectJ如何进行切点匹配呢? 因为,就我所知的,就有好几处Spring Aop依赖AspectJ的例子: 1. spring 实现的ltw,org.springframework.context.weaving.AspectJWeavingEnabler里面依赖了org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter,这个是ltw的范畴,和今天的讲解其实关系不大,有兴趣可以去翻本系列的ltw相关的几篇; 2. org.springframework.aop.aspectj.AspectJExpressionPointcut,这个是重头,目前的spring aop,我们写的切点表达式,最后就是在内部用该数据结构来保存; 3. 大家如果仔细看ComponentScan注解,里面有个filter字段,可以让你自定义要扫描哪些类,filter有个类型字段,分别有如下几种枚举值: ```java /** * Specifies which types are eligible for component scanning. */ Filter[] includeFilters() default {}; /** * Specifies which types are not eligible for component scanning. * @see #resourcePattern */ Filter[] excludeFilters() default {}; /** * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}. */ @Retention(RetentionPolicy.RUNTIME) @Target({}) @interface Filter { /** * The type of filter to use. *

Default is {@link FilterType#ANNOTATION}. * @see #classes * @see #pattern */ // 讲解点1 FilterType type() default FilterType.ANNOTATION; ... /** * The pattern (or patterns) to use for the filter, as an alternative * to specifying a Class {@link #value}. *

If {@link #type} is set to {@link FilterType#ASPECTJ ASPECTJ}, * this is an AspectJ type pattern expression. If {@link #type} is * set to {@link FilterType#REGEX REGEX}, this is a regex pattern * for the fully-qualified class names to match. * @see #type * @see #classes */ String[] pattern() default {}; } ``` 其中,讲解点1,可以看到,里面默认是ANNOTATION类型,实际还有其他类型; 讲解点2,如果type选择ASPECTJ,则这里写AspectJ语法的切点表达式即可。 ```java public enum FilterType { /** * Filter candidates marked with a given annotation. * @see org.springframework.core.type.filter.AnnotationTypeFilter */ ANNOTATION, /** * Filter candidates assignable to a given type. * @see org.springframework.core.type.filter.AssignableTypeFilter */ ASSIGNABLE_TYPE, /** * 讲解点1 * Filter candidates matching a given AspectJ type pattern expression. * @see org.springframework.core.type.filter.AspectJTypeFilter */ ASPECTJ, /** * Filter candidates matching a given regex pattern. * @see org.springframework.core.type.filter.RegexPatternTypeFilter */ REGEX, /** Filter candidates using a given custom * {@link org.springframework.core.type.filter.TypeFilter} implementation. */ CUSTOM } ``` 纵观以上几点,可以发现,Spring Aop集成AspectJ,只是把切点这一套语法、@Aspect这类注解、切点的解析,都直接使用AspectJ的,没有自己另起炉灶。但是核心呢,是没有使用AspectJ的编译期注入和ltw的。 下面我们仔细讲解,上面的第二点,这也是最重要的一点。 # Spring Aop是在实现aop时(上面第二点),如何集成AspectJ 这里不会讲aop的实现流程,大家可以去翻前面几篇,从这篇往下的几篇。 [曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)](https://www.cnblogs.com/grey-wolf/p/12314954.html) ##解析xml或注解,获取AspectJExpressionPointcut 在aop解析xml或者@Aspect时,最终切点是用AspectJExpressionPointcut 类型来表示的,且被注册到了ioc容器,后续可以通过getBean直接获取该切点 ##AspectJAwareAdvisorAutoProxyCreator 后置处理器,判断切点是否匹配,来生成代理 在AspectJAwareAdvisorAutoProxyCreator 这个BeanPostProcessor对target进行处理时,会先判断该target是否需要生成代理,此时,就会使用到我们前面讲解的东西。 判断该target是否匹配切点,如果匹配,则生成代理;否则不生成。 ```java protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { ... // 获取能够匹配该target bean的拦截器,即aspect切面 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: ```java @Override protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) { List advisors = findEligibleAdvisors(beanClass, beanName); if (advisors.isEmpty()) { return DO_NOT_PROXY; } return advisors.toArray(); } protected List findEligibleAdvisors(Class beanClass, String beanName) { // 讲解点1 List candidateAdvisors = findCandidateAdvisors(); // 讲解点2 List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); return eligibleAdvisors; } ``` 讲解点1,获取全部的切面集合; 讲解点2,过滤出能够匹配target bean的切面集合 ```java protected List findAdvisorsThatCanApply( List candidateAdvisors, Class beanClass, String beanName) { ProxyCreationContext.setCurrentProxiedBeanName(beanName); try { return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); } finally { ProxyCreationContext.setCurrentProxiedBeanName(null); } } ``` ```java public static List findAdvisorsThatCanApply(List candidateAdvisors, Class<?> clazz) { if (candidateAdvisors.isEmpty()) { return candidateAdvisors; } for (Advisor candidate : candidateAdvisors) { // canApply就是判断切面和target的class是否匹配 if (canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); } } return eligibleAdvisors; } ``` 所以,重点就来到了canApply方法: ```java public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { if (advisor instanceof PointcutAdvisor) { PointcutAdvisor pca = (PointcutAdvisor) advisor; //讲解点1 return canApply(pca.getPointcut(), targetClass); } else { // It doesn't have a pointcut so we assume it applies. return true; } } ``` 讲解点1,就是首先pca.getPointcut()获取了切点,然后调用了如下方法: ```java org.springframework.aop.support.AopUtils#canApply public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) { //讲解点1 if (!pc.getClassFilter().matches(targetClass)) { return false; } MethodMatcher methodMatcher = pc.getMethodMatcher(); // 讲解点2 Set classes = new HashSet(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); classes.add(targetClass); for (Class<?> clazz : classes) { Method[] methods = clazz.getMethods(); for (Method method : methods) { // 讲解点3 if (methodMatcher.matches(method, targetClass)) { return true; } } } return false; } ``` 这里,其实就是使用Pointcut来匹配target class了。具体两个过程: * 讲解点1,使用PointCut的classFilter,直接过滤掉不匹配的target Class * 讲解点2,这里是获取target类实现的所有接口 * 讲解点3,在2的基础上,获取每个class的每个method,判断是否匹配切点 所以,匹配切点的工作,落在了 ```java methodMatcher.matches(method, targetClass) ``` 因为,AspectJExpressionPointcut 这个类,自己实现了MethodMatcher,所以,上面的`methodMatcher.matches(method, targetClass)`实现逻辑,其实就在: `org.springframework.aop.aspectj.AspectJExpressionPointcut#matches` ![](https://img2020.cnblogs.com/blog/519126/202003/519126-20200305085941786-46915195.png) 我们只要看它怎么来实现matches方法即可。 ```java public boolean matches(Method method, Class targetClass, boolean beanHasIntroductions) { checkReadyToMatch(); Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass); ShadowMatch shadowMatch = getShadowMatch(targetMethod, method); if (shadowMatch.alwaysMatches()) { return true; } else if (shadowMatch.neverMatches()) { return false; } else { // the maybe case return (beanHasIntroductions || matchesIgnoringSubtypes(shadowMatch) || matchesTarget(shadowMatch, targetClass)); } } private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) { // 讲解点1 ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod); if (shadowMatch == null) { synchronized (this.shadowMatchCache) { // Not found - now check again with full lock... Method methodToMatch = targetMethod; shadowMatch = this.shadowMatchCache.get(methodToMatch); if (shadowMatch == null) { // 讲解点2 shadowMatch = this.pointcutExpression.matchesMethodExecution(targetMethod); if (shadowMatch.maybeMatches() && fallbackPointcutExpression!=null) { shadowMatch = new DefensiveShadowMatch(shadowMatch, fallbackPointcutExpression.matchesMethodExecution(methodToMatch)); } //讲解点3 this.shadowMatchCache.put(targetMethod, shadowMatch); } } } return shadowMatch; } ``` 这里三个讲解点。 * 1,判断是否有该method的结果缓存,没有则,进入讲解点2 * 2,使用pointcutExpression.matchesMethodExecution(targetMethod)匹配,返回值为shadowMatch,这个和我们最前面讲的AspectJ的切点匹配,已经串起来了。 * 3,放进缓存,方便后续使用。 至于其pointcutExpression的生成,这个和AspectJ的类似,就不说了。 ## 如果生成代理,对代理调用目标方法时,还会进行一次切点匹配 假设,经过上述步骤,我们生成了代理,这里假设为jdk动态代理类型,其最终的动态代理对象的invocationHandler类如下: ```java final class JdkDynamicAopProxy implements AopProxy, InvocationHandler ``` 其invoke方法内: ```java public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; ... try { Object retVal; target = targetSource.getTarget(); // 讲解点1 List 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()) { retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); } else { // We need to create a method invocation... invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); } return retVal; } } ``` 我们只关注讲解点,这里讲解点1:获取匹配目标方法和class的拦截器链。 ```java public List getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, Class targetClass) { List interceptorList = new ArrayList(config.getAdvisors().length); boolean hasIntroductions = hasMatchingIntroductions(config, targetClass); AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); for (Advisor advisor : config.getAdvisors()) { if (advisor instanceof PointcutAdvisor) { // Add it conditionally. PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; // 讲解点1 if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) { MethodInterceptor[] interceptors = registry.getInterceptors(advisor); //讲解点2 MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); //讲解点3 if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) { if (mm.isRuntime()) { ... } else { interceptorList.addAll(Arrays.asList(interceptors)); } } } } } return interceptorList; } ``` 三个讲解点。 * 1,判断切点的classfilter是否不匹配目标class,如果是,直接跳过 * 2,获取切点的methodMatcher,这里和前面讲解的串起来了,最终拿到的就是AspectJExpressionPointcut * 3,判断methodMatcher是否匹配目标method。因为前面已经缓存过了,所以这里会很快。 # 总结 希望我的讲解,让大家看明白了,如有不明白之处,可留言,我会继续改进。 总的来说,spring aop就是把aspectJ当个工具来用,切点语法、切点解析、还有大家常用的注解定义切面@Aspect、@Pointcut等等,都是aspectJ的: org.aspectj.lang.annotation.Aspect org.aspectj.lang.annotation.Pointcut。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们