6.2 Spring的AOPAOP(Aspect Orient Programming),也就是面向切面编程,作为面向对象编程的一种补充。问世的时间并不太长,甚至在国内的翻译还不太统一(有些书翻译成面向方面编程),但它确实极好地补充了面向对象编程的方式。面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面。 可以这样理解,面向对象编程是从静态角度考虑程序结构,面向切面编程是从动态角度考虑程序运行过程。 Spring AOP是Spring框架的一个重要组件,极好地补充了Spring IoC容器的功能。Spring AOP将Spring IoC容器与AOP组件紧密结合,丰富了IoC容器的功能。当然,即使不使用AOP组件,依然可以使用Spring的IoC容器。 6.2.1 AOP的基本概念AOP从程序运行角度考虑程序的流程,提取业务处理过程的切面。AOP面向的是程序运行中各个步骤,希望以更好的方式来组合业务处理的各个步骤。 AOP框架并不与特定的代码耦合,AOP框架能处理程序执行中的特定点,而不是某个具体的程序。AOP框架具有如下两个特征: ● 各步骤之间的良好隔离性。 ● 源代码无关性。 下面是关于面向切面编程的一些术语: ● 切面,业务流程运行的某个特定步骤,就是运行过程的关注点,关注点可能横切多个对象。 ● 连接点,程序执行过程中明确的点,如方法的调用或异常的抛出。Spring AOP中,连接点总是方法的调用,Spring并没有显式地使用连接点。 ● 处理(Advice),AOP框架在特定的连接点执行的动作。处理有around、before和throws等类型。大部分框架都以拦截器作为处理模型。 ● 切入点,系列连接点的集合,它确定处理触发的时机。AOP框架允许开发者自己定义切入点,如使用正则表达式。 ● 引入,添加方法或字段到被处理的类。Spring允许引入新的接口到任何被处理的对象。例如,可以使用一个引入,使任何对象实现IsModified接口,以此来简化缓存。 ● 目标对象,包含连接点的对象。也称为被处理对象或被代理对象。 ● AOP代理,AOP框架创建的对象,包含处理。简单地说,代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。前者为实现接口的目标对象的代理,后者为不实现接口的目标对象的代理。 注意:面向切面编程是比较前沿的知识,而国内大部分翻译人士翻译计算机文献时,总是一边开着各种词典和翻译软件,一边逐词去看文献,不是先从总体上把握知识的架构。因此,难免导致一些术语的翻译词不达意,例如,Socket被翻译成“套接字”等。在面向切面编程的各术语翻译上,也存在较大的差异。对于Advice一词,有翻译为“通知”的,有翻译为“建议”的,如此种种,不一而足。实际上,Advice指AOP框架在特定切面所做的事情,故而笔者翻译为“处理”,希望可以表达Advice的真正含义。 6.2.2 AOP的代理所谓AOP代理,就是AOP框架动态创建的对象,这个对象通常可以作为目标对象的替代品,而AOP代理提供比目标对象更加强大的功能。真实的情形是,当应用调用AOP代理的方法时,AOP代理会在自己的方法中回调目标对象的方法,从而完成应用的调用。 关于AOP代理的典型例子就是Spring中的事务代理Bean。通常,目标Bean的方法不是事务性的,而AOP代理包含目标Bean的全部方法,而且这些方法经过加强变成了事务性方法。简单地说,目标对象是蓝本,AOP代理是目标对象的加强,在目标对象的基础上,增加属性和方法,提供更强大的功能。 目标对象包含一系列切入点。切入点可以触发处理连接点集合。用户可以自己定义切入点,如使用正则表达式。AOP代理包装目标对象,在切入点处加入处理。在切入点加入的处理,使得目标对象的方法功能更强。 Spring默认使用JDK动态代理实现AOP代理,主要用于代理接口。也可以使用CGLIB代理。实现类的代理,而不是接口。如果业务对象没有实现接口,默认使用CGLIB代理。但面向接口编程是良好的习惯,尽量不要面向具体类编程。因此,业务对象通常应实现一个或多个接口。 下面是一个简单动态代理模式的示例,首先有一个Dog的接口,接口如下: public interface Dog { //info方法声明 public void info(); //run方法声明 public void run(); } 然后,给出该接口的实现类,实现类必须实现两个方法,源代码如下: public class DogImpl implements Dog { //info方法实现,仅仅打印一个字符串 public void info() { System.out.println("我是一只猎狗"); } //run方法实现,仅仅打印一个字符串 public void run() { System.out.println("我奔跑迅速"); } } 上面的代码没有丝毫独特之处,是典型的面向接口编程的模型,为了有更好的解耦,采用工厂来创建Dog实例。工厂源代码如下: public class DogFactory { //工厂本身是单态模式,因此,将DogFactory作为静态成员变量保存 private static DogFactory df; //将Dog实例缓存 private Dog gundog; //默认的构造器,单态模式需要的构造器是private private DogFactory() { } //单态模式所需的静态方法,该方法是创建本类实例的唯一方法点 public static DogFactory instance() { if (df == null) { df = new DogFactory(); } return df; } //获得Dog实例 public Dog getDog(String dogName) { //根据字符串参数决定返回的实例 if (dogName.equals("gundog")) { //返回Dog实例之前,先判断缓存的Dog是否存在,如果不存在才创建, 否则直接返回缓存的Dog实例 if (gundog == null ) { gundog = new DogImpl(); } return gundog; } return null; } } 下面是一个通用的处理类,该处理类没有与任何特定的类耦合,它可以处理所有的目标对象。从JDK 1.3起,Java的import java.lang.reflect下增加InvocationHandler接口,该接口是所有处理类的根接口。 该类处理类的源代码如下: public class ProxyHandler implements InvocationHandler { //需被代理的目标对象 private Object target; //执行代理的目标方法时,该invoke方法会被自动调用 public Object invoke(Object proxy, Method method, Object[] args)throws { Object result = null; if (method.getName().equals("info")) { System.out.println("======开始事务..."); result =method.invoke(target, args); System.out.println("======提交事务..."); } else { result =method.invoke(target, args); } return result; } //通过该方法,设置目标对象 public void setTarget(Object o) { this.target = o; } } 该处理类实现InvocationHandler接口,实现该接口必须实现invoke(Object proxy, Method method, Object[] args)方法,程序调用代理的目标方法时,自动变成调用invoke方法。 该处理类并未与任何接口或类耦合,它完全是通用的,它的目标实例是Object类型,可以是任何的类型。 在invoke方法内,对目标对象的info方法进行简单加强,在开始执行目标对象的方法之前,先打印开始事务,执行目标对象的方法之后,打印提交事务。 通过method对象的invoke方法,可以完成目标对象的方法调用,执行代码如下: result =method.invoke(target, args); 下面是代理工厂: public class MyProxyFactory { /** * 实例Service对象 * @param serviceName String * @return Object */ public static Object getProxy(Object object) { //代理的处理类 ProxyHandler handler = new ProxyHandler(); //把该dog实例托付给代理操作 handler.setTarget(object); //第一个参数是用来创建动态代理的ClassLoader对象,只要该对象能访问Dog接口 //第二个参数是接口数组,正是代理该接口数组 //第三个参数是代理包含的处理实例 return Proxy.newProxyInstance(DogImpl.class.getClassLoader(), object.getClass().getInterfaces(),handler); } } 代理工厂里有一行代码: Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),handler); Proxy.newProxyInstance()方法根据接口数组动态创建代理类实例,接口数组通过object.getClass().getInterfaces()方法获得,创建的代理类是JVM在内存中动态创建的,该类实现传入接口数组的全部接口。 因此,Dynamic Proxy要求被代理的必须是接口的实现类,否则无法为其构造相应的动态类。因此,Spring对接口实现类采用Dynamic Proxy实现AOP,而对没有实现任何接口的类,则通过CGLIB实现AOP代理。 下面是主程序: public class TestDog { public static void main(String[] args) { Dog dog = null; //创建Dog实例,该实例将作为被代理对象 Dog targetObject = DogFactory.instance().getDog("gundog"); //以目标对象创建代理 Object proxy = MyProxyFactory.getProxy(targetObject); if (proxy instanceof Dog) { dog = (Dog)proxy; } //测试代理的方法 dog.info(); dog.run(); } } 代理实例会实现目标对象实现的全部接口。因此,代理实例也实现了Dog接口,程序运行结果如下: [java] ======开始事务... [java] 我是一只猎狗 [java] ======提交事务... [java] 我奔跑迅速 代理实例加强了目标对象的方法——仅仅打印了两行字符串。当然,此种加强没有实际意义。试想一下,若程序中打印字符串的地方,换成真实的事务开始和事务提交,则代理实例的方法为目标对象的方法增加了事务性。 6.2.3 创建AOP代理通过前面的介绍,AOP代理就是由AOP框架动态生成的一个对象,该对象可作为目标对象使用,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP方法在特定切面插入处理,在处理之间回调目标对象的方法。 AOP代理所包含的方法与目标对象所包含的方法的示意图,如图6.1所示。 Spring中AOP代理由Spring的IoC容器负责生成和管理,其依赖关系也由IoC容器负责管理。因此,AOP代理能够引用容器中的其他Bean实例,这种引用由IoC容器的依赖注入提供。 Spring的AOP代理大都由ProxyFactoryBean工厂类产生,如图6.1所示,产生一个AOP代理至少有两个部分:目标对象和AOP框架所加入的处理。因此,配置ProxyFactoryBean时需要确定如下两个属性: ● 代理的目标对象。 ● 处理(Advice)。 注意:关于Advice的更多知识,此处由于篇幅原因,无法深入讨论。实际上,Spring也提供了很多种Advice的实现,如需要更深入了解Spring AOP,请读者参考笔者所著的《Spring2.0宝典》。 所有代理工厂类的父类是org.springframework.aop.framework.ProxyConfig。因此,该类的属性是所有代理工厂的共同属性,这些属性也是非常关键的,包括: ● proxyTargetClass,确定是否代理目标类,如果需要代理目标是类,该属性设为true,此时需要使用CGLIB生成代理;如果代理目标是接口,该属性设为false,默认是false。 ● optimize,确定是否使用强优化来创建代理。该属性仅对CGLIB代理有效;对JDK 动态代理无效。 ● frozen,确定是否禁止改变处理,默认是false。 ● exposeProxy,代理是否可以通过ThreadLocal访问,如果exposeProxy属性为true,则可通过AopContext.currentProxy()方法获得代理。 ● aopProxyFactory,所使用的AopProxyFactory具体实现。该参数用来指定使用动态代理、CGLIB或其他代理策略。默认选择动态代理或CGLIB。一般不需要指定该属性,除非需要使用新的代理类型,才指定该属性。 配置ProxyFactoryBean工厂bean时,还需要指定它的特定属性,ProxyFactoryBean的特定属性如下所示: ● proxyInterfaces,接口名的字符串数组。如果没有确定该参数,默认使用CGLIB代理。 ● interceptorNames,处理名的字符串数组。此处的次序很重要,排在前面的处理,优先被调用。此处的处理名,只能是当前工厂中处理的名称,而不能使用bean引用。处理名字支持使用通配符(*)。 ● singleton,工厂是否返回单态代理。默认是true,无论 getObject()被调用多少次,将返回相同的代理实例。如果需要使用有状态的处理——例如,有状态的mixin,可改变默认设置,prototype处理。 |