资源描述
CONFIDENTIALJava高级框架应用开发案高级框架应用开发案例教程例教程(十一十一)Spring面向方面编程面向方面编程和事务处理和事务处理2 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司主要内容11.1 AOP概念11.2 Spring的切入点11.3 Spring的通知类型11.4 Spring中的advisor11.5 用ProxyFactoryBean创立AOP代理11.6 事务处理11.7 工程案例3 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念我们来介绍另外一个重要的概念:我们来介绍另外一个重要的概念:AOP(AspectOrientedProgramming),也就是面向方面编程的技术。也就是面向方面编程的技术。AOP是基于是基于IoC根底上,根底上,是对是对OOP的有益补充。的有益补充。AOP将应用系统分为两局部,核心业务逻辑将应用系统分为两局部,核心业务逻辑Corebusinessconcerns及横向的通用逻辑,也就是所谓的方面及横向的通用逻辑,也就是所谓的方面Crosscuttingenterpriseconcerns,例如所有大中型应用都要涉及到的持久化管理,例如所有大中型应用都要涉及到的持久化管理Persistent,事务管理,事务管理TransactionManagement,平安管理,平安管理Security,日志管理,日志管理Logging,和调试管理,和调试管理Debugging等等。等等。11.1AOP概念概念让我们从定义一些重要的让我们从定义一些重要的AOP概念开始。概念开始。方面方面Aspect:一个关注点的模块化,这个关注点实现可能一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点应用中一个很好的横切关注点例子。方面用例子。方面用Spring的的Advisor或拦截器实现。或拦截器实现。连接点连接点Joinpoint:程序执行过程中明确的点,如方法的调用或特定的异常被抛出。程序执行过程中明确的点,如方法的调用或特定的异常被抛出。通知通知Advice:在特定的连接点,在特定的连接点,AOP框架执行的动作。各种类框架执行的动作。各种类型的通知包括型的通知包括“around、“before和和“throws通知。通知通知。通知类型将在下面讨论。许多类型将在下面讨论。许多AOP框架框架包括包括Spring都是以拦截器做通知模型,维护一个都是以拦截器做通知模型,维护一个“围绕连接点的拦截器链。围绕连接点的拦截器链。切入点切入点Pointcut:指定一个通知将被引发的一系列连接点的集合。指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正那么表框架必须允许开发者指定切入点:例如,使用正那么表达式。达式。引入引入Introduction:添加方法或字段到被通知的类。添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现入使任何对象实现IsModified接口,来简化缓存。接口,来简化缓存。目标对象目标对象TargetObject:包含连接点的对象。也被称作被通知或被代理对象。包含连接点的对象。也被称作被通知或被代理对象。4 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念nAOP代理代理AOPProxy:AOP框架创立的对象,包含通知。在框架创立的对象,包含通知。在Spring中,中,AOP代理代理可以是可以是JDK动态代理或者动态代理或者CGLIB代理。代理。n编织编织Weaving:组装方面来创立一个被通知对象。这可以在编译时完成例如使用组装方面来创立一个被通知对象。这可以在编译时完成例如使用AspectJ编译器,也可以在运行时完成。编译器,也可以在运行时完成。Spring和其他纯和其他纯JavaAOP框架一样,在运框架一样,在运行时完成织入。行时完成织入。n各种通知类型包括各种通知类型包括:nAround通知通知:包围一个连接点的通知,如方法调用。这是最强大的通知。包围一个连接点的通知,如方法调用。这是最强大的通知。Aroud通知通知在方法调用前后完成自定义的行为。它们负责选择继续执行连接点或通过返回它们自在方法调用前后完成自定义的行为。它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出异常来短路执行。己的返回值或抛出异常来短路执行。nBefore通知通知:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行除在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行除非它抛出一个异常。非它抛出一个异常。nThrows通知通知:在方法抛出异常时执行的通知。在方法抛出异常时执行的通知。Spring提供强制类型的提供强制类型的Throws通知,因通知,因此你可以书写代码捕获感兴趣的异常和它的子类,不需要从此你可以书写代码捕获感兴趣的异常和它的子类,不需要从Throwable或或Exception强制类型转换。强制类型转换。nAfterreturning通知通知:在连接点正常完成后执行的通知,例如,一个方法正常返回,没在连接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。有抛出异常。5 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念其中其中Around通知是最通用的通知类型。大局部基于拦截的通知是最通用的通知类型。大局部基于拦截的AOP框架,如框架,如Nanning和和JBoss4,只提供,只提供Around通知。通知。如同如同AspectJ,Spring提供所有类型的通知,我们推荐你使用最为适宜的通知类型来实现提供所有类型的通知,我们推荐你使用最为适宜的通知类型来实现需要的行为。例如,如果只是需要用一个方法的返回值来更新缓存,你最好实现一个需要的行为。例如,如果只是需要用一个方法的返回值来更新缓存,你最好实现一个afterreturning通知而不是通知而不是around通知,虽然通知,虽然around通知也能完成同样的事情。使用通知也能完成同样的事情。使用最适宜的通知类型使编程模型变得简单,并能减少潜在错误。例如你不需要调用在最适宜的通知类型使编程模型变得简单,并能减少潜在错误。例如你不需要调用在around通知中所需使用的的通知中所需使用的的MethodInvocation的的proceed()方法,因此就调用失败。方法,因此就调用失败。切入点的概念是切入点的概念是AOP的关键,使的关键,使AOP区别于其它使用拦截的技术。切入点使通知独立于区别于其它使用拦截的技术。切入点使通知独立于OO的的层次选定目标。例如,提供声明式事务管理的层次选定目标。例如,提供声明式事务管理的around通知可以被应用到跨越多通知可以被应用到跨越多个对象的一组方法上。个对象的一组方法上。因此切入点构成了因此切入点构成了AOP的结构要素。的结构要素。6 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念下面让我们实现一个下面让我们实现一个SpringAOP的例子。在这个例子中,我们将实现一个的例子。在这个例子中,我们将实现一个beforeadvice,这意味着,这意味着advice的代码在被调用的的代码在被调用的public方法开始前被执行。以下是这个方法开始前被执行。以下是这个beforeadvice的实现代码:的实现代码:代码:代码:packagecom.ascenttech.springaop.test;importjava.lang.reflect.Method;importorg.springframework.aop.MethodBeforeAdvice;publicclassTestBeforeAdviceimplementsMethodBeforeAdvicepublicvoidbefore(Methodm,Objectargs,Objecttarget)throwsThrowableSystem.out.println(Helloworld!(by+this.getClass().getName()+);7 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司接口接口MethodBeforeAdvice只有一个方法只有一个方法before需要实现,它定义了需要实现,它定义了advice的实的实现。现。before方法共用三个参数,它们提供了相当丰富的信息。参数方法共用三个参数,它们提供了相当丰富的信息。参数Methodm是是advice开始后执行的方法。方法名称可以用作判断是否执行代码的条件。开始后执行的方法。方法名称可以用作判断是否执行代码的条件。Objectargs是传给被调用的是传给被调用的public方法的参数数组。当需要记日志时,参方法的参数数组。当需要记日志时,参数数args和被执行方法的名称,都是非常有用的信息。你也可以改变传给和被执行方法的名称,都是非常有用的信息。你也可以改变传给m的的参数,但要小心使用这个功能;编写最初主程序的程序员并不知道主程序可参数,但要小心使用这个功能;编写最初主程序的程序员并不知道主程序可能会和传入参数的发生冲突。能会和传入参数的发生冲突。Objecttarget是执行方法是执行方法m对象的引用。对象的引用。11.1AOP概念概念8 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念在下面的在下面的BeanImpl类中,每个类中,每个public方法调用前,都会执行方法调用前,都会执行advice:代码:代码:packagecom.ascenttech.springaop.test;publicclassBeanImplimplementsBeanpublicvoidtheMethod()System.out.println(this.getClass().getName()+.+newException().getStackTrace()0.getMethodName()+()+saysHELLO!);9 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司类类BeanImpl实现了下面的接口实现了下面的接口Bean:代码:代码:packagecom.ascenttech.springaop.test;publicinterfaceBeanpublicvoidtheMethod();虽然不是必须使用接口,但面向接口而不是面向实现编程是良好的编程实践,虽然不是必须使用接口,但面向接口而不是面向实现编程是良好的编程实践,Spring也鼓励这样做也鼓励这样做11.1AOP概念概念10 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念pointcut和和advice通过配置文件来实现,因此,接下来你只需编写主方法的通过配置文件来实现,因此,接下来你只需编写主方法的Java代码:代码:代码:代码:packagecom.ascenttech.springaop.test;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.FileSystemXmlApplicationContext;publicclassMainpublicstaticvoidmain(Stringargs)/ReadtheconfigurationfileApplicationContextctx=newFileSystemXmlApplicationContext(springconfig.xml);/InstantiateanobjectBeanx=(Bean)ctx.getBean(bean);/Executethepublicmethodofthebean(thetest)x.theMethod();11 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念我们从读入和处理配置文件开始,接下来马上要创立它。这个配置文件将作为粘我们从读入和处理配置文件开始,接下来马上要创立它。这个配置文件将作为粘合程序不同局部的合程序不同局部的“胶水。读入和处理配置文件后,我们会得到一个创立胶水。读入和处理配置文件后,我们会得到一个创立工厂工厂ctx。任何一个。任何一个Spring管理的对象都必须通过这个工厂来创立。对象通过管理的对象都必须通过这个工厂来创立。对象通过工厂创立后便可正常使用。工厂创立后便可正常使用。12 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念仅仅用配置文件便可把程序的每一局部组装起来。仅仅用配置文件便可把程序的每一局部组装起来。代码:代码:com.ascenttech.springaop.test.BeantheAdvisor13 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念com.ascenttech.springaop.test.Bean.theMethod14 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.1AOP概念概念四个四个bean定义的次序并不重要。我们现在有了一个定义的次序并不重要。我们现在有了一个advice,一个包含了正那么表达式,一个包含了正那么表达式pointcut的的advisor,一个主程序类和一个配置好的接口,通过工厂,一个主程序类和一个配置好的接口,通过工厂ctx,这个接口返回,这个接口返回自己本身实现的一个引用。自己本身实现的一个引用。BeanImpl和和TestBeforeAdvice都是直接配置。我们用一个唯一的都是直接配置。我们用一个唯一的ID创立一个创立一个bean元素,元素,并指定了一个实现类。这就是全部的工作。并指定了一个实现类。这就是全部的工作。advisor通过通过Springframework提供的一个提供的一个RegexMethodPointcutAdvisor类来实现。我类来实现。我们用们用advisor的一个属性来指定它所需的的一个属性来指定它所需的advice-bean。第二个属性那么用正那么表达。第二个属性那么用正那么表达式定义了式定义了pointcut,确保良好的性能和易读性。,确保良好的性能和易读性。最后配置的是最后配置的是bean,它可以通过一个工厂来创立。,它可以通过一个工厂来创立。bean的定义看起来比实际上要复杂。的定义看起来比实际上要复杂。bean是是ProxyFactoryBean的一个实现,它是的一个实现,它是Springframework的一局部。这个的一局部。这个bean的行为通过一下的三个属性来定义:的行为通过一下的三个属性来定义:属性属性proxyInterface定义了接口类。定义了接口类。属性属性target指向本地配置的一个指向本地配置的一个bean,这个,这个bean返回一个接口的实现。返回一个接口的实现。属性属性interceptorNames是唯一允许定义一个值列表的属性。这个列表包含所有需要在是唯一允许定义一个值列表的属性。这个列表包含所有需要在beanTarget上执行的上执行的advisor。注意,。注意,advisor列表的次序是非常重要的。列表的次序是非常重要的。15 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.2Spring的切入点的切入点11.2Spring的切入点的切入点让我们看看让我们看看Spring如何处理切入点这个重要的概念。如何处理切入点这个重要的概念。1.概念概念Spring的切入点模型能够使切入点独立于通知类型被重用。的切入点模型能够使切入点独立于通知类型被重用。同样的切入点有可能接受不同的通知。同样的切入点有可能接受不同的通知。org.springframework.aop.Pointcut接口是重要的接口,用来指定通知到特定的类和方法目标。完整接口是重要的接口,用来指定通知到特定的类和方法目标。完整的接口定义如下的接口定义如下:publicinterfacePointcutClassFiltergetClassFilter();MethodMatchergetMethodMatcher();将将Pointcut接口分成两个局部有利于重用类和方法的匹配局部,并且组合细粒度的操作如和另一个方接口分成两个局部有利于重用类和方法的匹配局部,并且组合细粒度的操作如和另一个方法匹配器执行一个并法匹配器执行一个并“的操作。的操作。16 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.2Spring的切入点的切入点ClassFilter接口被用来将切入点限制到一个给定的目标类的集合。接口被用来将切入点限制到一个给定的目标类的集合。如果如果matches()永远返回永远返回true,所有的目标类都将被,所有的目标类都将被匹配。匹配。publicinterfaceClassFilterbooleanmatches(Classclazz);MethodMatcher接口通常更加重要。完整的接口如下:接口通常更加重要。完整的接口如下:publicinterfaceMethodMatcherbooleanmatches(Methodm,ClasstargetClass);booleanisRuntime();booleanmatches(Methodm,ClasstargetClass,Objectargs);matches(Method,Class)方法被用来测试这个切入点是否匹配目标类的给定方法。这个测试可以在方法被用来测试这个切入点是否匹配目标类的给定方法。这个测试可以在AOP代理创立的时代理创立的时候执行,防止在所有方法调用时都需要进行测试。如果候执行,防止在所有方法调用时都需要进行测试。如果2个参数的匹配方法对某个方法返回个参数的匹配方法对某个方法返回true,并且,并且MethodMatcher的的isRuntime()也返回也返回true,那么,那么3个参数的匹配方法将在每次方法调用的时候被调用。这使切入点个参数的匹配方法将在每次方法调用的时候被调用。这使切入点能够在目标通知被执行之前立即查看传递给方法调用的参数。能够在目标通知被执行之前立即查看传递给方法调用的参数。大局部大局部MethodMatcher都是静态的,意味着都是静态的,意味着isRuntime()方法返回方法返回false。这种情况下。这种情况下3个参数的匹配方法永远不会被调用。个参数的匹配方法永远不会被调用。如果可能,尽量使切入点是静态的,使当如果可能,尽量使切入点是静态的,使当AOP代理被创立时,代理被创立时,AOP框架能够缓存切入点的框架能够缓存切入点的测试结果。测试结果。17 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.2Spring的切入点的切入点2.切入点的运算切入点的运算Spring支持的切入点的运算有支持的切入点的运算有:值得注意的是并和交。值得注意的是并和交。并表示只要任何一个切入点匹配的方法。并表示只要任何一个切入点匹配的方法。交表示两个切入点都要匹配的方法。交表示两个切入点都要匹配的方法。并通常比较有用。并通常比较有用。切入点可以用切入点可以用类的静态方法来组合,或者使用同一个包中的类的静态方法来组合,或者使用同一个包中的ComposablePointcut类。类。3.实用切入点实现实用切入点实现Spring提供几个实用的切入点实现。一些可以直接使用。另一些需要子类化来实现应用相提供几个实用的切入点实现。一些可以直接使用。另一些需要子类化来实现应用相关的切入点。关的切入点。3.1.静态切入点静态切入点静态切入点只基于方法和目标类,而不考虑方法的参数。静态切入点足够满足大多数情况静态切入点只基于方法和目标类,而不考虑方法的参数。静态切入点足够满足大多数情况的使用。的使用。Spring可以只在方法第一次被调用的时候计算静态切入点,不需要在每次方可以只在方法第一次被调用的时候计算静态切入点,不需要在每次方法调用的时候计算。法调用的时候计算。18 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.2Spring的切入点的切入点让我们看一下让我们看一下Spring提供的一些静态切入点的实现。提供的一些静态切入点的实现。1).正那么表达式切入点正那么表达式切入点一个很显然的指定静态切入点的方法是正那么表达式。除了一个很显然的指定静态切入点的方法是正那么表达式。除了Spring以外,其它的以外,其它的AOP框架框架也实现了这一点。是一个通用的正那么表达式切入点,它使用也实现了这一点。是一个通用的正那么表达式切入点,它使用Perl5的正那么表达式的的正那么表达式的语法。语法。使用这个类你可以定义一个模式的列表。如果任何一个匹配,那个切入点将被计算成使用这个类你可以定义一个模式的列表。如果任何一个匹配,那个切入点将被计算成true。所以结果相当于是这些切入点的并集。所以结果相当于是这些切入点的并集。用法如下用法如下:.*get.*.*absquatulate19 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.2Spring的切入点的切入点RegexpMethodPointcut一个实用子类,一个实用子类,RegexpMethodPointcutAdvisor,允许我们同时引用一个通知。,允许我们同时引用一个通知。记住通知记住通知可以是拦截器,可以是拦截器,before通知,通知,throws通知等等。通知等等。)这简化了这简化了bean的装配,因为一个的装配,因为一个bean可以同时当作切入点和通可以同时当作切入点和通知,如下所示:知,如下所示:.*get.*.*absquatulateRegexpMethodPointcutAdvisor可以用于任何通知类型。可以用于任何通知类型。RegexpMethodPointcut类需要类需要JakartaORO正那么表达式包。正那么表达式包。2)属性驱动的切入点属性驱动的切入点一类重要的静态切入点是元数据驱动的一类重要的静态切入点是元数据驱动的切入点。切入点。它使用元数据属性的值:典型地,使用源代码级元数据。它使用元数据属性的值:典型地,使用源代码级元数据。20 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.2Spring的切入点的切入点3.2.动态切入点动态切入点动态切入点的演算代价比静态切入点高的多。它们不仅考虑静态信息,还要考虑方法的动态切入点的演算代价比静态切入点高的多。它们不仅考虑静态信息,还要考虑方法的参数。这意味着它们必须在每次参数。这意味着它们必须在每次方法调用的时候都被计算;并且不能缓存结果方法调用的时候都被计算;并且不能缓存结果,因为参数是变化的。,因为参数是变化的。1).控制流切入点控制流切入点Spring的控制流切入点概念上和的控制流切入点概念上和AspectJ的的cflow切入点一致,虽然没有其那么强大当前没有方法指定一个切入点在切入点一致,虽然没有其那么强大当前没有方法指定一个切入点在另一个切入点后执行。另一个切入点后执行。一个控制流切入点匹配当前的调用栈。例如,连接点被一个控制流切入点匹配当前的调用栈。例如,连接点被包或者包或者SomeCaller类中一个方法类中一个方法调用的时候,触发该切入点。控制流切入点的实现类是调用的时候,触发该切入点。控制流切入点的实现类是。4.切入点超类切入点超类Spring提供非常实用的切入点的超类帮助你实现你自己的切入点。提供非常实用的切入点的超类帮助你实现你自己的切入点。因为静态切入点非常实用,你很可能子类化因为静态切入点非常实用,你很可能子类化StaticMethodMatcherPointcut,如下所示。,如下所示。这只需要实现一个抽象方法这只需要实现一个抽象方法虽然可以改写其它的方法来自定义行为。虽然可以改写其它的方法来自定义行为。classTestStaticPointcutextendsStaticMethodMatcherPointcutpublicbooleanmatches(Methodm,ClasstargetClass)/returntrueifcustomcriteriamatch当然也有动态切入点的超类。当然也有动态切入点的超类。Spring1.0RC2或以上版本,自定义切入点可以用于任何类型的通知。或以上版本,自定义切入点可以用于任何类型的通知。21 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.2Spring的切入点的切入点5.自定义切入点自定义切入点因为因为Spring中的切入点是中的切入点是Java类,而不是语言特性如类,而不是语言特性如AspectJ,因此可以,因此可以定义自定义切入点,无论静态还是动态。但是,没有直接支持用定义自定义切入点,无论静态还是动态。但是,没有直接支持用AspectJ语语法书写的复杂的切入点表达式。不过,法书写的复杂的切入点表达式。不过,Spring的自定义切入点也可以任意的的自定义切入点也可以任意的复杂。复杂。22 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型的通知类型的通知类型现在让我们看看现在让我们看看SpringAOP是如何处理通知的。是如何处理通知的。1.通知的生命周期通知的生命周期Spring的通知可以跨越多个被通知对象共享,或者每个被通知对象有自己的通知。这分别的通知可以跨越多个被通知对象共享,或者每个被通知对象有自己的通知。这分别对应对应per-class或或per-instance通知。通知。Per-class通知使用最为广泛。它适合于通用的通知,如事务通知使用最为广泛。它适合于通用的通知,如事务adisor。它们不依赖被代理。它们不依赖被代理的对象的状态,也不添加新的状态。它们仅仅作用于方法和方法的参数。的对象的状态,也不添加新的状态。它们仅仅作用于方法和方法的参数。Per-instance通知适合于导入,来支持混入通知适合于导入,来支持混入mixin。在这种情况下,通知添加状态到。在这种情况下,通知添加状态到被代理的对象。被代理的对象。可以在同一个可以在同一个AOP代理中混合使用共享和代理中混合使用共享和per-instance通知。通知。23 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型2.Spring中通知类型中通知类型Spring提供几种现成的通知类型并可扩展提供任意的通知类型。让我们看看根本概念和提供几种现成的通知类型并可扩展提供任意的通知类型。让我们看看根本概念和标标准的通知类型。准的通知类型。1)InterceptionaroundadviceSpring中最根本的通知类型是中最根本的通知类型是interceptionaroundadvice.Spring使用方法拦截器的使用方法拦截器的around通知是和通知是和AOP联盟接口兼容的。实现联盟接口兼容的。实现around通知的通知的类需类需要实现接口要实现接口MethodInterceptor:publicinterfaceMethodInterceptorextendsInterceptorObjectinvoke(MethodInvocationinvocation)throwsThrowable;invoke()方法的方法的MethodInvocation参数暴露将被调用的方法、目标连接点、参数暴露将被调用的方法、目标连接点、AOP代理和代理和传递给被调用方法的参数。传递给被调用方法的参数。invoke()方法应该返回调用的结果:连接点的返回值。方法应该返回调用的结果:连接点的返回值。24 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型一个简单的一个简单的MethodInterceptor实现看起来如下实现看起来如下:publicclassDebugInterceptorimplementsMethodInterceptorpublicObjectinvoke(MethodInvocationinvocation)throwsThrowableSystem.out.println(Before:invocation=+invocation+);Objectrval=invocation.proceed();System.out.println(Invocationreturned);returnrval;注意注意MethodInvocation的的proceed()方法的调用。方法的调用。这个调用会应用到目标连接点的拦截器链中的每一个拦截器。大局部这个调用会应用到目标连接点的拦截器链中的每一个拦截器。大局部拦截器会调用这个方法,并返回它的返回值。但是,拦截器会调用这个方法,并返回它的返回值。但是,一个一个MethodInterceptor,和任何,和任何around通知一样,可以返回通知一样,可以返回不同的值或者抛出一个异常,而不同的值或者抛出一个异常,而不调用不调用proceed方法。但是,没有好的原因你要这么做。方法。但是,没有好的原因你要这么做。MethodInterceptor提供了和其他提供了和其他AOP联盟的兼容实现的交互能力。这一节下面要讨论的其他的通知类型实现了联盟的兼容实现的交互能力。这一节下面要讨论的其他的通知类型实现了AOP公公共的概念,但是以共的概念,但是以Spring特定的方式。虽然使用特定特定的方式。虽然使用特定通知类型有很多优点,但如果你可能需要在其他的通知类型有很多优点,但如果你可能需要在其他的AOP框架框架中使用,请坚持使用中使用,请坚持使用MethodInterceptoraround通知类型。注意目前切入点不能和其它框架交互操作,并且通知类型。注意目前切入点不能和其它框架交互操作,并且AOP联盟目前也没有定义切入联盟目前也没有定义切入点接口。点接口。25 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型2).Before通知通知Before通知是一种简单的通知类型。通知是一种简单的通知类型。这个通知不需要一个这个通知不需要一个MethodInvocation对象,因为对象,因为它只在进入一个方法它只在进入一个方法前被调用。前被调用。Before通知的主要优点是它不需要调用通知的主要优点是它不需要调用proceed()方法,方法,因此没有无意中忘掉继续执行拦因此没有无意中忘掉继续执行拦截器链的可能性。截器链的可能性。MethodBeforeAdvice接口如下所示。接口如下所示。(Spring的的API设计允许成员变量的设计允许成员变量的before通知,虽通知,虽然一般的对象都可以应用成员变量拦截,但然一般的对象都可以应用成员变量拦截,但Spring有可能永远不会实现它。有可能永远不会实现它。publicinterfaceMethodBeforeAdviceextendsBeforeAdvicevoidbefore(Methodm,Objectargs,Objecttarget)throwsThrowable;注意返回类型是注意返回类型是void。Before通知可以在连接点执行之前通知可以在连接点执行之前插入自定义的行为,但是不能插入自定义的行为,但是不能改变返回值。如果一个改变返回值。如果一个before通知抛出一个异常,这将中断拦截器通知抛出一个异常,这将中断拦截器链的进一步执行。链的进一步执行。这个异常将沿着拦截器链后退着向上传播。如果这个异常是这个异常将沿着拦截器链后退着向上传播。如果这个异常是unchecked的,或者出现的,或者出现在被调用的方法的签名中,它将会被直接传递给客户代码;否那么,它将被在被调用的方法的签名中,它将会被直接传递给客户代码;否那么,它将被AOP代理代理包装到一个包装到一个unchecked的异常里。的异常里。26 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型下面是下面是Spring中一个中一个before通知的例子,这个例子计数所有正常返回的方法:通知的例子,这个例子计数所有正常返回的方法:publicclassCountingBeforeAdviceimplementsMethodBeforeAdviceprivateintcount;publicvoidbefore(Methodm,Objectargs,Objecttarget)throwsThrowable+count;publicintgetCount()returncount;Before通知可以被用于任何类型的切入点。通知可以被用于任何类型的切入点。27 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型3)Throws通知通知如果连接点抛出异常,如果连接点抛出异常,Throws通知在连接点返回后被调用。通知在连接点返回后被调用。Spring提供强类型的提供强类型的throws通知。注意这意味着通知。注意这意味着接口不包含任何方法:它是一个标记接口,标识给定的对象实现接口不包含任何方法:它是一个标记接口,标识给定的对象实现了一个或多个强类型的了一个或多个强类型的throws通知方法。这些方法形式通知方法。这些方法形式如下:如下:afterThrowing(Method,args,target,subclassOfThrowable)只有最后一个参数是必需的。只有最后一个参数是必需的。这样从一个参数到四个参数,依赖于通知是否对方法和方法这样从一个参数到四个参数,依赖于通知是否对方法和方法的参数感兴趣。下面是的参数感兴趣。下面是throws通知的例子。通知的例子。如果抛出如果抛出RemoteException异常包括子类异常包括子类,这个通知会被调用这个通知会被调用publicclassRemoteThrowsAdviceimplementsThrowsAdvicepublicvoidafterThrowing(RemoteExceptionex)throwsThrowable/Dosomethingwithremoteexception28 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型如果抛出如果抛出ServletException异常,异常,下面的通知会被调用。和上面的通知不一样,它声明了四个参数,所以它可以访问被下面的通知会被调用。和上面的通知不一样,它声明了四个参数,所以它可以访问被调用的方法,方法的参数和目标对象调用的方法,方法的参数和目标对象:publicstaticclassServletThrowsAdviceWithArgumentsimplementsThrowsAdvicepublicvoidafterThrowing(Methodm,Objectargs,Objecttarget,ServletExceptionex)/Dosomethingwillallarguments最后一个例子演示了如何在一个类中使用两个方法来同时处理最后一个例子演示了如何在一个类中使用两个方法来同时处理RemoteException和和ServletException异常。任意个数异常。任意个数的的throws方法可以被组合在一个类中。方法可以被组合在一个类中。publicstaticclassCombinedThrowsAdviceimplementsThrowsAdvicepublicvoidafterThrowing(RemoteExceptionex)throwsThrowable/DosomethingwithremoteexceptionpublicvoidafterThrowing(Methodm,Objectargs,Objecttarget,ServletExceptionex)/DosomethingwillallargumentsThrows通知可被用于任何类型的切入点。通知可被用于任何类型的切入点。29 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型4).AfterReturning通知通知Spring中的中的afterreturning通知必须实现通知必须实现接口,如下所示:接口,如下所示:publicinterfaceAfterReturningAdviceextendsAdvicevoidafterReturning(ObjectreturnValue,Methodm,Objectargs,Objecttarget)throwsThrowable;Afterreturning通知可以访问返回值不能改变、被调用的方法、方法的参数和通知可以访问返回值不能改变、被调用的方法、方法的参数和目标对目标对象。象。30 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型下面的下面的afterreturning通知统计所有成功的没有抛出异常的方法调用:通知统计所有成功的没有抛出异常的方法调用:publicclassCountingAfterReturningAdviceimplementsAfterReturningAdviceprivateintcount;publicvoidafterReturning(ObjectreturnValue,Methodm,Objectargs,Objecttarget)throwsThrowable+count;publicintgetCount()returncount;这方法不改变执行路径。如果它抛出一个异常,这个异常而不是返回值将被沿着拦截器链这方法不改变执行路径。如果它抛出一个异常,这个异常而不是返回值将被沿着拦截器链向上抛出。向上抛出。Afterreturning通知可被用于任何类型的切入点。通知可被用于任何类型的切入点。31 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型5).Introduction通知通知Spring将将introduction通知看作一种特殊类型的拦截通知。通知看作一种特殊类型的拦截通知。Introduction需要实现需要实现IntroductionAdvisor,和和IntroductionInterceptor接口:接口:publicinterfaceIntroductionInterceptorextendsMethodInterceptorbooleanimplementsInterface(Classintf);继承自继承自AOP联盟联盟MethodInterceptor接口的接口的invoke()方法必须实现导入:也就是说,如果被调用的方法方法必须实现导入:也就是说,如果被调用的方法是在是在导入的接口中,导入拦截器负责处理这个方法调用,它不能调用导入的接口中,导入拦截器负责处理这个方法调用,它不能调用proceed()方法。方法。Introduction通知不能被用于任何切入点,因为它只能作用于类层次上,而不是方法。通知不能被用于任何切入点,因为它只能作用于类层次上,而不是方法。你可以只用你可以只用InterceptionIntroductionAdvisor来实现导入通知,它有下面的方法:来实现导入通知,它有下面的方法:publicinterfaceInterceptionIntroductionAdvisorextendsInterceptionAdvisorClassFiltergetClassFilter();IntroductionInterceptorgetIntroductionInterceptor();ClassgetInterfaces();这里没有这里没有MethodMatcher,因此也没有和导入通知关联的,因此也没有和导入通知关联的切入点。只有类过滤是符合逻辑的。切入点。只有类过滤是符合逻辑的。getInterfaces()方法返回方法返回advisor导入的接口。导入的接口。32 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型让我们看看一个来自让我们看看一个来自Spring测试套件中的简单例子。我们假设想要导入下面的接口到一个测试套件中的简单例子。我们假设想要导入下面的接口到一个或者多个对象中或者多个对象中:publicinterfaceLockablevoidlock();voidunlock();booleanlocked();在这个例子中,我们想要能够将被通知对象类型转换为在这个例子中,我们想要能够将被通知对象类型转换为Lockable,不管它们的类型,并且,不管它们的类型,并且调用调用lock和和unlock方法。如果我们调用方法。如果我们调用lock()方法,我们希望所有方法,我们希望所有setter方法抛出方法抛出LockedException异常。异常。这样我们能添加一个方面使的对象不可变,而它们不需要知这样我们能添加一个方面使的对象不可变,而它们不需要知道这一点:这是一个很好的道这一点:这是一个很好的AOP例子。例子。首先,我们需要一个做大量转化的首先,我们需要一个做大量转化的IntroductionInterceptor。在这里,我们继承在这里,我们继承org.springframework.aop.support.DelegatingIntroductionInterceptor实用类。我实用类。我们可以直接实现们可以直接实现IntroductionInterceptor接口,但是大多数情况下接口,但是大多数情况下DelegatingIntroductionInterceptor是最适宜的。是最适宜的。33 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型DelegatingIntroductionInterceptor的设计是将导入的设计是将导入委托到真正实现导入接口的接口,隐委托到真正实现导入接口的接口,隐藏完成这些工作的拦截器。委托可以使用构造方法参数藏完成这些工作的拦截器。委托可以使用构造方法参数设置到任何对象中;默认的委设置到任何对象中;默认的委托就是自己当无参数的构造方法被使用时。这样在下面的例子里,委托是托就是自己当无参数的构造方法被使用时。这样在下面的例子里,委托是DelegatingIntroductionInterceptor的子类的子类LockMixin。给定一个委托默认是自身。给定一个委托默认是自身的的DelegatingIntroductionInterceptor实例寻找被这个委托而不实例寻找被这个委托而不是是IntroductionInterceptor实现的所有接口,并支持它们中任何一个导入。子类如实现的所有接口,并支持它们中任何一个导入。子类如LockMixin也可能调用也可能调用suppressInterflace(Classintf)方法隐藏不应暴露的接口。然方法隐藏不应暴露的接口。然而,不管而,不管IntroductionInterceptor准备支持多少接口,准备支持多少接口,IntroductionAdvisor将控制哪将控制哪个接口将被实际个接口将被实际暴露。一个导入的接口将隐藏目标的同一个接口的所有实现。暴露。一个导入的接口将隐藏目标的同一个接口的所有实现。34 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型LockMixin继承继承DelegatingIntroductionInterceptor并自己实现并自己实现Lockable。父类自动选择支持导入的。父类自动选择支持导入的Lockable,所以,所以我们不需要指定它。我们不需要指定它。用这种方法我们可以导入任意数量的接口。用这种方法我们可以导入任意数量的接口。注意注意locked实例变量的使用。这有效地添加额外的状态到目标实例变量的使用。这有效地添加额外的状态到目标对象。对象。publicclassLockMixinextendsDelegatingIntroductionInterceptorimplementsLockableprivatebooleanlocked;publicvoidlock()this.locked=true;publicvoidunlock()this.locked=false;publicbooleanlocked()returnthis.locked;publicObjectinvoke(MethodInvocationinvocation)throwsThrowableif(locked()&invocation.getMethod().getName().indexOf(set)=0)thrownewLockedException();returnsuper.invoke(invocation);35 北京亚思晟北京亚思晟北京亚思晟北京亚思晟 科技有限公司科技有限公司科技有限公司科技有限公司11.3Spring的通知类型的通知类型通常不要需要改写通常不要需要改写invoke()方法:实现方法:实现DelegatingIntroductionInterceptor就足够了,如果是导入的方就足够了,如果是导入的方法,法,DelegatingIntroductionInterceptor实现会调用委托方法,实现会调用委托方法,否那么继续沿着连接点处理。在否那么继续沿着连接点处理。在现在的情况下,我们需要添加一个检查:在上锁现在的情况下,我们需要添加一个检查:在上锁状态下不能调用状态下不能调用setter方法。方法。所需的导入所需的导入advisor是很简单的。只有保存一个独立的是很简单的。只有保存一个独立的LockMixin实例,并指定导入的接口,在这里就实例,并指定导入的接口,在这里就是是Lockable。一个稍微复杂一点例子可能需要一个导入拦截器可以。一个稍微复杂一点例子可能需要一个导入拦截器可以定义成定义成prototype的引用:的引用:在这种情况下,在这种情况下
展开阅读全文