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>