1 增强类型
Spring中提供的增强类包含两类1.Spring定义的扩展增强 2.aoppalliance定义的扩展增强
其中Spring提供的扩展增强主要针对于方法即方法级增强,aoppalliace即提供了方法级的增强(MethodInterceptor)也提供引介增强(IntroductionInterceptor),引介增强可以给类添加属性和行为所以引介增强属于类级别
1.1 前置增强
顾名思义就是在目标方法执行前织入增强。BeforeAdvice表示前置增强。属于Spring提供的增强所以属于方法级增强。MethodBeforeAdvice为目前可用前置增强。
1.2 后置增强
顾名思义就是在目标方法执行后织入增强。AfterReturningAdvice表示后置增强。属于Spring提供的增强所以属于方法级增强。
1.3 环绕赠强
MethodInterceptor是前置增强和后置增强的综合,在目标方法执行前后都织入增强。可以用该增强替换前置增强和后置增强
1.4 异常抛出增强
ThrowsAdvice表示在目标方法抛出异常时实施增强
1.5 引介增强
IntroductionInterceptor在目标类中添加属性和行为
2 ProxyFactory简介
通过编码的方式实现增强时需要使用ProxyFactory,ProxyFactory底层采用JDK或者CGLib动态代理将对应的增强织入到目标方法或者类中。
AopProxy提供了两个final的实现类
1.Cglib2AopProxy:针对类的代理
2.JdkDynamicAopProxy:针对接口的代理
如果ProxyFactory#setInterfaces(Class[])指定了需要代理的接口,那么ProxyFactory使用JdkDynamicAopProxy,否则使用Cglib2AopProxy.可以通过ProxyFactory#setOptimize(true)让ProxyFactory启动优化代理方式,这样使用接口的代理也可以使用Cglib2AopProxy。通过ProxyFactory#addAdvice(Advice)或者ProxyFactory#addAdvice(int index,Advice)向目标对象中添加增强,其中index为增强的顺序,顺序的序列号从0开始。按照添加顺序执行或者index指定的顺序执行。index值越小对应的增强优先执行
3 实现前置增强
具体的思路如下:
1.定义接口
2.目标类实现接口
3.实现MethodBeforeAdvice接口,重写before方法.此处可以通过过滤的方式实现只对特定类的特定方法织入增强
4.创建ProxyFactory
5.设置ProxyFactory的目标对象,为ProxyFactory添加增强,通过ProxyFactory获得代理实例
6.采用代理对象调用接口方法
public interface Dog {
public void eat();
}
public class SiberianHusky implements Dog {
@Override
public void eat() {
System.out.println("哈士奇 eat");
}
}
public class SiberianHuskyAdvice implements MethodBeforeAdvice {
/**
*
* @param method 目标对象方法
* @param args 目标对象方法参数
* @param target 目标对象
* @throws Throwable
*/
//此处可以通过过滤的方式实现只对特定类的特定方法织入增强,当增强方法发生异常时,将阻止目标方法的执行
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + " " + method.getName() + " 之前摇尾巴");
}
}
@Test
public void testEatBeforeAdvice() {
ProxyFactory proxyFactory = new ProxyFactory();
//创建目标对象
Dog target = new SiberianHusky();
//创建增强对象
Advice advice = new SiberianHuskyAdvice();
//设置目标对象
proxyFactory.setTarget(target);
//设置增强
proxyFactory.addAdvice(advice);
//获得目标代理对象
Dog proxy = (Dog) proxyFactory.getProxy();
//方法调用
proxy.eat();
}
输出
com.before.advice.SiberianHusky eat 之前摇尾巴
哈士奇 eat
以上是采用编码的方式进行测试,下面是采用配置的方式进行测试
<!--定义目标对象-->
<bean id="target" class="com.before.advice.SiberianHusky"></bean>
<!--定义增强对象-->
<bean id="beforeAdvice" class="com.before.advice.SiberianHuskyAdvice"></bean>
<!--配置ProxyFactory-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--需要织入目标对象的增强,此处采用的是BeanName,多个增强使用逗号分割,增强执行的顺序和添加的顺序一致-->
<property name="interceptorNames" value="beforeAdvice"></property>
<!--代理需要实现的接口,多个接口采用逗号分割-->
<property name="proxyInterfaces" value="com.before.advice.Dog"></property>
<!--代理的目标对象-->
<property name="target" ref="target"></property>
<!--optimize属性为true时强制使用CGLib,单例时推荐使用CGLib,
其他情况建议采用JDK动态代理,CGLib创建代理时速度比较慢
但是创建的代理对象运行效率高,JDK刚好相反.该配置为可选配置-->
<!--<property name="optimize" value="true"></property>-->
<!--proxyTargetClass表示是否对类代理而不是接口,当设置为true时使用CGLib,该配置为可选配置-->
<!--<property name="proxyTargetClass" [value | ref]=""></property>-->
</bean>
BeanFactory beanFactory = new ClassPathXmlApplicationContext("com/advice/config/config.xml");
Dog dog = (Dog) beanFactory.getBean("proxy");
dog.eat();
4 实现后置增强
实现后置增强的思路和实现前置增强的思路基本一致。唯一的不同是需要实现AfterReturningAdvice接口并重写afterReturning(....)
public class SiberianHuskyEatAfterAdvice implements AfterReturningAdvice {
/**
*
* @param returnObj 目标方法返回值
* @param method 目标对象方法
* @param args 目标对象方法参数
* @param target 目标对象
* @throws Throwable
*/
//此处可以通过过滤的方式实现只对特定类的特定方法织入增强,
//增强方法抛出异常,如果异常为目标方法声明的异常则将异常归并的目标方法中
//否则Spring将异常转化为运行时异常抛出
@Override
public void afterReturning(Object returnObj, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + " " + method.getName() + " 之后汪汪汪");
}
}
采用code调用
Advice afterAdvice = new SiberianHuskyEatAfterAdvice();
proxyFactory.addAdvice(afterAdvice);
采用配置以及调用该增强
<bean id="afterAdvice" class="com.after.advice.SiberianHuskyEatAfterAdvice"></bean>
<!--配置ProxyFactory-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--需要织入目标对象的增强,此处采用的是BeanName,多个增强使用逗号分割,增强执行的顺序和添加的顺序一致-->
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterAdvice</value>
</list>
</property>
<!--代理需要实现的接口,多个接口采用逗号分割-->
<property name="proxyInterfaces" value="com.before.advice.Dog"></property>
<!--代理的目标对象-->
<property name="target" ref="target"></property>
<!--optimize属性为true时强制使用CGLib,单例时推荐使用CGLib,
其他情况建议采用JDK动态代理,CGLib创建代理时速度比较慢
但是创建的代理对象运行效率高,JDK刚好相反.该配置为可选配置-->
<!--<property name="optimize" value="true"></property>-->
<!--proxyTargetClass表示是否对类代理而不是接口,当设置为true时使用CGLib,该配置为可选配置-->
<!--<property name="proxyTargetClass" [value | ref]=""></property>-->
</bean>
BeanFactory beanFactory = new ClassPathXmlApplicationContext("com/advice/config/config.xml");
Dog dog = (Dog) beanFactory.getBean("proxy");
dog.eat();
5 实现环绕增强
和实现前置增强的方式思路一致。唯一不同的是需要实现MethodInterceptor接口,重写invoke(MethodInvocation invocation)方法。MethodInvocation封装了目标方法的入参可以通过getArguments()获得目标方法的入参数组。通过procced()方法反射调用目标对象的方法。
public class SiberianHuskyARoundAdvice implements MethodInterceptor {
//同样可以通过过滤来筛选类和方法
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//获得目标方法参数列表
methodInvocation.getArguments();
System.out.println(methodInvocation.getMethod().getClass().getName() + " " + methodInvocation.getMethod().getName()+ " 之前摇尾巴");
//调用目标方法
methodInvocation.proceed();
System.out.println(methodInvocation.getMethod().getClass().getName() + " " + methodInvocation.getMethod().getName()+ " 之后汪汪汪");
//返回目标方法返回值
return methodInvocation.getMethod().getReturnType();
}
采用code调用
Advice roundAdvice = new SiberianHuskyARoundAdvice();
proxyFactory.addAdvice(roundAdvice);
采用配置以及调用该增强
<bean id="roundAdvice" class="com.around.advice.SiberianHuskyARoundAdvice"></bean>
<!--配置ProxyFactory-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--需要织入目标对象的增强,此处采用的是BeanName,多个增强使用逗号分割,增强执行的顺序和添加的顺序一致-->
<property name="interceptorNames">
<list>
<!--<value>beforeAdvice</value>
<value>afterAdvice</value>-->
<value>roundAdvice</value>
</list>
</property>
<!--代理需要实现的接口,多个接口采用逗号分割-->
<property name="proxyInterfaces" value="com.before.advice.Dog"></property>
<!--代理的目标对象-->
<property name="target" ref="target"></property>
<!--optimize属性为true时强制使用CGLib,单例时推荐使用CGLib,
其他情况建议采用JDK动态代理,CGLib创建代理时速度比较慢
但是创建的代理对象运行效率高,JDK刚好相反.该配置为可选配置-->
<!--<property name="optimize" value="true"></property>-->
<!--proxyTargetClass表示是否对类代理而不是接口,当设置为true时使用CGLib,该配置为可选配置-->
<!--<property name="proxyTargetClass" [value | ref]=""></property>-->
</bean>
BeanFactory beanFactory = new ClassPathXmlApplicationContext("com/advice/config/config.xml");
Dog dog = (Dog) beanFactory.getBean("proxy");
dog.eat();
6 异常抛出增强
实现该增强的具体思路如下
1.通过实现ThrowsAdvice接口定义增强
2.实现具体的逻辑,在业务需要的情况下抛出异常
3.创建ProxyFactory
4.设置ProxyFactory的目标对象,为ProxyFactory添加增强,此时是为类实现增强所以需要使用CGLib生成代理类因此需要调用setProxyTargetClass(true)。通过ProxyFactory获得代理实例
5.采用代理对象调用接口方法
实现ThrowsAdvice接口,该接口属于标签接口,该接口中不含有任何的接口方法。需要自定义如下的方法
public void afterThrowing([Method method,Object[] args, Object target,] Throwable exp) {}
该方法必须符合如下要求
1.前三个参数为可选参数,这三个参数要么同时提供,要么同时不提供。
2.方法名必须是afterThrowing
3.最后一个参数必须是Throwable或者其子类
从类的继承树上来看,两个类的距离越近,两个类就越相似。因此在目标方法抛出对应异常时优先选择和抛出异常相似度高的afterThrowing方法.
public class Errors {
public void throwIndexError() throws ArrayIndexOutOfBoundsException {
throw new ArrayIndexOutOfBoundsException("数组越界异常");
}
}
public class ErrorAdvice implements ThrowsAdvice {
public void afterThrowing(Method method,Object[] args, Object target, NullPointerException exp) {
System.out.println(method.getName() + " " + exp.getMessage());
}
public void afterThrowing(Throwable exp) {
System.out.println(exp.getMessage());
}
public void afterThrowing(ArrayIndexOutOfBoundsException exp) {
System.out.println(exp.getMessage());
}
}
采用code调用
ProxyFactory proxyFactory = new ProxyFactory();
//创建目标对象
Errors target = new Errors();
//创建增强对象
Advice errorAdvice = new ErrorAdvice();
//设置目标对象
proxyFactory.setTarget(target);
//设置增强
proxyFactory.addAdvice(errorAdvice);
proxyFactory.setProxyTargetClass(true);
//获得目标代理对象
Errors proxy = (Errors) proxyFactory.getProxy();
//方法调用
try {
proxy.throwIndexError();
} catch (Exception e) {}
采用配置以及调用该增强
配置1
<!--增强类-->
<bean id="errorAdvice" class="com.throwed.advice.ErrorAdvice"></bean>
<!--具体业务逻辑类-->
<bean id="errorTarget" class="com.throwed.advice.Errors"></bean>
<!--ProxyFactoey-->
<bean id="errorProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames" value="errorAdvice"></property>
<property name="targetName" value="errorTarget"></property>
<property name="proxyTargetClass" value="true"></property>
</bean>
配置2
<!--增强类-->
<bean id="errorAdvice" class="com.throwed.advice.ErrorAdvice"></bean>
<!--具体业务逻辑类-->
<bean id="errorTarget" class="com.throwed.advice.Errors"></bean>
<!--ProxyFactoey-->
<bean id="errorProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyTargetClass="true"
p:target-ref="errorTarget"
p:interceptorNames="errorAdvice"/>
调用
Errors error = (Errors) beanFactory.getBean("errorProxy");
error.throwNullPointError();
7 实现引介增强
该种增强不是在方法周围织入增强,而是为目标类添加新的属性或者行为。所以该种增强属于类级别。因此通过该种增强可以为目标类添加接口实现并为目标类创建实现接口的代理。引介增强的接口为IntroductionIntercepter,属于标签接口。该接口中没有定义任何的方法。可以通过扩展DelegatingIntroductionIntercepter实现自己的增强。
实现引介增强的具体思路如下
1.定义接口,该接口将用来给目标类添加属性或行为
2.编写引介增强类,扩展DelegatingIntroductionIntercepter并实现接口在。重写DelegatingIntroductionIntercepter#invoke(...)方法以及实现接口方法
3.创建ProxyFactory
4.设置ProxyFactory的目标对象,为ProxyFactory添加增强,通过ProxyFactory获得代理实例
5.采用代理对象调用目标对象和接口方法
public interface Monitorable {
public void setActive(boolean isActive);
}
//目标类
public class UserDao {
public void insert(){
System.out.println("插入数据");
}
}
public class DaoIntroductionAdvice extends DelegatingIntroductionInterceptor implements Monitorable {
private ThreadLocal<Boolean> isActive = new ThreadLocal<Boolean>();
@Override
public void setActive(boolean isActive) {
this.isActive.set(isActive);
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object returnObj = null;
if(this.isActive.get() != null && this.isActive.get()) {
System.out.println("输出调用方法:" + mi.getMethod().getName());
returnObj = super.invoke(mi);//调用目标方法
} else {
returnObj = super.invoke(mi);
}
return returnObj;
}
}
ProxyFactory proxyFactory = new ProxyFactory();
//创建目标对象
UserDao target = new UserDao();
//创建增强对象
Advice advice = new DaoIntroductionAdvice();
//设置目标对象
proxyFactory.setTarget(target);
//设置增强
proxyFactory.addAdvice(advice);
proxyFactory.setProxyTargetClass(true);
//获得目标代理对象
UserDao proxy = (UserDao) proxyFactory.getProxy();
((Monitorable) proxy).setActive(true);
xml配置
<!--目标对象-->
<bean id="userDaoTarget" class="com.introduction.advice.UserDao"></bean>
<!--引介增强实现类-->
<bean id="introductionAdvice" class="com.introduction.advice.DaoIntroductionAdvice"></bean>
<!--ProxyFactory-->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--采用生成子类方式实施增强,底层采用的CGLib,所以此处设置为ture-->
<property name="proxyTargetClass" value="true"></property>
<property name="targetName" value="userDaoTarget"></property>
<!--配置引介增强-->
<property name="interceptorNames" value="introductionAdvice"></property>
<!--实现的接口-->
<property name="interfaces" value="com.introduction.advice.Monitorable"></property>
</bean>
UserDao dao = (UserDao) beanFactory.getBean("userDaoProxy");
((Monitorable) dao).setActive(true);
dao.insert();