Spring AOP
AOP(Aspect Orient Programming),也就是面向切面编程。可以这样理解,面向对象编程(OOP)是从静态角度考虑程序结构,面向切面编程(AOP)是从动态角度考虑程序运行过程。
在日常生活中,会遇到各种各样的中介机构,比如猎头公司,律师事务所,婚姻介绍所,房产公司等。在这些单位工作的人员均可称为代理人。代理人的共同特征是可以代替委托人去和第三方通信。譬如:律师代替委托人打官司,猎头代替委托人物色人才,红娘代替委托人寻找对象,房产代理人代替委托人出租房屋。
代理人可以在第三方和委托人之间转发或过滤消息,但是不能取代委托人的任务。譬如你要找女朋友,委托你一要好的哥们去帮你物色,结果被他给物色了。这就不叫代理。
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
先介绍AOP的四种通知类型:
1、前置通知:在目标方法执行之前调用。
实现MethodBeforeAdvice接口,接口方法如下:
void before(Method method,Object[] args,Object target) throws Throwable;
这个接口为我们提供了获得目标方法,参数,目标对象的机会。注意:我们没必要在这个方法里面调用目标方法(可以通过反射调用),因为,这个接口方法结束后目标方法将调用,这才叫前置嘛(在目标方法之前)。要想终止程序的继续运行,只有抛异常或调用System.exit()
实现AfterReturningAdvice接口,该接口方法如下:
void afterReturning(Object returnValue,Method method,
Object[] args, Object target) throws Throwable
这个方法多了一个目标方法的返回值参数。同理要结束程序流程只有抛异常和调用System.exit()方法。
3、环绕通知:拦截目标方法的执行,可在该类型通知里自行调用目标方法。
实现MethodInterceptor接口,该接口在aopalliance.jar包里,这个包是aop联盟(aop编程接口,为实现aop编程接口重用)的包,可以理解成aop编程规范接口。
接口方法如下:
Object invoke(MethodInvocation invocation) throws Throwable;
环绕通知和上述2个通知的区别:
1)由于环绕通知是拦截目标方法的执行,所以在这个方法里用户可以通过invocation.proceed()方法调用目标方法,这点和MethodBeforeAdvice不同,它的目标方法总是会执行,除非抛异常或执行System.exit()方法。
2)正常情况下这个方法返回的就是目标方法(invocation.proceed()返回的结果)的返回值,但可以在这个接口方法里改变目标方法的返回值,除非必须如此,否则最好不要这样使用。这个接口与AOP联盟的AOP框架兼容。
因为要显示的调用目标方法(invocation.proceed()),所以更推荐使用上述2种方式。
4、异常通知:当目标方法抛出异常时调用。
实现ThrowsAdvice接口,该接口是标记接口,没有定义任何一个必须实现的方法,但实现该接口的类必须至少有一个如下形式的方法:
下面看一个AOP例子
切面类TestAspect
package com.spring.aop;
/**
* 切面
*
*/
public class TestAspect {
public void doAfter(JoinPoint jp) {
System.out.println("log Ending method: "
+ jp.getTarget().getClass().getName() + "."
+ jp.getSignature().getName());
}
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time = System.currentTimeMillis();
Object retVal = pjp.proceed();
time = System.currentTimeMillis() - time;
System.out.println("process time: " + time + " ms");
return retVal;
}
public void doBefore(JoinPoint jp) {
System.out.println("log Begining method: "
+ jp.getTarget().getClass().getName() + "."
+ jp.getSignature().getName());
}
public void doThrowing(JoinPoint jp, Throwable ex) {
System.out.println("method " + jp.getTarget().getClass().getName()
+ "." + jp.getSignature().getName() + " throw exception");
System.out.println(ex.getMessage());
}
private void sendEx(String ex) {
//TODO 发送短信或邮件提醒
}
}
package com.spring.service;
/**
* 接口A
*/
public interface AService {
public void fooA(String _msg);
public void barA();
}
package com.spring.service;
/**
*接口A的实现类
*/
public class AServiceImpl implements AService {
public void barA() {
System.out.println("AServiceImpl.barA()");
}
public void fooA(String _msg) {
System.out.println("AServiceImpl.fooA(msg:"+_msg+")");
}
}
package com.spring.service;
/**
* Service类B
*/
public class BServiceImpl {
public void barB(String _msg, int _type) {
System.out.println("BServiceImpl.barB(msg:"+_msg+" type:"+_type+")");
if(_type == 1)
throw new IllegalArgumentException("测试异常");
}
public void fooB() {
System.out.println("BServiceImpl.fooB()");
}
}
ApplicationContext.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-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
default-autowire="autodetect">
<aop:config>
<aop:aspect id="TestAspect" ref="aspectBean">
<!--配置com.spring.service包下所有类或接口的所有方法-->
<aop:pointcut id="businessService"
expression="execution(* com.spring.service.*.*(..))" />
<aop:before pointcut-ref="businessService" method="doBefore"/>
<aop:after pointcut-ref="businessService" method="doAfter"/>
<aop:around pointcut-ref="businessService" method="doAround"/>
<aop:after-throwing pointcut-ref="businessService" method="doThrowing" throwing="ex"/>
</aop:aspect>
</aop:config>
<bean id="aspectBean" class="com.spring.aop.TestAspect" />
<bean id="aService" class="com.spring.service.AServiceImpl"></bean>
<bean id="bService" class="com.spring.service.BServiceImpl"></bean>
</beans>
测试类AOPTest
public class AOPTest extends AbstractDependencyInjectionSpringContextTests {
private AService aService;
private BServiceImpl bService;
protected String[] getConfigLocations() {
String[] configs = new String[] { "/applicationContext.xml"};
return configs;
}
/**
* 测试正常调用
*/
public void testCall()
{
System.out.println("SpringTest JUnit test");
aService.fooA("JUnit test fooA");
aService.barA();
bService.fooB();
bService.barB("JUnit test barB",0);
}
/**
* 测试After-Throwing
*/
public void testThrow()
{
try {
bService.barB("JUnit call barB",1);
} catch (IllegalArgumentException e) {
}
}
public void setAService(AService service) {
aService = service;
}
public void setBService(BServiceImpl service) {
bService = service;
}
}
运行结果如下:
log Begining method: com.spring.service.AServiceImpl.fooA
AServiceImpl.fooA(msg:JUnit test fooA)
log Ending method: com.spring.service.AServiceImpl.fooA
process time: 0 ms
log Begining method: com.spring.service.AServiceImpl.barA
AServiceImpl.barA()
log Ending method: com.spring.service.AServiceImpl.barA
process time: 0 ms
log Begining method: com.spring.service.BServiceImpl.fooB
BServiceImpl.fooB()
log Ending method: com.spring.service.BServiceImpl.fooB
process time: 0 ms
log Begining method: com.spring.service.BServiceImpl.barB
BServiceImpl.barB(msg:JUnit test barB type:0)
log Ending method: com.spring.service.BServiceImpl.barB
process time: 0 ms
log Begining method: com.spring.service.BServiceImpl.barB
BServiceImpl.barB(msg:JUnit call barB type:1)
log Ending method: com.spring.service.BServiceImpl.barB
method com.spring.service.BServiceImpl.barB throw exception
测试异常
讲解:
我们先看配置文件中 有:before after around throw
测试类中顺序为 fooA barA fooB barB 还有一个异常测试 barB
先从fooA开始,由于配置文件中为 before after around throw 那么对于fooA,就可以从 dobefore doafter doaround dothrow 开始
在做fooA之前 先 做before 做完fooA然后做 after 然后对于环绕通知共享前置后后置信息
所以对于fooA 输出为
log Begining method: com.spring.service.AServiceImpl.fooA
AServiceImpl.fooA(msg:JUnit test fooA)
log Ending method: com.spring.service.AServiceImpl.fooA
process time: 0 ms
至于为什么会没有throw因为在fooA执行过程中并没有抛出异常。
同理 其他的也这样理解