浅析Spring AOP

    在正常的业务流程中,每每存在着一些业务逻辑,例如安全审计、日志管理,它们存在于每个业务中,然而却和实际的业务逻辑没有太强的关联关系。正则表达式

        

                   图1spring

  这些逻辑咱们称为横切逻辑。若是把横切的逻辑代码写在业务代码中,散落在各个地方,则会变得很是难以维护,代码也会显得过于臃肿。express

  Spring AOP为处理这些问题提供了一种很好的方法。安全

1 AOP术语

  1 通知(advice)。通知是指真正执行的目标逻辑。例如为系统记日志,那么通知作的事情,就是记录日志。app

  2 链接点(joinpoit)。链接点指什么时候执行advice的代码。系统中若是想要为全部的start()方法执行以前,记录一条"our system is starting"的日志,那么,start()方法被调用的时机,称为一个链接点。ide

  3 切点(pointcut)。切点指何处执行advice的代码。例如指定特定的一个路径下的start()方法被调用时,执行advice的代码,那么,这个特定的路径能够理解为切点。一般咱们使用正则表达式定义匹配的类和方法来肯定切点。函数

  4 切面(aspect)。 切面是通知、切点和链接点的所有内容。它定义了什么时候在何处,完成什么功能。源码分析

  5 织入(weaving)。织入指实际代码执行到切点时,完成对目标对象代理的过程。织入有以下三种方式:学习

    a.编译器织入。须要特殊编译器支持,例如AspectJ的织入编译器,在编译期间将代码织入。测试

    b.类加载时。目标类在载入JVM时将切面引入。须要特殊的classLoader支持,改变目标类的字节码,增长新的动做。

    c.运行期。在运行期运用动态代理技术,代理目标类。Spring AOP就是使用这种方式。

2 Spring对AOP的支持

  Spring对AOP提供了多种支持,大致上分为2种思路。

  1 使用动态代理,在运行期对目标类生成代理对象。代理类封装了目标类,并拦截被通知的方法的调用,再将调用转发给真正的目标类方法调用。在代理类中,能够执行切面逻辑。基于这种模式,仅限于对方法的拦截。若是想要实现更细粒度的拦截,例如特定的字段的修改,则须要使用第2种思路了。

  2 使用AspectJ特有的AOP语言。这种方式的成本在于,须要学习AspectJ的语法。

  大多数状况下,第一种思路已经能够知足咱们的需求。

3 Demo

  咱们可使用execution指示器来指示切点。如图2

  

                   图2

  execution指示器具体指定了哪一个包下面哪一个类执行什么方法时,会被织入横切逻辑。Spring AOP的demo网上有不少,可是原理其实都同样。这里贴出其中一种。

  咱们须要2个类,一个类(Biz)用做正常的业务逻辑,一个类用做横切(AdviceTest)。同时写一个类Main测试

  1.Biz

1 public class Biz {
2     public void doBiz(){
3         System.out.println("doing biz");
4     }
5 }
View Code

  2.AdviceTest

 1 public class AdviceTest implements Advice, MethodInterceptor {
 2 
 3     @Override
 4     public Object invoke(MethodInvocation invocation) throws Throwable {
 5         advice();
 6         invocation.proceed();
 7         return null;
 8     }
 9 
10     public void advice(){
11         System.out.println("this is a advice");
12     }
13 
14 }
View Code

  3.Main

1 public class Main {
2     public static void main(String[] args) {
3         ApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[]{"config/spring/spring-aop.xml"});
4         Biz biz = (Biz)applicationContext.getBean("biz");
5         biz.doBiz();
6     }
7 }
View Code

  4.Spring配置

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <beans
 3         xmlns="http://www.springframework.org/schema/beans"
 4         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5         xmlns:aop="http://www.springframework.org/schema/aop"
 6         xsi:schemaLocation="
 7             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 8             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
 9             ">
10 
11     <bean id="adviceTest" class="aop.AdviceTest"/>
12     
13     <bean id="biz" class="aop.Biz"/>
14     
15     <aop:config>
16         <aop:pointcut id="bizPointCut" expression="execution(* aop.Biz.*(..))"/>
17         <aop:advisor pointcut-ref="bizPointCut" advice-ref="adviceTest"/>
18     </aop:config>
19 
20 </beans>
View Code

  执行Main函数代码,输出以下 

this is a advice
doing biz

  可见,实际的biz逻辑已经通过加强。

4 源码分析

  Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪一种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是若是目标类是接口,则使用JDK动态代理技术,不然使用Cglib来生成代理。在咱们的例子中,显然使用的是Cglib。如图3

  

                           图3

  为何咱们配置了Biz的bean,获得的却不是Biz的实例呢?这和Spring对xml文件的标签解析策略有关。对于AOP相关的bean的解析,在AopNamespaceHandler类里面有相关代码,感兴趣的同窗能够去研究下。

  咱们继续往下面单步调试,发现进入了CglibAopProxy类里面。

 1 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
 2             Object oldProxy = null;
 3             boolean setProxyContext = false;
 4             Class<?> targetClass = null;
 5             Object target = null;
 6             try {
 7                 if (this.advised.exposeProxy) {
 8                     // Make invocation available if necessary.
 9                     oldProxy = AopContext.setCurrentProxy(proxy);
10                     setProxyContext = true;
11                 }
12                 // May be null. Get as late as possible to minimize the time we
13                 // "own" the target, in case it comes from a pool...
14                 target = getTarget();
15                 if (target != null) {
16                     targetClass = target.getClass();
17                 }
18                 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
19                 Object retVal;
20                 // Check whether we only have one InvokerInterceptor: that is,
21                 // no real advice, but just reflective invocation of the target.
22                 if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
23                     // We can skip creating a MethodInvocation: just invoke the target directly.
24                     // Note that the final invoker must be an InvokerInterceptor, so we know
25                     // it does nothing but a reflective operation on the target, and no hot
26                     // swapping or fancy proxying.
27                     Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
28                     retVal = methodProxy.invoke(target, argsToUse);
29                 }
30                 else {
31                     // We need to create a method invocation...
32                     retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
33                 }
34                 retVal = processReturnType(proxy, target, method, retVal);
35                 return retVal;
36             }
37             finally {
38                 if (target != null) {
39                     releaseTarget(target);
40                 }
41                 if (setProxyContext) {
42                     // Restore old proxy.
43                     AopContext.setCurrentProxy(oldProxy);
44                 }
45             }
46         }
View Code

  无耻的把代码直接贴出来。咱们重点关注下下面这一行

   retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); 

  在本文例子的场景下,会在此建立一个CglibMethodInvocation执行上下文,并执行proceed()方法。

                         图4

最终执行AdviceTest中invoke()方法的调用。在AdviceTest的invoke方法中,咱们能够自定义本身想要执行的加强逻辑invocation.proceed()来执行目标类的方法。

  

                       图5

5 总结

  Spring AOP为许多与业务无关的逻辑的执行,提供了一种很好的解决思路。本文也只是抛砖引玉,在实际的Spring源码中,仍是比较复杂的,还须要细细研究才行。

 

参考文献:

 《Spring In Action》

 《Spring源码深度解析》

  

做者: mayday芋头
本博客中未标明转载的文章归做者mayday芋头和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。
相关文章
相关标签/搜索