1. 面向切面编程(AOP)的概念
参见如下链接:
http://baike.baidu.com/view/1865230.htm
简单地说,AOP就是在程序的纵向流程上添加横向的切面逻辑,相当于给已有的业务逻辑增加额外的功能,而不改动原有的代码。
2. Spring中的AOP流程
完整的AOP要素包括:
切面(Aspect),逻辑(Advice),连接点(Joinpoint),切入点(Pointcut),指令(introduction)目标(Target),代理(Proxy),插入(Weaving)
但是在Spring框架中,AOP的部分要素已经被实现好了,只有如下的流程需要开发者来补充完整,就像IoC一样:
Advice →Pointcut →Proxy
3. Advice
Spring中提供了四种类型的Advice,分别是:前置、后置、拦截、异常。
实现这四种类型的Advice分别需要实现下列四个接口:
前置:MethodBeforeAdvice,目标方法调用前执行
后置:AfterReturningAdvice,目标方法调用后执行
拦截:MethodInterceptor,目标方法在调用前被拦截
异常:ThrowsAdvice,目标方法抛出异常时执行
上述Advice在配置xml时只需要指定bean的id和class即可,无特殊要求。
4. Pointcut
默认下,Pointcut是不需要设置的。上述四种Advice对它们所作用的目标对象的每个方法都会被调用。但实际应用中,可能不需要每个方法都调用AOP的逻辑,因此可以指定Pointcut来限制Advice作用的方法。
虽然Spring提供了完整接口可供开发者自定义Pointcut,但是也提供更为简便的内置类来简化开发。主要有如下几种:
1) NameMatchMethodPointcut
继承自NameMatchMethodPointcut类的切入点,可以在Advice执行之前自动匹配在切入点中设置好的方法名,目标对象中只有指定的方法才会调用Advice。
继承了NameMatchMethodPointcut的Pointcut类必须覆盖它的matches方法,并且在其中调用它的setMappedName(StringmethodName)来指定要拦截的方法名。methodName字符串还可使用*通配符。
实现后的Pointcut类必须通过DefaultPointcutAdvisor进行装配,以完成Advice的插入(Weaving)。详见示例。
2) RegexpMethodPointcutAdvisor
这种切入点使用Spring中内置的通过正则式来匹配方法名的切入点类,只需要在配置文件中装配RegexpMethodPointcutAdvisor,并在装配的过程在属性名为patterns(或pattern)的property中指定需要匹配的正则式即可。
3) ControlFlowPointcut
与上述两个类相比,ControlFlowPointcut(控制流切入点)的目标更为精细。它也是通过Spring中内置的控制流切入点类实现,并不需要编写额外的切入点类。
在配置的时候,首先需要配置ControlFlowPointcut,并向它的构造方法中传入两个参数,第一个(index=0)为Advice所用的类名,第二个(index=1)为Advice作用的方法名。
之后,再配置DefaultPointcutAdvisor实现Advice的插入。
5. Proxy
可以看出,在Spring中,AOP的要素是逐层插入(Weaving)的,即Advice插入Pointcut,而Pointcut也需要插入Proxy,以实现代理对目标类以及切面逻辑的共同调用。
在Spring,对Proxy的插入只需要配置ProxyFactoryBean即可,在其内部进一步配置Advice列表(list)和目标类(target),以及代理调用目标类的接口。其过程与Servlet中配置拦截器颇为相似。详见示例。
6. 示例
package aop.advice;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class MethodBeforeAdviceImpl implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println("=====前置开始=====");
System.out.println("类名:" + target.getClass().getName()
+ "\t方法名:" + method.getName());
System.out.print("参数列表:");
for (Object arg: args) {
System.out.print(arg + ", " + arg.getClass().getName() + ";\t");
}
args[0] = 123;
System.out.println();
System.out.println("将第一个参数改为123");
System.out.println("=====前置结束=====");
}
}
后置Advice
package aop.advice;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class AfterReturningAdviceImpl implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("~~~~~后置开始~~~~~");
System.out.println("返回值为:" + returnValue + ", "
+ returnValue.getClass().getName());
System.out.print("将第二个参数改为345并再调用一次:");
args[1] = 345;
System.out.println(method.invoke(target, args));
System.out.println("~~~~~后置结束~~~~~");
}
}
拦截Advice
package aop.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MethodInterceptorImpl implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("*****拦截开始*****");
if (invocation.getArguments()[1] instanceof Integer) {
System.out.println("若第二个参数为整型则强制设定结果为-100");
System.out.println("*****拦截结束*****");
return -100;
}
System.out.println("*****拦截结束*****");
return invocation.proceed();
}
}
异常Advice
package aop.advice;
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
public class ThrowsAdviceImpl implements ThrowsAdvice {
public void afterThrowing(Throwable e) {
System.out.println("-----异常开始-----");
System.out.println(e.getClass().getName() + "\t" + e.getMessage());
System.out.println("-----异常结束-----");
}
// ThrowsAdvice只是标识接口,实现类中必须至少实现两个不同签名的afterThrowing方法中的一个
// public void afterThrowing(Method method, Object[] args, Object target, Throwable e) {
//
// }
}
按方法名匹配的Pointcut
package aop.pointcut;
import java.lang.reflect.Method;
import org.springframework.aop.support.NameMatchMethodPointcut;
public class NameMatchMethodPointcutImpl extends NameMatchMethodPointcut {
private static final long serialVersionUID = -3944947836761348760L;
public boolean matches(Method method, Class targetClass) {
this.setMappedName("divide");
// this.setMappedNames(new String[]{"add", "divide"});
return super.matches(method, targetClass);
}
}
目标类及其接口
package aop.service;
public interface IMath {
public int add(int a, int b);
public double add(double a, double b);
public double divide(double a, double b);
}
package aop.service;
public class Math implements IMath {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public double divide(double a, double b) throws IllegalArgumentException {
if (b == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return a / b;
}
}
配置文件
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="math" class="aop.service.Math" />
<bean id="before" class="aop.advice.MethodBeforeAdviceImpl" />
<bean id="after" class="aop.advice.AfterReturningAdviceImpl" />
<bean id="exception" class="aop.advice.ThrowsAdviceImpl" />
<bean id="intercept" class="aop.advice.MethodInterceptorImpl" />
<bean id="methodNamePointcut" class="aop.pointcut.NameMatchMethodPointcutImpl" />
<bean id="nameAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<ref bean="methodNamePointcut" />
</property>
<property name="advice">
<ref bean="before" />
</property>
</bean>
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern">
<value>aop.service.IMath.divide</value>
</property>
<property name="advice">
<ref bean="after" />
</property>
</bean>
<bean id="cfPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
<constructor-arg>
<value>aop.Main</value>
</constructor-arg>
<constructor-arg>
<value>add</value>
</constructor-arg>
</bean>
<bean id="cfAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<ref bean="cfPointcut" />
</property>
<property name="advice">
<ref bean="intercept" />
</property>
</bean>
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<list>
<value>aop.service.IMath</value>
</list>
</property>
<property name="interceptorNames">
<list>
<!-- <value>after</value> -->
<value>regexpAdvisor</value>
<!-- <value>before</value> -->
<value>nameAdvisor</value>
<value>exception</value>
<!-- <value>intercept</value> -->
<value>cfAdvisor</value>
</list>
</property>
<property name="target">
<ref bean="math" />
</property>
</bean>
</beans>
执行入口
package aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import aop.service.IMath;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext(
"src\\applicationContext.xml");
IMath m = (IMath)context.getBean("proxy");
System.out.println(add(m, 1, 2));
System.out.println("..........................................................................................");
System.out.println(m.add(4.0, 5.5));
System.out.println("..........................................................................................");
System.out.println(m.divide(10, 2));
System.out.println("..........................................................................................");
System.out.println(m.divide(5, 0));
}
public static int add(IMath m, int a, int b) {
return m.add(a, b);
}
}
7. 说明
1) 加入AOP之后,方法的执行顺序是:
前置Advice →拦截Advice →目标方法 → 后置Advice
若目标方法中抛出了异常,则立即跳转执行异常Advice,再跳转回来执行异常处理(try…catch或throws)
2) 前置Advice和拦截Advice都能改动目标方法的参数值,区别在于,执行前置Advice之后一定要指定目标方法,而拦截Advice能够阻止目标方法的执行;只有在拦截Advice中执行了invocation.proceed()方法才会执行目标方法,其返回值就是目标方法的返回值
3) 因为后置Advice是在目标方法返回(return)之后才执行,因此它不能对目标方法产生影响,但是可以通过反射机制的Method得知目标方法的一些信息
4) 异常Advice实现的ThrowsAdvice接口只是一个标识接口,需要手动实现afterThrowing方法两个签名中的其中一个
5) 代理的拦截列表(interfaceNames)可以接口两种类型的逻辑:Advice和Advisor。后者即是前述的自定义Pointcut的Advice;Spring中提供了许多接口可供实现Advisor,这里只用到了较常用的几个内置比较完整的抽象类
6) 拦截列表(interfaceNames)的顺序不影响Advice/Advisor执行的顺序
7) 完成AOP配置之后,在生成bean时需要用代理的id来替代目标类的id,即如:
IMath m = (IMath)context.getBean("proxy"); // 而不是用目标类的id:math
8) 代理的配置中必须在proxyInterfaces属性中指定代理和目标类共用的接口的全名