Spring AOP编程的核心是通过IoC生成代理对象,代理对象的配置通常有如下几个属性:
<property name="interfaces">
</propety>
<property name="targetName">
</property>
<property name="interceptorNames">
</property>
其中,interfaces是目标对象所实现的接口,targetName是目标对象的名字,interceptorNames是Advice或者Advisor的名字。
不同类型的Advice
1、前置通知(before advice)
前置通知需要实现MethodBeforeAdvice接口,并覆盖接口中的如下方法:
void before(Method method,Object[] args,Object target)
该方法将在目标对象的方法调用前被调用。
使用前置通知,需要创建前置通知类,实现MethodBeforeAdvice接口,如下所示:
public class LogBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("Invoke LogBeforeAdvice.before");
Logger.log("LogBeforeAdvice:"+arg0.getName());
}
}
2、后置通知(after advice)
与前置通知对应,Spring框架的AOP组件中提供了后置通知类型。
后置通知在一个方法被调用后执行。后置通知必须实现接口AfterReturningAdvice,并覆盖其中的方法:
void afterReturning(Object return Value,Method method,Object[] args,Object target)
后置通知的实现类如下:
public class LogAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("Invoke LogAfterAdvice.afterReturning");
Logger.log("LogAfterAdvice: "+arg1.getName()+" return "+arg0);
}
}
3、环绕通知(around advice)
环绕通知可以再被拦截的方法执行过程的任何一个阶段被调用,也可能由于某种原因,而不调用被拦截的方法,这是前置和后置通知做不到的。
前置和后置通知不能阻止调用被拦截的方法。
环绕通知必须实现MethodInterceptor接口,覆盖接口中的方法:
Objectinvoke (MethodInvocation invocation)
invoke方法的参数类型是MethodInvocation,MethodInvocation类中提供了proceed方法,用来调用被拦截的方法。
如果环绕通知中没有调用proceed方法,则目标方法不被调用。
4、异常通知(exception advice)
异常通知在被拦截的方法抛出异常时调用。
异常通知必须实现ThrowsAdvice接口。
该接口中没有方法,是一个标记接口。实现该接口后,异常通知类可以定义多个方法,方法必须符合如下规范:
afterThrowing([Method],[args],[target],Throwable subclass)
afterThrowing方法的参数Method]、[args]、[target]都是可选项,必须制定的参数是Throwable,即抛出异常的具体类型。
异常通知的实现类如下所示:
public class LogThrwosAdvice implements ThrowsAdvice {
public void afterThrowing(RegisterException e) {
System.out.println("Invoke LogThrwosAdvice.afterThrowing");
Logger.log("LogThrwosAdvice: "+e.getMessage());
}
public void faterThrowing(IOException e){
Logger.log(e.getMessage());
}
}
上述异常通知中定义了两个方法,方法中指定了不同异常类型的形式参数,分别处理RegisterException和IOException异常,当被拦截的方法抛出了对应类型的异常时,将被异常通知类拦截。
至此,已经学习了Spring框架的AOP组件中常用的四种Advice类型。
总结Advice的使用步骤:
①使用IoC配置Advice
要使用Advice,首先必须在配置文件中使用IoC配置其对应的bean。
<bean id="logbefore" class="advice.LogBeforeAdvice">
</bean>
<bean id="logafter" class="advice.LogAfterAdvice">
</bean>
<bean id="logaround" class="advice.LogAroundAdvice">
</bean>
<bean id="logthrows" class="advice.LogThrowsAdvice">
</bean>
②、在代理对象中引用Advice
配置了Advice的bean后,在需要使用Advice的代理对象中,就可以通过引用bean的id值使用Advice。
一个代理对象可以使用多个Advice,同时,一个Advice也可以被多个代理对象使用。
<bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces">
<list>
<value>service.CustomerService</value>
</list>
</property>
<property name="targetName">
<value>service</value>
</property>
<property name="interceptorNames">
<list>
<value>logbefore</value>
<value>logafter</value>
<value>logaround</value>
<value>logthrows</value>
</list>
</property>
</bean>
③、测试拦截器的使用
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import exception.RegisterException;
import service.CustomerService;
import vo.Customer;
public class TestAdvice {
public static void main(String[] args) {
ApplicationContext ctxt= new ClassPathXmlApplicationContext("applicationContext.xml");
CustomerService serviceProxy = (CustomerService) ctxt.getBean("serviceProxy");
try {
serviceProxy.register(new Customer("aom","aom",23,"BJ"));
} catch (RegisterException e) {
// TODO Auto-generated catch block
System.out.println("custname already exist.");
}
}
}
当数据库不存在该用户时,register方法不抛出异常,输出结果如下:
Invoke LogBeforeAdvice.before
Invoke LogAroundAdvice.invoke
invoke register...
Invoke LogAfterAdvice.afterReturning
日志文件记录如下信息:
Wed Jul 30 14:44:10 CST 2014: LogBefore: register
Wed Jul 30 14:44:10 CST 2014: LogAroundAdvice :register
Wed Jul 30 14:44:11 CST 2014: LogAfterAdvice: register return null
当再次执行时,此时数据库已存在该用户,将抛出RegisterException,被异常拦截器拦截,输出结果如下:
Invoke LogBeforeAdvice.before
Invoke LogAroundAdvice.invoke
invoke register...
Invoke LogThrwosAdvice.afterThrowing
custname already exist.
日志文件记录如下信息:
Wed Jul 30 14:46:18 CST 2014: LogBefore: register
Wed Jul 30 14:46:18 CST 2014: LogAroundAdvice :register
Wed Jul 30 14:46:19 CST 2014: LogThrowsAdvice: null
执行下面测试代码:
public class TestAdvice {
public static void main(String[] args) throws RegisterException {
ApplicationContext ctxt= new ClassPathXmlApplicationContext("applicationContext.xml");
CustomerService serviceProxy = (CustomerService) ctxt.getBean("serviceProxy");
serviceProxy.register(new Customer("aom","aom",23,"BJ"));
System.out.println("main end");
}
}
上述代码中没有捕获RegisterException异常,而是将异常声明抛出。由于用户名已存在数据库中,执行上述代码将发生异常,
输出结果如下:
Invoke LogBeforeAdvice.before
Invoke LogAroundAdvice.invoke
invoke register...
Invoke LogThrowsAdvice.afterThrowing
Exception in thread "main" exception.RegisterException
at service.CustomerServiceImpl.register(CustomerServiceImpl.java:31)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
上述代码中发生异常后,并没有被捕获,依然会调用异常通知,
但是“main end”没有被输出,说明异常没有被捕获,main方法中断。
可见,异常通知不是用来捕获异常的,而是用来定义发生异常后需要处理的功能。