深入浅出 spring AOP (一) 源出处
先不讨论AOP的各种名词,也不作各种AOP的比较,我将在例子中介绍各种名词。
1。先写一个javabean,就是target object。
package org.tatan.test;
public class MessageBean {
public void write() {
System.out.print("AOP example");
}
2。写一个AOP的advice类MethodInterceptor是AOP联盟的标准接口,它是最简单最实用的连接点(joinpoint),实现了around advice ,你可以在他返回前调用target的方法。
package org.tatan.test;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MessageCode implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.print("this is a ");
Object returnValue = invocation.proceed();
return returnValue ;
}
3。把MessageCode advice weave 到proxy factory,proxy factory是整个架构的核心
先创建instance of MessageBean,然后创建代理的instance ,MessageCode advice 传递给的
addAdvice()方法,设置Target Object,调用getProxy()产生代理对象。
import org.springframework.aop.framework.ProxyFactory;
public class AOPExample {
public static void main(String[] args) {
MessageBean target = new MessageBean();
ProxyFactory pf = new ProxyFactory();
pf.addAdvice(new essageCode());
MessageBean proxy = (MessageBean) pf.getProxy();
//输出原始信息
target.write();
proxy.write();
4。classpath中加入cglib-nodep-2.1_2.jar ,spring.jar,aopalliance.jar,commons-logging.jar
结果
AOP example
this is a AOP example
有人问我,为什末使用CGLIB proxy而不使用JDK Dynamic Proxies,这和spring aop使用的原则相关。
package org.tatan.test;
public interface Worker {
void doSomeWork(int numOfTimes);
}
目标类
package org.tatan.test;
public void doSomeWork(int numOfTimes) {
for (int i = 0; i < numOfTimes; i++) {
}
}
Advice执行流程
package org.tatan.test;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class AroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
Object returnValue = invocation.proceed();
Method m = invocation.getMethod();
Object target = invocation.getThis();
Object[] args = invocation.getArguments();
System.out.println("Executed method: " + m.getName());
System.out.println("On object of type: " + target.getClass().getName());
System.out.println("With arguments:");
for (int i=0;i<args.length;i++) {
System.out.println("---->" + args[i]);
}
System.out.println();
return returnValue;
}
package org.tatan.test;
import org.springframework.aop.framework.ProxyFactory;
public class AOPExample2 {
public static void main(String[] args) {
Worker bean = getWorkerBean();
bean.doSomeWork(100000000);
}
private static Worker getWorkerBean() {
WorkerBean target = new WorkerBean();
ProxyFactory pf = new ProxyFactory();
pf.setTarget(target);
pf.addAdvice(new AroundAdvice());
pf.setInterfaces(new Class[]{Worker.class});
return (Worker) pf.getProxy();
}
}
如果调用了setInterfaces();就不要cglib了,使用JDK Dynamic Proxies,只是使用JDK Dynamic Proxies程序执行的效率比较低。
使用CGLIB的Frozen效率比标准的CGLIB效率高。
package org.tatan.test;
import org.springframework.aop.framework.ProxyFactory;
public class AOPExample2 {
public static void main(String[] args) {
Worker bean = getWorkerBean();
bean.doSomeWork(100000000);
}
private static Worker getWorkerBean() {
WorkerBean target = new WorkerBean();
ProxyFactory pf = new ProxyFactory();
pf.setTarget(target);
pf.addAdvice(new AroundAdvice());
// pf.setInterfaces(new Class[]{Worker.class});
pf.setFrozen(true);
return (Worker) pf.getProxy();
}
原则上使用CGLIB,因为既可以使用类,还可以使用接口,JDK proxy 只能代理口。
2.目标类的方法不能是final的,因为spring要生成目标类的子类,任何要advised的方法都要overide,所以不允许final method。
spring AOP使用,使用CGLIB应该使用接口不是类,这点务必注意。
使用BeanNameAutoProxyCreator声明事务,
<!-- define transaction interceptor -->
<bean id="txInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager"><ref bean="transactionManager"/></property>
<property name="transactionAttributeSource"><ref bean="txAttributes"/></property>
<!-- Define transactional methods (NameMatchTransactionAttributeSource
applies specific attributes to methods that match to a pattern) -->
<bean id="txAttributes" class="org.springframework.transaction.interceptor.
NameMatchTransactionAttributeSource">
<property name="properties">
<!-- 使用Autoproxy定义事务的beans,应用于Controllers -->
<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames"><value>txInterceptor</value></property>
<property name="beanNames"><value>*Controller</value></property>
<!-- 事务管理的beans -->
<bean id="userManager" class="com.tatan.domain.user.UserManager" singleton="true" dependency-check="objects">
<constructor-arg index="0"><ref bean="userController"/></constructor-arg>
spring文档说明“when BeanNameAutoProxyCreator postprocesses the target object and create the proxy, it causes the proxy to be inserted into the Application context under the name of the original bean”,
UserController 应该是个接口,而不是类,默认情况下,spring使用dynamic proxies,它只使用接口。如果使用CGLIB,最好proxyTargetClass设为true。
使用TransactionProxyFactoryBean也是如此
<bean id="MimeTarget" class="com。tatan.task.MimeTarget">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
<property name="MimeDao"><ref bean="MimeDao"/></property>
<bean id="MimeTargetProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager"/></property>
<property name="target"><ref local="MimeTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
</props>
</property>
<bean id="MimeTrigger" class="org.springframework.scheduling.timer.ScheduledTimerTask">
<property name="timerTask"><ref bean="MimeTargetProxyFactoryBean"/></property>
<property name="delay"><value>1000</value></property>
<property name="period"><value>500000</value></property>
这样会抛出异常,exception org.springframework.beans.TypeMismatchException: Failed to convert property value of type [$Proxy0] to required type [java.util.TimerTask] for property 'timerTask'; nested exception is java.lang.IllegalArgumentException: argument type mismatch
TimerTask是个类,只能用CGLIB产生代理,TransactionProxyFactoryBean 默认使用接口来产生target object,将proxyTargetClass = true即可使用CGLIB proxy代理。
spring的Advice分为5种,Before,After returning,Around,Throws,Introduction。使用这些Advice可以完成AOP相关部分90%的编码,剩余的10%只好依靠AspectJ了。在大多数情况下,around advice可以完成Before,After returning,Throws的所有功能。Before advice是比较有用的advice,它可以修改传递给method的参数,可以通过异常中断method的执行,通常用于检测用户的权限。Servlet过滤器是Before advice的一种方式,提供了在servlet调用前执行其他处理的能力。
package org.tatan.test;
public interface Worker {
void doSomeWork(int numOfTimes);
package org.tatan.test;
public class WorkerBean implements Worker {
public void doSomeWork(int numOfTimes) {
for (int i = 0; i < numOfTimes; i++) {
System.out.print(i);
}
}
package org.tatan.test;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class SimpleBeforeAdvice implements MethodBeforeAdvice {
public static void main(String[] args) {
Resource res = new ClassPathResource("org/tatan/test/bean2.xml");
AbstractBeanFactory ft = new XmlBeanFactory(res);
//Instantiate an object
Worker testObject = (Worker) ft.getBean("businesslogicbean");
testObject.doSomeWork(100);
public void before(Method method, Object[] args, Object target)
System.out.println("Before method: " + method.getName());
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<bean id="businesslogicbean"
<property name="proxyTargetClass">
<property name="interceptorNames">
<bean id="BeforeAdvice" class="org.tatan.test.SimpleBeforeAdvice" />
class="org.springframework.aop.interceptor.TraceInterceptor" />
<bean id="beanTarget" class="org.tatan.test.WorkerBean" />
Spring使用TraceInterceptor,你可以把她添加到你的代理bean中当作一个拦截器。TransactionProxyFactoryBean有"preInterceptors"和 "postInterceptors"属性,ProxyFactoryBean只有"interceptorNames"属性,这和springlive第九章有点出入,log4j配置文件,详见我的blog(http://blogger.org.cn/blog/more.asp?name=hongrui&id=7968#11881)
log4j.properties
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
<property name="password">
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" >
<value>com.tatan.util.XxxUtil</value>
<property name="targetMethod">
</property>
<property name="arguments">
<value>saflj</value>
return new String("Iamstupid");
深入浅出 spring AOP (六)
前面的几个例子都是拦截所有类的所有方法,但是我们主要是拦截某些类的某些方法,使用Pointcut可以做到。pointcut是一系Joinpoint的集合,它定义了需要注入advice的位置。AOP框架必须允许开发者指定切入点,advisor是pointcut和advice的装配器,是将advice织入预定义位置的代码中。
ClassFilter getClassFilter ();
Pointcut interface只有两个方法,返回ClassFilter and MethodMatcher的实例。ClassFilter接口被用来将切入点限制到一个给定的目标类的集合。 如果matches()永远返回true,所有的目标类都将被匹配。
matches(Method, Class) 方法被用来测试这个切入点是否匹 配目标类的给定方法。这个测试可以在AOP代理创建的时候执行,避免在所有方法调用时都需要进行 测试。如果2个参数的匹配方法对某个方法返回true,并且MethodMatcher的 isRuntime()也返回true,那么3个参数的匹配方法将在每次方法调用的时候被调用。这使切入点能够在目标通知被执行之前立即查看传递给方法调用的参数。
大部分MethodMatcher都是静态的,意味着isRuntime()方法 返回false。这种情况下3个参数的匹配方法永远不会被调用。
如果可能,尽量使用静态切入点。spring提供Pointcut interface的7种实现,详见它的文档。
在使用Pointcut之前,必须先创建Advisor或指定PointcutAdvisor。一个advisor就是一个aspect的完整的模块化表示,一个advisor应该包括通知和切入点。Spring中很多内建的切入点都有对应的PointcutAdvisor,DefaultPointcutAdvisor 是最通用的advisor类,它可以和ethodInterceptor、 BeforeAdvice或者ThrowsAdvice一起使用。在应用较小时,只有很少类需要被切入时,ProxyFactoryBean 可以使用。当有许多类需要被切入时,为每个代理创建ProxyFactoryBean就显得很繁琐。可以通过容器来创建代理。Spring提供了两个类实现自动代理:BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator。BeanNameAutoProxyCreator为匹配名字的Bean产生代理,它可以使用在将一个或者多个aspect应用在命名相似的Bean中。
public Object invoke(MethodInvocation invocation) throws Throwable {
Object returnValue = invocation.proceed();
Method m = invocation.getMethod();
Object target = invocation.getThis();
Object[] args = invocation.getArguments();
System.out.println("Executed method: " + m.getName());
System.out.println("On object of type: " + target.getClass().getName());
System.out.println("---->" + args[i]);
<bean id="myAdvice" class="com.tatan.MyAdvice"/>
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice">
<ref local="myAdvice"/>
<property name="mappedName">
<value>getConnection</value>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
</list>
<property name="beanNames">
<list>
<value>dataSource</value>
</list>