在Spring中,最常用的AOP框架是AspectJ,使用AspectJ实现AOP有2种方式:
基于XML的声明式AspectJ
1、在项目中添加包spring-aspects.jar(spring自带)、aspectjweaver.jar(需要自己下载添加)。
2、新建包user,包下新建类User
1 public class User{ 2 public void addUser(){ 3 System.out.println("正在添加用户"); 4 System.out.println("添加用户成功"); 5 } 6 }
User可实现接口。
3、新建包my_aspect,包下新建切面类MyAspect。注意是新建Class,不是新建Aspect。
1 public class MyAspect { 2 //前置通知 3 public void myBefore(){ 4 System.out.println("正在检查权限"); 5 System.out.println("权限已够"); 6 } 7 8 //后置通知 9 public void myAfterReturning(){ 10 System.out.println("正在记录日志"); 11 System.out.println("日志记录完毕"); 12 } 13 14 //环绕通知,同时在前后增强。 15 public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 16 myBefore(); 17 Object object=proceedingJoinPoint.proceed(); 18 myAfterReturning(); 19 return object; 20 } 21 22 //异常通知 23 public void myAfterThrowing(Throwable e){ 24 System.out.println("出错了:"+e.getMessage()); 25 } 26 27 //最终通知 28 public void myAfter(){ 29 System.out.println("正在释放资源"); 30 System.out.println("资源释放完毕"); 31 } 32 }
根据需要,写通知。函数名是自定义的,但不能使用aspectj的关键字。
可使用形参 JoinPoint joinPoint ,通过 joinPoint.getSignature().getName() 获取增强的方法名。
4、在xml中配置
<!--目标类--> <bean id="user" class="user.User" /> <!--切面类--> <bean id="myAspect" class="my_aspect.MyAspect" /> <!--AOP配置--> <aop:config> <!--配置切面,一个aspect配置一个切面--> <aop:aspect ref="myAspect"> <!--配置全局切入点--> <aop:pointcut id="myPointCut" expression="execution(* user.*.*(..))" /> <!--配置要使用的通知--> <aop:before method="myBefore" pointcut-ref="myPointCut" /> <aop:after-returning method="myAfterReturning" pointcut="execution(void user.User.addUser())" /> </aop:aspect> </aop:config>
<aop:pointcut />配置的是全局切入点(所有通知都可引用),可在通知中使用pointcut-ref属性引用。也可以在单个通知中使用pointcut属性配置自己的切入点。
method属性指定这个通知对应的切面类中的方法。如果方法使用了参数JoinPoint(切入点),不用额外传参,pointcut/point-ref传递的就是切入点。
切入点指的就是目标方法。
- 前置通知
<aop:before method="myBefore" pointcut-ref="myPointCut" />
- 后置通知
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" />
后置通知可使用returning属性指定指定一个参数,这个参数表示目标方法的返回值,会被传递给后置通知的方法。returning属性只有后置通知能使用。
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal" />
- 环绕通知(前、后增强)
<aop:around method="myAround" pointcut-ref="myPointCut" />
环绕方法的参数 ProceedingJoinPoint proceedingJoinPoint 是JoinPoint的子类,不用额外传参。
如果同时使用前置/后置通知、环绕通知,则先执行环绕前,再执行前置(如果有),执行目标方法,执行后置(如果有),然后执行环绕后。
- 异常通知
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
异常方法需要一个参数 Throwable e ,需要用throwing属性传递参数。
异常通知在目标方法发生异常、抛出异常时自动执行,throwing表示的就是目标方法抛出的异常。
目标方法发生异常时,会先执行异常方法,然后在控制台打印错误信息。
- 最终通知
<aop:after method="myAfter" pointcut-ref="myPointCut" />
最终通知是在目标方法执行后、后置方法执行后/环绕方法执行后(如果有),才执行的。
如果之前的代码(目标方法)发生异常或者被异常中止,后置通知以及环绕通知的后半部分是不会执行的。
最终通知,就算前面代码发生异常、被异常中止,也会执行,除非退出JVM。所以常在最终方法中做一些断开连接、关闭资源的操作。
5、新建包test,包下新建测试类Test
1 public class Test { 2 public static void main(String[] args) { 3 ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml"); 4 User user=applicationContext.getBean("user", User.class); 5 user.addUser(); 6 } 7 }
基于注解的声明式AspectJ
基于代理类的AOP很繁琐,基于xml的声明式AspectJ便捷很多,但如果要配置的切面很多,xml文件会很臃肿。基于注解的声明式AspectJ可解决此问题。
基于注解的声明式AspectJ是最常用的,简便、xml文件也比较简洁。
1、在项目中添加包spring-aspects.jar(spring自带)、aspectjweaver.jar(需要自己下载添加)。
2、新建包user,包下新建类User(可实现接口)
1 @Repository("user") 2 public class User{ 3 public void addUser(){ 4 System.out.println("正在添加用户"); 5 System.out.println("添加用户成功"); 6 } 7 }
原本要在xml中配置目标类的Bean,这里使用注解自动装配。@Repository(“user”)是Dao层的注解,因为添加用户一般要操作数据库,这里只是写了一个模拟。
我们显式指定Bean的id/name为user,其实@Repository默认id/name就是类名的camel写法。
3、新建包my_aspect,包下新建切面类MyAspect
1 @Aspect 2 @Component 3 public class MyAspect { 4 //全局切入点 5 @Pointcut("execution(void user.User.*())") 6 private void myPointCut(){} 7 8 //前置通知 9 @Before("myPointCut()") 10 public void myBefore(){ 11 System.out.println("正在检查权限"); 12 System.out.println("权限已够"); 13 } 14 15 //后置通知 16 @AfterReturning("execution(void user.User.*())") 17 public void myAfterReturning(){ 18 System.out.println("正在记录日志"); 19 System.out.println("日志记录完毕"); 20 } 21 22 //环绕通知,同时在前后增强。 23 public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 24 myBefore(); 25 Object object=proceedingJoinPoint.proceed(); 26 myAfterReturning(); 27 return object; 28 } 29 30 //异常通知 31 public void myAfterThrowing(Throwable e){ 32 System.out.println("出错了:"+e.getMessage()); 33 } 34 35 //最终通知 36 public void myAfter(){ 37 System.out.println("正在释放资源"); 38 System.out.println("资源释放完毕"); 39 40 } 41 }
原本要在xm中配置切面Bean,这里使用@Aspect(使用aspectj配置,相当于<aop:congif>元素)、 @Component(配置为Bean,相当于<bean>元素) 2个注解配置。
需要使用注解把要使用的方法标注为对应的通知方法,需要指定切入点。
可先配置全局切入点:
//全局切入点 @Pointcut("execution(void user.User.*())") private void myPointCut(){}
然后在注解中通过 “方法名()” 引用:
//前置通知 @Before(value = "myPointCut()")
如果只配置切入点这一个参数,可简写:
//前置通知 @Before("myPointCut()")
也可以自行配置:
//后置通知 @AfterReturning("execution(void user.User.*())")
异常通知比较特殊,需要传递一个异常参数:
//异常通知 @AfterThrowing(value = "myPointCut()",throwing = "e")
只有使用了注解标注的方法才会作为通知自动调用。
其实就是把xml中的配置转换为相应的注解配置。
4、在xml中配置
<!--启用Spring注解,指定使用了注解的包,有多个包时逗号分隔-->
<context:component-scan base-package="user,my_aspect" />
<!--启用AspectJ的注解-->
<aop:aspectj-autoproxy />
5、新建包test,包下新建测试类Test
1 public class Test { 2 public static void main(String[] args) { 3 ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml"); 4 User user=applicationContext.getBean("user", User.class); 5 user.addUser(); 6 } 7 }