Spring AOP主要有3中使用方式,直接使用api,使用xml配置文件,和使用annotation简化xml配置文件
这里通过一个例子说明了这几种不同的用法,这个例子的需求是:方便的监控某个方法的运行时间。
如果用纯OO的方法,可能你不得不把代码加到每一个需要监控的方法中,或使用某种设计模式解决这个问题,无论如何,都改动了原有的代码。
对此,AOP显然是一个好的解决方案。
被检测的对象
package aoptest;
public class WorkerBean {
public void doSomeWork1(int noOfTimes) {
for (int x = 0; x < noOfTimes; x++) {
work();
}
}
public void doSomeWork2(int noOfTimes) {
for (int x = 0; x < noOfTimes; x++) {
work();
}
}
private void work() {
System.out.print("");
}
}
首先我们定义了一个被检测类,它有两个方法doSomeWork1, doSomeWork2, 我们要监控这两个方法需要运行多长时间。无论你采用哪种AOP的使用方式,都不会改变这个类的代码。
1 使用API
1.1 定义一个advice类
package aoptest.code;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.StopWatch;
public class ProfilingAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// start the stop watch
StopWatch sw = new StopWatch();
sw.start(invocation.getMethod().getName());
Object returnValue = invocation.proceed();
sw.stop();
dumpInfo(invocation, sw.getTotalTimeMillis());
return returnValue;
}
private void dumpInfo(MethodInvocation invocation, long ms) {
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 x = 0; x < args.length; x++) {
System.out.print(" > " + args[x]);
}
System.out.print("/n");
System.out.println("Took: " + ms + " ms");
}
}
这是一个为Around(环绕)类型的advice,它使用StopWatch记录被AOP的方法的执行时间,然后在console中打印出相关信息。
1.2 测试
package aoptest.code;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import aoptest.WorkerBean;
public class ProfilingExample {
public static void main(String[] args) {
WorkerBean bean = getWorkerBean();
bean.doSomeWork1(10000000);
bean.doSomeWork2(10000000);
}
private static WorkerBean getWorkerBean() {
// 1. create a Pointcut
JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut();
pc.setPattern(".*doSomeWork.*");
//AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
//pc.setExpression("execution(* aoptest.WorkerBean.doSomeWork*(..))");
// 2. create a Advice
ProfilingAdvice advice = new ProfilingAdvice();
// 3. create a Advisor(Aspect)
Advisor advisor = new DefaultPointcutAdvisor(pc, advice);
// 4. create a Target
WorkerBean target = new WorkerBean();
// 5. create a ProxyFactory to Weaving
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target);
factory.addAdvisor(advisor);
return (WorkerBean) factory.getProxy();
}
}
注释1 使用正则表达式定义了一个pointcut,所有包含doSomeWork的method
注释2 生成了一个advice的实例
注释3 生成了一个advisor, 其实就是aspect, 它就是一个pointcut和advice的组合
注释4 生成了一个target对象,就是要被aop的对象
注释5 通过proxyFactory把aspect编织到target中
可以感觉到Spring是使用动态代理的方式实现AOP的。
以上示例说明了Spring AOP Api的一般使用过程,当然Spring提供了丰富的api进行不同操作,但概念是一致的。例子中注掉的两行代码就是使用AspectJ表达式定义一个pointcut,AspectJ表达式的表达能力更强。
Spring提倡的是无侵入式的编程方式,所以在实际使用时,我们一般不会直接使用proxyFactory来生成被AOP的对象,我们会使用xml来定义它们。
2 使用xml schema
说白了,就是通过xml配置文件告诉spring如何进行这个weaving过程。
2.1 编写配置文件apotest/schema2/beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1. define the Target bean -->
<bean id="workerBean" class="aoptest.WorkerBean" />
<!-- 2. define the advice bean -->
<bean id="profilingAdvice" class="aoptest.code.ProfilingAdvice" />
<aop:config>
<!-- 3. define the pointcut -->
<aop:pointcut id="logBeanExecution"
expression="execution(* aoptest.WorkerBean.doSomeWork*(..))" />
<!-- 4. define the advisor(aspect) -->
<aop:advisor pointcut-ref="logBeanExecution" advice-ref="profilingAdvice" />
</aop:config>
</beans>
这里还是使用上个例子中实现的advice(aoptest.code.ProfilingAdvice)。
注释1 定义一个Target Bean, 就是我们要监控的类
注释2 定义一个advice Bean
注释3 定义一个pointcut, 这里使用aspectj表达式
注释4 定义一个advisor, 就是aspect啦,它组合了上面定义的pointcut和advice
2.2 测试
package aoptest.schema2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import aoptest.WorkerBean;
public class ProfilingExample {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("aoptest/schema2/beans.xml");
WorkerBean bean = (WorkerBean) ac.getBean("workerBean");
bean.doSomeWork1(10000000);
bean.doSomeWork2(10000000);
}
}
代码干净多了,Spring容器在生成workerBean时,会根据aspect定义来判断是否对这个bean进行AOP, 显然它的两个方法符合条件。
注意,这里使用的advice是上个例子中实现的advice(aoptest.code.ProfilingAdvice),它实现了MethodInterceptor这个接口,Spring AOP提供了另外一种定义advice的方式,它不用实现任何接口。
3 使用xml schema,无接口的advice
3.1 定义一个无接口的advice
package aoptest.schema1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class ProfilingAdvice {
public Object log(ProceedingJoinPoint pjp) throws Throwable {
// start the stop watch
StopWatch sw = new StopWatch();
sw.start(pjp.getSignature().getName());
Object returnValue = pjp.proceed();
sw.stop();
dumpInfo(pjp, sw.getTotalTimeMillis());
return returnValue;
}
private void dumpInfo(ProceedingJoinPoint pjp, long ms) {
Object[] args = pjp.getArgs();
System.out.println("Executed method: " + pjp.getSignature().getName());
System.out.println("On object of type: " + pjp.getTarget().getClass().getName());
System.out.println("With arguments:");
for (int x = 0; x < args.length; x++) {
System.out.print(" > " + args[x]);
}
System.out.print("/n");
System.out.println("Took: " + ms + " ms");
}
}
其中方法log有个参数,类型为ProceedingJoinPoint,我们可以通过这个参数得到target的信息。如果你不需要target的任何信息,可以不需要这个参数。由于这是一个环绕类型的advice,没有这个参数是不行的。
3.2 编写配置文件apotest/schema1/beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1. define the Target bean -->
<bean id="workerBean" class="aoptest.WorkerBean" />
<!-- 2. define the advice bean -->
<bean id="profilingAdvice" class="aoptest.schema1.ProfilingAdvice" />
<aop:config>
<!-- 3. define the aspect -->
<aop:aspect id="myAspect" ref="profilingAdvice">
<!-- 4. define the pointcut -->
<aop:pointcut id="logBeanExecution"
expression="execution(* aoptest.WorkerBean.doSomeWork*(..))" />
<!-- 5. define the advice type -->
<aop:around pointcut-ref="logBeanExecution" method="log" />
</aop:aspect>
</aop:config>
</beans>
注释1 定义Target
注释2 定义advice
注释3 定义aspect
注释4 定义这个aspect包含的pointcut
注释5 定义这个aspect包含的advice
3.3 测试
package aoptest.schema1;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import aoptest.WorkerBean;
public class ProfilingExample {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("aoptest/schema1/beans.xml");
WorkerBean bean = (WorkerBean) ac.getBean("workerBean");
bean.doSomeWork1(10000000);
bean.doSomeWork2(10000000);
}
}
4 使用annotation简化xml配置文件
4.1 定义一个无接口的advice
package aoptest.annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
// 1. @Aspect annotation
@Aspect
public class ProfilingAdvice {
// 2. @Pointcut annotation
@Pointcut("execution(* aoptest.WorkerBean.doSomeWork*(..))")
private void logBeanExecution() { }
// 3. @Around annotation
@Around("logBeanExecution()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
// start the stop watch
StopWatch sw = new StopWatch();
sw.start(pjp.getSignature().getName());
Object returnValue = pjp.proceed();
sw.stop();
dumpInfo(pjp, sw.getTotalTimeMillis());
return returnValue;
}
private void dumpInfo(ProceedingJoinPoint pjp, long ms) {
Object[] args = pjp.getArgs();
System.out.println("Executed method: " + pjp.getSignature().getName());
System.out.println("On object of type: " + pjp.getTarget().getClass().getName());
System.out.println("With arguments:");
for (int x = 0; x < args.length; x++) {
System.out.print(" > " + args[x]);
}
System.out.print("/n");
System.out.println("Took: " + ms + " ms");
}
}
这个advice的代码中只是比上一个例子中的advice多了一些annotation。
注释1 标注这是一个aspect
注释2 把logBeanExecution标注为一个pointcut的名字
注释3 把方法log标注为一个类型为Around的advice, 并使用名为logBeanExecution的pointcut
所以,严格的说这个advice加上这些标注后就不能称之为advice了,它就是一个aspect,这里仍然用这个类名是为了和上个例子做比较。
4.2 编写配置文件apotest/schema1/beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="workerBean" class="aoptest.WorkerBean" />
<bean class="aoptest.annotation.ProfilingAdvice"/>
<aop:aspectj-autoproxy />
</beans>
配置文件简单多了,你只需要定义这些bean即可,spring容器会自动发现哪些bean是aspect,并把它们编织到合适的其他bean中。
4.3 测试
package aoptest.annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import aoptest.WorkerBean;
public class ProfilingExample {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("aoptest/annotation/beans.xml");
WorkerBean bean = (WorkerBean) ac.getBean("workerBean");
bean.doSomeWork1(10000000);
bean.doSomeWork2(10000000);
}
}
5. 使用哪一种方式
如果你使用jdk1.4,那么就不能使用annotation了。
API的方式一般我们不会使用。但是如果我们需要动态性,就是说target是在程序运行时动态生成的,原因可能是状态是动态的,不能通过配置文件确定,这时我们可能只能使用API的方式了。
如果你使用的是java5以上版本,是否应该使用annotation呢?这是个使用spring的普遍存在的问题,值得讨论...