Spring-AOP
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的注解支持
@EnableAspectJAutoProxy
xml配置文件
使用aop:aspectj-autoproxy标签开启对AspectJ的注解支持
<aop:aspectj-autoproxy/>
接入点
try{
// 前置
// 目标方法执行
// 返回
}catch(){
// 异常
}finally{
// 后置
}
增强类(通知)
为增强类配置两个注解:
- @Comonent:增强类同样要被IoC容器接管才可以生效
- @Aspect:配置切面:切点+增强类
@Comonent
@Aspect
增强方法
创建增强方法后,为每一个增强方法加注解确定切点位置
- 前置通知:@Before
- 返回通知:@AfterReturning
- 异常通知:@AfterThrowing
- 后置通知:@After
- 环绕通知:@Around
我们需要获取到增强的目标方法的信息:方法名、参数、访问修饰符、所属类的信息
在增强方法的形参中,添加(JoinPoint joinPoint),该参数为目标方法的封装对象,此形参适用于所有增强方法
Signature中文释义:签名
@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),该参数为目标方法的返回值
@AfterReturning(value = "execution(* com..impl.*.*(..))",returning = "returnValue")
public void afterReturning(Object returnValue) {
}
在出现异常(AfterThrowing)后,获取目标方法的异常信息
通过@AfterThrowing注释的属性throwing配置接受异常信息的形参名
在形参列表中加(Throwable throwable),该参数为目标方法的异常信息
@AfterThrowing(value = "execution(* com..impl.*.*(..))",throwing = "throwable")
public void afterThrowing(Throwable throwable) {
}
切点表达式
AOP切点表达式(Pointcut Expression)是一种用于指定切点的语言
它可以通过定义匹配规则,来选择需要被切入的目标对象
在增强方法的注解之中,写切点表达式,表明在何位置进行代码的织入
具体写法
excution中文释义:执行
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,切点表达式作为注解的值
@Pointcut("execution(* com..impl.*.*(..))")
public void pointcut() {}
将增强方法的注解的切点表达式值,改为引用此方法
@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类用以存储切点
@Component
public class MyPointcut {
@Pointcut("execution(* com..impl.*.*(..))")
public void pointcut() {}
}
使用全类名+方法名的方式应用切点表达式
@Component
@Aspect
public class MyAdvice {
@Before("com.xiaobai.Pointcut.MyPointcut.pointcut()")
public void before(JoinPoint joinPoint) {
……
}
……
}
环绕通知
个人理解:环绕通知的写法和动态代理的写法就很相似了
与其他增强方法不同点
- ProceedingJoinPoint接口下的joinPoint对象多了一个proceed方法,这个方法用以执行目标方法
- 环绕通知方法需要有返回值,该方法的返回值是目标方法的返回值
使用@Around注解表示环绕通知
@Component
@Aspect
public class AroundAdvice {
@Around("com.xiaobai.Pointcut.MyPointcut.pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs(); // 通过jointPoint获取方法的参数
Object result = null;
try {
// 增强代码 -> before
result = joinPoint.proceed(args); // 调用proceed执行方法
// 增强代码 -> afterReturning
} catch (Throwable e) {
throw new RuntimeException(e);
// 增强代码 -> afterThrowing
} finally {
// 增强代码 -> After
}
return result;
}
}
增强优先级
当有多个增强作用与同一个目标时,我们针对于增强类设置优先级
使用@Order注解,标识增强类的优先级
@Order(1) -> @Order(10),数字越小,优先级越高
优先级别越高,离核心代码越近,反之越小
使用cglib底层代理
如果存在于接口的话,SpringAOP框架底层默认使用JDK动态代理的方式
当没有接口的时候,SpringAOP框架底层默认使用cglib动态代理的方式
cglib会为目标类生成代理类为其子类,这样就不用受迫于接口的限制
注:因为没有接口,所以在测试类注入时是直接注入目标类类型的代理对象,而不再是接口类型
注意事项
当开启AOP时,存储到IoC容器里的不再是实现类本身的对象,而是代理增强类的对象
当底层使用JDK动态代理时,自动装配需要使用接口类型,而不能用实现类型,因为实现类没有对象存储到IoC容器中
XML方式实现AOP框架
<?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-->
<aop:config>
<!-- @Pointcut-->
<aop:pointcut id="pointcut" expression="execution(* com..impl.*.*(..))"/>
<!-- @Aspect + @Order(1)-->
<aop:aspect ref="myAdvice" order="1">
<!-- @Before("pointcut()")-->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- @AfterReturning(value = "pointcut()",returning = "returnValue")-->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="returnValue"/>
<!-- @AfterThrowing(value = "pointcut()",throwing = "throwable")-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"/>
<!-- @After("pointcut()")-->
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>