方志朋 2019-10-28
除了依赖注入(DI),Spring框架提供的另一个核心功能是对面向方面的编程(AOP)的支持。 AOP通常被称为实现横切关注点的工具。横切关注点一词是指应用程序中的逻辑不能与应用程序的其余部分分离并有效模块化的地方,并且可能导致代码重复和紧密耦合。通过使用AOP模块化单个逻辑(即关注点),可以将它们应用于应用程序的许多部分,而无需复制代码或创建硬依赖关系。日志记录和安全性是许多应用程序中横切关注点的典型示例。AOP是对面向对象编程(OOP)的补充,而不是与之竞争。 OOP非常擅长解决我们作为程序员遇到的各种问题。鉴于AOP可以在OOP之上运行,因此几乎不可能单独使用AOP来开发整个应用程序,尽管使用OOP当然可以开发整个应用程序,但是通过使用AOP解决涉及横切逻辑的某些问题,能更自己聪明地进行工作。
AOP基础知识
AOP的类型
Spring AOP体系结构
使用Spring AOP
AOP框架服务
集成AspectJ
与大多数技术一样,AOP带有其自己的特定概念和术语集,因此了解它们的含义很重要。以下是AOP的核心概念:
连接点(Joinpoint):连接点是在应用程序执行期间定义明确的点。连接点的典型示例包括对方法的调用,方法调用本身,类初始化和对象实例化。连接点是AOP的核心概念,它定义了应用程序中的点,可以在这些点上使用AOP插入其他逻辑。
增强(Advice):在特定联接点执行的代码是Advice,由类中的方法定义。Advice有多种类型,例如之前,在连接点之前执行,之后,在连接点之后执行。(为什么不是翻译成通知?过AOP将横切关注功能加到原有的业务逻辑上,这就是对原有业务逻辑的一种增强,这种增强可以是前置增强、后置增强、返回后增强、抛异常时增强和包围型增强。)
切点(Pointcut):切入点是用于定义何时执行建议的联接点的集合。通过创建切入点,您可以对如何将建议应用于应用程序中的组件进行精细控制。如前所述,典型的连接点是方法调用,或特定类中所有方法调用的集合。通常,您可以在复杂的关系中编写切入点,以在执行建议时进一步限制。
切面(Aspect):切面是封装在类中的建议和切入点的组合。这种结合产生了应包含在应用程序中以及应在何处执行的逻辑的定义。
织入(Weaving):这是在适当的时候将各切面插入应用程序代码中的过程。对于编译时AOP解决方案,这种编织通常是在构建时完成的。同样,对于运行时AOP解决方案,编织过程在运行时动态执行。 AspectJ支持另一种编织机制,称为加载时间编织(LTW),在该机制中,它拦截底层的JVM类加载器,并在由类加载器加载字节码时提供对字节码的编织。
目标(Target):通过AOP流程修改了执行流程的对象称为目标对象。通常,你会看到目标对象,即建议对象。
引介(Introduction):这是可以通过向对象引入其他方法或字段来修改其结构的过程。可以使用引介AOP使任何对象实现特定的接口,而无需对象的类显式实现该接口。这样,即使一个业务类原本没有实现某个接口,通过引介功能,可以动态的为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
AOP有两种不同的类型:静态和动态。它们之间的区别实际上是织造过程的发生点以及该过程的实现方式。
在静态AOP中,编织过程构成了应用程序构建过程中的另一步骤。用Java术语,可以通过修改应用程序的实际字节码,并根据需要更改和扩展应用程序代码,从而在静态AOP实现中实现编织过程。这是完成编织过程的一种很好的方式,因为最终结果只是Java字节码,并且您在运行时不会执行任何特殊技巧来确定何时应执行建议。这种机制的缺点是,即使只是想添加另一个连接点,对切面进行的任何修改都要求你重新编译整个应用程序。 AspectJ的编译时编织是静态AOP实现的一个很好的例子。
诸如Spring AOP之类的动态AOP实现与静态AOP实现的不同之处在于,编织过程是在运行时动态执行的。如何实现此目标取决于实现,但是正如你将看到的那样,Spring的方法是为所有建议对象创建代理,并允许根据需要调用建议。动态AOP的缺点是,通常它的性能不如静态AOP,但是性能却在稳步提高。动态AOP实现的主要好处是,因而无需修改??主应用程序代码即可轻松修改应用程序的整个切面集。
选择使用静态AOP还是动态AOP是一个相当困难的决定。两者都有其自身的优点,并且你不限于仅使用一种类型。通常,静态AOP实现的时间更长,并且趋向于具有更多功能丰富的实现,并且具有更多的可用连接点。通常,如果性能绝对至关重要,或者你需要在Spring中未实现的AOP功能,则需要使用AspectJ。在大多数其他情况下,Spring AOP是理想的选择。请记住,Spring已经提供了许多基于AOP的解决方案,例如事务管理,因此在滚动自己的框架之前,请检查其框架功能!与往常一样,让应用程序需求来决定您对AOP实施的选择,如果技术组合更适合自己的应用程序,则不要将自己局限于一个实施。通常,Spring AOP不如AspectJ复杂,因此它是理想的首选。
Spring的AOP实施可以看作是两个逻辑部分。 第一部分是AOP核心,它提供完全解耦的纯编程AOP功能(也称为Spring AOP API)。 AOP实现的第二部分是框架服务集,这些框架服务使AOP易于在您的应用程序中使用。 最重要的是,Spring的其他组件(例如事务管理器和EJB帮助器类)提供了基于AOP的服务,以简化应用程序的开发。
AOP Alliance(http://aopalliance.sourceforge.net/)是许多开源AOP项目的代表之间的共同努力,旨在为AOP实现定义标准的接口集。只要适用,Spring都会使用AOP Alliance接口,而不是定义自己的接口。这使您可以在支持AOP Alliance接口的多个AOP实现中重用某些建议。
先看一个AOP的例子,编写一个名为Agent的类来打印“Bond”:
package com.apress.prospring5.ch5; public class Agent { public void speak() { System.out.print("Bond"); } }
然后我们增强该方法,按AOP的属于来说,就是想该方法添加Advice,在该方法执行前打印“James ”,在该方法指向后打印“!”
package com.apress.prospring5.ch5; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class AgentDecorator implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.print("James "); Object retVal = invocation.proceed(); System.out.println("!"); return retVal; } }
MethodInterceptor 接口是一个标准的 AOP Alliance接口,用于为方法调用连接点实施环绕增强。MethodInvocation对象表示正在建议的方法调用,并且使用此对象,我们控制何时允许进行方法调用。能够在调用方法之前,之后以及返回之前执行操作。在这段代码中,将“James ”为控制台输出,通过调用 invocation.proceed() 来调用该方法(指 speak()),然后打印"!"。
下面是一个测试实例,创建目标实例和用代理工厂创建代理进行编织:
package com.apress.prospring5.ch5; import org.springframework.aop.framework.ProxyFactory; public class AgentAOPDemo { public static void main(String... args) { Agent target = new Agent(); ProxyFactory pf = new ProxyFactory(); pf.addAdvice(new AgentDecorator()); pf.setTarget(target); Agent proxy = (Agent) pf.getProxy(); target.speak(); System.out.println(""); proxy.speak(); } }
使用 ProxyFactory 类创建目标对象的代理,同时编织advice。通过调用addAdvice() 将 AgentDecorator 建议传递给 ProxyFactory,并通过调用 setTarget() 来指定编织目标,调用getProxy() 生成代理,这里调用了原始目标和代理的speak方法。
输出:
代理的 speak() 调用会导致 AgentDecorator 中的代码执行,从而输出所需的"James Bond!"。
Spring AOP的核心架构基于代理。使用ProxyFactory是创建AOP代理的纯编程方法,还可以依靠Spring提供的声明式 AOP 配置机制(ProxyFactoryBean类,aop命名空间和@AspectJ样式的注释)来利用声明式代理创建。在运行时,Spring分析为 ApplicationContext 中的bean定义的横切关注点,并动态生成代理bean(包装基础目标bean)。代替直接调用目标bean,调用者被注入代理bean。然后,代理bean分析运行状况(即,连接点,切入点或advice),并相应地编织适当的advice。
当要增强的目标对象实现接口时,Spring将使用JDK动态代理创建目标的代理实例。但是,如果建议的目标对象未实现接口(例如,它是一个具体的类),则CGLIB将用于创建代理实例。
Spring AOP中最明显的最简化的其中一种是仅支持一种连接点类型:方法调用。方法调用联接点是迄今为止可用的最有用的联接点,使用它可以完成许多使AOP在日常编程中有用的任务。
在Spring AOP中,切面由实现Advisor接口的类的实例表示。Spring提供了便捷的Advisor的实现,可以在应用程序中重复使用它们。从而消除了一定要自己创建自定义Advisor实施的需要。Advisor 有两个子接口:PointcutAdvisor 和 IntroductionAdvisor。 PointcutAdvisor 接口继承了 Advisor 接口并使用使用切入点管理应用于连接点的advice。 在Spring中,引介被视为特殊的增强,通过使用 IntroductionAdvisor 接口,可以操作,引介所适用的类。
ProxyFactory类控制Spring AOP中的编织和代理创建过程。 在创建代理之前,必须指定建议对象或目标对象。 您可以通过使用setTarget() 方法来执行此操作。 在内部,ProxyFactory 将代理创建过程委托给DefaultAopProxyFactory的一个实例,该实例又委托给Cglib2AopProxy或JdkDynamicAopProxy。
public class ProxyFactory extends ProxyCreatorSupport { …… public Object getProxy() { return createAopProxy().getProxy(); } …… } 10 public class ProxyCreatorSupport extends AdvisedSupport { …… /** * Create a new ProxyCreatorSupport instance. */ public ProxyCreatorSupport() { this.aopProxyFactory = new DefaultAopProxyFactory(); } …… /** * Subclasses should call this to get a new AOP proxy. They should <b>not</b> * create an AOP proxy with {@code this} as an argument. */ protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } return getAopProxyFactory().createAopProxy(this); } …… }
DefaultAopProxyFactory.java
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } } …… }
Spring支持六种 advice,如下表所述: