死磕Spring AOP系列4:剖析AOP schema方式原理

简介:

通过前3篇,大家应该可以清楚的知道:AOP代理原理有3元素

  1. BeanPostProcessor,作为代理对象初始入口

  2. Advisor&Pointcut&MethodMatcher完成匹配

  3. Advice的声明及链式结构维护

三个问题在前面的讲解中已经讲解了。其中:

Advice的链式结构,是通过ProxyFactory统一维护的管理的,在《编程式实现AOP》中已说明;

匹配在前面系列2及系列3中也做了说明;

代理对象初始入口也在系列第3讲中进行了讲解。

本文,主要从这3各方面入手,对常用的aop schema做一个全方位的剖析。主要内容

  1. 使用aop schema方式做一个简单的演示demo

  2. 确认并剖析相关的BeanPostProcessor

  3. 确认并剖析相关的Advisor,PointCut


1.使用aop schema方式做一个简单的演示demo

例子代码来源于http://www.tutorialspoint.com/spring/schema_based_aop_appoach.htm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//1 切面类
package  com.tutorialspoint;
 
public  class  Logging {
 
    /** 
     * This is the method which I would like to execute
     * before a selected method execution.
     */
    public  void  beforeAdvice(){
       System.out.println( "Going to setup student profile." );
    }
 
    /** 
     * This is the method which I would like to execute
     * after a selected method execution.
     */
    public  void  afterAdvice(){
       System.out.println( "Student profile has been setup." );
    }
 
    /** 
     * This is the method which I would like to execute
     * when any method returns.
     */
    public  void  afterReturningAdvice(Object retVal){
       System.out.println( "Returning:"  + retVal.toString() );
    }
 
    /**
     * This is the method which I would like to execute
     * if there is an exception raised.
     */
    public  void  AfterThrowingAdvice(IllegalArgumentException ex){
       System.out.println( "There has been an exception: "  + ex.toString());   
    }
    
}
//2 业务模拟类
package  com.tutorialspoint;
 
public  class  Student {
    private  Integer age;
    private  String name;
 
    public  void  setAge(Integer age) {
       this .age = age;
    }
    public  Integer getAge() {
      System.out.println( "Age : "  + age );
       return  age;
    }
 
    public  void  setName(String name) {
       this .name = name;
    }
    public  String getName() {
       System.out.println( "Name : "  + name );
       return  name;
    }
    
    public  void  printThrowException(){
       System.out.println( "Exception raised" );
        throw  new  IllegalArgumentException();
    }
}

XML(Beans.xml)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<? xml  version = "1.0"  encoding = "UTF-8" ?>
< beans  xmlns = "http://www.springframework.org/schema/beans"
        xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop = "http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
 
     < aop:config >
         < aop:aspect  id = "log"  ref = "logging" >
             < aop:pointcut  id = "selectAll"
                           expression = "execution(* com.tutorialspoint.*.*(..))" />
             < aop:before  pointcut-ref = "selectAll"  method = "beforeAdvice" />
             < aop:after  pointcut-ref = "selectAll"  method = "afterAdvice" />
             < aop:after-returning  pointcut-ref = "selectAll"
                                  returning = "retVal"
                                  method = "afterReturningAdvice" />
             < aop:after-throwing  pointcut-ref = "selectAll"
                                 throwing = "ex"
                                 method = "AfterThrowingAdvice" />
         </ aop:aspect >
     </ aop:config >
 
     <!-- Definition for student bean -->
     < bean  id = "student"  class = "com.tutorialspoint.Student" >
         < property  name = "name"   value = "Zara"  />
         < property  name = "age"   value = "11" />
     </ bean >
 
     <!-- Definition for logging aspect -->
     < bean  id = "logging"  class = "com.tutorialspoint.Logging" />
 
</ beans >

Main

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainApp {
    public static void main(String[] args) {
       ApplicationContext context = 
              new ClassPathXmlApplicationContext("com/tutorialspoint/Beans.xml");
 
             Student student = (Student) context.getBean("student");
 
       student.getName();
       student.getAge();
       
//      student.printThrowException();
    }
}

执行结果

Going to setup student profile.
Name : Zara
Student profile has been setup.
Returning:Zara
Going to setup student profile.
Age : 11
Student profile has been setup.
Returning:11


通过查看日志,可以捕获到以下信息,为咱们剖析源码找到了分析点。

...

DEBUG: org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator - Creating implicit proxy for bean 'student' with 0 common interceptors and 5 specific interceptors
DEBUG: org.springframework.aop.framework.CglibAopProxy - Creating CGLIB proxy: target source is SingletonTargetSource for target object [com.tutorialspoint.Student@6fc2862b]
DEBUG: org.springframework.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public java.lang.String com.tutorialspoint.Student.getName()

2.找寻aop schema对应的BeanPostProcessor

aop的schema 对应的spring beanPostProcessor,是Spring自动指派的,对我们是透明的,这一点和以前讲到的BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator不同,咱们没有声明。

首先,aop是客制化的标签(不是bean标签,都是客制化标签),要找到这个位置。熟悉spring自定义标签的朋友应该都知道,需要找到aop标签*.handler文件.位置在$base/spring-aop\src\main\resources\META-INF\spring.handlers.

内容如下

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
2.1 分析AopNamespaceHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public  class  AopNamespaceHandler  extends  NamespaceHandlerSupport {
 
    /**
      完成一些解析器
     * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
    
     */
    public  void  init() {
       // In 2.0 XSD as well as in 2.1 XSD.
       //负责解析<aop:config>
       registerBeanDefinitionParser( "config" new  ConfigBeanDefinitionParser());
       //负责即系<aspectj-autoproxy>
       registerBeanDefinitionParser( "aspectj-autoproxy" new  AspectJAutoProxyBeanDefinitionParser());
       registerBeanDefinitionDecorator( "scoped-proxy" new  ScopedProxyBeanDefinitionDecorator());
       // Only in 2.0 XSD: moved to context namespace as of 2.1
       registerBeanDefinitionParser( "spring-configured" new  SpringConfiguredBeanDefinitionParser());
    }
}
//接下来关注的重点是ConfigBeanDefinitionParser
 
class  ConfigBeanDefinitionParser  implements  BeanDefinitionParser {
public  BeanDefinition parse(Element element, ParserContext parserContext) {
    CompositeComponentDefinition compositeDef =
          new  CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    parserContext.pushContainingComponent(compositeDef);
     //配置the auto proxy creator
    configureAutoProxyCreator(parserContext, element);
 
    //接下来解析xml节点元素
    List<Element> childElts = DomUtils.getChildElements(element);
    for  (Element elt: childElts) {
       String localName = parserContext.getDelegate().getLocalName(elt);
       if  (POINTCUT.equals(localName)) {
          parsePointcut(elt, parserContext);
       }
       else  if  (ADVISOR.equals(localName)) {
          parseAdvisor(elt, parserContext);
       }
       else  if  (ASPECT.equals(localName)) {
          parseAspect(elt, parserContext);
       }
    }
 
    parserContext.popAndRegisterContainingComponent();
    return  null ;
}
 
private  void  configureAutoProxyCreator(ParserContext parserContext, Element element) {
    AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
}
 
}
 
--------------------
public  abstract  class  AopNamespaceUtils {
     public  static  void  registerAspectJAutoProxyCreatorIfNecessary(
           ParserContext parserContext, Element sourceElement) {
     
        BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
              parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
        registerComponentIfNecessary(beanDefinition, parserContext);
     }
     
}
--------------------
public  abstract  class  AopConfigUtils {
public  static  final  String AUTO_PROXY_CREATOR_BEAN_NAME =
       "org.springframework.aop.config.internalAutoProxyCreator" ;
//AspectJAwareAdvisorAutoProxyCreator注册到spring容器
public  static  BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
    return  registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator. class , registry, source);
}
}

到这儿,任务也算完成了。AspectJAwareAdvisorAutoProxyCreator就是我们苦苦寻找的BeanPostProcessor.

wKioL1dSgHbzqfPYAAA3oRhqiac025.png

真巧它和DefaultAdvisorAutoProxyCreator是兄弟。接下来,就是剖析AspectJAwareAdvisorAutoProxyCreator。

wKiom1dSiqTjdzFkAABVVlpdHl0698.png

wKiom1dSiqXjcecJAAB9x2fdwmE025.png

结合《死磕Spring AOP系列3》,可以将getAdvicesAndAdvisorsForBean作为分析的起点,以前说过该方法是AbstractAutoProxyCreator的抽象方法,由子类实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public  abstract  class  AbstractAdvisorAutoProxyCreator  extends  AbstractAutoProxyCreator {
 
...
@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();
}
//查找适合的Advisors
protected  List<Advisor> findEligibleAdvisors(Class beanClass, String beanName) {
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors); //交给子类实现扩展
    if  (!eligibleAdvisors.isEmpty()) {
       eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return  eligibleAdvisors;
}
 
}

2.3剖析AspectJAwareAdvisorAutoProxyCreator.extendAdvisors方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public  class  AspectJAwareAdvisorAutoProxyCreator  extends  AbstractAdvisorAutoProxyCreator {
     //添加 ExposeInvocationInterceptor to the beginning of the advice chain
     protected  void  extendAdvisors(List<Advisor> candidateAdvisors) {
        AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors);
     }
}
 
public  abstract  class  AspectJProxyUtils {
 
    public  static  boolean  makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {
       // Don't add advisors to an empty list; may indicate that proxying is just not required
       if  (!advisors.isEmpty()) {
          boolean  foundAspectJAdvice =  false ;
          for  (Advisor advisor : advisors) {
             // Be careful not to get the Advice without a guard, as
             // this might eagerly instantiate a non-singleton AspectJ aspect
             if  (isAspectJAdvice(advisor)) {
                foundAspectJAdvice =  true ;
             }
          }
          if  (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {
             advisors.add( 0 , ExposeInvocationInterceptor.ADVISOR); //添加到链
             return  true ;
          }
       }
       return  false ;
    }
 
    /**
     *判断是不是AspectJAdvice
     * Determine whether the given Advisor contains an AspectJ advice.
     * @param advisor the Advisor to check
     */
    private  static  boolean  isAspectJAdvice(Advisor advisor) {
       return  (advisor  instanceof  InstantiationModelAwarePointcutAdvisor ||
             advisor.getAdvice()  instanceof  AbstractAspectJAdvice ||
             (advisor  instanceof  PointcutAdvisor &&
                    ((PointcutAdvisor) advisor).getPointcut()  instanceof  AspectJExpressionPointcut));
    }
 
}
 
//就做一件事,对外曝光MethodInvocation,放到ThreadLocal中
public  class  ExposeInvocationInterceptor  implements  MethodInterceptor, Ordered, Serializable {
 
    /** Singleton instance of this class */
    public  static  final  ExposeInvocationInterceptor INSTANCE =  new  ExposeInvocationInterceptor();
 
    private  static  final  ThreadLocal<MethodInvocation> invocation =
          new  NamedThreadLocal<MethodInvocation>( "Current AOP method invocation" ); private  ExposeInvocationInterceptor() {
}    public  Object invoke(MethodInvocation mi)  throws  Throwable {
       MethodInvocation oldInvocation = invocation.get();
       invocation.set(mi);
       try  {
          return  mi.proceed();
       }
       finally  {
          invocation.set(oldInvocation);
       }
    }
 
    public  int  getOrder() {
       return  Ordered.HIGHEST_PRECEDENCE +  1 ;
    }
}

3.剖析Spring pointcut匹配逻辑

如图

wKiom1dSl0_RJ_piAACWOCIUj28287.png

重点关注AspectJExpressionPointcut.该类同时实现了MethodMatcher和ClassFilter两个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public  class  AspectJExpressionPointcut  extends  AbstractExpressionPointcut
       implements  ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
       
       //ClassFilter实现
    public  boolean  matches(Class targetClass) {
            checkReadyToMatch();
        try  {
           return  this .pointcutExpression.couldMatchJoinPointsInType(targetClass);
        catch  (ReflectionWorldException e) {
           logger.debug( "PointcutExpression matching rejected target class" , e);  
        }
  }
//MethodMatcher实现
public  boolean  matches(Method method, Class targetClass,  boolean  beanHasIntroductions) {
    checkReadyToMatch();
    Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
    ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);
    // Special handling for this, target, @this, @target, @annotation
    // in Spring - we can optimize since we know we have exactly this class,
    // and there will never be matching subclass at runtime.
    if  (shadowMatch.alwaysMatches()) {
       return  true ;
    }
    else  if  (shadowMatch.neverMatches()) {
       return  false ;
    }
    else  {
       // the maybe case
       return  (beanHasIntroductions || matchesIgnoringSubtypes(shadowMatch) || matchesTarget(shadowMatch, targetClass));
    }
}
 
}
 
public  class  PointcutExpressionImpl  implements  PointcutExpression {
//AspectJExpressionPointcut 实现ClassFilter接口时候调用。
public  boolean  couldMatchJoinPointsInType(Class aClass) {
    ResolvedType matchType = world.resolve(aClass.getName());
    ReflectionFastMatchInfo info =  new  ReflectionFastMatchInfo(matchType,  null this .matchContext, world);
    boolean  couldMatch = pointcut.fastMatch(info).maybeTrue();
    if  (MATCH_INFO) {
       System.out.println( "MATCHINFO: fast match for '"  this .expression +  "' against '"  + aClass.getName() +  "': "
             + couldMatch);
    }
    return  couldMatch;
}}

4序列图

wKioL1dSo0DzzKwUAADKOB1lvvc207.png


5.总结

截止到现在已经讲解了BeanNameAutoProxyCreator,DefaultAdvisorAutoProxyCreator及今天的AspectJAwareAdvisorAutoProxyCreator。虽然复杂度有所区别,但底层设计本质是一样的。Spring 在AbstractAutoProxyCreator进行了抽象处理,实现了扩展性。



本文转自 randy_shandong 51CTO博客,原文链接:http://blog.51cto.com/dba10g/1786117,如需转载请自行联系原作者

相关文章
|
4天前
|
XML 监控 安全
Spring特性之一——AOP面向切面编程
Spring特性之一——AOP面向切面编程
15 1
|
4天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
2天前
|
前端开发 Java 关系型数据库
使用IDEA搭建一个Spring + AOP (权限管理 ) + Spring MVC
使用IDEA搭建一个Spring + AOP (权限管理 ) + Spring MVC
|
4天前
|
JSON 前端开发 Java
【JavaEE】Spring全家桶实现AOP-统一处理
【JavaEE】Spring全家桶实现AOP-统一处理
5 0
|
4天前
|
前端开发 Java 开发者
【JavaEE】面向切面编程AOP是什么-Spring AOP框架的基本使用
【JavaEE】面向切面编程AOP是什么-Spring AOP框架的基本使用
10 0
|
4天前
|
Java Spring 容器
Spring AOP浅谈
Spring AOP浅谈
10 1
|
4天前
|
XML Java 数据格式
Spring高手之路18——从XML配置角度理解Spring AOP
本文是全面解析面向切面编程的实践指南。通过深入讲解切面、连接点、通知等关键概念,以及通过XML配置实现Spring AOP的步骤。
22 6
Spring高手之路18——从XML配置角度理解Spring AOP
|
4天前
|
监控 安全 Java
Spring cloud原理详解
Spring cloud原理详解
18 0
|
4天前
|
XML Java 数据格式
Spring使用AOP 的其他方式
Spring使用AOP 的其他方式
16 2
|
4天前
|
XML Java 数据格式
Spring 项目如何使用AOP
Spring 项目如何使用AOP
25 2