写在前面的话

相关背景及资源:

曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享

曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解

曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下

曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)

曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)

曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)

曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)

曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成

曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)

曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)

曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)

曹工说Spring Boot源码(20)-- 码网恢恢,疏而不漏,如何记录Spring RedisTemplate每次操作日志

曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了

工程代码地址 思维导图地址

工程结构图:

概要

本讲,主要讲讲,spring aop和aspectJ到底啥关系,如果说spring aop依赖aspectJ,那么,到底是哪儿依赖它了?

得讲证据啊,对不对?

其实,我可以先说下结论。spring aop是基于代理的,有接口的时候,就是基于jdk 动态代理,jdk动态代理是只能对方法进行代理的,因为在Proxy.newInstance创建代理时,传入的第三个参数为java.lang.reflect.InvocationHandler,该接口只有一个方法:

  1. public Object invoke(Object proxy, Method method, Object[] args)
  2. 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文件,里面清楚看到了

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  2. <modelVersion>4.0.0</modelVersion>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-aop</artifactId>
  5. <version>5.1.9.RELEASE</version>
  6. <name>Spring AOP</name>
  7. ...
  8. <dependencies>
  9. <dependency>
  10. <groupId>org.springframework</groupId>
  11. <artifactId>spring-beans</artifactId>
  12. <version>5.1.9.RELEASE</version>
  13. <scope>compile</scope>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework</groupId>
  17. <artifactId>spring-core</artifactId>
  18. <version>5.1.9.RELEASE</version>
  19. <scope>compile</scope>
  20. </dependency>
  21. <dependency>
  22. <groupId>com.jamonapi</groupId>
  23. <artifactId>jamon</artifactId>
  24. <version>2.81</version>
  25. <scope>compile</scope>
  26. <optional>true</optional>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.apache.commons</groupId>
  30. <artifactId>commons-pool2</artifactId>
  31. <version>2.6.0</version>
  32. <scope>compile</scope>
  33. <optional>true</optional>
  34. </dependency>
  35. // 就是这里
  36. <dependency>
  37. <groupId>org.aspectj</groupId>
  38. <artifactId>aspectjweaver</artifactId>
  39. <version>1.9.4</version>
  40. <scope>compile</scope>
  41. <optional>true</optional>
  42. </dependency>
  43. </dependencies>
  44. </project>

所以,大家看到,spring aop依赖了aspectjweaver。到底为什么依赖它,就是我们本节的主题。

在此之前,我们先简单了解下AspectJ。

AspectJ如何比较切点是否匹配目标Class

假设我有如下类:

  1. package foo;
  2. public interface Perform {
  3. public void sing();
  4. }

然后,我们再用AspectJ的方式来定义一个切点:

  1. execution(public * *.Perform.sing(..))

大家一看,肯定知道,这个切点是可以匹配这个Perform类的sing方法的,但是,如果让你用程序实现呢?你怎么做?

我听说Spring最早的时候,是不依赖AspectJ的,自己写正则来完成上面的判断是否匹配切点的逻辑,但后来,不知道为啥,就变成了AspectJ了。

如果我们要用AspectJ来判断,有几步?

引入依赖

maven的pom里,只需要引入如下依赖:

  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjweaver</artifactId>
  4. <version>1.8.2</version>
  5. </dependency>

定义切点解析器


  1. private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();
  2. static {
  3. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
  4. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
  5. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
  6. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
  7. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
  8. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
  9. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
  10. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
  11. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
  12. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
  13. }
  14. #下面这个方法,就是来获取切点解析器的,cl是一个classloader类型的实例
  15. /**
  16. * Initialize the underlying AspectJ pointcut parser.
  17. */
  18. private static PointcutParser initializePointcutParser(ClassLoader cl) {
  19. PointcutParser parser = PointcutParser
  20. .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
  21. SUPPORTED_PRIMITIVES, cl);
  22. return parser;
  23. }

大家可以看到,要获得PointcutParser的实例,只需要调用其一个静态方法,这个静态方法虽然很长,但还是很好读的,读完基本知道方法啥意思了:获取一个利用指定classloader、支持指定的原语集合的切点解析器。

参数1:SUPPORTED_PRIMITIVES

我们定义了一个集合,集合里塞了一堆集合,这些集合是什么呢?我简单摘抄了几个:

  1. 位于org.aspectj.weaver.tools.PointcutPrimitive类:
  2. public static final PointcutPrimitive CALL = new PointcutPrimitive("call",1);
  3. public static final PointcutPrimitive EXECUTION = new PointcutPrimitive("execution",2);
  4. public static final PointcutPrimitive GET = new PointcutPrimitive("get",3);
  5. public static final PointcutPrimitive SET = new PointcutPrimitive("set",4);
  6. public static final PointcutPrimitive INITIALIZATION = new PointcutPrimitive("initialization",5);

其实,这些就是代表了切点中的一些语法原语,SUPPORTED_PRIMITIVES这个集合,就是加了一堆原语,从SUPPORTED_PRIMITIVES的名字可以看出,就是说:我支持解析哪些切点。

参数2:ClassLoader cl

大家知道,切点表达式里是如下结构:public/private 返回值 包名.类名.方法名(参数...);这里面的类名部分,如果明确指定了,是需要去加载这个class的。这个cl就是用于加载切点中的类型部分。

原注释如下:

  1. * When resolving types in pointcut expressions, the given classloader is used to find types.

这里有个比较有意思的部分,在生成的PointcutParser实例中,是怎么保存这个classloader的呢?

  1. private WeakClassLoaderReference classLoaderReference;
  2. /**
  3. * Set the classloader that this parser should use for type resolution.
  4. *
  5. * @param aLoader
  6. */
  7. protected void setClassLoader(ClassLoader aLoader) {
  8. this.classLoaderReference = new WeakClassLoaderReference(aLoader);
  9. world = new ReflectionWorld(this.classLoaderReference.getClassLoader());
  10. }

可以看到,进来的classloader,作为构造器参数,new了一个WeakClassLoaderReference实例。

  1. public class WeakClassLoaderReference{
  2. protected final int hashcode;
  3. //1. 重点关注处
  4. private final WeakReference loaderRef;
  5. public WeakClassLoaderReference(ClassLoader loader) {
  6. loaderRef = new WeakReference(loader);
  7. if(loader == null){
  8. // Bug: 363962
  9. // Check that ClassLoader is not null, for instance when loaded from BootStrapClassLoader
  10. hashcode = System.identityHashCode(this);
  11. }else{
  12. hashcode = loader.hashCode() * 37;
  13. }
  14. }
  15. public ClassLoader getClassLoader() {
  16. ClassLoader instance = (ClassLoader) loaderRef.get();
  17. // Assert instance!=null
  18. return instance;
  19. }
  20. }

上面的讲解点1,大家看到,使用了弱引用来保存,我说下原因,主要是为了避免在应用上层已经销毁了该classloader加载的所有实例、所有Class,准备回收该classloader的时候,却因为PointcutParser长期持有该classloader的引用,导致没法垃圾回收。

使用切点解析器,解析切点表达式

  1. /**
  2. * Build the underlying AspectJ pointcut expression.
  3. */
  4. private static PointcutExpression buildPointcutExpression(ClassLoader classLoader, String expression) {
  5. PointcutParser parser = initializePointcutParser(classLoader);
  6. // 讲解点1
  7. return parser.parsePointcutExpression(expression);
  8. }

讲解点1,就是目前所在位置。我们拿到切点表达式后,利用parser.parsePointcutExpression(expression)解析,返回的对象为PointcutExpression类型。

测试

  1. public static void main(String[] args) throws NoSuchMethodException {
  2. boolean b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Performer.class);
  3. System.out.println(b);
  4. b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Main.class);
  5. System.out.println(b);
  6. b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Perform.class);
  7. System.out.println(b);
  8. }
  9. /**
  10. * 测试class匹配
  11. * @param expression
  12. * @param clazzToBeTest
  13. * @return
  14. */
  15. public static boolean testClassMatchExpression(String expression, Class<?> clazzToBeTest) {
  16. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  17. PointcutExpression pointcutExpression = buildPointcutExpression(classLoader, expression);
  18. boolean b = pointcutExpression.couldMatchJoinPointsInType(clazzToBeTest);
  19. return b;
  20. }

输出如下:

true Performer实现了Perform接口,所有匹配

false Main类,当然不能匹配

true 完全匹配

说完了class匹配,下面我们看看怎么实现方法匹配。

AspectJ如何比较切点是否匹配目标方法

方法匹配的代码也很简单,如下:

  1. public static void main(String[] args) throws NoSuchMethodException {
  2. boolean b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Performer.class);
  3. System.out.println(b);
  4. b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Main.class);
  5. System.out.println(b);
  6. b = testClassMatchExpression("execution(public * foo.Perform.*(..))", Perform.class);
  7. System.out.println(b);
  8. Method sing = Perform.class.getMethod("sing");
  9. b = testMethodMatchExpression("execution(public * *.*.sing(..))",sing);
  10. System.out.println(b);
  11. }
  12. /**
  13. * 测试方法匹配
  14. * @param expression
  15. * @return
  16. */
  17. public static boolean testMethodMatchExpression(String expression, Method targetMethod) {
  18. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  19. PointcutExpression pointcutExpression = buildPointcutExpression(classLoader, expression);
  20. ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(targetMethod);
  21. if (shadowMatch.alwaysMatches()) {
  22. return true;
  23. } else if (shadowMatch.neverMatches()) {
  24. return false;
  25. } else if (shadowMatch.maybeMatches()) {
  26. System.out.println("可能匹配");
  27. }
  28. return false;
  29. }

主要是这个方法:

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). * */

其有如下几个方法:

  1. public interface ShadowMatch {
  2. /**
  3. * True iff the pointcut expression will match any join point at this
  4. * shadow (for example, any call to the given method).
  5. */
  6. boolean alwaysMatches();
  7. /**
  8. * True if the pointcut expression may match some join points at this
  9. * shadow (for example, some calls to the given method may match, depending
  10. * on the type of the caller).
  11. * <p>If alwaysMatches is true, then maybeMatches is always true.</p>
  12. */
  13. boolean maybeMatches();
  14. /**
  15. * True iff the pointcut expression can never match any join point at this
  16. * shadow (for example, the pointcut will never match a call to the given
  17. * method).
  18. */
  19. boolean neverMatches();
  20. ...
  21. }

这个接口就是告诉你,匹配了切点后,你可以找它拿结果,结果可能是:总是匹配;总是不匹配;可能匹配。

什么情况下,会返回可能匹配,我目前还没试验出来。

我跟过AspectJ的代码,发现解析处主要在以下方法:

org.aspectj.weaver.patterns.SignaturePattern#matchesExactlyMethod

有兴趣的小伙伴可以看下,方法很长,以下只是一部分。

  1. private FuzzyBoolean matchesExactlyMethod(JoinPointSignature aMethod, World world, boolean subjectMatch) {
  2. if (parametersCannotMatch(aMethod)) {
  3. // System.err.println("Parameter types pattern " + parameterTypes + " pcount: " + aMethod.getParameterTypes().length);
  4. return FuzzyBoolean.NO;
  5. }
  6. // OPTIMIZE only for exact match do the pattern match now? Otherwise defer it until other fast checks complete?
  7. if (!name.matches(aMethod.getName())) {
  8. return FuzzyBoolean.NO;
  9. }
  10. // Check the throws pattern
  11. if (subjectMatch && !throwsPattern.matches(aMethod.getExceptions(), world)) {
  12. return FuzzyBoolean.NO;
  13. }
  14. // '*' trivially matches everything, no need to check further
  15. if (!declaringType.isStar()) {
  16. if (!declaringType.matchesStatically(aMethod.getDeclaringType().resolve(world))) {
  17. return FuzzyBoolean.MAYBE;
  18. }
  19. }
  20. ...
  21. }

这两部分,代码就讲到这里了。我的demo源码在:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/aspectj-pointcut-matcher-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有个类型字段,分别有如下几种枚举值:

    1. /**
    2. * Specifies which types are eligible for component scanning.
    3. */
    4. Filter[] includeFilters() default {};
    5. /**
    6. * Specifies which types are not eligible for component scanning.
    7. * @see #resourcePattern
    8. */
    9. Filter[] excludeFilters() default {};
    10. /**
    11. * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
    12. * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
    13. */
    14. @Retention(RetentionPolicy.RUNTIME)
    15. @Target({})
    16. @interface Filter {
    17. /**
    18. * The type of filter to use.
    19. * <p>Default is {@link FilterType#ANNOTATION}.
    20. * @see #classes
    21. * @see #pattern
    22. */
    23. // 讲解点1
    24. FilterType type() default FilterType.ANNOTATION;
    25. ...
    26. /**
    27. * The pattern (or patterns) to use for the filter, as an alternative
    28. * to specifying a Class {@link #value}.
    29. * <p>If {@link #type} is set to {@link FilterType#ASPECTJ ASPECTJ},
    30. * this is an AspectJ type pattern expression. If {@link #type} is
    31. * set to {@link FilterType#REGEX REGEX}, this is a regex pattern
    32. * for the fully-qualified class names to match.
    33. * @see #type
    34. * @see #classes
    35. */
    36. String[] pattern() default {};
    37. }

    其中,讲解点1,可以看到,里面默认是ANNOTATION类型,实际还有其他类型;

    讲解点2,如果type选择ASPECTJ,则这里写AspectJ语法的切点表达式即可。

    1. public enum FilterType {
    2. /**
    3. * Filter candidates marked with a given annotation.
    4. * @see org.springframework.core.type.filter.AnnotationTypeFilter
    5. */
    6. ANNOTATION,
    7. /**
    8. * Filter candidates assignable to a given type.
    9. * @see org.springframework.core.type.filter.AssignableTypeFilter
    10. */
    11. ASSIGNABLE_TYPE,
    12. /**
    13. * 讲解点1
    14. * Filter candidates matching a given AspectJ type pattern expression.
    15. * @see org.springframework.core.type.filter.AspectJTypeFilter
    16. */
    17. ASPECTJ,
    18. /**
    19. * Filter candidates matching a given regex pattern.
    20. * @see org.springframework.core.type.filter.RegexPatternTypeFilter
    21. */
    22. REGEX,
    23. /** Filter candidates using a given custom
    24. * {@link org.springframework.core.type.filter.TypeFilter} implementation.
    25. */
    26. CUSTOM
    27. }

纵观以上几点,可以发现,Spring Aop集成AspectJ,只是把切点这一套语法、@Aspect这类注解、切点的解析,都直接使用AspectJ的,没有自己另起炉灶。但是核心呢,是没有使用AspectJ的编译期注入和ltw的。

下面我们仔细讲解,上面的第二点,这也是最重要的一点。

Spring Aop是在实现aop时(上面第二点),如何集成AspectJ

这里不会讲aop的实现流程,大家可以去翻前面几篇,从这篇往下的几篇。

曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

解析xml或注解,获取AspectJExpressionPointcut

在aop解析xml或者@Aspect时,最终切点是用AspectJExpressionPointcut 类型来表示的,且被注册到了ioc容器,后续可以通过getBean直接获取该切点

AspectJAwareAdvisorAutoProxyCreator 后置处理器,判断切点是否匹配,来生成代理

在AspectJAwareAdvisorAutoProxyCreator 这个BeanPostProcessor对target进行处理时,会先判断该target是否需要生成代理,此时,就会使用到我们前面讲解的东西。

判断该target是否匹配切点,如果匹配,则生成代理;否则不生成。

  1. protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
  2. ...
  3. // 获取能够匹配该target bean的拦截器,即aspect切面
  4. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
  5. // 如果返回结果为:需要生成代理;则生成代理
  6. if (specificInterceptors != DO_NOT_PROXY) {
  7. this.advisedBeans.put(cacheKey, Boolean.TRUE);
  8. Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors,
  9. new SingletonTargetSource(bean));
  10. this.proxyTypes.put(cacheKey, proxy.getClass());
  11. return proxy;
  12. }
  13. this.advisedBeans.put(cacheKey, Boolean.FALSE);
  14. return bean;
  15. }

我们主要看getAdvicesAndAdvisorsForBean:

  1. @Override
  2. protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) {
  3. List advisors = findEligibleAdvisors(beanClass, beanName);
  4. if (advisors.isEmpty()) {
  5. return DO_NOT_PROXY;
  6. }
  7. return advisors.toArray();
  8. }
  9. protected List<Advisor> findEligibleAdvisors(Class beanClass, String beanName) {
  10. // 讲解点1
  11. List<Advisor> candidateAdvisors = findCandidateAdvisors();
  12. // 讲解点2
  13. List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
  14. return eligibleAdvisors;
  15. }

讲解点1,获取全部的切面集合;

讲解点2,过滤出能够匹配target bean的切面集合

  1. protected List<Advisor> findAdvisorsThatCanApply(
  2. List<Advisor> candidateAdvisors, Class beanClass, String beanName) {
  3. ProxyCreationContext.setCurrentProxiedBeanName(beanName);
  4. try {
  5. return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
  6. }
  7. finally {
  8. ProxyCreationContext.setCurrentProxiedBeanName(null);
  9. }
  10. }
  1. public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
  2. if (candidateAdvisors.isEmpty()) {
  3. return candidateAdvisors;
  4. }
  5. for (Advisor candidate : candidateAdvisors) {
  6. // canApply就是判断切面和target的class是否匹配
  7. if (canApply(candidate, clazz)) {
  8. eligibleAdvisors.add(candidate);
  9. }
  10. }
  11. return eligibleAdvisors;
  12. }

所以,重点就来到了canApply方法:

  1. public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
  2. if (advisor instanceof PointcutAdvisor) {
  3. PointcutAdvisor pca = (PointcutAdvisor) advisor;
  4. //讲解点1
  5. return canApply(pca.getPointcut(), targetClass);
  6. }
  7. else {
  8. // It doesn't have a pointcut so we assume it applies.
  9. return true;
  10. }
  11. }

讲解点1,就是首先pca.getPointcut()获取了切点,然后调用了如下方法:

  1. org.springframework.aop.support.AopUtils#canApply
  2. public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
  3. //讲解点1
  4. if (!pc.getClassFilter().matches(targetClass)) {
  5. return false;
  6. }
  7. MethodMatcher methodMatcher = pc.getMethodMatcher();
  8. // 讲解点2
  9. Set<Class> classes = new HashSet<Class>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
  10. classes.add(targetClass);
  11. for (Class<?> clazz : classes) {
  12. Method[] methods = clazz.getMethods();
  13. for (Method method : methods) {
  14. // 讲解点3
  15. if (methodMatcher.matches(method, targetClass)) {
  16. return true;
  17. }
  18. }
  19. }
  20. return false;
  21. }

这里,其实就是使用Pointcut来匹配target class了。具体两个过程:

  • 讲解点1,使用PointCut的classFilter,直接过滤掉不匹配的target Class
  • 讲解点2,这里是获取target类实现的所有接口
  • 讲解点3,在2的基础上,获取每个class的每个method,判断是否匹配切点

所以,匹配切点的工作,落在了

  1. methodMatcher.matches(method, targetClass)

因为,AspectJExpressionPointcut 这个类,自己实现了MethodMatcher,所以,上面的methodMatcher.matches(method, targetClass)实现逻辑,其实就在:

org.springframework.aop.aspectj.AspectJExpressionPointcut#matches

我们只要看它怎么来实现matches方法即可。

  1. public boolean matches(Method method, Class targetClass, boolean beanHasIntroductions) {
  2. checkReadyToMatch();
  3. Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
  4. ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);
  5. if (shadowMatch.alwaysMatches()) {
  6. return true;
  7. }
  8. else if (shadowMatch.neverMatches()) {
  9. return false;
  10. }
  11. else {
  12. // the maybe case
  13. return (beanHasIntroductions || matchesIgnoringSubtypes(shadowMatch) || matchesTarget(shadowMatch, targetClass));
  14. }
  15. }
  16. private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
  17. // 讲解点1
  18. ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod);
  19. if (shadowMatch == null) {
  20. synchronized (this.shadowMatchCache) {
  21. // Not found - now check again with full lock...
  22. Method methodToMatch = targetMethod;
  23. shadowMatch = this.shadowMatchCache.get(methodToMatch);
  24. if (shadowMatch == null) {
  25. // 讲解点2
  26. shadowMatch = this.pointcutExpression.matchesMethodExecution(targetMethod);
  27. if (shadowMatch.maybeMatches() && fallbackPointcutExpression!=null) {
  28. shadowMatch = new DefensiveShadowMatch(shadowMatch,
  29. fallbackPointcutExpression.matchesMethodExecution(methodToMatch));
  30. }
  31. //讲解点3
  32. this.shadowMatchCache.put(targetMethod, shadowMatch);
  33. }
  34. }
  35. }
  36. return shadowMatch;
  37. }

这里三个讲解点。

  • 1,判断是否有该method的结果缓存,没有则,进入讲解点2
  • 2,使用pointcutExpression.matchesMethodExecution(targetMethod)匹配,返回值为shadowMatch,这个和我们最前面讲的AspectJ的切点匹配,已经串起来了。
  • 3,放进缓存,方便后续使用。

至于其pointcutExpression的生成,这个和AspectJ的类似,就不说了。

如果生成代理,对代理调用目标方法时,还会进行一次切点匹配

假设,经过上述步骤,我们生成了代理,这里假设为jdk动态代理类型,其最终的动态代理对象的invocationHandler类如下:

  1. final class JdkDynamicAopProxy implements AopProxy, InvocationHandler

其invoke方法内:


  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. MethodInvocation invocation;
  3. Object oldProxy = null;
  4. boolean setProxyContext = false;
  5. TargetSource targetSource = this.advised.targetSource;
  6. Class targetClass = null;
  7. Object target = null;
  8. ...
  9. try {
  10. Object retVal;
  11. target = targetSource.getTarget();
  12. // 讲解点1
  13. List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
  14. // Check whether we have any advice. If we don't, we can fallback on direct
  15. // reflective invocation of the target, and avoid creating a MethodInvocation.
  16. if (chain.isEmpty()) {
  17. retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
  18. }
  19. else {
  20. // We need to create a method invocation...
  21. invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
  22. // Proceed to the joinpoint through the interceptor chain.
  23. retVal = invocation.proceed();
  24. }
  25. return retVal;
  26. }
  27. }

我们只关注讲解点,这里讲解点1:获取匹配目标方法和class的拦截器链。

  1. public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
  2. Advised config, Method method, Class targetClass) {
  3. List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
  4. boolean hasIntroductions = hasMatchingIntroductions(config, targetClass);
  5. AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
  6. for (Advisor advisor : config.getAdvisors()) {
  7. if (advisor instanceof PointcutAdvisor) {
  8. // Add it conditionally.
  9. PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
  10. // 讲解点1
  11. if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
  12. MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
  13. //讲解点2
  14. MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
  15. //讲解点3
  16. if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
  17. if (mm.isRuntime()) {
  18. ...
  19. }
  20. else {
  21. interceptorList.addAll(Arrays.asList(interceptors));
  22. }
  23. }
  24. }
  25. }
  26. }
  27. return interceptorList;
  28. }

三个讲解点。

  • 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。

曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了的更多相关文章

  1. Spring Boot源码(八):Spring AOP源码

    关于spring aop的应用参见:Spring AOP-基于@AspectJ风格 spring在初始化容器时就会生成代理对象: 关于创建bean的源码参见:Spring Boot源码(六):Bean ...

  2. 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享

    写在前面的话&&About me 网上写spring的文章多如牛毛,为什么还要写呢,因为,很简单,那是人家写的:网上都鼓励你不要造轮子,为什么你还要造呢,因为,那不是你造的. 我不是要 ...

  3. 曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  4. 曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  5. 曹工说Spring Boot源码(23)-- ASM又立功了,Spring原来是这么递归获取注解的元注解的

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  6. 曹工说Spring Boot源码(24)-- Spring注解扫描的瑞士军刀,asm技术实战(上)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  7. 曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  8. 曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎

    曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎 写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean De ...

  9. 曹工说Spring Boot源码(27)-- Spring的component-scan,光是include-filter属性的各种配置方式,就够玩半天了.md

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

随机推荐

  1. day22- hashlib模块-摘要算法(哈希算法)

    # python的hashlib提供了常见的摘要算法,如md5(md5算法),sha1等等.摘要:digest # 摘要算法又称哈希算法.散列算法. # 它通过一个函数,把任意长度的数据(明文)转换为 ...

  2. Java常用基本类库总结

    1.String成员方法 boolean equals(Object obj);//判断字符串的内容是否相同,区分大小写. boolean equalsIgnoreCase(String str);/ ...

  3. LiauidCrystal

    1.begin()函数语法: lcd.begin(cols,rows) cols:列数: rows:行数: 2.print()函数,语法: lcd.print(data) lcd.print(data ...

  4. MySQL表与表的关系

    表与表的关系 一对多关系 ID name gender dep_name dep_desc 1 Chen male 教学部 教书育人 2 Old flying skin male 外交部 漂泊游荡 3 ...

  5. 学习python-20191203-Python Flask高级编程开发鱼书_第02章 Flask的基本原理与核心知识

    视频01: 做一个产品时,一定要对自己的产品有一个明确的定位.并可以用一句话来概括自己产品的核心价值或功能. 鱼书网站几个功能 1.选择要赠送的书籍,向他人赠送书籍(价值功能,核心价值的主线): 2. ...

  6. Filezilla Server搭建FTP服务器

    一.下载Filezilla  Server 官网网址:https://filezilla-project.org 下载链接 :https://filezilla-project.org/downloa ...

  7. 为Nginx启用目录浏览功能

    今天工作需要,要给客户提供一个patch的下载地址,于是想用nginx的目录浏览功能来做,需要让客户看到指定一个目录下的文件列表,然后让他自己来选择该下载那个文件: 我们都知道在apache下可以配置 ...

  8. org.apache.http.NoHttpResponseException

    org.apache.http.NoHttpResponseException 异常: org.apache.http.NoHttpResponseException: The target serv ...

  9. [LC] 64. Minimum Path Sum

    Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which ...

  10. Markdown 内嵌 HTML 语法

    Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式.Markdown内嵌HTML,本文总结了一些常用的HTML标记用于扩展Markdow ...