270 likes | 665 Views
第 20 章 Spring 的 AOP. IoC 以外 Spring 的另一个关键的组件就是 AOP 框架。尽管如此, Spring IoC 容器并不依赖于 AOP ,这意味着可以自由选择是否使用 AOP , AOP 提供强大的中间件解决方案,这使得 Spring IoC 容器更加完善。. 20.1 Spring AOP 简介.
E N D
第20章 Spring的AOP • IoC以外Spring的另一个关键的组件就是AOP框架。尽管如此,Spring IoC容器并不依赖于AOP,这意味着可以自由选择是否使用AOP,AOP提供强大的中间件解决方案,这使得Spring IoC容器更加完善。
20.1 Spring AOP简介 • 面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。 除了类(classes)以外,AOP提供了 切面。切面对关注点进行模块化,例如横切多个类型和对象的事务管理。(这些关注点术语通常称作横切(crosscutting)关注点。)
20.1.1 AOP的概念 • AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
20.1.2 AOP的术语 • 首先从定义一些重要的AOP概念开始。这些术语不是Spring特有的。不幸的是,Spring术语并不是特别的直观;如果Spring使用自己的术语,将会变得更加令人困惑。 • 切面(Aspect): 连接点(Joinpoint): • 通知(Advice): 切入点(Pointcut): • 引入(Introduction): 目标对象(Target Object): • AOP代理(AOP Proxy):
20.1.3 Spring的AOP有何特点 • Spring 2.0引入了一种更加简单并且更强大的方式来自定义切面,用户可以选择使用基于模式(schema-based)的方式或者使用@AspectJ注解。这两种风格都完全支持通知(Advice)类型和AspectJ的切入点语言,虽然实际上仍然使用Spring AOP进行织入(Weaving)。这也是Spring推荐的做法,本书将重点介绍这种方法。 • Spring缺省使用J2SE动态代理(dynamic proxies)来作为AOP的代理。这样任何接口都可以被代理(本书前面Spring入门一章就是采用这种方法)。
20.1.4 Spring AOP的功能和目标 • Spring AOP用纯Java实现。它不需要专门的编译过程。Spring AOP不需要控制类装载器层次,因此它适用于J2EE web容器或应用服务器。 • Spring目前仅支持使用方法调用作为连接点(join point)(在Spring bean上通知方法的执行)。虽然可以在不影响到Spring AOP核心API的情况下加入对成员变量拦截器支持,但Spring并没有实现成员变量拦截器。
20.2 @AspectJ配置AOP • 在Spring 2.0中最激动人心的增强之一是关于Spring AOP,它变得更加便于使用而且更加强大,主要是通过复杂而成熟的AspectJ语言的支持功能来实现,而同时保留纯的基于代理的Java运行时。 • "@AspectJ"使用了Java 5的注解,可以将切面声明为普通的Java类。AspectJ 5发布的 AspectJ project中引入了这种@AspectJ风格。 Spring 2.0 使用了和AspectJ 5一样的注解,使用了AspectJ 提供的一个库来做切点(pointcut)解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ 的编译器或者织入器(weaver)。
20.2.1 启用@AspectJ支持 • 为了在Spring配置中使用@AspectJ aspects,必须首先启用Spring对基于@AspectJ aspects的配置支持,自动代理(autoproxying)基于通知是否来自这些切面。自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确认通知是否如期进行。
20.2.2 声明一个切面 • 在启用@AspectJ支持的情况下,在application context中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并用于配置在Spring AOP。下面是在application context中的一个常见的bean定义,这个bean指向一个使用了@Aspect注解的bean类: • <bean id="myAspect" class="spring2.aop.NullAspect"> • <!—为这个Bean配置属性,与Spring中普通的Bean没有区别 --> • </bean>
20.2.3 声明一个切入点(pointcut) • 切入点决定了连接点关注的内容,由切入点来控制“通知”什么时候执行。Spring AOP 只支持 Spring bean 方法执行连接点,只能关注方法和不能关注属性(某些AOP如AspectJ可以关注属性)。所以可以把切入点看做是匹配 Spring bean 上方法的执行。一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了关注那个方法的执行。在@AspectJ 注解风格的 AOP 中,一个切入点签名通过一个普通的方法定义来提供,并且切入点表达式使用 @Pointcut 注解来表示(作为切入点签名的方法必须返回 void 类型)。
20.2.4 合并切入点表达式 • 切入点表达式可以使用using '&', '||' 和 '!'等通配符来合并.还可以通过名字来指向切入点表达式。以下的例子展示了三种切入点表达式: • 在一个方法执行连接点代表了任意public方法的执行时匹配(anyPublicOperation); • 在一个代表了在交易模块中的任意的方法执行时匹配(inTrading)
20.2.5 声明通知 • 通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者之前和之后运行。切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。 • 根据通知的执行时机来区分,通知可分为前置通知、返回后通知、抛出后通知、后通知等几种: • 1.前置通知(Before advice) 2.返回后通知(After returning advice) • 3.抛出后通知(After throwing advice) 4.后通知(After (finally) advice) • 5.环绕通知(Around Advice) 6.通知的顺序
20.2.6 通知的参数(Advice parameters) • Spring 2.0 提供了完整的通知类型。这意味着可以在通知定义中声明所需的参数,而不总是使用Object[]。 • (1)访问当前的连接点 • (2)传递参数给通知(Advice) • (3)在切入点中定义参数。这个切入点在匹配某个连接点的时候“提供”了一个Account对象,然后直接从通知中访问那个命名的切入点。
20.2.7 使用引入(Introductions)来定义接口 • 引入(Introductions)(在AspectJ中被称为inter-type声明)使得一个切面可以定义为被通知对象实现一个给定的接口,并且可以代表那些对象提供具体实现。 • 使用 @DeclareParents注解来定义引入。这个注解被用来定义匹配的类型拥有一个新的父亲。 比如,给定一个接口 UsageTracked,然后接口的具体实现 DefaultUsageTracked 类, 接下来的切面声明了所有的service接口的实现都实现了 UsageTracked 接口。
20.3 XML方式 配置AOP • 如果无法使用Java 5,或者比较喜欢使用XML格式,Spring2.0也提供了使用新的"aop"命名空间来定义一个切面。和使用@AspectJ风格完全一样,切入点表达式和通知类型同样得到了支持,因此在这一节中将着重介绍新的语法和回顾前面所讨论的如何写一个切入点表达式和通知参数的绑定。
20.3.1 声明一个切面 • 在Spring的配置文件中,所有的切面和通知器都必须定义在<aop:config> 元素内部。 一个application context可以包含多个 <aop:config>。 一个 <aop:config> 可以包含pointcut,advisor和aspect元素(它们必须按照这样的顺序进行声明)。
20.3.2 声明一个切入点 • 切入点可以在切面里面声明,这种情况下切入点只在切面内部可见。切入点也可以直接在<aop:config>下定义,这样就可以使多个切面和通知器共享该切入点。 • 一个描述service层中表示所有service执行的切入点可以如下定义: • <aop:config> • <!--声明一个切面--> • <aop:pointcut id="businessService" • expression="execution(* spring2.aop.service.*.*(..))"/> • </aop:config>
20.3.3 声明通知 • 和@AspectJ风格一样,基于XML配置也支持5种通知类型并且两者具有同样的语义。 • 1.前置通知 • 2.返回后通知 • 3.抛出异常后通知(After throwing advice) • 4.后通知(After (finally) advice) • 5.环绕通知
20.3.4 通知参数 • Schema-based声明风格和@AspectJ支持一样,支持通知的全名形式。通过通知方法参数名字来匹配切入点参数。如果希望显式指定通知方法的参数名(而不是依靠先前提及的侦测策略),可以通过 arg-names 属性来实现,arg-names属性接受由逗号分割的参数名列表。示例如下: • <!—前置通知--> • <aop:before • pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)" • method="audit" • arg-names="auditable"> • </aop:before>
20.3.5 使用引入 • Intrduction (在AspectJ中成为inter-type声明)允许一个切面声明一个通知对象实现指定接口,并且提供了一个接口实现类来代表这些对象。 • 在 aop:aspect 内部使用 aop:declare-parents 元素定义Introduction。 该元素用于用来声明所匹配的类型有了一个新的父类型(所以有了这个名字)。 例如,给定接口 UsageTracked,以及这个接口的一个实现类 DefaultUsageTracked,下面声明的切面所有实现service接口的类同时实现UsageTracked 接口。
20.3.6 使用@AspectJ还是XML • 选择使用Spring AOP,可以选择@AspectJ或者XML风格。总的来说,如果使用Java 5,建议使用@AspectJ风格。显然如果不是运行在Java 5上,XML风格是最佳选择。XML和@AspectJ 之间权衡的细节将在下面进行讨论。 • XML风格对现有的Spring用户来说更加习惯。它可以使用在任何Java级别中。并且通过纯粹的POJO来支持。对于XML风格,从配置中可以清晰的表明在系统中存在那些切面。XML风格可以保证代码和配置的分离,改变配置无需重新编译,这在某些场合可能是必须的。
20.4 Spring2.0以前的AOP • 前面的章节介绍了Spring 2.0中提供的由@AspectJ和基于Schema的两种切面定义的AOP。在这一节里,将介绍如何使用纯代理的方式也就是不借助于AspectJ来实现AOP,在Spring 2.0以前的版本中就是采用在这种方法。 • 对于新的应用程序,推荐使用前一章介绍的Spring 2.0 AOP支持,但是当使用已有系统时,或是阅读书籍和文章时,很有可能会遇到 Spring 1.2风格的例子。所以了解他也是有必要的。Spring 2.0是完全向前兼容Spring 1.2的,本节将以一个实例来简要说明如何利用动态代理实现AOP的。
20.4.1 声明切面 • 如代码所示是本例的目标类,可以看到这个JavaBean实现了一个接口。这里相当于声明切面的功能,在接口中定义的这两个方法可以作为切入点。 • 代码 AOP接口:IHello.java • //AOP接口,只是供代理使用 • public interface IHello { • void sayHello(String name); • void sayBye(String name); • }
20.4.2 创建通知 • 在Spring1.X中通知需要实现特定的接口,如前置通知MethodBeforeAdvice、异常通知RemoteThrowsAdvice、后置通知AfterReturningAdvice等等还有一些特殊用途的通知类型。 • 本例中的通知类型是一个方法的拦截器。这个拦截器只打印一句Log。
20.4.3 配置文件 • 创建这些元素之后就要通过Spring把它们配置在一起工作了,如代码20-12所示。除了原来定义的helloBean以外,又增加了两个Bean的定义:doSthInterceptor和helloProxy。helloProxy的类型是ProxyFactoryBean,这个类是用来使用动态代理生成实例。
20.4.4 运行实例 • 最后看下如何执行这个AOP调用,与前面例子类似,同样需要使用BeanFactory来获取JavaBean,这里并不是直接获取 • BeanFactory factory = new XmlBeanFactory(resource); • IHello hello1 = (IHello) factory.getBean("helloProxy"); • hello1.sayHello("Tommy"); • hello1.sayBye("GeekOne");
20.5 小结 • 本章详细介绍了Spring中AOP原理和使用方法。本文一共介绍了三种方法,推荐使用的是第一种@AspectJ方式。AOP是Spring的基础,包括后文要讲到的用Spring来解决实际问题,如Web MVC、事务处理、与其他框架整合等等都是依赖于AOP的。