首页 > 代码库 > Spring AOP的本质

Spring AOP的本质

不用再百科什么AOP了,我先推荐几篇文章或者系列文章:(感谢这些博文的原作者)

    0.  Spring AOP 详解   http://pandonix.iteye.com/blog/336873/ 

    1.  AOP技术基础系列     http://wayfarer.cnblogs.com/articles/241024.html

    2.  我对AOP的理解 http://jinnianshilongnian.iteye.com/blog/1474325

    3.  Spring AOP本质系列  http://lavasoft.blog.51cto.com/62575/74942

要看原文请去我给出的作者的博客地址去,尊重版权。

为了防止以上链接失效,下面在本文中进行了转载,如果这样的形式使您(原作者)的利益受到了损害,我会在您通知我后的第一时间内删除。

-----本文对他们的观点也会做出总结和思考


 

Spring AOP本质系列(原文出自熔岩:http://lavasoft.blog.51cto.com/62575/74942)

现补充一个这个:

Spring AOP 应用模型

一、概述

AOP是Aspect-oriented programming,中文翻译为面向切面编程。

面向切面编程AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。

Spring的一个关键的组件就是 AOP框架。 尽管如此,Spring IoC容器并不依赖于AOP,这意味着你可以自由选择是否使用AOP,AOP提供强大的中间件解决方案,这使得Spring IoC容器更加完善。

Spring中所使用的AOP:

  • 提供声明式企业服务,特别是为了替代EJB声明式服务。 最重要的服务是 声明性事务管理(declarative transaction management) , 这个服务建立在Spring的抽象事务管理(transaction abstraction)之上。

  • 允许用户实现自定义的切面,用AOP来完善OOP的使用。

这样你可以把Spring AOP看作是对Spring的一种增强,它使得Spring可以不需要EJB就能提供声明式事务管理; 或者也可以使用Spring AOP框架的全部功能来实现自定义的切面。

二、AOP术语

首先让我们从定义一些重要的AOP概念开始。这些术语不是Spring特有的。 不幸的是,Spring术语并不是特别的直观;如果Spring使用自己的术语,将会变得更加令人困惑。

  • 切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect注解(@AspectJ风格)来实现。

  • 连接点(Joinpoint): 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。 在Spring AOP中,一个连接点 总是 代表一个方法的执行。 通过声明一个org.aspectj.lang.JoinPoint类型的参数可以使通知(Advice)的主体部分获得连接点信息。

  • 通知(Advice): 在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。

  • 切入点(Pointcut): 匹配连接点(Joinpoint)的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。 切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

  • 引入(Introduction): (也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。 Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。 例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

  • 目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(advised) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

  • AOP代理(AOP Proxy): AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。 注意:Spring 2.0最新引入的基于模式(schema-based)风格和@AspectJ注解风格的切面声明,对于使用这些风格的用户来说,代理的创建是透明的。

  • 织入(Weaving): 把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。 这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。 Spring和其他纯Java AOP框架一样,在运行时完成织入。

通知的类型:

  • 前置通知(Before advice): 在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

  • 返回后通知(After returning advice): 在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

  • 抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。

  • 后通知(After (finally) advice): 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

  • 环绕通知(Around Advice): 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。

三、给个最简单的Demo看看

/** * Created by IntelliJ IDEA.<br> * <b>User</b>: leizhimin<br> * <b>Date</b>: 2008-4-21 10:19:15<br> * <b>Note</b>: 公共接口,代理类和被代理类都实现此业务接口 */ public interface IHello {     public void hello(String name); }

 

/** * Created by IntelliJ IDEA.<br> * <b>User</b>: leizhimin<br> * <b>Date</b>: 2008-4-21 10:19:47<br> * <b>Note</b>: 被代理类 */ public class HelloSpeaker implements IHello {     public void hello(String name) {         System.out.println("Hello, " + name);     } }

 

/** * Created by IntelliJ IDEA.<br> * <b>User</b>: leizhimin<br> * <b>Date</b>: 2008-4-21 10:44:39<br> * <b>Note</b>: 测试类,客户端 */ public class SpringAOPDemo {     public static void main(String args[]){ //        ApplicationContext context = BeanContextHelper.getApplicationContext();         ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");         IHello helloproxy = (IHello)context.getBean("helloProxy");         helloproxy.hello("lavasoft");     } }

 

/** * Created by IntelliJ IDEA.<br> * <b>User</b>: leizhimin<br> * <b>Date</b>: 2008-4-21 10:22:22<br> * <b>Note</b>: 在被代理方法执行前添加的动作 */ public class LogBeforeAdvice implements MethodBeforeAdvice {     private Logger logger = Logger.getLogger(this.getClass().getName());     public void before(Method method, Object[] objects, Object o) throws Throwable {         logger.log(Level.INFO, "method start..." + method);     } }

 

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean id="logBeforeAdvice" class="com.lavasoft.springnote.ch02.aoptest.LogBeforeAdvice"/>     <bean id="helloSpeaker" class="com.lavasoft.springnote.ch02.aoptest.HelloSpeaker"/>     <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">         <property name="proxyInterfaces">             <value>com.lavasoft.springnote.ch02.aoptest.IHello</value>         </property>         <property name="target">             <ref bean="helloSpeaker"/>         </property>         <property name="interceptorNames">             <list>                 <value>LogBeforeAdvice</value>             </list>         </property>     </bean> </beans>

 

运行结果:

log4j:WARN No appenders could be found for logger (org.springframework.core.CollectionFactory). log4j:WARN Please initialize the log4j system properly. 2008-4-25 9:25:31 com.lavasoft.springnote.ch02.aoptest.LogBeforeAdvice before 信息: method start...public abstract void com.lavasoft.springnote.ch02.aoptest.IHello.hello(java.lang.String) Hello, lavasoft Process finished with exit code 0

四、Advice层次结构

org.aopalliance.aop.Advice

  • org.springframework.aop.AfterReturningAdvice
  • org.springframework.aop.BeforeAdvice
    • org.springframework.aop.MethodBeforeAdvice
  • org.springframework.aop.DynamicIntroductionAdvice
    • org.springframework.aop.IntroductionInterceptor (also extends org.aopalliance.intercept.MethodInterceptor)
  • org.aopalliance.intercept.Interceptor
    • org.aopalliance.intercept.MethodInterceptor
      • org.springframework.aop.IntroductionInterceptor (also extends org.springframework.aop.DynamicIntroductionAdvice)
  • org.springframework.aop.ThrowsAdvice

Advice 是在切面的某个特定的连接点上执行的动作。

Advice是一个标识接口,有很多的子接口。BeforeAdvice就是在被代理方法调用前所执行的接口动作。


Spring AOP 本质(1)

AOP本质是拦截,拦截的本质是代理,代理分动态和静态,静态代理很简单,功能有限,应用不是很广泛,Spring中主要用的动态代理。

用Spring做开发,AOP的实现仅仅是编程实现一些接口,然后配置一下即可。这个可以参看“Spring AOP 模型”一文。

为什么配置一下即可,究竟Spring框架内部做了如何的处理,实现了代理。下面可以看看下面的例子就明白了。

/** * 被代理类 */ public class MessageWriter{     /**      * 业务方法      */     public void writeMessage() {         System.out.print("World");     } }
 
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 实现包围通知,实际就是方法拦截器,MethodInterceptor是AOP联盟定义的接口. */ public class MessageDecorator implements MethodInterceptor {     public Object invoke(MethodInvocation invocation) throws Throwable {         System.out.print("Hello ");         Object retVal = invocation.proceed();         System.out.println("!");         return retVal;     } }
 
import org.springframework.aop.framework.ProxyFactory; /** * AOP测试 */ public class HelloWorldAOPExample {     public static void main(String[] args) {         //目标对象(被代理的对象)         MessageWriter target = new MessageWriter();                  //产生一个代理工厂         ProxyFactory pf = new ProxyFactory();         //添加代理工厂的拦截器         pf.addAdvice(new MessageDecorator());         //设置被代理对象         pf.setTarget(target);         //获取一个代理实例         MessageWriter proxy = (MessageWriter) pf.getProxy();                  //从目标对象直接输出信息         target.writeMessage();         System.out.println("\n------------");         //从代理对象输出信息         proxy.writeMessage();     } }

例子中用ProxyFactory类来创建目标对象的代理,同时织入通知。通过调用addAdvice(new MessageDecorator()),把MessageDecorator通知传给ProxyFactory,然后通过调用setTarget(target)设定织入的目标对象。设定了目标对象,也织入了通知,就可以调用ProxyFactory.getProxy()来获得一个代理对象。

运行结果:

- Using JDK 1.4 collections
World
------------
Hello World!
Process finished with exit code 0

从中可以看到,Spring的代理很牛,不一定要求代理和被代理类都要实现同一个接口,Spring可以代理任何的类,当然final类除外,因为final类不允许继承。

参考资料:

《Pro Spring》



Spring AOP 本质(2)

Spring AOP架构的核心是建立在代理上的。

Spring AOP代理只支持一种连接点----方法调用。其功能是AOP规范的一个子集,但足够用了。

Spring代理有两种实现方式:JDK动态代理和CGLIB代理。CGLIB的代理性能要比JDK动态代理的性能好很多,不过开发人员不用需要关注这些,Spring自动会判断使用何种代理,并且默认Spring会选择使用CGLIB的代理,在此不做深入讨论。

Spring AOP代理是通过ProxyFactory类来获取代理对象的。最简单的过程是:

1、创建代理工厂:new一个ProxyFactory实例。

2、给代理工厂加入通知者:ProxyFactory.addAdvisor(),通知者构建方式很多。

3、将目标加入工厂:ProxyFactory.setTarget()设置要代理的对象。

4、获取目标的代理实例:ProxyFactory.getProxy(),通过代理实例来执行被代理的也无法方法。

在Spring AOP中,有两种方法将一个通知加入到代理中去。一是直接使用addAdvice()方法,二是使用addAdvice()和Advisor(通知者)类。前者在ProxyFactory中会委派给addAdvisor(),加入的是DefaultPointcutAdvisor。

Spring支持五类通知,这些通知都实现了Advice接口:

org.springframework.aop.AfterReturningAdvice

org.springframework.aop.IntroductionInterceptor

org.springframework.aop.MethodBeforeAdvice

org.aopalliance.intercept.MethodInterceptor

org.springframework.aop.ThrowsAdvice

在实际中,只需要实现以上五个标准接口即可,另外这些接口都是AOP联盟定义的标准接口。

下面通过实现MethodBeforeAdvice接口改写“Spring AOP 本质(1) ”中的例子,其他的不用改变,只需要将测试类实现MethodBeforeAdvice接口即可。

下面是改写后的代码:

import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.framework.ProxyFactory; public class SimpleBeforeAdvice implements MethodBeforeAdvice {     public static void main(String[] args) {         //被代理对象         MessageWriter target = new MessageWriter();         //代理工厂         ProxyFactory pf = new ProxyFactory();         //给工厂添加通知(者),实际上ProxyFactory在后台加入的是一个DefaultPointcutAdvisor        pf.addAdvice(new SimpleBeforeAdvice());         //将目标加入代理工厂         pf.setTarget(target);         //从工厂获取代理实例(产品)         MessageWriter proxy = (MessageWriter) pf.getProxy();         //从代理实例上调用业务方法         proxy.writeMessage();     }     public void before(Method method, Object[] args, Object target)             throws Throwable {         System.out.println("Before method: " + method.getName());     } }

运行结果:

- Using JDK 1.4 collections
Before method: writeMessage
World
Process finished with exit code 0


Spring AOP 本质(3)

Spring AOP很牛,AOP是OOP的补充,而非竞争者。

前面的例子离实际的应用太遥远。不足以显式AOP的力量,现在就用AOP前置通知来检查用户的身份,只有通过检查的才能调用业务方法。

在没有使用AOP之前,我们是如何实现的?想想看。

1、写一个安全检查类,又其他类继承,并在子类的业务方法中调用安全检查的方法。

比如:Struts1时代就是继承Action,其类结构如下:

package org.apache.struts.action; public class Action {     public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, ServletRequest servletRequest, ServletResponse servletResponse) throws java.lang.Exception { /* compiled code */ }     public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse) throws java.lang.Exception { /* compiled code */ }     .... }

在开发中自己实现的UserAction需要继承Struts的Action,可以考虑做一个抽象,比如叫做CheckAciton,在其中重写execute方法,并加入安全检查机制,并且增加一个抽象请求处理方法
public ActionForward real(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse)throws java.lang.Exception;
作为业务请求处理的方法,放到重写的execute方法内部调用。

public class CheckAction extends Action{     public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse) throws java.lang.Exception {     //todo: 安全检查逻辑     return real(actionMapping,actionForm,httpServletRequest,httpServletResponse);     }     public abstract ActionForward real(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse) throws java.lang.Exception; }
这样以后业务上的别的Aciton只要继承了CheckAction,还需要实现real方法,别的方法),即可为该Action加入安全检查的逻辑。
public class DoSomethingAction extends CheckAction{     public abstract ActionForward real(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse) throws java.lang.Exception{     //todo: 单纯处理实际的业务请求     return ...     }     .... }

这样做也很麻烦,还可以使用动态代理为每个业务接口加上安全检查的逻辑,但是性能更差,更麻烦。

这个还算可行的方案,实现也很容易。但是很死板,如果有多种验证策略就比较棘手了。

没有对比就显式不出来Spring AOP的优势。下面看看Spring的优雅处理:

/** * 用户登录信息载体 */ public class UserInfo {     private String userName;     private String password;     public UserInfo(String userName, String password) {         this.userName = userName;         this.password = password;     }          public String getPassword() {         return password;     }     public String getUserName() {         return userName;     } }
 
/** * 业务组件:被代理的对象 */ public class SecureBean {     /**      * 示范性的业务方法,这个方法将被拦截,加入一些附加逻辑      */     public void businessOperate() {         System.out.println("业务方法businessOperate()被调用了!");     } }
 
/** * 安全管理类:检查用户登录和管理用户注销登录的业务逻辑。 */ public class SecurityManager {     //为每一个SecurityManager创建一个本地线程变量threadLocal,用来保存用户登录信息UserInfo     private static ThreadLocal threadLocal = new ThreadLocal();     /**      * 用户登录方法,允许任何用户登录。      * @param userName      * @param password      */     public void login(String userName, String password) {         // 假定任何的用户名和密码都可以登录         // 将用户登录信息封装为UerInfo对象,保存在ThreadLocal类的对象threadLocal里面         threadLocal.set(new UserInfo(userName, password));     }     public void logout() {         // 设置threadLocal对象为null         threadLocal.set(null);         int x = 0;     }     public UserInfo getLoggedOnUser() {         // 从本地线程变量中获取用户信息UerInfo对象         return (UserInfo) threadLocal.get();     } }
 
import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; /** * 前置通知类 */ public class SecurityAdvice implements MethodBeforeAdvice {     private SecurityManager securityManager;     public SecurityAdvice() {         this.securityManager = new SecurityManager();     }     /**      * 前置通知的接口方法实现。仅允许robh用户登录,强制设定的。      */     public void before(Method method, Object[] args, Object target)             throws Throwable {         UserInfo user = securityManager.getLoggedOnUser();         if (user == null) {             System.out.println("没有用户凭证信息!,本前置通知仅仅允许robh用户登录,不信你试试看!");             throw new SecurityException(                     "你必须在调用此方法" + method.getName() + "前进行登录:");         } else if ("robh".equals(user.getUserName())) {             System.out.println("用户robh成功登录:OK!");         } else {             System.out.println("非法用户"+user.getUserName()+",请使用robh登录,用户调用的方法是:" + method.getName());             throw new SecurityException("用户" + user.getUserName()                     + " 不允许调用" + method.getName() + "方法!");         }     } }
 
import org.springframework.aop.framework.ProxyFactory; /** * 测试类,客户端 */ public class SecurityExample {     public static void main(String[] args) {         //得到一个 security manager         SecurityManager mgr = new SecurityManager();         //获取一个SecureBean的代理对象         SecureBean bean = getSecureBean();         //尝试用robh登录         mgr.login("robh", "pwd");   //检查登录情况         bean.businessOperate();     //业务方法调用         mgr.logout();               //注销登录         //尝试用janm登录         try {             mgr.login("janm", "pwd");       //检查登录情况             bean.businessOperate();         //业务方法调用         } catch (SecurityException ex) {             System.out.println("发生了异常: " + ex.getMessage());         } finally {             mgr.logout();                   //注销登录         }         // 尝试不使用任何用户名身份调用业务方法         try {             bean.businessOperate();         //业务方法调用         } catch (SecurityException ex) {             System.out.println("发生了异常: " + ex.getMessage());         }     }     /**      * 获取SecureBean的代理对象      *      * @return SecureBean的代理      */     private static SecureBean getSecureBean() {         //创建一个目标对象         SecureBean target = new SecureBean();         //创建一个通知         SecurityAdvice advice = new SecurityAdvice();         //获取代理对象         ProxyFactory factory = new ProxyFactory();         factory.setTarget(target);         factory.addAdvice(advice);         SecureBean proxy = (SecureBean) factory.getProxy();         return proxy;     } }
运行结果:
- Using JDK 1.4 collections 用户robh成功登录:OK! 业务方法businessOperate()被调用了! 非法用户janm,请使用robh登录,用户调用的方法是:businessOperate 发生了异常: 用户janm 不允许调用businessOperate方法! 没有用户凭证信息!,本前置通知仅仅允许robh用户登录,不信你试试看! 发生了异常: 你必须在调用此方法businessOperate前进行登录: Process finished with exit code 0

观察运行结果,精确实现了验证的要求。

这里从底层观察Spring AOP的应用,实际应用中最好还是通过xml配置耦合代码。只有明白了AOP其中奥秘,使用Spring的配置才能深谙其中的精妙!


Spring AOP 本质(4)

这一主要看看Spring AOP是如何实现通知包围的。

Spring AOP包围通知在功能上和前置通知加后置通知类似,但还是有区别的:包围通知可以修改返回值,还可以阻止、替换目标方法的执行。

Spring里的包围通知是实现MethodInterceptor接口的拦截器。

Spring包围通知有着很广泛的应用,比如远程代理和事务管理,都是由拦截器完成。另外,拦截器也是剖析程序运行的好方法。

下面利用Spring AOP包围通知实现监控业务方法的执行运行过程耗时情况。

/** * 业务组件 */ public class WorkerBean {     public void doSomeWork(int noOfTimes) {         for(int x = 0; x < noOfTimes; x++) {             work();         }     }          private void work() {         System.out.print("");     } }
 
import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.util.StopWatch; /** * 拦截器,实现方法包围通知 */ public class ProfilingInterceptor implements MethodInterceptor {     public Object invoke(MethodInvocation invocation) throws Throwable {         //启动一个 stop watch         StopWatch sw = new StopWatch();         //运行计时器         sw.start(invocation.getMethod().getName());         //执行业务方法         Object returnValue =http://www.mamicode.com/ invocation.proceed();         //停止计时器         sw.stop();         //垃圾信息输出         dumpInfo(invocation, sw.getTotalTimeMillis());         //返回业务方法返回值         return returnValue;     }     /**      * 垃圾信息输入方法,实际上输出的是方法运行的计时信息      */     private void dumpInfo(MethodInvocation invocation, long ms) {         //获取被调用方法         Method m = invocation.getMethod();         //获取被调用方法所属的对象         Object target = invocation.getThis();         //获取被调用方法的参数         Object[] args = invocation.getArguments();         System.out.println("所执行的方法: " + m.getName());         System.out.println("对象的类型: " + target.getClass().getName());         System.out.println("方法的参数:");         for (int x = 0; x < args.length; x++) {             System.out.print("    > " + args[x]);         }         System.out.print("\n");         System.out.println("抓取方法运行的时间: " + ms + " ms");     } }
 
import org.springframework.aop.framework.ProxyFactory; /** * 客户端测试方法 */ public class ProfilingExample {     public static void main(String[] args) {         //创建代理对象         WorkerBean bean = getWorkerBean();         //在代理对象上调用业务方法         bean.doSomeWork(10000000);     }     /**      * 代理对象工厂      */     private static WorkerBean getWorkerBean() {         //创建目标对象         WorkerBean target = new WorkerBean();         //构建代理对象工厂         ProxyFactory factory = new ProxyFactory();         factory.setTarget(target);         factory.addAdvice(new ProfilingInterceptor());         //生产一个代理对象         return (WorkerBean)factory.getProxy();     } }

运行结果:
- Using JDK 1.4 collections 所执行的方法: doSomeWork 对象的类型: com.apress.prospring.ch6.profiling.WorkerBean 方法的参数:     > 10000000 抓取方法运行的时间: 3453 ms Process finished with exit code 0

从输出的结果中,可以看到程序方法调用的方法名、参数、所属类,以及执行方法所耗费的时间。

另外说一下org.springframework.util.StopWatch类,这是个计时器类,此工具类主要可以获取计时器类start()和stop()两次方法调用间的时间。具体可以查看Spring的API文档。另外,我也在apache commons 包里面也有org.apache.common.lang.time.StopWatch。


Spring AOP 本质(5)

Spring 的AOP核心技术还是动态代理,Spring的代理实现方式有两种,默认使用CGLIB,也可以选择使用JDK的代理,这一切对开发人员来说是透明的,可以不关心这些。

但是,Spring实在是太强了,太吸引人了,以至于不得不研读其底层的API实现。

Spring代理生成的过程还是和JDK有一定区别的,经过研读分析Spring AOP底层源码,画出了其原理图。并且为了对比JDK的动态代理的实现,也给出JDK动态代理原理图。

技术分享

下面,给出个JDK实现动态代理的例子,Spring的例子在AOP本质系列中比比皆是,故此不再给出。

/** * Created by IntelliJ IDEA.<br> * <b>User</b>: leizhimin<br> * <b>Date</b>: 2008-5-9 14:16:18<br> * <b>Note</b>: 目标抽象接口 */ public interface ITarget {     public void doSomething(); } /** * Created by IntelliJ IDEA.<br> * <b>User</b>: leizhimin<br> * <b>Date</b>: 2008-5-9 14:17:03<br> * <b>Note</b>: 目标类 */ public class Target implements ITarget {     public void doSomething() {         System.out.println(">>>正在调用Targer.doSomething()方法!");     } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * Created by IntelliJ IDEA.<br> * <b>User</b>: leizhimin<br> * <b>Date</b>: 2008-5-9 14:23:07<br> * <b>Note</b>: 代理处理器实现 */ public class MyInvocationHandler implements InvocationHandler {     private Target target;     public MyInvocationHandler(Object obj) {         target = (Target) obj;     }     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         System.out.println(">>>代理处理器start...");         Object result = method.invoke(target, args);         System.out.println(">>>代理处理器end...");         return result;     } } import java.lang.reflect.Proxy; /** * Created by IntelliJ IDEA.<br> * <b>User</b>: leizhimin<br> * <b>Date</b>: 2008-5-9 14:27:20<br> * <b>Note</b>: 客户端 */ public class Test {     public static void main(String args[]) {         //创建目标对象         Target target = new Target();         //创建代理实例调用处理程序(相当于SpringAOP中切面)         MyInvocationHandler myInvocationHandler = new MyInvocationHandler(target);         //通过Proxy生成一个代理对象obj,obj可以宣称实现了目标对象所实现的接口ITarget         ITarget proxyTarget = (ITarget) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), myInvocationHandler);         //从代理对象上调用目标对象的方法         proxyTarget.doSomething();     } }
运行结果:
>>>代理处理器start... >>>正在调用Targer.doSomething()方法! >>>代理处理器end... Process finished with exit code 0


Spring AOP本质(5)-Pointcut

在前面四个例子中,只要实现一个Advice,然后调用ProxyFactory.addAdvice()方法为代理设定通知,不用设置切入点,从代理对上调用的方法就被通知到了。其原因就在于执行addAdvice()方法时,ProxyFactory会将Advice对象委派给addAdvistor()方法,后台会自动创建一个DefaultPointcutAdvistor实例,并将Advice加入其中。而默认的DefaultPointcutAdvistor会将切入点设为所有的方法。

假如我们不想通过代理来执行某些方法,也就是说,在执行某些方法的时候不通知,这时候该如何实现呢?

Spring提供一系列接口来实现这个目标。最主要的接口如下:
implements org.springframework.aop.Pointcut
org.springframework.aop.ClassFilter
org.springframework.aop.MethodMatcher

下面看看几个关键接口的定义:

1、切入点(Pointcut)

/** * 切入点 */ public interface Pointcut {     //切入点的一个单例     public static final Pointcut TRUE = TruePointcut.INSTANCE;      //类过滤器       public ClassFilter getClassFilter();     //方法过滤器     public MethodMatcher getMethodMatcher(); }
 
/** * 类过滤器 */ public interface ClassFilter {     //类过滤器单例     public static final ClassFilter TRUE = TrueClassFilter.INSTANCE;     //类匹配方法     public boolean matches(Class class1); } /** * 方法过滤器 */ public interface MethodMatcher {     //方法过滤器单例     public static final MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;     //静态方法匹配方法     public boolean matches(Method method, Class class1);     //判断静态还是动态匹配,返回true动态匹配,false静态匹配     public boolean isRuntime();     //对象(动态)匹配方法     public boolean matches(Method method, Class class1, Object aobj[]); }

2、通知者(Advisor)
/** * 通知者接口 */ public interface Advisor {     //切面是否为per instance     public boolean isPerInstance();     //获取切面上的通知     public Advice getAdvice(); }
 
/** * 通知者子接口,Spring中标准的切面都应该实现此接口 */ public PointcutAdvisor extends Advisor {     //获取通知者的切点     public Pointcut getPointcut(); }

为了看的明白,还是回顾一下框架图:
技术分享

还有很多接口和类没有画出,这里简要说明下。

在org.springframework.aop.support包下,还有一些很重要的切点类,是Spring定义好的,几乎可以满足所用应用的需要。

DynamicMethodMatcherPointcut
NameMatchMethodPointcut
Perl5RegexpMethodPointcut
StaticMethodMatcherPointcut
JdkRegexpMethodPointcut
ControlFlowPointcut
ComposablePointcut

与这些切点对应,还有一些切面类,名字都是以PointcutAdvisor结尾。

通过上面的原理图,简单查看一下API,就可以直到,通过通知Advice和切点Pointcut可以生成通知者Advisor。有了通知者,有了目标对象,就可以通过ProxyFactory生成代理对象。

下面给个例子看看Spring如何通过切点来选取类和方法的,并如通知所选取的方法。

例子:扩展StaticMethodMatcherPointcut,实现静态切入点过滤。

/** * 业务组件:BeanOne */ public class BeanOne {     public void foo() {         System.out.println("BeanOne的foo()被调用!");     }         public void bar() {         System.out.println("BeanOne的bar()被调用!");     } }
 
/** * 业务组件:BeanTwo */ public class BeanTwo {     public void foo() {         System.out.println("BeanTwo的foo()被调用!");     }         public void bar() {         System.out.println("BeanTwo的bar()被调用!");     } }
 
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 自定义通知:Advice */ public class SimpleAdvice implements MethodInterceptor {     public Object invoke(MethodInvocation invocation) throws Throwable {         System.out.println(">> 业务方法调用前动作,被代理调用目标方法是: " + invocation.getMethod().getName());         Object retVal = invocation.proceed();         System.out.println(">> 业务方法调用结束后动作!");         return retVal;     } }
 
import java.lang.reflect.Method; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcut; /** * 自定义静态切入点:Pointcut */ public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {     public boolean matches(Method method, Class cls) {         //类方法名为foo时候匹配         System.out.println("切入点方法匹配,正在匹配"+cls.getName()+"的"+method.getName()+"方法!");         return ("foo".equals(method.getName()));     }     public ClassFilter getClassFilter() {         return new ClassFilter() {             public boolean matches(Class cls) {                 System.out.println("切入点类匹配,正在匹配"+cls.getName()+"类!");                 //BeanOne类匹配                 return (cls == BeanOne.class);             }         };     } }
 
import org.aopalliance.aop.Advice; import org.springframework.aop.Advisor; import org.springframework.aop.Pointcut; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor; /** * 客户端测试 */ public class StaticPointcutExample {     public static void main(String[] args) {         //创建目标对象         BeanOne one = new BeanOne();         BeanTwo two = new BeanTwo();         //定义代理对象         BeanOne proxyOne;         BeanTwo proxyTwo;         //创建一个切入点         Pointcut pc = new SimpleStaticPointcut();         //创建一个通知         Advice advice = new SimpleAdvice();         //创建一个通知者(即通知和切入点的结合)         Advisor advisor = new DefaultPointcutAdvisor(pc, advice);         //创建一个代理工厂         ProxyFactory pf = new ProxyFactory();         //将方面加入工厂         pf.addAdvisor(advisor);         //将目标加入工厂         pf.setTarget(one);         //获取代理对象产品         proxyOne = (BeanOne) pf.getProxy();  //这个时候会进行匹配检查         //创建一个代理工厂         pf = new ProxyFactory();         pf.addAdvisor(advisor);         pf.setTarget(two);         proxyTwo = (BeanTwo) pf.getProxy();         /*        org.springframework.aop.framework.ProxyFactory中        设置的代理目标一次仅能一个,这点不要犯迷糊,我查过源码了.       */         //从代理产品上调用目标方法         proxyOne.foo();         proxyTwo.foo();         proxyOne.bar();         proxyTwo.bar();     } }

运行结果:
- Using JDK 1.4 collections 切入点类匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne类! 切入点方法匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne的foo方法! 切入点类匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne类! 切入点方法匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne的bar方法! 切入点类匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne类! 切入点方法匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne的hashCode方法! 切入点类匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne类! 切入点方法匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne的toString方法! 切入点类匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanTwo类! 切入点类匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanTwo类! 切入点类匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanTwo类! 切入点类匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanTwo类! 切入点类匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne类! 切入点方法匹配,正在匹配com.apress.prospring.ch6.staticpc.BeanOne的foo方法! >> 业务方法调用前动作,被代理调用目标方法是: foo BeanOne的foo()被调用! >> 业务方法调用结束后动作! BeanTwo的foo()被调用! BeanOne的bar()被调用! BeanTwo的bar()被调用! Process finished with exit code 0


Spring AOP本质(7)

上一个里面,给出静态方法切点匹配的例子,现在给出一个动态的实现例子:

/** * 业务组件 */ public class SampleBean {     public void foo(int x) {         System.out.println("SampleBean的foo(int x)方法被调用,参数x="  +x);     }     public void bar() {          System.out.println("SampleBean的无参bar()方法被调用!");     } }
 
import java.lang.reflect.Method; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.DynamicMethodMatcherPointcut; /** * 自定义动态切入点:Pointcut */ public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {     /**      * 重写了静态方法匹配器      */     public boolean matches(Method method, Class cls) {         System.out.println("SimpleDynamicPointcut:静态方法匹配器正在尝试匹配方法:" + method.getName());         //仅当方法名为foo时候才匹配         return ("foo".equals(method.getName()));     }     /**      * 实现了动态方法匹配器      */     public boolean matches(Method method, Class cls, Object[] args) {         System.out.println("SimpleDynamicPointcut:动态方法匹配器正在尝试匹配方法:" + method.getName());         int x = ((Integer) args[0]).intValue();         //仅当方法参数为不等于100才匹配         return (x != 100);     }     /**      * 重写了类匹配器      */     public ClassFilter getClassFilter() {         return new ClassFilter() {             public boolean matches(Class cls) {                 System.out.println("SimpleDynamicPointcut:切入点类匹配,正在匹配"+cls.getName()+"类!");                                 return (cls == SampleBean.class);             }         };     } }
 
/** * 自定义通知:Advice */ public class SimpleAdvice implements MethodInterceptor {     public Object invoke(MethodInvocation invocation) throws Throwable {         System.out.println(">> 业务方法调用前动作,被代理调用目标方法是: " + invocation.getMethod().getName());         Object retVal = invocation.proceed();         System.out.println(">> 业务方法调用结束后动作!");         return retVal;     } }
 
import org.springframework.aop.Advisor; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor; import com.apress.prospring.ch6.staticpc.SimpleAdvice; /** * 动态方法匹配器切入点:客户端测试 */ public class DynamicPointcutExample {     public static void main(String[] args) {         //创建目标对象         SampleBean target = new SampleBean();         // 创建通知者         Advisor advisor = new DefaultPointcutAdvisor(                 new SimpleDynamicPointcut(), new SimpleAdvice());                  //创建代理工厂         ProxyFactory pf = new ProxyFactory();         //将目标加入工厂         pf.setTarget(target);         //创建通知者         pf.addAdvisor(advisor);         //获取代理实例(产品)         SampleBean proxy = (SampleBean)pf.getProxy();         //调用代理方法,注意参数的变化         proxy.foo(1);         proxy.foo(10);         proxy.foo(100);                  proxy.bar();         proxy.bar();         proxy.bar();     } }
运行结果:
- Using JDK 1.4 collections SimpleDynamicPointcut:切入点类匹配,正在匹配com.apress.prospring.ch6.dynamicpc.SampleBean类! SimpleDynamicPointcut:静态方法匹配器正在尝试匹配方法:foo SimpleDynamicPointcut:切入点类匹配,正在匹配com.apress.prospring.ch6.dynamicpc.SampleBean类! SimpleDynamicPointcut:静态方法匹配器正在尝试匹配方法:bar SimpleDynamicPointcut:切入点类匹配,正在匹配com.apress.prospring.ch6.dynamicpc.SampleBean类! SimpleDynamicPointcut:静态方法匹配器正在尝试匹配方法:hashCode SimpleDynamicPointcut:切入点类匹配,正在匹配com.apress.prospring.ch6.dynamicpc.SampleBean类! SimpleDynamicPointcut:静态方法匹配器正在尝试匹配方法:toString SimpleDynamicPointcut:切入点类匹配,正在匹配com.apress.prospring.ch6.dynamicpc.SampleBean类! SimpleDynamicPointcut:静态方法匹配器正在尝试匹配方法:foo SimpleDynamicPointcut:动态方法匹配器正在尝试匹配方法:foo >> 业务方法调用前动作,被代理调用目标方法是: foo SampleBean的foo(int x)方法被调用,参数x=1 >> 业务方法调用结束后动作! SimpleDynamicPointcut:动态方法匹配器正在尝试匹配方法:foo >> 业务方法调用前动作,被代理调用目标方法是: foo SampleBean的foo(int x)方法被调用,参数x=10 >> 业务方法调用结束后动作! SimpleDynamicPointcut:动态方法匹配器正在尝试匹配方法:foo SampleBean的foo(int x)方法被调用,参数x=100 SampleBean的无参bar()方法被调用! SampleBean的无参bar()方法被调用! SampleBean的无参bar()方法被调用! Process finished with exit code 0

 

 

 

 

AOP技术基础系列(原文出自张逸:http://wayfarer.cnblogs.com/articles/241012.html)

引言

软件设计因为引入面向对象思想而逐渐变得丰富起来。“一切皆为对象”的精义,使得程序世界所要处理的逻辑简化,开发者可以用一组对象以及这些对象之间的关系将软件系统形象地表示出来。而从对象的定义,进而到模块,到组件的定义,利用面向对象思想的封装、继承、多态的思想,使得软件系统开发可以向搭建房屋那样,循序渐进,从砖石到楼层,进而到整幢大厦的建成。应用面向对象思想,在设计规模更大、逻辑更复杂的系统时,开发周期反而能变的更短。自然其中,需要应用到软件工程的开发定义、流程的过程控制,乃至于质量的缺陷管理。但从技术的细节来看,面向对象设计技术居功至伟。然而,面向对象设计的唯一问题是,它本质是静态的,封闭的,任何需求的细微变化都可能对开发进度造成重大影响。

可能解决该问题的方法是设计模式。GOF将面向对象软件的设计经验作为设计模式纪录下来,它使人们可以更加简单方便地复用成功的设计和体系结构,帮助开发人员做出有利于系统复用的选择。设计模式解决特定的设计问题,使面向对象设计更灵活、优雅,最终复用性更好。然而,设计模式虽然给了我们设计的典范与准则,通过最大程度的利用面向对象的特性,诸如利用继承、多态,对责任进行分离、对依赖进行倒置,面向抽象,面向接口,最终设计出灵活、可扩展、可重用的类库、组件,乃至于整个系统的架构。在设计的过程中,通过各种模式体现了对象的行为,暴露的接口,对象间关系,以及对象分别在不同层次中表现出来的形态。然而鉴于对象封装的特殊性,“设计模式”的触角始终在接口与抽象中大做文章,而对于对象内部则无能为力。

Aspect-Oriented Programming(面向方面编程,AOP)正好可以解决这一问题。它允许开发者动态地修改静态的OO模型,构造出一个能够不断增长以满足新增需求的系统,就象现实世界中的对象会在其生命周期中不断改变自身,应用程序也可以在发展中拥有新的功能。AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

面向方面编程(AOP)是施乐公司帕洛阿尔托研究中心(Xerox PARC)在上世纪90年代发明的一种编程范式。但真正的发展却兴起于近几年对软件设计方兴未艾的研究。由于软件系统越来越复杂,大型的企业级应用越来越需要人们将核心业务与公共业务分离。AOP技术正是通过编写横切关注点的代码,即“方面”,分离出通用的服务以形成统一的功能架构。它能够将应用程序中的商业逻辑同对其提供支持的通用服务进行分离,使得开发人员从重复解决通用服务的劳动中解脱出来,而仅专注于企业的核心商业逻辑。因此,AOP技术也就受到越来越多的关注,而应用于各种平台下的AOP技术也应运而生。但由于AOP技术相对于成熟的OOP技术而言,在性能、稳定性、适用性等方面还有待完善,同时AOP技术也没有形成一个统一的标准,这使得AOP技术的研究更具有前沿性的探索价值。

 

 

AOP技术基础

2.1 AOP技术起源

AOP技术的诞生并不算晚,早在1990年开始,来自Xerox Palo Alto Research Lab(即PARC)的研究人员就对面向对象思想的局限性进行了分析。他们研究出了一种新的编程思想,借助这一思想或许可以通过减少代码重复模块从而帮助开发人员提高工作效率。随着研究的逐渐深入,AOP也逐渐发展成一套完整的程序设计思想,各种应用AOP的技术也应运而生。

AOP技术在Java平台下是最先得到应用的。就在PARC对于面向方面编程进行研究的同时,美国Northeastern University的博士生Cristina Lopes和其同事也开始了类似的思考。最终,美国国防先进技术研究计划署(Defense Advanced Research Projects Agency即DARPA)注意到了这项工作,并提供了科研经费,鼓励将二者的工作成果结合起来。他们通过定义一套Java语言的扩展系统,使开发者可以方便的进行面向方面的开发,这套扩展系统被称为AspectJ。之后,AspectJ在2002年被转让给Eclipse Foundation,从而成为在开源社区中AOP技术的先锋,也是目前最为流行的AOP工具。

AspectWerkz则是基于Java的动态的、轻量级AOP框架。AspectWerkz仍然是开源社区中的产品,由BEA System提供赞助,开发者则是BEA的两名员工Jonas Bonér和Alexandre Vasseur。最近版本是AspectWerkz 2.0。2005年1月,AspectJ和AspectWerkz达成协议,同意将二者的成果综合到一起,取其精华创建一个单一的工具。他们合作的第一个发布版本为AspectJ 5,它扩展了AspectJ语言,以支持基于Annotation开发风格而又支持类似AspectJ代码风格。AspectJ 5也为Java 5的语言特性提供完全的AOP支持。

在Java阵营中,商用软件制造商JBoss在其2004年推出的JBoss 4.0中,引入了AOP框架和组件。在JBoss 4.0中,用户可以在JBoss应用服务器外部单独使用JBoss AOP,该版本为JBoss AOP 1.0,是在2004年10月发布的。在2005年,JBoss AOP框架又发布了1.3.0版本,新版本对加载期织入(Weev)和切点(point cut)匹配的性能做了很大的优化,使应用程序的启动时间大大缩短。

作为轻型的Framework,Spring在开发轻量级的J2EE时,应用是非常广泛的。它通过IoC模式(Inversion of Control,控制反转模式)来实现AOP,通常被称为Spring AOP。在2004年,被作为Spring框架的扩展而发布,目前版本已更新到1.1.3。Spring AOP作为一种非侵略性的,轻型的AOP框架,开发者无需使用预编译器或其他的元标签,在Java程序中应用AOP。目前,AOP的功能完全集成到了Spring事务管理、日志和其他各种特性的上下文中。

在.Net的阵营中,AOP技术的应用远不如Java阵营对AOP的关注。2005年1月,微软发布的Enterprise Library提供了7种不同的“应用程序块(application blocks)”。有个别专家认为,这些组件可以被认为是方面。但该观点并未得到一致的认同。事实上,在.Net平台下,推动AOP技术发展的原动力并非微软,而是开源社区。虽然,微软的技术专家们亦然听到了在.Net Framework中增加AOP技术的群众呼声,但作为如此巨大的软件公司,要让它灵活地转变战略方向,显然是不太现实的。正因为此,才赐予了开源社区在AOP技术的研究与探索上一个巨大的发展空间。

与Java阵营中的AOP技术不同,目前在.Net平台下的各种AOP工具,基本上还停留在实验室阶段。但一些在技术上领先且逐渐成熟的AOP产品,也在开源社区中渐露峥嵘。这其中主要包括Aspect#,AspectDNG,Eos AOP等。

Aspect#是基于Castle动态代理技术来实现的。Castle源于Apache Avalon项目,其目的在于实现一个轻量级的IoC容器。Aspect#于2005年6月被收录为Castle的其中一个子项目。它是针对CLI(.Net和Mono)实现的AOP框架,利用了反射、代理等机制。目前的Aspect#版本为2.1.1。

AspectDNG目前的版本为0.7,仍然处于beta版的阶段。它的实现技术是基于rail的静态织入。Rail属于IL级别下的代码织入,它自定义的一套xml格式的ILML语言,能够将原有的程序集拆散成ILML格式,以便于对静态程序集进行修改和扩展,从而达到静态织入的目的。因为AspectDNG是属于IL级别下的代码织入,因此在.Net平台下,并不受具体的编程语言限制。

Eos AOP与AspectDNG一样,仍然采用静态织入的方式,但从语法定义上,它更近似于AspectJ关于AOP的实现。它扩展了C#语法,引入了aspect、introduce、before、after等关键字,并且提供了专用的Eos编译器。Eos项目是于2004年9月开始启动,2005年6月推出的0.3.3版本为最新版本,主要的开发人员为Hridesh Rajan和Kevin Sullivan。前者为Virginia大学计算机系的研究生,Eos项目最初是由Hridesh Rajan提出的;而后者则为该计算机系的副教授(Associate Professor)。所以自Eos诞生之初,就带有浓厚的学院派特色。

从AOP技术的整体发展来看,高性能、稳定、可扩展、易用的AOP框架是其趋势与目标。从上述对各种AOP技术的分析来看,AOP技术无疑是具有共同特点的,而各种实现技术就是围绕着这些共性深入与延伸。接下来,我将概要地介绍AOP的本质,以及它的技术要素。

2.2 AOP技术本质

2.2.1 技术概览

AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。然而殊途同归,实现AOP的技术特性却是相同的,分别为:

1、join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。
2、point cut(切入点):本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。
3、advice(通知):是point cut的执行代码,是执行“方面”的具体逻辑。
4、aspect(方面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。
5、introduce(引入):为对象引入附加的方法或属性,从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。

上述的技术特性组成了基本的AOP技术,大多数AOP工具均实现了这些技术。它们也可以是研究AOP技术的基本术语。

2.2.2 横切技术

“横切”是AOP的专有名词。它是一种蕴含强大力量的相对简单的设计和编程技术,尤其是用于建立松散耦合的、可扩展的企业系统时。横切技术可以使得AOP在一个给定的编程模型中穿越既定的职责部分(比如日志记录和性能优化)的操作。

如果不使用横切技术,软件开发是怎样的情形呢?在传统的程序中,由于横切行为的实现是分散的,开发人员很难对这些行为进行逻辑上的实现或更改。例如,用于日志记录的代码和主要用于其它职责的代码缠绕在一起。根据所解决的问题的复杂程度和作用域的不同,所引起的混乱可大可小。更改一个应用程序的日志记录策略可能涉及数百次编辑——即使可行,这也是个令人头疼的任务。

在AOP中,我们将这些具有公共逻辑的,与其他模块的核心逻辑纠缠在一起的行为称为“横切关注点(Crosscutting Concern)”,因为它跨越了给定编程模型中的典型职责界限。

2.2.2.1 横切关注点

一个关注点(concern)就是一个特定的目的,一块我们感兴趣的区域,一段我们需要的逻辑行为。从技术的角度来说,一个典型的软件系统包含一些核心的关注点和系统级的关注点。举个例子来说,一个信用卡处理系统的核心关注点是借贷/存入处理,而系统级的关注点则是日志、事务完整性、授权、安全及性能问题等,许多关注点——即横切关注点(crosscutting concerns)——会在多个模块中出现。如果使用现有的编程方法,横切关注点会横越多个模块,结果是使系统难以设计、理解、实现和演进。AOP能够比上述方法更好地分离系统关注点,从而提供模块化的横切关注点。

例如一个复杂的系统,它由许多关注点组合实现,如业务逻辑、性能,数据存储、日志和调度信息、授权、安全、线程、错误检查等,还有开发过程中的关注点,如易懂、易维护、易追查、易扩展等,图2.1演示了由不同模块实现的一批关注点组成一个系统。

技术分享
图2.1 把模块作为一批关注点来实现

通过对系统需求和实现的识别,我们可以将模块中的这些关注点分为:核心关注点和横切关注点。对于核心关注点而言,通常来说,实现这些关注点的模块是相互独立的,他们分别完成了系统需要的商业逻辑,这些逻辑与具体的业务需求有关。而对于日志、安全、持久化等关注点而言,他们却是商业逻辑模块所共同需要的,这些逻辑分布于核心关注点的各处。在AOP中,诸如这些模块,都称为横切关注点。应用AOP的横切技术,关键就是要实现对关注点的识别。

如果将整个模块比喻为一个圆柱体,那么关注点识别过程可以用三棱镜法则来形容,穿越三棱镜的光束(指需求),照射到圆柱体各处,获得不同颜色的光束,最后识别出不同的关注点。如图2.2所示:

技术分享
图2.2 关注点识别:三棱镜法则

上图识别出来的关注点中,Business Logic属于核心关注点,它会调用到Security,Logging,Persistence等横切关注点。

public class BusinessLogic
{
    public void SomeOperation()
    {
       //验证安全性;Securtity关注点;
       //执行前记录日志;Logging关注点;

       DoSomething();

       //保存逻辑运算后的数据;Persistence关注点;
       //执行结束记录日志;Logging关注点;
    }
}

AOP的目的,就是要将诸如Logging之类的横切关注点从BusinessLogic类中分离出来。利用AOP技术,可以对相关的横切关注点封装,形成单独的“aspect”。这就保证了横切关注点的复用。由于BusinessLogic类中不再包含横切关注点的逻辑代码,为达到调用横切关注点的目的,可以利用横切技术,截取BusinessLogic类中相关方法的消息,例如SomeOperation()方法,然后将这些“aspect”织入到该方法中。例如图2.3:

技术分享
图2.3 将横切关注点织入到核心关注点中

通过利用AOP技术,改变了整个系统的设计方式。在分析系统需求之初,利用AOP的思想,分离出核心关注点和横切关注点。在实现了诸如日志、事务管理、权限控制等横切关注点的通用逻辑后,开发人员就可以专注于核心关注点,将精力投入到解决企业的商业逻辑上来。同时,这些封装好了的横切关注点提供的功能,可以最大限度地复用于商业逻辑的各个部分,既不需要开发人员作特殊的编码,也不会因为修改横切关注点的功能而影响具体的业务功能。

为了建立松散耦合的、可扩展的企业系统,AOP应用到的横切技术,通常分为两种类型:动态横切和静态横切。

2.2.2.2 动态横切

动态横切是通过切入点和连接点在一个方面中创建行为的过程,连接点可以在执行时横向地应用于现有对象。动态横切通常用于帮助向对象层次中的各种方法添加日志记录或身份认证。在很多应用场景中,动态横切技术基本上代表了AOP。

动态横切技术的核心主要包括join point(连接点),point cut(切入点),advice(通知)和aspect(方面)。在前面,我已经概要地介绍了这些术语分别代表的含义。接下来,我将以一个具体的实例来进一步阐述它们在AOP动态横切中实现的意义。

考虑一个电子商务系统,需要对订单进行添加、删除等管理操作。毫无疑问,在实际的应用场景中,这些行为应与权限管理结合,只有获得授权的用户方能够实施这些行为。采用传统的设计方法,其伪代码如下:
public class OrderManager
{
    private ArrayList m_Orders;
    public OrderManager()
    {
       m_Orders = new ArrayList();
    }
    public void AddOrder(Order order)
    {
        if (permissions.Verify(Permission.ADMIN))
        {

            m_Orders.Add(order);
        }
    }

    public void RemoveOrder(Order order)
    {
        if (permissions.Verify(Permission.ADMIN))
        {
            m_Orders.Remove(order);
        }
    }
}

同样的,在该电子商务系统中,还需要对商品进行管理,它采用了同样的授权机制:
public class ProductManager
{
    private ArrayList m_Products;
    public ProductManager()
    {
        m_Products = new ArrayList();
    }
    public void AddProduct(Product product)
    {
        if (permissions.Verify(Permission.ADMIN))
        {
             m_Products.Add(product);
        }
    }
    public void RemoveProduct(Product product)
    {
        if (permissions.Verify(Permission.ADMIN))
        {
             m_Products.Remove(product);
        }
    }
}

如此以来,在整个电子商务系统中,核心业务包括订单管理和商品管理,它们都需要相同的权限管理,如图2.4所示:

技术分享
图2.4 电子商务系统的权限验证实现

毫无疑问,利用AOP技术,我们可以分离出系统的核心关注点和横切关注点,从横向的角度,截取业务管理行为的内部消息,以达到织入权限管理逻辑的目的。当执行AddOrder()等方法时,系统将验证用户的权限,调用横切关注点逻辑,因此该方法即为AOP的join point。对于电子商务系统而言,每个需要权限验证的方法都是一个单独的join point。由于权限验证将在每个方法执行前执行,所以对于这一系列join point,只需要定义一个point cut。当系统执行到join point处时,将根据定义去查找对应的point cut,然后执行这个横切关注点需要实现的逻辑,即advice。而point cut和advice,就组合成了一个权限管理aspect。

技术分享
图2.5 AOP动态横切的技术实现

由于aspect是一个封装的对象,我们可以定义这样一个aspect:
private static aspect AuthorizationAspect{……}

然后在这个aspect中定义point cut,在point cut中,定义了需要截取上下文消息的方法,例如:
private pointcut authorizationExecution():
execution(public void OrderManager.AddOrder(Order)) ||
execution(public void OrderManager.DeleteOrder(Order)) ||
execution(public void ProductManager.AddProduct(Product)) ||
execution(public void ProductManager.DeleteProduct(Product));

由于权限验证是在订单管理方法执行之前完成,因此在before advice中,定义权限检查:
before(): authorizationExecution()
{
    if !(permissions.Verify(Permission.ADMIN))
    {
        throw new UnauthorizedException();
    }
}

通过定义了这样一个完整的aspect,当系统调用OrderManager或ProductManager的相关方法时,就触发了point cut,然后调用相应的advice逻辑。如此以来,OrderManager和ProductManager模块就与权限管理模块完全解除了依赖关系,同时也消除了传统设计中不可避免的权限判断的重复代码。这对于建立一个松散耦合、可扩展的系统软件是非常有利的。

2.2.2.3 静态横切

静态横切和动态横切的区别在于它不修改一个给定对象的执行行为。相反,它允许通过引入附加的方法字段和属性来修改对象的结构。此外,静态横切可以把扩展和实现附加到对象的基本结构中。在AOP实现中,通常将静态横切称为introduce或者mixin。

静态横切在AOP技术中,受到的关注相对较少。事实上,这一技术蕴含的潜力是巨大的。使用静态横切,架构师和设计者能用一种真正面向对象的方法有效地建立复杂系统的模型。静态横切允许您不用创建很深的层次结构,以一种本质上更优雅、更逼真于现实结构的方式,插入跨越整个系统的公共行为。尤其是当开发应用系统时,如果需要在不修改原有代码的前提下,引入第三方产品和API库,则静态横切技术将发挥巨大的作用。

举例来说,当前已经实现了一个邮件收发系统,其中类Mail完成了收发邮件的功能。但在产品交付后,发现该系统存在缺陷,在收发邮件时,未曾实现邮件地址的验证功能。现在,第三方产品已经提供了验证功能的接口IValidatable:
public interface IValidatable
{
    bool ValidateAddress();
}

我们可以利用设计模式中的Adapter模式,来完成对第三方产品API的调用。我们可以定义一个新的类MailAdapter,该类实现了IValidatable接口,同时继承了Mail类:
public class MailAdapter:Mail,IValidatable
{
     public bool ValidateAddress()
     {
         if(this.getToAddress() != null)
         {
             return true;
         }
         else
         {
             return false;
         }
     }
}

通过引入MailAdapter类,原来Mail对象完成的操作,将全部被MailAdapter对象取代。然而,此种实现方式虽然能解决引入新接口的问题,但类似下面的代码,却是无法编译通过的:
Mail mail = new Mail();
IValidatable validate = ((IValidatable)mail).ValidateAddress();

必须将第一行代码作如下修改:
Mail mail = new MailAdapter();

利用AOP的静态横切技术,可以将IValidatable接口织入到原有的Mail类中,这是一种非常形象的introduce功能,其实现仍然是在aspect中完成:
import com.acme.validate.Validatable;

public aspect MailValidateAspect
{
    declare parents: Mail implements IValidatable;

    public boolean Mail.validateAddress()
    {
         if(this.getToAddress() != null)
         {
              return true;
         }
         else
         {
              return false;
         }
    }
}

静态横切的方法,并没有引入类似MailAdapter的新类,而是通过定义的MailValidateAspect方面,利用横切技术为Mail类introduce了新的方法ValidateAddress(),从而实现了Mail的扩展。因此如下的代码完全可行。
Mail mail = new Mail();
IValidatable validate = ((IValidatable)mail).ValidateAddress();

2.3 AOP技术的优势

AOP技术的优势是显而易见的。在面向对象的世界里,人们提出了各种方法和设计原则来保障系统的可复用性与可扩展性,以期建立一个松散耦合、便于扩展的软件系统。例如GOF提出的“设计模式”,为我们提供了设计的典范与准则。设计模式通过最大程度的利用面向对象的特性,诸如利用继承、多态,对责任进行分离、对依赖进行倒置,面向抽象,面向接口,最终设计出灵活、可扩展、可重用的类库、组件,乃至于整个系统的架构。在设计的过程中,通过各种模式体现对象的行为、暴露的接口、对象间关系、以及对象分别在不同层次中表现出来的形态。然而鉴于对象封装的特殊性,“设计模式”的触角始终在接口与抽象中大做文章,而对于对象内部则无能为力。

通过“横切”技术,AOP技术就能深入到对象内部翻云覆雨,截取方法之间传递的消息为我所用。由于将核心关注点与横切关注点完全隔离,使得我们能够独立的对“方面”编程。它允许开发者动态地修改静态的OO模型,构造出一个能够不断增长以满足新增需求的系统,就象现实世界中的对象会在其生命周期中不断改变自身,应用程序也可以在发展中拥有新的功能。

设计软件系统时应用AOP技术,其优势在于:

(一)在定义应用程序对某种服务(例如日志)的所有需求的时候。通过识别关注点,使得该服务能够被更好的定义,更好的被编写代码,并获得更多的功能。这种方式还能够处理在代码涉及到多个功能的时候所出现的问题,例如改变某一个功能可能会影响到其它的功能,在AOP中把这样的麻烦称之为“纠结(tangling)”。

(二)利用AOP技术对离散的方面进行的分析将有助于为开发团队指定一位精于该项工作的专家。负责这项工作的最佳人选将可以有效利用自己的相关技能和经验。

(三)持久性。标准的面向对象的项目开发中,不同的开发人员通常会为某项服务编写相同的代码,例如日志记录。随后他们会在自己的实施中分别对日志进行处理以满足不同单个对象的需求。而通过创建一段单独的代码片段,AOP提供了解决这一问题的持久简单的方案,这一方案强调了未来功能的重用性和易维护性:不需要在整个应用程序中一遍遍重新编写日志代码,AOP使得仅仅编写日志方面(logging aspect)成为可能,并且可以在这之上为整个应用程序提供新的功能。

总而言之,AOP技术的优势使得需要编写的代码量大大缩减,节省了时间,控制了开发成本。同时也使得开发人员可以集中关注于系统的核心商业逻辑。此外,它更利于创建松散耦合、可复用与可扩展的大型软件系统。

 

 

Java平台AOP技术研究

 

3.1 Java平台AOP技术概览

3.1.1 AOP技术在Java平台中的应用

AOP在实验室应用和商业应用上,Java平台始终走在前面。从最初也是目前最成熟的AOP工具——AspectJ,到目前已经融和在企业级容器JBoss中的JBoss AOP,均建立在Java平台上。

前面已经描述到,AOP的目的就是将核心关注点和横切关注点分离,实际上这就是一种分散关注(seperation of concerns)的思路。在Java平台下,如果要开发企业级的应用,非J2EE莫属。一个J2EE应用系统只有部署在J2EE容器中才能运行,那么为什么要划分为J2EE容器和J2EE应用系统? 通过对J2EE容器运行机制的分析,我们发现:实际上J2EE容器分离了一般应用系统的一些通用功能,例如事务机制、安全机制以及对象池或线程池等性能优化机制。这些功能机制是每个应用系统几乎都需要的,因此可以从具体应用系统中分离出来,形成一个通用的框架平台,而且,这些功能机制的设计开发有一定难度,同时运行的稳定性和快速性都非常重要,必须经过长时间调试和运行经验积累而成,因此,形成了专门的J2EE容器服务器产品,如Tomcat JBoss、Websphere、WebLogic等。

从J2EE将应用系统和容器分离的策略,我们能够看到AOP的影子。J2EE应用系统就相当于AOP技术中的核心关注点,它的内容主要包括企业系统的商业逻辑;而J2EE容器则类似于横切关注点,实现的是通用的功能机制。不过业界在选择J2EE容器时,对于EJB这种重量级容器服务器而言,虽然欣赏其高效、稳定及企业级的容器服务,但对于整个容器的高开销、高成本以及过于复杂的解决方案均深怀戒心。因此,随着J2EE的逐步演化,“轻量级容器架构”通过开源社区如激流一般的驱动力,逐渐占据了J2EE技术的强势地位。而所谓“轻量级容器”与EJB提供的重量级架构的区别,就在于借助了AOP技术和IoC(Inversion of Control,反转模式)机制,降低了代码对于专用接口的依赖性,以简短、轻便、专注、可移植的方式实现业务对象。事实上,我们看到的美好前景是,如果所有企业级服务都可以通过AOP机制提供给普通Java对象,那么深盔重铠的应用服务器就不再有存在的价值了。

正是看到了AOP技术在企业级开发中的巨大潜力,而“轻量级容器”也唤起了改革EJB容器的呼声(事实上,最新的 EJB V3.0 标准就使用了轻量级容器模型),越来越多的AOP工具在Java平台下应运而生,从而形成了目前AOP工具百家争鸣的局面。其中,应用最为广泛的主要包括AspectJ、Spring AOP和JBoss AOP等。

3.1.2 Java平台下AOP工具的比较

AOP是一项新技术,而在Java平台下实现该技术的工具也非常多。虽然AOP的技术要素从本质上来讲是一致的,但各种工具的实现方法也各有不同,本节基于AOP的技术要素,对当前应用较广泛的AspectJ、Spring AOP和JBoss AOP进行比较。

3.1.2.1 AOP实现机制的区别

同样是实现AOP,且AOP的技术要素完全相同,但各种AOP工具对于AOP实现的底层机制却是不尽相同的。

AspectJ采用了源代码生成技术来实现AOP。它提供了一套独有的基于Java平台的AOP语法,以及专有的AspectJ编译器。编译器在编译具有AspectJ语法的Java程序时,能够识别诸如aspect,pointcut等特殊关键字,然后利用静态织入的方式,修改需要被截取的方法所属类的源代码,把advice或者introduce的业务逻辑代码注入到正确的位置。利用AspectJ,可以将核心关注点完全独立出来,然后通过AspectJ语法,编写符合核心关注点要求的横切关注点代码,最后通过AspectJ编译器,将这两者在后期结合起来。采用这种静态织入技术,使得运用了AOP技术的系统在运行性能上未受到任何损失,因为它没有利用反射技术或代理技术,而仅仅是程序的静态扩展而已。然而这种源代码生成方式实现的AOP虽然在性能上具备一定的优势,但它同时会给开发带来一定的问题。例如代码的后期修改会给系统带来不可估量的影响。

Spring AOP是Spring框架中的一部分,但可以作为一个独立的模块单独存在。Spring AOP实现AOP技术从本质上来讲,是利用了JDK提供的动态代理技术。而从实际的实现方式来看,则是利用了IoC(Inversion of Control,反转模式)机制,同时采用了AOP联盟(AOP Alliance)的通用AOP接口。首先,Spring AOP通过xml配置文件配置了pointcut,并利用Interceptor(拦截机)作为设定的触发条件。Interceptor是由用户自定义的,它相当于是AOP中的advice,但该Interceptor需要实现AOP联盟的通用AOP接口,例如org.aopalliance.intercept.MethodInterceptor。最后定义一个Spring AOP ProxyFactory用于加载执行AOP组件,并利用IoC机制将advice注入到接口以及实现类中。

JBoss 4.0提供了AOP框架。与Spring一样,这个框架可与JBoss应用服务器紧密结合,也可以单独运行在自己的应用中。JBoss AOP同样需要Interceptor拦截器来完成对方法的拦截,它要求所有的Interceptor都必须实现org.jboss.aop.Interceptor接口。在这个接口中最重要的方法就是invoke()。该方法对元数据直接进行操作,并利用反射的原理去拦截方法的消息。Interceptor相当于AOP的advice,至于pointcut,则在xml配置文件中配置。可以看出,Spring AOP和JBoss AOP在实现上属于动态织入的方式,它们与AspectJ在实现上是迥然不同的两种方式。

3.1.2.2 关于“Aspect(方面)”的区别

在对aspect的声明上,可以使用类似Java的代码,注释或xml。考虑一个常用的例子,对Account类的授权策略,如果以AOP技术来实现,运用不同的AOP工具,它们在方面声明技术上的差异,是显而易见的。

Aspect 中的方面声明类似于 Java 语言中的类声明,如图3.1 所示。

技术分享
图3.1 AspectJ中的方面声明

由于 AspectJ 是 Java 语言语法和语义的扩展,所以它提供了自己的一套处理方面的关键字。除了包含字段和方法之外,AspectJ 的方面声明还包含pointcut和advice成员。示例中的pointcut使用了修饰符(modifier)和通配符(wildcard)模式来表达“所有公共方法”。对帐户的访问,由 pointcut 参数提供。advice使用这个参数,而pointcut则用 this(account) 把它绑定。这样做的效果,就是捕获了正在执行的方法所隶属的Account对象。否则,advice的主体与方法的主体相似。advice可以包含认证代码,或者就像在这个示例中一样,可以调用其他方法。

JBoss AOP 基于 XML 的风格来声明方面,如图 3.2 所示。

技术分享
图3.2 JBoss AOP的方面声明

在 XML 风格中,aspect、pointcut和advice的声明都以 XML 形式表示的。advice的实现,用的是普通的 Java 方法,由JBoss AOP框架调用。pointcut和pointcut到advice的绑定都在方面中用XML注释声明。JBoss 没有显式地绑定 Account 参数,而是提供了对当前正在执行的对象的反射访问,因此需要把类型转换到对应的类型。JBoss AOP还可以通过标签的方式对方面进行声明。标签均以“@”字符开始,它的使用有点类似于.Net中的Attribute。

Spring AOP同样是基于 XML 的风格来声明方面,如图3.3所示。

技术分享
图3.3 Spring AOP的方面声明

与JBoss AOP类似,Spring的advice实现是带有特殊参数的Java方法,由 Spring 框架调用。XML描述accountBean,Spring框架通过它访问 Account 对象,包括通知使用的拦截器 advisor 及其匹配模式,还有应用到模式的向前(before) 通知。

由于Spring AOP利用了IoC机制,因此比较JBoss AOP而言,在xml配置文件中提供了更加精细的配置。而构建、运行和配置 Spring AOP 方面的过程则与JBoss AOP基本相同,不过Spring AOP依赖的是Spring框架方便的、最小化的运行时配置,所以不需要独立的启动器。

3.1.2.3 语言机制的区别

    由于实现机制和语法风格的不同,三种AOP工具在语言机制上也有很大的不同,以下从四个方面来描述AspectJ、JBossAOP和Spring AOP之间的区别。

(1)pointcut匹配和复合:AspectJ和 JBoss AOP 提供了类似的类型模式支持。它们都允许签名方面的匹配,对于 Java 5 应用程序来说,这些匹配包括注释和泛型。AspectJ提供了一种简洁的引用多个类型的技术(例如 Account+ 表示帐户的所有子类型)。所有的工具都支持通配符匹配。Spring AOP 还提供了对正则表达式的支持。虽然这看起来可能是一个强大的优势,但还是要指出其他技术已经选择了放弃正则表达式,好让pointcut读起来不是太难,同时不会存在潜在的损害。pointcut复合操作符基本上都是相同的。Spring AOP 不提供“非”操作,这个操作通常与没有在 Spring AOP 连接点模型的容器(containment)连接点结合使用。

(2)advice形式:AspectJ 支持比其他技术更多的advice形式,而 JBoss AOP 只支持一种advice形式。每种通知形式都可以表达成 around advice,所以 JBoss 的技术是无限的,而且它确实提供了额外的简单性。不好的一面是它损失了简洁性。另外,强迫advice去遵守普通的 Java 规则(就像注释和 XML 风格做的那样),在一些情况下容易出问题,因为这些规则是为方法设计的。AspectJ 拥有把被通知方法的异常“软化”的能力,这很有用,但是不符合方法异常检测的标准语义。

(3)join point上下文:在 AspectJ中,通过指定和绑定pointcut参数访问动态连接点的状态,类似于在 Java 语言中声明方法参数的技术(请参阅图3.1)。这为连接点上下文提供了静态类型化的好处。JBoss AOP 和 Spring AOP 反射性地访问连接点的状态,这消除了在切入点表达式中参数绑定的复杂性,代价是参数静态类型化。Java 程序员习惯了方法参数静态类型化带来的好处,同时还可以从pointcut参数的静态类型化得到同样的好处。所以,在 JBoss AOP 最近的发行版本中,有提供静态类型化的“args”的计划。

(4)扩展性:aspect的扩展性支持库方面的部署,这样可以在日后为特定程序将这些库方面具体化。例如,一个方面库可以提供应用程序监视需要的全部逻辑和基础设施。但是,要采用某个特定项目的库,那么库使用的pointcut必须扩展成应用程序特定的join point。AspectJ 用抽象方面支持扩展性,抽象方面包含抽象的pointcut和具体的advice。扩展抽象方面的子方面必须具体化pointcut。JBoss AOP 使用了完全不同的技术,没有使用抽象切入点机制。扩展是通过生成aspect的子类、并在 XML 中或通过注释定义新的advice绑定而实现的。pointcut到advice的显式绑定为JBoss AOP提供了显著优势,从而可以很容易地把方面扩展到新系统,无需要生成子类。

3.2 Java平台下AOP主流工具研究

3.2.1 AsepctJ研究

AspectJ作为Java编程语言扩展的AOP工具,使得我们运用AOP技术能够像普通的Java编程那样,特殊之处,仅在于我们需要使用AspectJ提供的特殊语法。接下来,我将通过一些实例,介绍如何运用AspectJ实现AOP技术。

3.2.1.1 AspectJ语言特性

设定我们的开发项目中需要应用到日志记录,根据前面介绍的AOP知识,我们已经能够从这个需求中识别出横切关注点——日志记录。因此,我们需要定义关于“日志记录”的aspect:

public aspect AutoLog
{
    pointcut publicMethods() : execution(public * org.apache.cactus..*(..));
    pointcut logObjectCalls() : execution(* Logger.*(..));
    pointcut loggableCalls() : publicMethods() && ! logObjectCalls(); 

    before() : loggableCalls()
    {
      Logger.entry(thisJoinPoint.getSignature().toString());
    }
    after() : loggableCalls()
    {
      Logger.exit(thisJoinPoint.getSignature().toString());
    }
}

如果仅仅熟悉Java编程,会发现有很多关键字是Java语言中不曾包含的,它们均是AspectJ提供的。

分析上述的代码,首先是aspect的声明,它类似于Java中的类声明,定义了一个aspect:AutoLog。在这个方面中分别包含了pointcut和advice。

pointcut共有三个:publicMethod、logObjectCalls和loggableCalls。publicMethod将选择org.apache.cactus包中的所有公共(public)方法的执行。所谓“选择”,就意味着它的join point为其选择的方法。当这些方法被调用时,就会执行pointcut的advice代码。而在pointcut中,execution 是一个原始的 Pointcut(就象 int 是一种原始的 Java 类型)。它选择与括号中定义的方法说明匹配的任何方法的执行。方法说明允许包含通配符。logObjectCalls的pointcut则选择Logger 类中的所有方法的执行。第三个pointcut比较特殊,它使用&& !合并了前两个 Pointcut,这意味着它选者了除Logger类中的公共方法以外, org.apache.cactus 中所有的公共方法。

advice在aspect中,被用来完成实际的日志纪录。advice有三种,分别为before、after和around。如上述代码中定义的advice:
before() : loggableCalls()
{
    Logger.entry(thisJoinPoint.getSignature().toString());
}

该advice的定义表示的含义是,如果org.apache.cactus中所有的公共方法(Logger类的公共方法除外)被执行,则在这些方法执行之前,需要先执行该advice定义的逻辑。

3.2.1.2 AspectJ的高级语言特性

在本文第二部分介绍AOP技术时,提到了横切技术的分类。其中,静态横切技术能够扩展一个对象的结构。使用引入(Introduction),Aspect 可以向类中添加新的方法和变量、声明一个类实现一个接口或将检查异常转换为未检查异常(unchecked exception)。

3.2.1.2.1 向现有类添加变量和方法

假设您有一个表示持久存储的数据缓存的对象。为了测量数据的“更新程度”,您可能决定向该对象添加时间戳记字段,以便容易地检测对象是否与后备存储器同步。由于对象表示业务数据,根据AOP的知识,我们应该将这种机制性细节从对象中隔离。使用 AspectJ,可以用如下代码中所显示的语法来向现有的类添加时间戳记:

public aspect Timestamp
{
    private long ValueObject.timestamp;
    public long ValueObject.getTimestamp()
    {
       return timestamp;
    }
    public void ValueObject.timestamp()
    {    
       this.timestamp = System.currentTimeMillis();
    }
}

通过introduction,我们就非常方便的为ValueObject类型添加了timestamp的变量和相关方法。除了必须限定在哪个类上声明引入的方法和成员变量以外,声明引入的方法和成员变量几乎与声明常规类成员相同。

3.2.1.2.2实现多继承功能

利用introduction,AspectJ允许向接口和类添加成员,也突破了Java语言只能单继承的限制,允许程序按C++方式那样实现多继承。如果您希望上述的aspect Timestamp能够泛化 (generalize),以便能够对各种对象重用时间戳记代码,可以定义一个称为 TimestampedObject 的接口,并使用引入(Introduction)来将相同成员和变量添加到接口而不是添加到具体类中,如下所示:
public interface TimestampedObject
{
    long getTimestamp();
    void timestamp();
}
public aspect Timestamp
{
    private long TimestampedObject.timestamp;
    public long TimestampedObject.getTimestamp()
    {
        return timestamp;
    }
    public void TimestampedObject.timestamp()
    {
        this.timestamp = System.currentTimeMillis();
    }
}

Timestamp方面由于在TimestampedObject接口中引入(introduction)了方法的实现,使得TimestampedObject接口改变其本质,成为了一个特殊的类类型。特殊之处就在于一个已经继承了一个类的类类型,通过AspectJ的语法,仍然可以再次继承TimestampedObject,这就间接地实现了类的多继承。而这个特殊的AspectJ语法就是declare parents语法。declare parents和其它AspectJ 类型表达一样,可以同时应用于多个类型:
declare parents: ValueObject || BigValueObject implements TimestampedObject;

3.2.1.3 编译器及工具支持

    要让aspect能够正常工作,必须将aspect加入到它们要修改的代码中去。这项工作由AspectJ提供的ajc编译器完成。ajc 编译器用来编译类和 Aspect 代码。ajc 既可以作为编译器也可以作为预编译器操作,生成有效的 .class 或 .java 文件,可以在任何标准 Java 环境(添加一个小的运行时 JAR)中编译和运行这些文件。

要使用 AspectJ 进行编译,将需要显式地指定希望在给定编译中包含的源文件(Aspect 和类),ajc不象javac那样简单地为相关导入模块搜索类路径。之所以这样做,是因为标准 Java 应用程序中的每个类都是相对分离的组件。为了正确操作,一个类只要求其直接引用的类的存在。Aspect 表示跨越多个类的行为的聚集。因此,需要将 AOP 程序作为一个单元来编译,而不能每次编译一个类。

AspectJ 当前版本的一个重要限制是其编译器只能将aspect加入到它拥有源代码的代码中。也就是说,不能使用ajc将Advice添加到预编译类中。AspectJ 团队认为这个限制只是暂时的,AspectJ 网站承诺未来的版本(正式版 2.0)将允许字节码的修改。

AspectJ发行版包含了几种开发工具。这预示着 AspectJ 将有美好的前景,因为它表明了作者对这一部分的一个重要承诺,使 AspectJ 对于开发人员将是友好的。对于面向 Aspect 的系统工具支持尤其重要,因为程序模块可能受到它们所未知的模块所影响。

随 AspectJ 一起发布的一个最重要的工具是图形结构浏览器,它展示了 Aspect 如何与其它系统组件交互。这个结构浏览器既可以作为流行的 IDE 的插件,也可以作为独立的工具。图3.4显示了先前讨论的日志记录示例的视图。

技术分享
图3.4 AspectJ提供的“结构浏览器”工具

除了结构浏览器和核心编译器之外,您还可以从 AspectJ 网站下载一个 Aspect 支持的调试器、一个javadoc工具、一个Ant任务以及一个Emacs 插件。

3.2.2 JBoss AOP研究

JBoss AOP关于AOP的实现与AspectJ是两种完全不同的风格。由于Java利用元数据来存储有关类型、方法、字段的相关信息,因此,可以通过Java提供的反射功能获得模块相关的元数据,对方法进行拦截,并将被拦截的方法与aspect逻辑进行关联。

3.2.2.1 拦截器(Interceptor)

在JBoss AOP中,是用拦截器来实现advice的。可以自定义拦截器,拦截方法调用、构造函数调用以及对字段的访问,但JBoss要求这些自定义的拦截器,必须实现org.jboss.aop.Interceptor接口:

public interface Interceptor
{
    public String getName();
    public InvocationResponse invoke(Invocation invocation) throws Throwable;
}

在JBoss AOP中,被拦截的字段、构造器和方法均被转化为通用的invoke方法调用。方法的参数接收一个Invocation对象,而方法的返回值、字段的存取以及构造函数则被填入一个InvocationResponse对象。Invocation对象同时还驱动拦截链。下面我们自定义一个拦截器,它能够拦截构造函数和方法的调用,并将跟踪信息打印到控制台上:
import org.jboss.aop.*;
import java.lang.reflect.*;

public class TracingInterceptor implements Interceptor
{
    public String getName()
    {
        return TracingInterceptor;
    }
    public InvocationResponse invoke(Invocation invocation) throws Throwable
    {
        String message = null;
        if (invocation.getType() == InvocationType.METHOD)
        {
            Method method = MethodInvocation.getMethod(invocation);
            message = method: + method.getName();
        }
        else
        {
            if (invocation.getType() == InvocationType.CONSTRUCTOR)
            {
                Constructor c = ConstructorInvocation.getConstructor(invocation);
                message = constructor: + c.toString();
            }
            else
            {
                // 不对字段作处理,太繁琐;
                return invocation.invokeNext();
            }
            System.out.println(Entering + message);
        }
        // 继续。调用真正的方法或者构造函数
        InvocationResponse rsp = invocation.invokeNext();
        System.out.println(Leaving + message);
        return rsp;
    }
}

在自定义的TracingInterceptor类中,invoke()方法对invocation的类型作判断,以根据方法、构造函数和字段类型,分别作出不同的操作。而其中,invocation.invokeNext()则表示通过一个拦截链获得下一个invocation。

定义的拦截必须在xml文件配置,使其绑定到具体的类。这个定义即为AOP中的切入点pointcut。例如具体的类为BusinessObject,则该pointcut在xml中的定义如下:
<?xml version="1.0" encoding="UTF-8">
<aop>
    <interceptor-pointcut class="BusinessObject">
        <interceptors>
            <interceptor class="TracingInterceptor" />
        </interceptors>
    </interceptor-pointcut>
</aop>

上面的pointcut绑定TracingInterceptor到一个叫做BusinessObject的类。如果要将该Interceptor绑定到多个类,还可以利用正则表达式。例如,如果你想绑定由JVM载入的类,类表达式将变为 .*。如果你仅仅想跟踪一个特定的包,那么表达式将是bruce.zhang.mypackge.*。

当JBoss AOP独立运行时,任何符合 META-INF/jboss-aop.xml模式的XML文件将被JBoss AOP 运行期程序载入。如果相关的路径被包含在任何JAR或你的CLASSPATH目录中,该XML文件将在启动时,由JBoss AOP 运行期程序载入。

JBoss AOP还提供了过滤功能,可以通过在xml文件中配置过滤的标志,使一些特定的方法(包括字段的访问)被过滤,从而不再执行Interceptor的相关逻辑。例如,我们要过滤BusinessObject类的所有get()和set()方法,以及main()方法,则可以修改上述的xml文件:
<?xml version="1.0" encoding="UTF-8">
<aop>
    <class-metadata group="tracing" class=" BusinessObject ">
        <method name="(get.*)|(set.*)">
            <filter>true</filter>
        </method>
        <method name="main">
            <filter>true</filter>
        </method>
    </class-metadata>
</aop>

相应的,Interceptor代码也应作相关的修改,使其能够识别配置文件中的filter属性:
public class TracingInterceptor implements Interceptor
{
    ……//getName()方法略;
    public InvocationResponse invoke(Invocation invocation) throws Throwable
    {
        String filter=(String)invocation.getMetaData(tracing, filter);
        if (filter != null && filter.equals(true))
            return invocation.invokeNext();
        ……//后面的代码略;
    }
}

3.2.2.2 引入(Introduction)

JBoss AOP同样提供introduction功能,通过它,就可以为现有的类引入第三方接口或类的API了。例如,我们可以为具体的类如BusinessObject提供Tracing的开关,使得BusinessObject对象能够根据具体的情况打开或关闭aspect的Tracing功能。为实现该功能,可以定义一个Tracing接口:
public interface Tracing
{
    void enableTracing();
    void disableTracing();
}

接下来需要定义一个混合类,它实现了接口Tracing。当BusinessObject类被实例化时,该混合类的实例就会被绑定到BusinessObject上。实现方法如下:
import org.jboss.aop.Advised;

public class TracingMixin implements Tracing
{
    Advised advised;

    Public TracingMixin(Object obj)
    {
        this.advised = (Advised)obj;
    }
    public void enableTracing()
    {
        advised._getInstanceAdvisor().getMetaData().addMetaData("tracing", "filter", true);
    }
    public void disableTracing()
    {
        advised._getInstanceAdvisor().getMetaData().addMetaData("tracing", "filter", false);
    }
}

enableTracing()方法将filter属性绑定到对象实例。disableTracing()方法作同样的事,但是将filter属性设置为false。

定义了Tracing接口和实现了该接口的混合类后,就可以在xml文件中定义一个pointcut,强制BusinessObject类实现Tracing接口:
<?xml version="1.0" encoding="UTF-8">
<aop>
    <introduction-pointcut class="BusinessObject">
        <mixin>
            <interfaces>Tracing</interfaces>
            <class>TracingMixin</class>
            <construction>new TracingMixin(this)</construction>
        </mixin>
    </introduction-pointcut>
</aop>

注意xml文件中的标签,它代表的含义是当BusinessObject对象被实例化时,将执行该标签内的代码。以本例而言,当创建BusinessObject对象时,一个TracingMixin类的实例将被创建。任何单行的Java代码都可以放到标签中。

通过“引入(introduction)”功能,在处理BusinessObject对象时,就可以视其为Tracing接口类型而进行操作了,如下的示例:

public class BusinessObject
{
    public BusinessObject () {}
    public void helloWorld() { System.out.println(Hello World!); }

    public static void main(String[] args)
    {
        BusinessObject bo = new BusinessObject ();
        Tracing trace = (Tracing)this;
        bo.helloWorld();
        System.out.println("Turn off tracing.");
        trace.disableTracing();
        bo.helloWorld();
        System.out.println("Turn on tracing.");
        trace.enableTracing();
        bo.helloWorld();
    }
}

注意如下代码:
Tracing trace = (Tracing)this;

此时this代表的即为BusinessObject,从Java代码的角度来看,由于BusinessObject并没有实现Tracing接口,因此这行代码所示的显式转换为Tracing类型是不成功的。但通过“引入”功能,使得BusinessObject通过混合类,实现了Tracing接口,从而使得如上的代码能够顺利执行。隐含的意义就是,我们没有修改BusinessObject的定义,而是通过AOP技术,为BusinessObject扩展实现了第三方提供的接口Tracing。

3.2.3 Spring AOP研究

Spring AOP使用纯Java实现,不需要特别的编译过程,也不需要控制类装载层次。与JBoss AOP相同,它仍然利用了拦截器完成对方法的拦截。然而,Spring AOP实现AOP的主要技术却主要来自于AOP联盟,如拦截器应实现org.aopalliance.intercept.MethodInterceptor 接口,而所有advice必须实现org.aopalliance.aop.Advice标签接口。此外,Spring实现AOP的目标也不同于其他大部分AOP框架,它的目标不是提供及其完善的AOP实现,而是提供一个和Spring IoC紧密整合的AOP实现,帮助解决企业应用 中的常见问题。因此,Spring AOP的功能通常是和Spring IoC容器联合使用的。AOP Advice是用普通的bean定义语法来定义的,Advice和pointcut本身由Spring IoC 管理。这是一个重要的其他AOP实现的区别。

3.2.3.1 切入点(pointcut)

Spring的切入点模型能够使pointcut独立于advice类型被重用。同样的pointcut有可能接受不同的advice。将Pointcut接口分成两个部分有利于重用类和方法的匹配部分,并且组合细粒度的操作(如和另一个方法匹配器执行一个“并”的操作)。

在Spring的切入点中,org.springframework.aop.Pointcut接口是重要的接口,它用来指定通知到特定的类和方法目标。完整的接口定义如下:
public interface Pointcut
{
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

ClassFilte类型也是一个接口,该接口被用来将切入点限制到一个给定的目标类的集合。 如果matches()永远返回true,所有的目标类都将被匹配。
public interface ClassFilter
{
    boolean matches(Class clazz);
}

MethodMatcher接口通常更加重要。完整的接口定义如下:
public interface MethodMatcher
{
    boolean matches(Method m, Class targetClass);
    boolean matches(Method m, Class targetClass, Object[] args);
    boolean isRuntime();
}

matches(Method, Class) 方法被用来测试这个切入点是否匹配目标类的给定方法。这个测试可以在AOP代理创建的时候执行,避免在所有方法调用时都需要进行 测试。如果2个参数的matches()方法对某个方法返回true,并且MethodMatcher的isRuntime()也返回true,那么3个参数的matches()方法将在每次方法调用的时候被调用。这使切入点能够在目标advice被执行之前立即查看传递给方法调用的参数。由于大部分MethodMatcher都是静态的,意味着isRuntime()方法会返回false。此种情况下,3个参数的matches()方法永远不会被调用。

Spring AOP提供了几个实用的切入点实现,其中较为常用的是正则表达式切入点:org.springframework.aop.support.RegexpMethodPointcut,它使用Perl 5的正则表达式的语法。使用这个类你可以定义一个模式的列表。如果任何一个匹配,那个切入点将被计算成 true。用法如下:
<bean id="settersAndAbsquatulatePointcut"
    class="org.springframework.aop.support.RegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*get.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

不过,更多情况下是直接使用RegexpMethodPointcut一个实用子类: RegexpMethodPointcutAdvisor。它允许我们同时引用一个advice(在Spring AOP中,advice可以是拦截器,也可以是before advice,throws advice等)。这就简化了bean的装配,因为一个bean可以同时当作pointcut和advice,如下所示:
<bean id="myPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref local="MyInterceptor" />
    </property>
    <property name="patterns">
        <list>
            <value>.*save.*</value>
            <value>.*do.*</value>
        </list>
    </property>
</bean>

注意配置文件中的myPointcutAdvisor,在Spring AOP中,一个advisor就是一个aspect完整的模块化表示。通过advisor,可以将pointcut和advice(在此处即为MyInterceptor)绑定起来。

3.2.3.2 通知(advice)

Spring AOP的advice可以跨越多个被advice对象共享,或者每个被advice对象有自己的advice。要实现advice,最简单的做法就是定义一个拦截器(Interceptor)。它采用了AOP联盟(AOP Alliance)的通用AOP接口(接口定义为aopalliance.jar)。要实现advice,需要实现aopalliance.jar中定义的MethodInterceptor接口。

例如,我们定义了一个业务对象接口BusinessObject及其实现类BusinessObjectImpl,该业务对象能够存储数据,其定义如下:
public interface BusinessObject
{
    public void save();
}
public class BusinessObjectImpl implements BusinessObject
{
    public void save()
    {
         System.out.println("saving domain object......");
    }
}

现在需要为业务对象BusinessObject的Save()方法,提供Lock机制。根据Spring AOP的实现方式,我们可以定义一个LockInterceptor来实现MethodInterceptor接口:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LockInterceptor implements MethodInterceptor
{
    public Object invoke(MethodInvocation invocation) throws Throwable
    {
        // TODO Auto-generated method stub
        lock();
        Object ret= invocation.proceed();
        unlock();
        return ret;
    }
    private void lock()
    {
        System.out.println("lock domain object...");
    }
    private void unlock()
    {
        System.out.println("unlock domain object...");
    }
}

为将interceptor与具体的advice绑定起来,需要在配置文件中配置bean:
&lt;bean id="MyInterceptor" class="test.aop.spring.LockInterceptor"/&gt;

3.2.3.3 AOP代理与IoC容器

由于Spring中提供了IoC容器(例如BeanFactory),因此我们可以通过Ioc机制,利用ProxyFactoryBean来创建AOP代理。ProxyFactoryBean和其他Spring的 FactoryBean实现一样,引入一个间接的层次。如果你定义一个名字为foo的ProxyFactoryBean,引用foo的对象所看到的不是ProxyFactoryBean实例本身,而是由实现ProxyFactoryBean的类的 getObject()方法所创建的对象。这个方法将创建一个包装了目标对象 的AOP代理。

AOP代理利用的是Java的动态代理技术,通过它就可以加载并执行AOP组件。同时,还需要通过IoC的方式将advice注入到接口以及其实现类。以前面的业务对象BusinessObject为例,在xml配置文件中的配置如下:
<bean id="myAOPProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces">
       <value>test.aop.spring.BusinessObject</value>
    </property>
    <property name="target">
       <ref local="impl" />
    </property>
    <property name="interceptorNames">
       <value>myPointcutAdvisor</value>
    </property>
</bean>
<bean id="impl" class="test.aop.spring.BusinessObjectImpl"/>

通过上述对pointcut、advice、advisor和AOP代理的配置,我们就可以轻易地在Spring中实现AOP,例如:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class App
{
    private BusinessObject bo = null;
    public static void main(String[] args)
    {
        ApplicationContext ctx=new FileSystemXmlApplicationContext("Bean.xml");
        bo= (BusinessObject) ctx.getBean("myAOPProxy");
        bo.save();
    }
}

首先,通过AOP代理获得BusinessObject对象。当调用BusinessObject对象的save()方法时,拦截器LockInterceptor根据RegexpMethodPointcutAdvisor配置的pointcut和advice之间的关系,判定该方法的调用为join point,从而拦截该方法调用,并注入advice的执行逻辑,即lock()和unlock(),最终实现了AOP。

3.2.3.4 引入(introduction)

在Spring AOP中,将introduction当作advice来处理。与一般的advice一样,introduction advice相当于一种特殊类型的拦截通知,需要实现IntroductionAdvisor和IntroductionInterceptor接口,而IntroductionInterceptor接口继承自MethodInterceptor:
public interface IntroductionInterceptor extends MethodInterceptor
{
    boolean implementsInterface(Class intf);
}

Introduction通知不能被用于任何pointcut,因为它只能作用于类层次上,而不是方法。我们可以只用InterceptionIntroductionAdvisor来实现导入通知,它有下面的方法:
public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor
{
    ClassFilter getClassFilter();
    IntroductionInterceptor getIntroductionInterceptor();
    Class[] getInterfaces();
}

接下来,我以JBoss AOP一节中的例子来说明introduction在Spring AOP中的应用。我们的目标仍然是为一个已有的业务对象引入第三方接口Tracing:
public interface Tracing
{
    void enableTracing();
    void disableTracing();
    boolean enabled();
}

首先,我们需要一个做大量转化的IntroductionInterceptor。在这里,我们继承 org.springframework.aop.support.DelegatingIntroductionInterceptor 实现类。当然我们可以直接实现IntroductionInterceptor接口,但是大多数情况下 DelegatingIntroductionInterceptor是最合适的。

DelegatingIntroductionInterceptor的设计是将introduction委托到真正实现introduction接口的接口,隐藏完成这些工作的拦截器。委托可以使用构造方法参数设置到任何对象中;默认的委托就是自己(当无参数的构造方法被使用时)。这样在下面的例子里,委托是DelegatingIntroductionInterceptor的子类 TracingMixin。给定一个委托(默认是自身)的 DelegatingIntroductionInterceptor实例寻找被这个委托(而不是IntroductionInterceptor)实现的所有接口,并支持它们中任何一个导入。子类如TracingMixi也可能调用suppressInterflace(Class intf) 方法来隐藏不应暴露的接口。然而,不管IntroductionInterceptor 准备支持多少接口,IntroductionAdvisor将控制哪个接口将被实际暴露。一个导入的接口将隐藏目标的同一个接口的所有实现。

这样,TracingMixin继承DelegatingIntroductionInterceptor并自己实现接口Tracing。父类自动选择支持introduction的Tracing,所以我们不需要指定它。用这种方法我们可以导入任意数量的接口。
public class TracingMixin extends DelegatingIntroductionInterceptor implements Tracing
{
    private boolean enabled;
    public void enableTracing ()
    {
        this.enabled = true;
    }

    public void disableTracing ()
    {
        this. enabled = false;
    }

    public boolean enabled()
    {
        return this.enabled;
    }
    public Object invoke(MethodInvocation invocation) throws Throwable
    {     
        return super.invoke(invocation);
    }
}

通常不要需要改写invoke()方法:实现DelegatingIntroductionInterceptor就足够了,如果是引入的方法,DelegatingIntroductionInterceptor实现会调用委托方法, 否则继续沿着连接点处理。

所需的introduction advisor是很简单的。只需保存一个独立的TracingMixin实例,并指定导入的接口,在这里就是Tracing。此时,TracingMixin没有相关配置,所以我们简单地使用new来创建它。

public class TracingMixinAdvisor extends DefaultIntroductionAdvisor
{
    public TracingMixinAdvisor() {
        super(new TracingMixin(),Tracing.class);
    }
}

我们可以非常简单地使用这个advisor。它不需要任何配置。(但是,有一点是必要的:就是不可能在没有IntroductionAdvisor 的情况下使用IntroductionInterceptor。) 和引入一样,通常 advisor必须是针对每个实例的,并且是有状态的。我们会有不同的TracingMixinAdvisor。每个被通知对象,会有不同的TracingMixin。advisor组成了被通知对象的状态的一部分。

在Spring中,Spring AOP的核心API已经基本稳定了。和Spring的其它部分一样, AOP框架是模块化的,在保留基础设计的同时提供扩展。在Spring 1.1到1.2阶段有很多地方可能会有所提高,但是这些地方也保留了向后兼容性。它们是:

(一)性能的提高:AOP代理的创建由工厂通过策略接口处理。因此能够支持额外的AOP 代理类型而不影响用户代码或核心实现。
(二)更具表达力的pointcut:Spring目前提供了一个具有表达力的切入点接口,同时添加了更多的切入点实现。Spring正在考虑提供一个简单但具有强大表达式语言的实现。

 

以及:

原文作者:pandonix

http://pandonix.iteye.com/blog/336873/

此前对于AOP的使用仅限于声明式事务,除此之外在实际开发中也没有遇到过与之相关的问题。最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP 来解决。一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习Spring AOP相关的内容。本文是权当本人的自己AOP学习笔记,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智。

  1. 对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况
  2. 监控部分重要函数,若抛出指定的异常,需要以短信或邮件方式通知相关人员
  3. 金控部分重要函数的执行时间

    事实上,以上需求没有AOP也能搞定,只是在实现过程中比较郁闷摆了。

  1. 需要打印日志的函数分散在各个包中,只能找到所有的函数体,手动添加日志。然而这些日志都是临时的,待问题解决之后应该需要清除打印日志的代码,只能再次手动清除^_^!
  2. 类 似1的情况,需要捕获异常的地方太多,如果手动添加时想到很可能明天又要手动清除,只能再汗。OK,该需求相对比较固定,属于长期监控的范畴,并不需求临 时添加后再清除。然而,客户某天要求,把其中20%的异常改为短信提醒,剩下的80%改用邮件提醒。改之,两天后,客户抱怨短信太多,全部改成邮件提 醒...
  3. 该需求通常用于监控某些函数的执行时间,用以判断系统执行慢的瓶颈所在。瓶颈被解决之后,烦恼同情况1

终于下定决心,采用AOP来解决!代码如下:

切面类TestAspect

package com.spring.aop;/** * 切面 * */public class TestAspect {    public void doAfter(JoinPoint jp) {        System.out.println("log Ending method: "                + jp.getTarget().getClass().getName() + "."                + jp.getSignature().getName());    }    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {        long time = System.currentTimeMillis();        Object retVal = pjp.proceed();        time = System.currentTimeMillis() - time;        System.out.println("process time: " + time + " ms");        return retVal;    }    public void doBefore(JoinPoint jp) {        System.out.println("log Begining method: "                + jp.getTarget().getClass().getName() + "."                + jp.getSignature().getName());    }    public void doThrowing(JoinPoint jp, Throwable ex) {        System.out.println("method " + jp.getTarget().getClass().getName()                + "." + jp.getSignature().getName() + " throw exception");        System.out.println(ex.getMessage());    }    private void sendEx(String ex) {        //TODO 发送短信或邮件提醒    }}
 
package com.spring.service;/** * 接口A */public interface AService {        public void fooA(String _msg);    public void barA();}
 
package com.spring.service;/** *接口A的实现类 */public class AServiceImpl implements AService {    public void barA() {        System.out.println("AServiceImpl.barA()");    }    public void fooA(String _msg) {        System.out.println("AServiceImpl.fooA(msg:"+_msg+")");    }}
 
package com.spring.service;/** *   Service类B */public class BServiceImpl {    public void barB(String _msg, int _type) {        System.out.println("BServiceImpl.barB(msg:"+_msg+" type:"+_type+")");        if(_type == 1)            throw new IllegalArgumentException("测试异常");    }    public void fooB() {        System.out.println("BServiceImpl.fooB()");    }}

ApplicationContext
<?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-2.0.xsd            http://www.springframework.org/schema/aop            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"    default-autowire="autodetect">    <aop:config>        <aop:aspect id="TestAspect" ref="aspectBean">            <!--配置com.spring.service包下所有类或接口的所有方法-->            <aop:pointcut id="businessService"                expression="execution(* com.spring.service.*.*(..))" />            <aop:before pointcut-ref="businessService" method="doBefore"/>            <aop:after pointcut-ref="businessService" method="doAfter"/>            <aop:around pointcut-ref="businessService" method="doAround"/>            <aop:after-throwing pointcut-ref="businessService" method="doThrowing" throwing="ex"/>        </aop:aspect>    </aop:config>        <bean id="aspectBean" class="com.spring.aop.TestAspect" />    <bean id="aService" class="com.spring.service.AServiceImpl"></bean>    <bean id="bService" class="com.spring.service.BServiceImpl"></bean></beans>

测试类AOPTest
public class AOPTest extends AbstractDependencyInjectionSpringContextTests {        private AService aService;        private BServiceImpl bService;        protected String[] getConfigLocations() {        String[] configs = new String[] { "/applicationContext.xml"};        return configs;    }            /**     * 测试正常调用     */    public void testCall()    {        System.out.println("SpringTest JUnit test");        aService.fooA("JUnit test fooA");        aService.barA();        bService.fooB();        bService.barB("JUnit test barB",0);    }        /**     * 测试After-Throwing     */    public void testThrow()    {        try {            bService.barB("JUnit call barB",1);        } catch (IllegalArgumentException e) {                    }    }        public void setAService(AService service) {        aService = service;    }        public void setBService(BServiceImpl service) {        bService = service;    }}
运行结果如下:
log Begining method: com.spring.service.AServiceImpl.fooAAServiceImpl.fooA(msg:JUnit test fooA)log Ending method: com.spring.service.AServiceImpl.fooAprocess time: 0 mslog Begining method: com.spring.service.AServiceImpl.barAAServiceImpl.barA()log Ending method: com.spring.service.AServiceImpl.barAprocess time: 0 mslog Begining method: com.spring.service.BServiceImpl.fooBBServiceImpl.fooB()log Ending method: com.spring.service.BServiceImpl.fooBprocess time: 0 mslog Begining method: com.spring.service.BServiceImpl.barBBServiceImpl.barB(msg:JUnit test barB type:0)log Ending method: com.spring.service.BServiceImpl.barBprocess time: 0 mslog Begining method: com.spring.service.BServiceImpl.barBBServiceImpl.barB(msg:JUnit call barB type:1)log Ending method: com.spring.service.BServiceImpl.barBmethod com.spring.service.BServiceImpl.barB throw exception测试异常

《Spring参考手册》中定义了以下几个AOP的重要概念,结合以上代码分析如下:

  • 切面(Aspect) :官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为,例如,AServiceImpl.barA()的调用就是切面TestAspect所关注的行为之一。“切面”在ApplicationContext中<aop:aspect>来配置。
  • 连接点(Joinpoint) :程序执行过程中的某一行为,例如,AServiceImpl.barA()的调用或者BServiceImpl.barB(String _msg, int _type)抛出异常等行为。
  • 通知(Advice) :“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志记录的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,例如TestAspect
  • 切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。例如,TestAspect中的所有通知所关注的连接点,都由切入点表达式execution(* com.spring.service.*.*(..))来决定
  • 目标对象(Target Object) :被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
  • AOP代理(AOP Proxy) 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将 <aop:config>proxy-target-class 属性设为true

通知(Advice)类型

  • 前置通知(Before advice) :在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。例如,TestAspect中的doBefore方法
  • 后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,TestAspect中的doAfter方法,所以AOPTest中调用BServiceImpl.barB抛出异常时,doAfter方法仍然执行
  • 返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明。
  • 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。例如,TestAspect中的doAround方法。
  • 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。 ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。例如,TestAspect中的doThrowing方法。

切入点表达式

  • 通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

     modifiers-pattern:方法的操作权限

     ret-type-pattern:返回值

     declaring-type-pattern:方法所在的包

     name-pattern:方法名

     parm-pattern:参数名

     throws-pattern:异常

其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。

  • 通知参数

可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,<aop:aspect>配置如下:

<aop:config>        <aop:aspect id="TestAspect" ref="aspectBean">            <aop:pointcut id="businessService"                expression="execution(* com.spring.service.*.*(String,..)) and args(msg,..)" />                <aop:after pointcut-ref="businessService" method="doAfter"/>        </aop:aspect>    </aop:config>
TestAspect的doAfter方法中就可以访问msg参数,但这样以来AService中的barA()和BServiceImpl中的barB()就不再是连接点,因为execution(* com.spring.service.*.*(String,..))只配置第一个参数为String类型的方法。其中,doAfter方法定义如下:
public void doAfter(JoinPoint jp,String msg)
  •   访问当前的连接点

任何通知(Advice)方法可以将第一个参数定义为 org.aspectj.lang.JoinPoint 类型。

JoinPoint 接口提供了一系列有用的方法, 比如

getArgs() (返回方法参数)、

getThis() (返回代理对象)、

getTarget() (返回目标)、

getSignature() (返回正在被通知的方法相关信息)和 toString()(打印出正在被通知的方法的有用信息)。

 

我对AOP的理解,原文地址:

http://jinnianshilongnian.iteye.com/blog/1474325

1、问题 

技术分享

问题:想要添加日志记录、性能监控、安全监测 

2、最初解决方案 

2.1、最初解决方案

技术分享

缺点:太多重复代码,且紧耦合

2.2、抽象类进行共性设计,子类进行个性设计,此处不讲解,缺点一荣俱荣,一损俱损

2.3、使用装饰器模式/代理模式改进的解决方案

装饰器模式:动态地给一个对象添加一些额外的职责。就增加功能来说, 装饰器模式相比生成子类更为灵活。

代理模式:为其他对象提供一种代理以控制对这个对象的访问。

技术分享

缺点:紧耦合,每个业务逻辑需要一个装饰器实现或代理

 

2.4、JDK动态代理解决方案(比较通用的解决方案) 

public class MyInvocationHandler implements InvocationHandler {     private Object target;    public MyInvocationHandler(Object target) {        this.target = target;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        //1.记录日志    2.时间统计开始      3.安全检查        Object retVal = method.invoke(target, args);        //4.时间统计结束        return retVal;       }    public static Object proxy(Object target) {        return Proxy.newProxyInstance(target.getClass().getClassLoader(),                 target.getClass().getInterfaces(), new MyInvocationHandler(target));    }}
编程模型 
//proxy     在其上调用方法的代理实例    //method 拦截的方法   //args       拦截的参数   Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        Object retVal=null;        //预处理        //前置条件判断        boolean ok = true;                if(!ok) {//不满足条件            throw new RuntimeException("你没有权限");        }        else {//反射调用目标对象的某个方法            retVal = method.invoke(target, args);        }        //后处理        return retVal;    } &nbsp;

缺点:使用麻烦,不能代理类,只能代理接口  

CGLIB动态代理解决方案(比较通用的解决方案)

public class MyInterceptor implements MethodInterceptor  {        private Object target;    public MyInterceptor(Object target) {        this.target = target;    }    @Override    public Object intercept(Object proxy, Method method, Object[] args,                                          MethodProxy invocation) throws Throwable {        //1.记录日志 2.时间统计开始   3.安全检查        Object retVal = invocation.invoke(target, args);        //4.时间统计结束        return retVal;       }    public static Object proxy(Object target) {        return Enhancer.create(target.getClass(), new MyInterceptor(target));    }}
编程模型
//proxy 在其上调用方法的代理实例    method拦截的方法    args  拦截的参数 //invocation 用来去调用被代理对象方法的@Overridepublic Object intercept(Object proxy, Method method, Object[] args,                                        MethodProxy invocation) throws Throwable {    //预处理    //前置条件判断    boolean ok = true;            if(!ok) {//不满足条件        throw new RuntimeException("出错了");    }    else {//调用目标对象的某个方法        Object retVal = invocation.invoke(target, args);    }    //后处理    return retVal;}

优点:能代理接口和类

缺点:使用麻烦,不能代理final类

动态代理本质 

本质:对目标对象增强

           最终表现为类(动态创建子类),看手工生成(子类)还是自动生成(子类)

代理限制:

           只能在父类方法被调用之前或之后进行增强(功能的修改),不能在中间进行修改,要想在方法调用中增强,需要ASM(java 字节码生成库)

其他动态代理框架

jboss:javassist (hibernate 3.3中默认为javassist)

                           (hibernate 3.3之前中默认为cglib)

技术分享

技术分享

2.5、AOP解决方案(通用且简单的解决方案)

@Aspectpublic class PayEbiAspect {        @Pointcut(value="execution(* pay(..))")    public void pointcut() {}    @Around(value="pointcut()")    public Object around(ProceedingJoinPoint pjp) throws Throwable {        //1.记录日志        //2.时间统计开始        //3.安全检查        Object retVal = pjp.proceed();//调用目标对象的真正方法        //4.时间统计结束        return retVal;    }}
编程模型
//2 切入点    @Pointcut(value="http://www.mamicode.com/execution(* *(..))")    public void pointcut() {}    //3 拦截器的interceptor    @Around(value="http://www.mamicode.com/pointcut()")    public Object around(ProceedingJoinPoint pjp) throws Throwable {        Object retVal=null;        //预处理        //前置条件判断        boolean ok = true;        if(!ok) {//不满足条件           throw new RuntimeException("你没有权限");        }        else {//调用目标对象的某个方法             retVal = pjp.proceed();         }        //后处理        return retVal;    }

缺点:依赖AOP框架 

AOP入门

概念:

n关注点:可以认为是所关注的任何东西,比如上边的支付组件;

n关注点分离:将问题细化为单独部分,即可以理解为不可再分割的组件,如上边的日志组件和支付组件;

n横切关注点:会在多个模块中出现,使用现有的编程方法,横切关注点会横越多个模块,结果是使系统难以设计、理解、实现和演进,如日志组件横切于支付组件。

织入:横切关注点分离后,需要通过某种技术将横切关注点融合到系统中从而完成需要的功能,因此需要织入,织入可能在编译期、加载期、运行期等进行。

nAOP是什么(Aspect   Oriented   Programming)

AOP是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。

AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

nAOP能干什么,也是AOP带来的好处

1:降低模块的耦合度

2:使系统容易扩展

3:设计决定的迟绑定:使用AOP,设计师可以推迟为将来的需求作决定,因为它

可以把这种需求作为独立的方面很容易的实现。

4:更好的代码复用性

技术分享

AOP基本概念

连接点(Joinpoint):

    表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点,AOP中表示在哪里做

切入点(Pointcut):

    选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,AOP中表示为在哪里做的集合

增强(Advice):或称为增强

    在连接点上执行的行为,增强提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置增强(before advice)、后置增强 (after advice)、环绕增强 (around advice),在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入增强 ;AOP中表示为做什么

方面/切面(Aspect):

      横切关注点的模块化,比如上边提到的日志组件。可以认为是增强、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;AOP中表示为在哪里做和做什么集合

目标对象(Target Object):

    需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被增强的对象,从而也可称为“被增强对象”;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,AOP中表示为对谁做

AOP代理(AOP Proxy):

    AOP框架使用代理模式创建的对象,从而实现在连接点处插入增强(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。

织入(Weaving):

    织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期、类装载期、运行期进行。

引入(inter-type declaration):

    也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象), AOP中表示为做什么(新增什么)

AOP的Advice类型

前置增强(Before advice):

    在某连接点之前执行的增强,但这个增强不能阻止连接点前的执行(除非它抛出一个异常)。

后置返回增强(After returning advice):

    在某连接点正常完成后执行的增强:例如,一个方法没有抛出任何异常,正常返回。

后置异常增强(After throwing advice):

    在方法抛出异常退出时执行的增强。

后置最终增强(After (finally) advice):

    当某连接点退出的时候执行的增强(不论是正常返回还是异常退出)。

环绕增强Around Advice):

    包围一个连接点的增强,如方法调用。这是最强大的一种增强类型。 环绕增强可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

技术分享

AOP开发步骤

  类似于IoC/DI容器开发步骤,需要描述哪个连接点需要哪个通用功能(增强

  参考:http://www.iteye.com/topic/1122310

横切关注点的表现有:
·代码纠结/混乱——当一个模块或代码段同时管理多个关注点时发生这种情况。如我既要实现业务、还要实现安全和事务。即有些关注点同时被多个不同的模块实现。实现了重复的功能。
·代码分散——当一个关注点分布在许多模块中并且未能很好地局部化和模块化时发生这种情况 。如许多模块调用用户是否登录验证代码。调用了重复的功能。

AOP包括三个清晰的开发步骤:

1:功能横切:找出横切关注点。

2:实现分离:各自独立的实现这些横切关注点所需要完成的功能。

3:功能回贴:在这一步里,方面集成器通过创建一个模块单元—— 方面来指定重组的规则。重组过程——也叫织入或结合—— 则使用这些信息来构建最终系统。

推荐阅读书籍:

AspectJ in Action

AOSD中文版--基于用例的面向方面软件开发

推荐阅读的帖子:AOP的实现机制

(推荐的文章也好,就不转载了,大家有时间自己去看)


总结和思考:

//还没消化好,囧,等我多看几遍吧、

Spring AOP的本质