SpringAOP
概述
AOP的全称是Aspect Oriented Programming(面向切面编程)
OOP语言提供了类与类之间纵向的关系(继承、接口),而AOP补充了横向的关系(比如在不改变目标类中源代码的情况下给com.john.demo.dao包下所有类中以insert和update开头的方法添加事务管理)
SpringAOP和AspectJ的区别
AspectJ是一个专门主打面向切面编程的框架。 它是使用一种特殊的语言(扩展自Java语言)来编写切面代码,后缀是.aj格式,并且需要使用专门的编译器将其编译成jvm可以运行的class文件。
SpringAOP底层也是使用了AspectJ的方案,但是在上层做了很多封装层面的工作,可以让开发人员直接使用Java代码来编写切面。并且由于使用的是标准的Java语言,所以并不需要在额外安装一个专门的编译器。但是由于开发人员直接接触的是Spring AOP,那么凡是Spring中没有实现的那些AOP功能,我们就用不了了,这种情况下只能跟产品经理撕逼或者去学习原生的AspectJ。
AOP的术语
-
切面(Aspect)
简单来说,切面就是我们要往目标代码中插入进去的代码。
-
连接点(Join Pointer)
理论上所有可能会被切入的地方都可以称之为连接点
-
切入点(Pointcut)
选择某个连接点切入,将切面代码织入进去。这个连接点就叫做切入点。
-
织入(Weaving)
把切面代码糅合到目标代码中的过程就是织入。
-
通知(Advice)
通知决定了切面代码织入到目标代码中后,运行的时机(比如是在目标方法执行前,还是执行后)。
在Spring中使用AOP
基于XML方式使用
-
把aop的schema引入
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
-
创建一个切面类,并且以bean的方式配置到IOC容器中
package com.lanou3g.spring; public class MyAspect { public void wakeup() { System.out.println("[前置通知]我刚学习SpringAOP睡着了,刚才谁打了我一下?"); } public void goToBed() { System.out.println("[后置通知]SpringAOP太难了,一不小心又睡着了"); } public void afterRetuing(Object message) { System.out.println("[后置返回通知]方法执行已经return了,方法返回值是:" + message); } public void afterThrowing(Throwable ex) { System.out.println("[后置异常通知]方法执行出现异常,异常原因:" + ex.getMessage()); } /** * 环绕通知 * 可以接受一个ProceedingJoinPoint参数 * 通过此参数可以获取到被切入方法的所有信息 * 还可以通过此参数来决定是否调用目标方法 */ public void aroundAdvice(ProceedingJoinPoint joinPoint) { // 连接点参数可以获取到被切入方法的所有信息 // 这里演示了如何获取被切入方法的名称 String targetMethodName = joinPoint.getSignature().getName(); System.out.println("[环绕通知]被切入的方法名:" + targetMethodName); // System.out.println("[环绕通知]即将开始新的一天, 早起的鸟儿有虫吃!"); try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("[环绕通知]这真是充实的一天, 早睡早起,方能养生!"); } }
<bean id="myAspect" class="com.lanou3g.spring.MyAspect" />
-
使用aop:config标签配置aop(将切面、切入点、通知结合到一起)
- 定义切入点表达式
- aop:aspect
- 引用外部定义的切面bean
- 配置通知,引用切入点表达式
<aop:config> <!-- 切入点表示匹配com.lanou3g.spring包下的所有类中所有以oneDay开头的方法,方法的参数、返回值不限 --> <aop:pointcut id="myPointcut" expression="execution(* com.lanou3g.spring..*.oneDay*(..))" /> <aop:aspect ref="myAspect"> <!-- 无论是否出现异常,只要被切入的方法开始运行,都会触发此通知 --> <aop:before method="wakeup" pointcut-ref="beforeOneDay" /> <!-- 无论是否出现异常,只要被切入的方法运行结束,都会触发此通知 --> <aop:after method="goToBed" pointcut-ref="beforeOneDay" /> <!-- 可以最大限度的对被切入方法附加功能,在方法执行前、后都可以通知(无论是否出现异常) ,还可以获取到被切入方法的所有信息,包括是否调用被切入的方法 --> <aop:around method="aroundAdvice" pointcut-ref="beforeOneDay" /> <!-- 被切入的方法正常返回值以后,会触发此通知 --> <aop:after-returning method="afterRetuing" pointcut-ref="beforeOneDay" returning="message" /> <!-- 被切入的方法抛出异常以后,会触发此通知,并且不会触发after-returning --> <aop:after-throwing method="afterThrowing" pointcut-ref="beforeOneDay" throwing="ex" /> </aop:aspect> </aop:config>
基于注解方式使用
-
开启AOP注解支持
方式一:注解的方式
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
方式二:xml中开启
<aop:aspectj-autoproxy/>
- 定义切面类
/** * 该切面用来插入起床的逻辑 */ @Aspect @Component //@Aspect注解没有将bean交给ioc容器管理的功能 public class MyAspect { @Before("com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()") public void wakeup() { System.out.println("[前置通知]我刚学习SpringAOP睡着了,刚才谁打了我一下?"); } @After("com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()") public void goToBed() { System.out.println("[后置通知]SpringAOP太难了,一不小心又睡着了"); } @AfterReturning(value = "com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()", returning = "message") public void afterRetuing(Object message) { System.out.println("[后置返回通知]方法执行已经return了,方法返回值是:" + message); } @AfterThrowing(value = "com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()", throwing = "ex") public void afterThrowing(Throwable ex) { System.out.println("[后置异常通知]方法执行出现异常,异常原因:" + ex.getMessage()); } /** * 环绕通知 * 可以接受一个ProceedingJoinPoint参数 * 通过此参数可以获取到被切入方法的所有信息 * 还可以通过此参数来决定是否调用目标方法 */ // @Around("com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()") public Object aroundAdvice(ProceedingJoinPoint joinPoint) { // 连接点参数可以获取到被切入方法的所有信息 // 这里演示了如何获取被切入方法的名称 String targetMethodName = joinPoint.getSignature().getName(); System.out.println("[环绕通知]被切入的方法名:" + targetMethodName); // System.out.println("[环绕通知]即将开始新的一天, 早起的鸟儿有虫吃!"); Object ret = null; try { ret = joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("[环绕通知]这真是充实的一天, 早睡早起,方能养生!"); return ret; } }
注意:@Aspect注解没有将bean交给ioc容器管理的功能,我们需要额外添加一个@Component注解
- 定义切入点
官方建议我们将所有的切入点统一定义到一个地方管理,在配置通知时通过引入的方式来使用。方便后期维护(一处修改,处处生效)
@Component public class MyPointcut { // 通过@Pointcut注解定义一个切入点 @Pointcut("execution(* oneDay(..))") public void allOneDayMehtod() {} }
- 在切面类中添加要切入的代码
参见定义切面部分
- 在切入的代码方法上添加通知的注解
参见定义切面部分