Spring学习笔记(七、Spring AOP API)
2017-06-19 本文已影响971人
鲁克巴克诗
上一篇:Spring学习笔记(六、Spring AOP基本概念)
一、Spring AOP API
- 这是Spring 1.2历史用法,现在(V4.0)仍然支持。
- 这是Spring AOP 基础,不得不了解。
- 现在的用法也是基于历史的,只是更简便了。
1. Pointcut
- 实现之一:NameMatchMethodPointcut,根据方法名字进行匹配。
- 成员变量:mappedNames,匹配的方法名集合。
创建BizLogic接口:
package test16;
/**
* Created by amber on 2017/6/19.
*/
public interface BizLogic {
String save();
}
创建实现类BizLogincImpl :
package test16;
/**
* Created by amber on 2017/6/19.
*/
public class BizLogincImpl implements BizLogic {
public String save() {
System.out.println("BizLogincImpl:save");
return "BizLogincImpl:save";
}
}
applicationContext:
<bean id="bizLogincTarget" class="test16.BizLogincImpl"></bean>
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>sa*</value>
</list>
</property>
</bean>
2. Before advice
- 一个简单的通知类型
- 只是在进入方法之前被调用,不需要MethodInvocation对象
- 前置通知可以在连接点执行之前插入自定义行为,但不能改变返回值。
创建前置通知类BeforeAdvice :
package test16;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* Created by amber on 2017/6/19.
*/
public class BeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("我是前置通知,拦截的方法名:" + method.getName() + " 目标类名:" + target.getClass().getName());
}
}
3. Throws advice
- 如果连接点抛出异常,throws advice 在连接点返回后被调用。
- 如果throws-advice的方法抛出异常,那么它将覆盖原有异常
- 接口org.springframework.aop.ThrowsAdvice 不包含任何方法,仅仅是一个声明,实现类需要实现类似下面的方法:
void afterThrowing([Method,args,target],ThrowableSubclass);
如:
public void afterThrowing(Exception ex)
public void afterThrowing(RemoteException ex)
public void afterThrowing(Method method,Object[] args,Object target,Exception ex)
public void afterThrowing(Method method,Object[] args,Object target,ServletException ex)
创建MyThrowsAdvice类:
package test16;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
/**
* Created by amber on 2017/6/19.
*/
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception ex) {
System.out.println("抛出异常通知: afterThrowing 1");
}
public void afterThrowing(Method method, Object[] objects, Object target, Exception ex) {
System.out.println("抛出异常通知: afterThrowing 2,出现异常的方法名:" + method.getName() + " 出现异常的类名:" + target.getClass().getName());
}
}
4. After Returning advice
- 后置通知必须实现org.springframework.aop.AfterReturningAdive接口
- 可以访问返回值(但不能进行修改),被调用的方法,方法的参数和目标。
- 如果抛出异常,将会抛出拦截器链,替代返回值。
创建AfterReturningAdvice 类:
package test16;
import java.lang.reflect.Method;
/**
* Created by amber on 2017/6/19.
*/
public class AfterReturningAdvice implements org.springframework.aop.AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("我是返回后通知, 拦截的方法名:"+method.getName()+" 目标类名:"+target.getClass().getName()+" 返回的值:"+returnValue );
}
}
5. Interception around advice
- Spring的切入点模型使得切入点可以独立与advice重用,以针对不同的advice可以使用相同的切入点。
创建MethodInterception 类:
package test16;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* Created by amber on 2017/6/19.
*/
public class MethodInterception implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("MethodInterceptor(类似环绕通知) invoke 1,拦截的方法: "+invocation.getMethod().getName()+" 拦截的目标类名:"+invocation.getStaticPart().getClass().getName());
Object obj=invocation.proceed();
System.out.println("MethodInterceptor(类似环绕通知) invoke 2,拦截的目标: "+obj);
return obj;
}
}
6. Introduction advice
- 引用通知概念:一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。
- Spring 把引入通知作为一种特殊的拦截通知
- 需要IntroductionAdvisor和IntroductionInterceptor
- 仅适用于类,不能和任何切入点一起使用
- 一个Spring test suite例子
- 如果调用lock()方法,希望所有的setter方法抛出LockedException异常(如使物体不变,Spring典型例子)
- 需要一个完成繁重任务的IntroductionInterceptor,这种情况下,可以使用org.springframework.aop.support.DelegatingIntroducationInterceptor。
有时间再做实验!!!
7. Advisor API in Spring
- Advisor是仅包含一个切入点表达式关联的单个通知的方面。
- 除了introductions,advisor可以用于任何通知。
- org.springframework.aop.support.DefaultPointcutAdvisor是最常用的类,它可以与MethodInterceptor,BeforeAdvice或者ThrowsAdvice一起使用。
- 它可以混合在Spring同一个AOP代理的advisor和advice。
二、ProxyFactoryBean
- 创建Spring AOP代理的基本方法是使用org.apringframework.aop.framework.ProxyFactoryBean。
- 这个类可以完全控制切入点和通知(advice)以及它们的顺序。
- 使用ProxyFactoryBean或者其他IoC相关类,来创建AOP代理的最重要好处是:通知和切入点也可以由IoC管理。
- 被代理类没有实现任何接口,使用CGLIB代理,否则JDK代理。
- 通过设置proxyTargetClass为true,可强制使用CGLIB
- 如果目标类实现了一个(或者多个)接口,那么创建代理的类型将依赖ProxyFactoryBean 的配置。
- 如果ProxyFactoryBean的proxyInterfaces属性被设置为一个或者多个全限定接口名,基于JDK的代理将被创建。
- 如果ProxyFactoryBean的proxyInterfaces属性没有被设置,但是目标类实现了一个(或者更多)接口,那么ProxyFactoryBean将自动检测到这个目标类已经实现了至少一个接口,创建一个基于JDK的代理。
关于CGLIB动态代理,参考:CGLib动态代理原理及实现
关于JDK动态代理,参考:JDK动态代理实现原理
1. Proxying interfaces
- 使用PointcutBean,不指定proxyInterfaces:
- applicationContext中:
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="bizLogincTarget" class="test16.BizLogincImpl"></bean>
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>sa*</value>
</list>
</property>
</bean>
<bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="beforeAdvice"/>
<property name="pointcut" ref="pointcutBean"/>
</bean>
<bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="bizLogincTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>defaultAdvisor</value>
<value>afterReturningAdvice</value>
<value>methodInterception</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
- 测试类:
@Test
public void test16() {
BizLogic logic=super.getBean("bizLogicImpl");
logic.save();
}
- 结果:
- 不使用PointcutBean,指定proxyInterfaces:
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
<bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>test16.BizLogic</value>
</property>
<property name="target">
<ref bean="bizLogicTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterReturningAdvice</value>
<value>methodInterception</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
结果:
Paste_Image.png- 可以使用匿名内部bean来隐藏目标和代理之前的区别
<property name="target">
<ref bean="bizLogicTarget"/>
</property>
引用beanId替换成直接引用路径。
<property name="target">
<bean class="test16.BizLogincImpl"/>
</property>
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
<bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>test16.BizLogic</value>
</property>
<property name="target">
<bean class="test16.BizLogincImpl"/>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterReturningAdvice</value>
<value>methodInterception</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
结果:
Paste_Image.png三、Proxing classes
- 前面的例子如果没有接口,这种情况下Spring会使用CGLIB代理,而不是JDK动态代理。
- 如果想,可以强制在任何情况下使用CGLIB,即使有接口。
- CGLIB代理的工作原理是在运行时生成目标类的子类,Spring配置这个生成的子类委托方法调用到原来的目标。
- 子类是用来实现Decorator模式,织入通知。
1. CGLIB的代理对用户来说是透明的,需要注意:
- final方法不能被通知,因为他们不能覆盖。
- 不用把CGLIB添加到classpath中,在Spring3.2中,CGLIB被重新包装并包含在Spring核心的JAR(即基于CGLIB的AOP就像JDK动态代理一样“开箱即用”)
2. 使用global advisors
- 用*做通配,匹配所有拦截器加入通知链。
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
<bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>test16.BizLogic</value>
</property>
<property name="target">
<bean class="test16.BizLogincImpl"/>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterReturningAdvice</value>
<value>method*</value>//只适用于实现了MethodInterceptor接口的拦截器,其他advice不适用哦!!!
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
3. 简化的Proxy定义
- 使用父子bean定义,以及内部bean定义,可能会带来更清洁更更简洁的代理定义(抽象属性标记父bean定义为抽象的这样它不能被实例化)
修改applicationContext:
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="baseProxyBean" class="org.springframework.aop.framework.ProxyFactoryBean" lazy-init="true" abstract="true"/>
<bean id="bizLogicImpl" parent="baseProxyBean">//将baseProxyBean作为父类
<property name="target">
<bean class="test16.BizLogincImpl"/>//直接引用实现类
</property>
<property name="proxyInterfaces">
<value>test16.BizLogic</value>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterReturningAdvice</value>
<value>methodInterception</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
结果:
Paste_Image.png
4. 使用ProxyFactory
- 使用Spring AOP而不依赖于Spring IoC。
- 大多数情况下,最佳实践是用IoC容器创建AOP代理。
-
虽然可以硬编码方式实现,但是Spring推荐使用配置或注解方式实现。
Paste_Image.png
5. 使用auto-proxy
- Spring也允许使用“自动代理”的bean定义,它可以自动代理选定的bean,这是建立在Spring的“bean post processer”功能基础之上的(在加载bean的时候可以修改)。
- BeanNameAutoProxyCreator。
- DefaultAdvisorAutoProxyCreator,当前IoC容器中自动应用,不用显示声明引用advisor的bean定义。