了解下面的就了解了Spring的声明式异常管理的原理
1.织入点的语法:
下面给出一些通用切入点表达式的例子。
任意公共方法的执行:
execution(public * *(..))
任何一个名字以“set”开始的方法的执行:
execution(* set*(..))
AccountService接口定义的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
在service包中定义的任意方法的执行:
execution(* com.xyz.service.*.*(..))
在service包或其子包中定义的任意方法的执行:
execution(* com.xyz.service..*.*(..))
在service包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service.*)
在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service..*)
实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):
this(com.xyz.service.AccountService)
'this'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得代理对象在通知体内可用。
2.声明通知
通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。
a)前置通知
一个切面里使用 @Before 注解声明前置通知
例子:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeExample { @Before("execution(* com.xyz.myapp.dao.*.*(..))") public void doAccessCheck() { // ... } }
b)后置通知(After returning advice)
返回后通知通常在一个匹配的方法返回的时候执行。使用 @AfterReturning 注解来声明:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } }
说明:你可以在相同的切面里定义多个通知,或者其他成员。 我们只是在展示如何定义一个简单的通知。这些例子主要的侧重点是正在讨论的问题。
有时候你需要在通知体内得到返回的值。你可以使用@AfterReturning 接口的形式来绑定返回值:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal") public void doAccessCheck(Object retVal) { // ... } }
在 returning属性中使用的名字必须对应于通知方法内的一个参数名。 当一个方法执行返回后,返回值作为相应的参数值传入通知方法。 一个returning子句也限制了只能匹配到返回指定类型值的方法。 (在本例子中,返回值是Object类,也就是说返回任意类型都会匹配)
请注意当使用后置通知时不允许返回一个完全不同的引用。
c)异常通知(After throwing advice)
抛出异常通知在一个方法抛出异常后执行。使用@AfterThrowing注解来声明:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @Aspect public class AfterThrowingExample { @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doRecoveryActions() { // ... } }
你通常会想要限制通知只在某种特殊的异常被抛出的时候匹配,你还希望可以在通知体内得到被抛出的异常。 使用throwing属性不仅可以限制匹配的异常类型(如果你不想限制,请使用 Throwable作为异常类型),还可以将抛出的异常绑定到通知的一个参数上。
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @Aspect public class AfterThrowingExample { @AfterThrowing( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", throwing="ex") public void doRecoveryActions(DataAccessException ex) { // ... } }
在throwing属性中使用的名字必须与通知方法内的一个参数对应。 当一个方法因抛出一个异常而中止后,这个异常将会作为那个对应的参数送至通知方法。 throwing 子句也限制了只能匹配到抛出指定异常类型的方法 (上面的示例为DataAccessException)。
d)最终通知(After (finally) advice)
不论一个方法是如何结束的,最终通知都会运行。使用@After 注解来声明。最终通知必须准备处理正常返回和异常返回两种情况。通常用它来释放资源。
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.After; @Aspect public class AfterFinallyExample { @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doReleaseLock() { // ... } }
e)环绕通知
最后一种通知是环绕通知。环绕通知在一个方法执行之前和之后执行。它使得通知有机会 在一个方法执行之前和执行之后运行。而且它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。 环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。(比如如果简单的前置通知也可以适用的情况下不要使用环绕通知)。
环绕通知使用@Around注解来声明。通知的第一个参数必须是 ProceedingJoinPoint类型。在通知体内,调用 ProceedingJoinPoint的proceed()方法会导致 后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个 Object[]对象-该数组中的值将被作为方法执行时的参数。
当传入一个Object[]对象的时候,处理的方法与通过AspectJ编译器处理环绕通知略有不同。 对于使用传统AspectJ语言写的环绕通知来说,传入参数的数量必须和传递给环绕通知的参数数量匹配 (不是后台的连接点接受的参数数量),并且特定顺序的传入参数代替了将要绑定给连接点的原始值 (如果你看不懂不用担心)。Spring采用的方法更加简单并且能更好匹配它基于代理(proxy-based)的执行语法, 如果你使用AspectJ的编译器和编织器来编译为Spring而写的@AspectJ切面和处理参数,你只需要知道这一区别即可。
有一种方法可以让你写出100%兼容Spring AOP和AspectJ的表达式,我们将会在后续的通知参数的章节中讨论它。
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect public class AroundExample { @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed();//环绕完了继续环绕给其他的用 // stop stopwatch return retVal; } }
方法的调用者得到的返回值就是环绕通知返回的值。 例如:一个简单的缓存切面,如果缓存中有值,就返回该值,否则调用proceed()方法。 请注意proceed可能在通知体内部被调用一次,许多次,或者根本不被调用,所有这些都是合法的。
测试@Before和@AfterReturning:
package cn.edu.hpu.aop; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class LogInterceptor { //在方法执行之前先执行这个方法 //execution是织入点语法 @Before("execution(public * cn.edu.hpu.dao..*.*(..))") public void before(){ System.out.println("method start"); } @AfterReturning("execution(public * cn.edu.hpu.dao..*.*(..))") public void afterReturning(){ System.out.println("method after ruturning"); } }
测试:
package cn.edu.hpu.service; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.edu.hpu.dao.UserDao; import cn.edu.hpu.model.User; public class UserServiceTest { @Test public void testAdd() throws Exception{ ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml"); UserService userService=(UserService)ctx.getBean("userService"); //System.out.println(userService.getUserDao()); User u=new User(); u.setUsername("u1"); u.setPassword("p1"); userService.add(u); ctx.destroy(); } }
结果:
method start
add success!!
method after ruturning
在每一个方法上都要写一个织入点语法,有点麻烦,如果每个织入点语法都是一样的话,我们可以这样写:
package cn.edu.hpu.aop; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class LogInterceptor { @Pointcut("execution(public * cn.edu.hpu.dao..*.*(..))") public void myMethod(){} //在方法执行之前先执行这个方法 //execution是织入点语法 @Before("myMethod()") public void before(){ System.out.println("method start"); } @AfterReturning("myMethod()") public void afterReturning(){ System.out.println("method after ruturning"); } }
下面测试@AfterThrowing:
现在UserDaoImpl中的save方法中添加异常
package cn.edu.hpu.dao.Impl; import java.util.List; import java.util.Map; import java.util.Set; import org.springframework.stereotype.Component; import cn.edu.hpu.dao.UserDao; import cn.edu.hpu.model.User; @Component("u") public class UserDaoImpl implements UserDao{ public void save(User u) { System.out.println("add success!!"); throw new RuntimeException("exception"); } }
package cn.edu.hpu.aop; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class LogInterceptor { @AfterThrowing("execution(public * cn.edu.hpu.dao..*.*(..))") public void afterThrowing(){ System.out.println("method after throwing"); } }
测试:
package cn.edu.hpu.service; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.edu.hpu.dao.UserDao; import cn.edu.hpu.model.User; public class UserServiceTest { @Test public void testAdd() throws Exception{ ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml"); UserService userService=(UserService)ctx.getBean("userService"); User u=new User(); u.setUsername("u1"); u.setPassword("p1"); userService.add(u); ctx.destroy(); } }
测试结果:
add success!!
method after throwing
编译器也报了异常
我们以后就可以实现"声明式"异常管理了,但实际上都是交给struts2来处理的。
下面测试@Around:
package cn.edu.hpu.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class LogInterceptor { @Pointcut("execution(public * cn.edu.hpu.dao..*.*(..))") public void myMethod(){} @Before("myMethod()") public void before(){ System.out.println("method start"); } @AfterReturning("myMethod()") public void afterReturning(){ System.out.println("method after ruturning"); } @Around("myMethod()") public void AroundMtethod(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("method around start"); pjp.proceed(); System.out.println("method around end"); } }
测试:
package cn.edu.hpu.service; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.edu.hpu.dao.UserDao; import cn.edu.hpu.model.User; public class UserServiceTest { @Test public void testAdd() throws Exception{ ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml"); UserService userService=(UserService)ctx.getBean("userService"); User u=new User(); u.setUsername("u1"); u.setPassword("p1"); userService.add(u); ctx.destroy(); } }
结果:
method start
method around start
add success!!
method after ruturning
method around end
虽然可以看出一些顺序,但写业务逻辑的时候不要依赖于这个顺序。可以按顺序写好方法再加进去。
注意一些问题:
如果一个类没有继承接口的话,就不能使用动态代理了,此时需要加cglibrary的jar包cglib-nodep-2.1.3.jar。
原因是:
当一个类实现了接口之后,就会使用JDK自带的proxy和InvocationHandler来帮你自动产生代理,没有实现接口的话,它会用直接操作二进制码的这种类库,也就是cglibrary,来帮你产生代理的代码。
最后,我们验证一下我们使用的是代理,而不是原来的类
测试代码:
package cn.edu.hpu.service; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.edu.hpu.dao.UserDao; import cn.edu.hpu.model.User; public class UserServiceTest { @Test public void testAdd() throws Exception{ ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml"); UserService userService=(UserService)ctx.getBean("userService"); System.out.println(userService.getClass()); ctx.destroy(); } }
结果:
class cn.edu.hpu.service.UserService$ $ EnhancerByCGLIB $ $c0951629(CSDN对“$”符号很敏感,所以加空格了)
说明使用的是代理。
总结:
整个的Annotation实现AOP编程是属于那种不太重要的东西。脑子里留印象,使用到回来查就可以。但是AOP的原理一定要懂。
转载请注明出处:http://blog.csdn.net/acmman/article/details/44344267