Spring-SpringAOP
如果想使用注解的方式配置SpringAOP,需要导入以下依赖
- spring-aop:SpringAOP框架核心依赖,此依赖被spring-context导入,无需额外导入
- spring-aspectj:SpringAOP和AspectJ注解层联合依赖,需要导入
- aspectj:Aspect J注解使用所需核心依赖,此依赖被spring-aspectj导入,无需额外导入
Aspect J是早期AOP实现的框架,SpringAOP借用了AspectJ中AOP的注解
开启AspectJ注解支持
配置类*
在配置类中,使用@EnableAspectJAutoProxy注解开启AspectJ的注解支持
xml配置文件
使用aop:aspectj-autoproxy标签开启对AspectJ的注解支持
1
| <aop:aspectj-autoproxy/>
|
接入点
1 2 3 4 5 6 7 8 9
| try{ }catch(){ }finally{ }
|
增强类(通知)
为增强类配置两个注解:
- @Comonent:增强类同样要被IoC容器接管才可以生效
- @Aspect:配置切面:切点+增强类
增强方法
创建增强方法后,为每一个增强方法加注解确定切点位置
- 前置通知:@Before
- 返回通知:@AfterReturning
- 异常通知:@AfterThrowing
- 后置通知:@After
- 环绕通知:@Around
我们需要获取到增强的目标方法的信息:方法名、参数、访问修饰符、所属类的信息
在增强方法的形参中,添加(JoinPoint joinPoint),该参数为目标方法的封装对象,此形参适用于所有增强方法
Signature中文释义:签名
1 2 3 4 5 6 7 8 9 10 11 12
| @Before("execution(* com..impl.*.*(..))") public void before(JoinPoint joinPoint) { String classname = joinPoint.getTarget().getClass().getName(); String name = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); int modifiers = joinPoint.getSignature().getModifiers(); String s = Modifier.toString(modifiers); }
|
在增强方法(AfterReturning)后,获取目标方法的执行结果
通过@AfterReturning注释的属性returning配置接受返回结果的形参名
在形参列表中加(Object returnValue),该参数为目标方法的返回值
1 2 3 4
| @AfterReturning(value = "execution(* com..impl.*.*(..))",returning = "returnValue") public void afterReturning(Object returnValue) {
}
|
在出现异常(AfterThrowing)后,获取目标方法的异常信息
通过@AfterThrowing注释的属性throwing配置接受异常信息的形参名
在形参列表中加(Throwable throwable),该参数为目标方法的异常信息
1 2 3 4
| @AfterThrowing(value = "execution(* com..impl.*.*(..))",throwing = "throwable") public void afterThrowing(Throwable throwable) {
}
|
切点表达式
AOP切点表达式(Pointcut Expression)是一种用于指定切点的语言
它可以通过定义匹配规则,来选择需要被切入的目标对象
在增强方法的注解之中,写切点表达式,表明在何位置进行代码的织入
具体写法
excution中文释义:执行
1
| execution(public int com.xiaobai.spring.aop.target.Calculator.div(int,int))
|
- execution:关键字,固定格式
- public:类的访问修饰符
- 如果不考虑访问修饰符和返回值,这两位整合成一起,写成*
- int:方法的返回值
- com.xiaobai.spring.aop.target:包名
- 单层模糊:com.xiaobai.spring.aop.*,包含到aop包的所有包
- 多层模糊:com..impl 任意层的模糊,不能以..开头,这句话的意思是找到所有层的impl的包
- 查询全部包:*..(单层模糊+多层模糊)
- Calculator:类名
- 模糊:*,即不考虑具体的类,表示包含当前包下的所有类
- 部分模糊:*Impl,表示寻找当前包下以Impl结尾的所有类
- div:方法名
- ():参数列表
- 没有参数:()
- 有具体参数:(int , String)
- 模糊参数:(..)
- 部分模糊:
- (String..):以String开头,后面有没有都可以
- (..int):以int结尾,前面有没有都可以
- (String..int):以String开头,以int结尾,中间有没有都可以
当在增强类上使用@Aspect切面注解之后,切点表达式带自动补全功能
切点表达式的重用
切点表达式的提取和复用
提取到当前增强类
新建一个方法,在方法上使用注解@Pointcut,切点表达式作为注解的值
1 2
| @Pointcut("execution(* com..impl.*.*(..))") public void pointcut() {}
|
将增强方法的注解的切点表达式值,改为引用此方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Component @Aspect public class MyAdvice { @Pointcut("execution(* com..impl.*.*(..))") public void pointcut() {}
@Before("pointcut()") public void before(JoinPoint joinPoint) { }
@AfterReturning(value = "pointcut()",returning = "returnValue") public void afterReturning(Object returnValue) { } @AfterThrowing(value = "pointcut()",throwing = "throwable") public void afterThrowing(Throwable throwable) { } @After("pointcut()") public void after(){ } }
|
切点表达式的存储类
创建Pointcut包 -> 建立MyPointcut类用以存储切点
1 2 3 4 5
| @Component public class MyPointcut { @Pointcut("execution(* com..impl.*.*(..))") public void pointcut() {} }
|
使用全类名+方法名的方式应用切点表达式
1 2 3 4 5 6 7 8 9
| @Component @Aspect public class MyAdvice { @Before("com.xiaobai.Pointcut.MyPointcut.pointcut()") public void before(JoinPoint joinPoint) { …… } …… }
|
环绕通知
个人理解:环绕通知的写法和动态代理的写法就很相似了
与其他增强方法不同点
- ProceedingJoinPoint接口下的joinPoint对象多了一个proceed方法,这个方法用以执行目标方法
- 环绕通知方法需要有返回值,该方法的返回值是目标方法的返回值
使用@Around注解表示环绕通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Component @Aspect public class AroundAdvice { @Around("com.xiaobai.Pointcut.MyPointcut.pointcut()") public Object around(ProceedingJoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); Object result = null; try { result = joinPoint.proceed(args); } catch (Throwable e) { throw new RuntimeException(e); } finally { } return result; } }
|
增强优先级
当有多个增强作用与同一个目标时,我们针对于增强类设置优先级
使用@Order注解,标识增强类的优先级
@Order(1) -> @Order(10),数字越小,优先级越高
优先级别越高,离核心代码越近,反之越小
使用cglib底层代理
如果存在于接口的话,SpringAOP框架底层默认使用JDK动态代理的方式
当没有接口的时候,SpringAOP框架底层默认使用cglib动态代理的方式
cglib会为目标类生成代理类为其子类,这样就不用受迫于接口的限制
注:因为没有接口,所以在测试类注入时是直接注入目标类类型的代理对象,而不再是接口类型
注意事项
当开启AOP时,存储到IoC容器里的不再是实现类本身的对象,而是代理增强类的对象
当底层使用JDK动态代理时,自动装配需要使用接口类型,而不能用实现类型,因为实现类没有对象存储到IoC容器中
XML方式实现AOP框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xiaobai"/>
<aop:aspectj-autoproxy/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com..impl.*.*(..))"/>
<aop:aspect ref="myAdvice" order="1">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="returnValue"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"/>
<aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
|