欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring学习之使用注解配置AOP

程序员文章站 2022-05-24 23:43:45
...

Spring学习之使用注解配置AOP

前言

在前面的学习中,基本了解了AOP的概念,以及在使用原始的方法在Spring中配置AOP,不过在前面我们也看到了,通过编程的方式来实现AOP是比较繁琐,而且扩展性比较低了,所以Spring对AOP的配置方式进行了很大的改进,并且提供了更加方便的配置方式,本小节主要来学习使用注解进行配合,这是在使用Spring AOP中最常使用的方式了。

切点函数

在前面的内容中,我们看到了在配置增强的时候,很难进行切点的配置,所以,接下来我们首先来看下Spring AOP的一个重大的改进,使用切点表达式来描述切点

一个切点表达式或者称之为切点函数主要由两个部分组成:关键字 + 操作参数,其中的关键字,也称之为切点函数,主要由以下四种类型

切点函数类型

  • 方法切点函数:用于匹配方法
    • execution():比较常使用
      • 入参:匹配方法的串
      • 说明:满足指定模式的所有方法连接点
    • @annotation()
      • 入参:注解类型
      • 说明:匹配标注了该注解的连接点
  • 方法入参切点函数:用于匹配方法的参数
    • args()
      • 入参:类名
      • 说明:匹配入参类型的连接点
    • @args()
      • 入参:注解
      • 说明:匹配入参类型为标志了该注解的连接点
  • 目标类切点函数:用于匹配对应的目标类而不是代理类
    • within()
      • 入参:类名模式串
      • 说明:参数类型只能指定到类级别,包含子类,不能指定方法,其余同execution
    • target()
      • 入参:类名
      • 说明:类型匹配的类下的所有方法,但不包含子类
    • @within()
      • 入参:注解
      • 说明:匹配所有标注该注解的类的类的所有方法
    • @target()
      • 入参:注解
      • 说明:目标对象标注了该注解,不包含子类
  • 代理类切点函数:用于匹配对应的代理类而不是目标类
    • @this()
      • 入参:类名
      • 说明:匹配当前代理对象的类型,而不是目标类型

除了切入点函数外,还有一些符号需要了解

  • *:表示匹配任意一个元素,比如,cn.xuhuanfeng.service.*表示匹配该包下的所有类,但是不包含子包
  • ..*:表示匹配包以及所有的子包
  • ..:如果用于参数,则表示任意参数,比如cn.xuhuanfeng.service.UserService.save(..)表示该包下的UserService类的含有任意参数的save方法
  • +:只能用于类,表示对应的类及其子类

切入点表达式的基本格式:访问限制符(public/protected/private) 方法返回类型(int/double/char/...) 包.类.方法(参数)

接下来,我们来通过一些具体的表达式例子来更好地理解上面的内容

首先假定有如下类型


public class UserService {

    public void login(){
        System.out.println("login...");
    }

    protected void logout(){
        System.out.println("logout...");
    }

    protected int login(int number){
        System.out.println("login in..." + number);
        return number;
    }

    public int loout(int number){
        System.out.println("logout ... " + number);
        return number;
    }
}

表达式以及对应的匹配方法如下所示

  • public void cn.xuhuanfeng.aop.anno.UserService.login(..) 匹配的方法为public void login()
  • public * cn.xuhuanfeng.aop.anno.UserService.login(..) 匹配的方法为pulic int login()
  • * cn.xuhuanfeng.aop.anno.UserService.login(..) 匹配的方法为public int login()以及protected int login()
  • * cn.xuhuanfeng.aop.anno.UserService.*() 匹配上面的所有的方法
  • * cn.xuhuanfeng.aop.anno.UserService.*(int) 匹配上面含有一个int类型参数的方法
  • * cn.xuhuanfeng.aop.anno.*.*(..) 匹配anno包下的任意类的任意方法
  • * cn.xuhuanfeng.aop.anno.UserService+.*(..) 匹配anno包下的UserService类及其子类的所有方法
  • * cn.xuhuanfeng.aop..*UserService.*(..) 匹配anno包及其子包下UserService类的任意方法

可以看到,通过切入点表达式,可以非常方便地描述任意类型的切点了,也就是说,通过这种方式,以及解决了之前无法很好地描述切点的问题了

注解配置AOP实例

接下来通过几个具体的实例来使用上面所提到的切入点表达式以及对应的切点方法


    // 使用 @Before表示在方法执行前进行增强
    // 使用execution拦截方法
    // 对应的表达式表示具体的拦截方法
    @Before("execution(* cn.xuhuanfeng.aop..*.UserService+.*(..))")
    public void addLog(){
        System.out.println("info....");
    }

    // 使用 @AfterReturning表示在方法返回后进行增强
    // 用@annotation拦截标注了Deprecated的方法
    @AfterReturning("@annotation(Deprecated)")
    public void log(){

    }

除了上面的@Before@AfterReturning还有@After@AfterThrowing@Around 等用于表示切点方位的注解,基本上都见名知义

初次之外,还有一个用于标注切点的注解@Pointcut,用于在方法上标注,并且提供对应的表达式,表示一个命名的切点,名称为函数名,这样,一个切点就可以在多处引用了


    // 声明切点
    @Pointcut("execution(* cn.xuhuanfeng.aop..*.UserService+.*(..))")
    public void pointcut(){}

    // 引用切点
    @Before("pointcut()")
    public void logging(){

    }

经过上面的配置,基本上切面就配置好了,不过,此时是如果进行测试会发现,增强并没有起作用,原因在于,还需要将对应的增强配置到Spring容器中,配置如下


@Component // 方便IoC容器对其进行管理
@Aspect // 声明为切面
class LogManager{
    // 省略上面的内容
}

然后在Spring配置文件中,添加一下配置

 
 <!--开启Bean自动扫描-->
 <context:component-scan base-package="cn.xuhuanfeng.aop.anno"/>
 <!--开启注解自动配置-->
 <aop:aspectj-autoproxy/>

经过上面的配置之后,所配置的增强就可以正常的工作了

除了简单的切入点函数外,Spring还支持通过逻辑运算符(&&、|| 、!)来组合切入点函数,组成复合表达式

// 通过逻辑运算符组成复合表达式,不过此时相应的参数要求也就发生变化了,这里需要注意一下
@Before("execution(* cn.xuhuanfeng.aop..*.UserService+.*(..)) && args(int)")
    public void addLog(){
        System.out.println("info....");
    }

参数绑定

有时候还需要在增强中获取目标方法的参数,此时可以有两种方式来获取,下面直接通过代码进行演示

通过JointPoint来获取


    @Before("execution(* cn.xuhuanfeng.aop..*.UserService+.*(..))")
    public void addLog(JoinPoint joinPoint){
        joinPoint.getArgs();  // 获得参数
        joinPoint.getTarget(); // 获得对应的目标对象
        joinPoint.getThis(); // 获得当前代理对象
        joinPoint.getSignature(); // 获得方法签名
    }

也可以直接声明所需要的参数,不过这里的匹配规则需要注意


    @Before("execution(* cn.xuhuanfeng.aop..*.UserService+.*(..)) && args(id, id2)")
    public void addLog(int id, int id2){
        /*
            匹配的规则如下:
                首先表达式中的参数名称匹配增强方法中参数的名称,如果匹配,
                    则表达式中的参数的类型为增强方法中匹配的参数的类型
                    然后在目标方法的参数中根据类型进行匹配,然后将对应的值绑定到增强方法参数中
         */
    }

同样这里需要注意的是,execution 是不能用于指定参数的,也就是说,如果要使用exection进行匹配的话,必须为其增加多一个args()函数,用于作为参数的绑定,其他的方位注解则可以用于绑定相应的参数

总结

本小节主要了解了切入点表达式的具体内容,以及相应的写法,还学习了在Spring中通过注解的方式配置AOP,并且学习了参数的绑定以及对应的注意事项