1. AOP 概述
1. AOP 简介
AOP通过横向抽取机制解决无法通过纵向继承体系进行抽象的重复性代码
注: AOP将分散在业务逻辑代码中的相同代码通过横向切割的方式抽取到一个独立的模块中,并能将这些独立的逻辑融合到业务逻辑中以完成和原来一样的业务逻辑
2. AOP 术语
连接点(Joinpoint)
- 特定点是程序执行的某个特定位置,如类开始初始化前,类初始化后,类的某个方法调用前/后,方法抛出异常后。一个类或一段代码拥有一些具有边界性质的特定点,这些代码中的特定点就被称为“连接点”。
- Spring仅支持方法的连接点,即仅能在方法调用前,方法调用后,方法抛出异常时及方法调用前后这些程序执行点织入增强。
- 连接点由两个信息确定:一是用方法表示的程序执行点,二是用相对位置表示的方位。如在Test.foo()方法执行前的连接点,执行点为Test.foo(),方法为该方法执行前的位置。Spring使用切点对执行点进行定位,而方位则在增强类型中增强
切点(Pointcut)
- 每个程序类都有多个连接点,如一个拥有两个方法的类,这两个方法就是连接点,即连接点是程序类中客观存在的事物。AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件,一个切点可以匹配多个连接点。
- 在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。确切地说,应该是执行点而非连接点,因为连接点是方法执行前/后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体的连接点上,需要提供方位信息
增强(Advice)
- 增强是织入目标类连接点上的一段程序代码。在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,就是执行点的方位。结合执行点的方位信息和切点信息,就可以找到特定的连接。
- 正因为增强即包含由于添加到目标连接点上的一段执行逻辑,又包含用于定位连接点的方位信息,所以Spring所提供的增强接口都是带方位名的,如BeforeAdivce,AfterReturningAdvice和ThrowsAdvice等。BeforeAdvice表示方法调用前的位置,而AfterReturningAdvice表示访问后的位置。
- 所以只有结合切点和增强,才能确定特定的连接点并实施逻辑增强。
目标对象(Target)
增强逻辑的织入目标类。如果没有AOP,那么目标业务类需要自己实现所有的逻辑,如7-1.
在AOP的帮助下,ForumService只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑就可以使用AOP动态织入特定的连接点上引介(Introduction)
引介是一种特殊的增强,为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,也可以动态地为该业务类添加接口的实现逻辑,使业务类成为这个接口的实现类织入(Weaving)
织入是将增强添加到目标类的具体连接点上的过程.AOP就像一台织布机,将目标类,增强或引介编织到一切。
AOP有三种织入方式:- 编译期织入: 这要求使用特殊的Java编译器
- 类装载期织入: 这要求使用特殊的类装载器
- 动态代理织入: 在运行期为目标类添加增强生成子类的方式 (Spring采用动态代理织入)
代理(Proxy)
一个类被AOP织入增强后,就产生一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以可以采用与调用原类相同的方式调用代理类。切面(Aspect)
切面由切点和增强(引介)组成,既包括横切逻辑的定义,也包括连接点的定义。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入切面所指定的连接点中
2. AOP 基础知识
注:
- CGLib所创建的动态代理对象的性能比JDK所创建的动态代理对象的性能高很多,但CGLib在创建代理对象时所花费的时间比JDK动态代理多。
- 对于singleton的代理对象或者具有实例池的代理,因为无需频繁地创建代理对象,所以比较适合采用CGLib动态代理技术;反之适合采用JDK动态代理技术
3. 创建增强类
增强类型:
注: 带<<spring>>
标识的接口是在Spring所定义的扩展增强接口,带<<aopalliance>>
标识的接口是AOP联盟定义的接口。按照增强在目标类方法中的连接点位置,可以分为5类:
- 前置增强:
org.springframework.aop.BeforeAdvice
代表前置增强,因为Spring只支持方法级的增强,所以MethodBeforeAdvice
是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的。 - 后置增强:
org.springframework.aop.AfterReturningAdvice
代表后置增强,表示在目标方法执行后实施增强。 - 环绕增强:
org.aopalliance.intercept.MethodInterceptor
代表环绕增强,表示在目标方法执行前后实施增强。 - 异常抛出增强:
org.springframework.aop.ThrowsAdvice
代表抛出异常增强,表示在目标方法抛出异常后实施增强。 - 引介增强:
org.springframework.aop.IntroductionInterceptor
代表引介增强,表示在目标类中添加一些新的方法和属性
- 前置增强:
前置增强:
1.实例:
public interface Waiter { void greetTo(String name); void serveTo(String name); }
public class NaiveWaiter implements Waiter{ public void greetTo(String name) { System.out.println("greet to"+name+"..."); } public void serveTo(String name) { System.out.println("serving"+name+"..."); } }
public class GreetingBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] objects, Object obj) throws Throwable { String clientName=(String)objects[0]; System.out.println("How are you!Mr "+clientName); } }
public class BeforeAdviceTest { @Test public void before(){ Waiter target=new NaiveWaiter(); BeforeAdvice advice=new GreetingBeforeAdvice(); //①Spring提供的代理工厂 ProxyFactory pf=new ProxyFactory(); //②设置代理目标 pf.setTarget(target); //③为代理目标添加增强 pf.addAdvice(advice); //④生成代理实例 Waiter proxy=(Waiter) pf.getProxy(); proxy.greetTo("John"); proxy.serveTo("Tom"); } }
输出:
2.解析ProxyFactory: ProxyFactory内部就是使用JDK或CGLib动态代理技术将增强应用到目标类中
Spring定义了org.springframework.aop.framework.AopProxy接口,并提供额两个final类型的实现类 :
注:- Cglib2AopProxy 使用CGLib动态代理技术创建代理
- JdkDynamicAopProxy 使用JDK动态代理技术创建代理
- 如果通过ProxyFactory的
setInterfaces(Class[] interfaces)
方法指定目标接口进行代理,则ProxyFactory使用JdkDynamicAopProxy - 如果是针对类的代理,则使用Cglib2AopProxy
- 还可以通过ProxyFactory的
setOptimize(true)
方法让ProxyFactory启动优化代理方式,这样,针对接口的代理也会使用Cglib2AopProxy
当我们指定接口进行代理时,将使用JDK动态代理技术:
//①Spring提供的代理工厂 ProxyFactory pf=new ProxyFactory(); //②设置代理目标 pf.setInterfaces(target.getClass().getInterfaces());//指定接口进行代理 pf.setTarget(target);
如果指定启动代理优化,则ProxyFactory还将使用Cglib2AopProxy代理:
//①Spring提供的代理工厂 ProxyFactory pf=new ProxyFactory(); //②设置代理目标 pf.setInterfaces(target.getClass().getInterfaces());//指定接口进行代理 pf.setTarget(target); pf.setOpaque(true); //启动优化
注: ProxyFactory通过addAdvice(Advice)方法添加一个增强,可以使用该方法添加多个增强。多个增强形成一个增强链,它们的调用顺序和添加顺序一致,可以通过addAdvice(int ,Advice)方法将增强添加到增强链的具体位置(初始位置为0)
3.在Spring中的配置:
注: ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean。ProxyFactoryBean负责为其他Bean创建代理实例,它在内部使用ProxyFactory来完成这项工作。ProxyFactoryBean的几个常用的可配置属性
- target: 代理的目标对象
- proxyInterfaces: 代理所要实现的接口,可以是多个接口
- interceptorNames: 需要织入目标对象的Bean列表,采用Bean的名称指定。这些Bean必须是实现了org.aopalliance.intercept.MethodInterceptor或org.springframework.aop.Advisor的Bean,配置中的顺序对应调用的顺序。
- singleton: 返回的代理是否单实例,默认为单实例。
- optimize: 当设置为true时,强制使用CGLib动态代理。对于singleton的代理,推荐使用CGLib;对于其他作用域类型的代理,最好使用JDK动态代理
- proxyTargetClass: 是否对类进行代理(而不是对接口进行代理)。当设置为true时,使用CGLib动态代理
后置增强: 后置增强在目标类方法调用后执行
public class GreetingAfterAdvice implements AfterReturningAdvice { //在目标类方法调用后执行 public void afterReturning(Object returnObj, Method method, Object[] args, Object obj) throws Throwable { System.out.println("Please enjoy yourself"); } }
注:
- 通过实现
AfterReturningAdvice
来定义后置增强的逻辑 AfterReturningAdvice接口也只定义了一个方法
afterReturning(Object returnObj, Method method, Object[] args, Object obj) throws Throwable
,returnObj
为目标实例返回的结果method
为目标类的方法,args为目标方法的入参Obj
为目标类实例
假设在后置增强中抛出异常,如果该异常是目标方法声明的异常,则该异常归并到目标方法中;如果不是目标方法所声明的异常,则Spring将其转为运行期异常抛出
<bean id="greetingBefore" class="com.smart.advice.GreetingBeforeAdvice"/> <bean id="greetingAfter" class="com.smart.advice.GreetingAfterAdvice" /> <bean id="target" class="com.smart.advice.NaiveWaiter"/> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.smart.advice.Waiter" p:interceptorNames="greetingBefore,greetingAfter" p:target-ref="target"/>
注: interceptorNames是String[]类型的
- 通过实现
环绕增强: 环绕增强允许在目标类方法调用前后织入横切逻辑,综合实现了前置,后置增强的功能
代码:
import org.aopalliance.intercept.MethodInterceptor; public class GreetingInterceptor implements MethodInterceptor { //截获目标类方法的执行,并在前后添加横切逻辑 public Object invoke(MethodInvocation invocation)throws Throwable{ Object[] args=invocation.getArguments(); //获取目标方法入参数组 String clientName=(String)args[0]; System.out.println("How are you!Mr."+clientName+"."); //在目标方法执行前调用 Object obj=invocation.proceed(); //通过反射机制调用目标方法 System.out.println("Please enjoy yourself"); //在目标方法执行后调用 return obj; } }
配置:
<bean id="greetingAround" class="com.smart.advice.GreetingInterceptor"/> <bean id="target" class="com.smart.advice.NaiveWaiter"/> <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.smart.advice.Waiter" p:interceptorNames="greetingAround" p:target-ref="target"/>
异常抛出增强: 异常抛出增强最适合的应用场景是事务管理,当参与事务的某个DAO发生异常时,事务管理器就必须回滚事务
下面通过TransactionManager这个异常抛出增强对业务方法进行增强处理,统一捕捉抛出的异常并回滚事务:
public class TransactionManager implements ThrowsAdvice { //定义增强逻辑 public void afterThrowing(Method method,Object[] args,Object target,Exception ex)throws Throwable{ System.out.println("------------"); System.out.println("method"+method.getName()); System.out.println("抛出异常"+ex.getMessage()); System.out.println("成功回滚事务"); } }
- ThrowsAdvice异常抛出增强接口没有定义任何方法,它是一个标签接口,在运行期Spring使用反射机制自行跑断,必须采用以下签名形式定义异常抛出的增强方法:
void afterThrowing(Method method,Object[] args,Object target,Throwable); - 方法名必须是afterThrowing,前三个入参Method method,Object[] args,Object target是可选的(要么都提供,要么都不提供),最后一个入参是Throwable或其子类。
- 可以在同一个异常抛出中定义多个afterThrowing()方法,当目标类方法抛出异常时,Spring会自动选用最匹配的增强方法。
- 标签接口是没有任何方法和属性的接口,不对实现类有任何语义上的要求,仅表明它的实现属于一个特定的类型,Java用它标识某一类对象。有两个用途:
- 通过标签接口标识同一类型的类,这些类本身可能并不具有相同的方法,如Advice接口
- 通过标签接口使程序或JVM采取一些特殊处理,如java.io.Serializable,它告诉JVM对象可以被序列化
<!--4. 异常抛出增强 --> <bean id="forumServiceTarget" class="com.smart.advice.ForumService" /> <bean id="transactionManager" class="com.smart.advice.TransactionManager" /> <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="transactionManager" p:target-ref="forumServiceTarget" p:proxyTargetClass="true"/>
测试代码:
public class ThrowAdviceTest { @Test public void throwAdvice(){ String configPath = "com/smart/advice/beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); ForumService forumService = (ForumService)ctx.getBean("forumService"); try{ forumService.removeForum(10); } catch (Exception e) {} try{ forumService.updateForum(new Forum()); } catch (Exception e) {} } }
输出:
- ThrowsAdvice异常抛出增强接口没有定义任何方法,它是一个标签接口,在运行期Spring使用反射机制自行跑断,必须采用以下签名形式定义异常抛出的增强方法: