SpringBoot(二十九)SpringBoot-AOP切面类

93 阅读6分钟

上文中我们了解了java中JDK和CGLIB两种动态代理的实现。

 

动态代理在Springboot中最大的应用就是AOP切面编程。

 

AOP其实我们在自定义注解中就已经应用过了。但是那只是简单的应用,这里我们来详细了解一下AOP编程。

 

AOP一种面向切面的编程思想。面向切面编程是将程序抽象成各个切面,即解剖对象的内部,将那些影响了多个类的公共行为抽取到一个可重用模块里,减少系统的重复代码,降低模块间的耦合度,增强代码的可操作性和可维护性。

 

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理、增强处理。

 

一:引入POM依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 

二:定义一个切面类

我这里就定义一个切面类,定义跟注解相关的切面类,请移步《SpringBoot(二十六)SpringBoot自定义注解

import com.alibaba.fastjson.JSONArray;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.JoinPoint;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ControllerAspect {

    /**
     * 定义一个切点,匹配controller包下的所有方法
     */
    @Pointcut("execution(* com.modules.controller..*.*(..)) ")
    public void controllerMethods() {}

    /**
     * 在切点方法执行前执行(这个方法 不需要返回值)
     * @param joinPoint
     */
    @Before("controllerMethods()")
    public void beforeControllerMethod(JoinPoint joinPoint)
    {
        System.out.println("进入了切面类的before");
        // 这里可以编写你想在方法执行前调用的代码
        System.out.println(joinPoint);
        Object[] args = joinPoint.getArgs();
        for (Object arg : args)
        {
            System.out.println("参数:" + arg);
        }
    }

    /**
     * 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("controllerMethods()")
    private Object testAop(ProceedingJoinPoint point) throws Throwable
    {
        System.out.println("======AopAspectJ执行环绕通知开始=========");
        Object obj = point.proceed();
        Object[] args = point.getArgs();
        //方法名
        String methodName = point.getSignature().getName();
        //对象
        Object target = point.getTarget();
        //类名
        String className = target.getClass().getName();
        System.out.println("类:" + className + ";方法:" + methodName + ";参数:" + JSONArray.toJSONString(args));
        System.out.println("======AopAspectJ执行环绕通知结束=========");
        return obj;
    }

    /**
     * 在切点方法执行后执行
     * @param joinPoint
     */
    @AfterReturning("controllerMethods()")
    public void afterControllerMethod(JoinPoint joinPoint)
    {
        // 这里可以编写你想在方法执行后调用的代码
        System.out.println("进入了切面类的after");
    }
}

通过上方的代码,我们可以发现,定义一个切面类其实很简单,使用@Aspect、@Component注解定义就可以了。

 

上方的切面类匹配的是所有controller包下的所有方法:

我们随意访问一个方法。

http://127.0.0.1:7001/java/index/getData

控制台输出:

======AopAspectJ执行环绕通知开始=========
进入了切面类的before
execution(Map com.modules.controller.fontend.IndexController.getData(Integer,String))
参数:1
参数:
进入了index方法!
进入了切面类的after
类:com.modules.controller.fontend.IndexController;方法:getData;参数:[1,""]
======AopAspectJ执行环绕通知结束=========

 

三:常用AOP注解

1:通知方法、注解

1)      切面(Aspect):一般是指被@Aspect修饰的类,代表着某一具体功能的AOP逻辑。

2)      切入点(Pointcut):选择对哪些方法进行增强。

3)      通知(Advice):对目标方法的增强,有一下五种增强的类型。

²  环绕通知(@Around):内部执行方法,可自定义在方法执行的前后操作。

²  前置通知(@Before):在方法执行前执行。

²  后置通知(@After):在方法执行后执行。

²  返回通知(@AfterReturning):在方法返回后执行。

²  异常通知(@AfterThrowing):在方法抛出异常后执行。

4)      连接点(JoinPoint):就是那些被切入点选中的方法。这些方法会被增强处理。

 

2:匹配表达式

表达式类型功能
execution()匹配方法,最全的一个
args()匹配入参类型
@args()匹配入参类型上的注解
@annotation()匹配方法上的注解
within()匹配类路径
@within()匹配类上的注解
this()匹配类路径,实际上AOP代理的类
target()匹配类路径,目标类
@target()匹配类上的注解

 

切点表达式实例:

execution(方法修饰符 返回类型 方法所属的包.类名.方法名称(方法参数))

括号外的 ‘..’ 表示多级目录

括号内的 ‘..’ 表示多个参数

 

例子:在AspectJ中,execution是一个常用的切入点表达式,用于匹配特定的方法执行。它可以根据方法的修饰符、返回类型、所属的包、类名以及方法名称等信息来定义切入点。以下是几个使用execution表达式的例子,以及对它们的解释:

 

1:匹配特定类中的方法:

execution(* com.example.MyClass.myMethod(..))

*这个表达式匹配com.example包中MyClass类里的myMethod方法。表示任何修饰符,..表示任何参数。

 

2:匹配任何返回类型的方法:

execution(* *.methodName(..))

这里第一个代表任何返回类型,第二个代表任何类,methodName是你想要匹配的方法名。

 

3:匹配特定包及子包下所有类的方法:

execution(* com.example.*.*(..))

这个表达式匹配com.example包及其所有子包中所有类的方法。

 

4:匹配特定修饰符的方法:

execution(public * com.example.MyClass.myMethod(..))

这个表达式仅匹配公开(public)的myMethod方法。

 

5:匹配特定参数类型的方法:

execution(* com.example.MyClass.myMethod(java.lang.Stringint))

这个表达式匹配MyClass中接受String和int作为参数的myMethod方法。

 

6:匹配继承自特定类的类中的方法:

execution(* com.example.*+.myMethod(..))

+表示匹配继承自com.example包中任何类的myMethod方法。

 

7:匹配注解了特定注解的方法:

execution(@com.example.MyAnnotation * *(..))

这个表达式匹配任何被@com.example.MyAnnotation注解的方法。

 

8:匹配特定异常类型的抛出:

execution(* *.*(..) throws java.lang.Exception)

这个表达式匹配任何可能抛出java.lang.Exception或其子类异常的方法。

 

9:组合使用多个条件:

execution(public * com.example.service.*.*(..))

这个表达式匹配com.example.service包中公开的任何类的方法。

 

在AspectJ中,execution表达式提供了强大的灵活性,允许你定义非常具体或非常广泛的切入点,以实现面向切面编程(AOP)的各种需求。

 

四:AOP注解失效的场景:

1:Spring的AOP只能拦截由Spring容器管理的Bean对象。如果您使用了非受Spring管理的对象,则AOP将无法对其进行拦截。注解也不会生效。

 

2:如果一个Bean内部的方法直接调用同一个Bean内部的另一个方法,AOP将无法拦截这个内部方法调用。因为AOP是基于代理的,只有通过代理对象才能触发AOP拦截。(@Transactional事务注解也是同理)

 

3:私有方法调用,Spring的AOP只能拦截public方法。

 

4:静态方法

Spring的AOP只能拦截非静态方法。如果您尝试拦截静态方法,AOP将无法生效。

 

5:final方法

AOP无法拦截final方法。final方法是不可重写的,因此AOP无法生成代理对象来拦截这些方法。直接在对象内部调用方法:如果您直接在对象内部调用方法而不通过代理对象,AOP将无法拦截。因此,建议始终通过代理对象调用方法以确保AOP的生效

 

6:异步方法

对于使用Spring的异步特性(如@Async注解)的方法,AOP拦截器可能无法正常工作。这是因为异步方法在运行时会创建新的线程或使用线程池,AOP拦截器无法跟踪到这些新线程中的方法调用。

 

以上大概就是AOP切面编程的基本使用。

 

有好的建议,请在下方输入你的评论。