Spring AOP 应用

Spring AOP 应用

1. 介绍

AOP:面向切面编程,对面向对象编程的一种补充。

AOP可以将一些公用的代码,自然的嵌入到指定方法的指定位置。

比如:

如上图,我们现在有四个方法,我们想在每个方法执行一开始,输出一个日志信息。但是这样做很麻烦,如果有100个、1000个方法,工作量会很大,而且难以维护。这时候就可以通过AOP进行解决。


2. 案例实战

2.1 需求分析及环境搭建

环境:SpringBoot + SpringBoot Web + SpringBoot AOP。

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

目标:控制器业务方法,统一进行日志输出。

新建User类,包含idname属性。

新建UserController

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/list")
    public List<User> list(){
        return Arrays.asList(
                new User(1,"张三"),
                new User(2,"李四"),
                new User(3,"王五")
        );
    }
    @GetMapping("/getById/{id}")
    public User getById(@PathVariable("id") Integer id){
        return new User(id,"张三");
    }
    @GetMapping("/deleteById/{id}")
    public boolean deleteById(@PathVariable("id") Integer id){
        return true;
    }
}

此时,我们的目标就是使用AOP的方式,给这个listdeleteByIdgetById方法加上日志。

日志要包括调用方法的名称、返回值以及参数列表。

2.3 AOP实现

1. 首先我们要让AOP知道哪些方法需要被AOP处理 -> 通过注解方式进行处理

java 复制代码
// 定义一个注解,来标记需要添加日志的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
    String value() default "";
}

定义好注解后,给需要使用日志的方法添加注解,如:

java 复制代码
@LogAnnotation("查询用户")   // 标记目标方法
@GetMapping("/getById/{id}")
public User getById(@PathVariable("id") Integer id){
    return new User(id,"张三");
}

2. 实现切面任务

新建LogAspect类,这就是生成切面对象的类。我们需要用@Component注解进行标注,交给IOC容器进行管理。此外,我们要用@Aspect注解标注其为一个切面。

然后,我们要将这个切面与我们刚刚标注的@LogAnnotation注解建立联系,让切面知道从哪个位置进行切入。实现的方法为,新建一个方法,然后给这个方法添加@Pointcut("@annotation(自定义注解的全类名)")。这样就成功建立的联系。

确定切入点后,我们就可以写切面的实际任务了。新建一个方法around。此时,我们要将确定切点的方法与切面实际处理任务的方法进行关联。实现的方法为,给实际处理任务的方法添加@Around("标记切点的方法名()")注解。

此时,我们只有一个around方法,要用这一个方法对listgetByIddeleteById三个方法进行处理。那么around方法如何分辨这三个不同的方法呢?这时就需要用到一个连接点对象ProceedingJoinPointaround的返回值为object类型,其要返回所切入方法的返回值。

然后,就可以实现日志输出功能了。

java 复制代码
@Aspect
@Component
@Slf4j
public class LogAspect {
    @Pointcut("@annotation(cn.codewei.aopstudy.annotation.LogAnnotation)")
    public void logPointCut() {
    }
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 方法名称
        String name = point.getSignature().getName();
        // 通过反射 获取注解中的内容
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
        String value = annotation.value();
        // 输出日志
        log.info("方法名称:{}, 方法描述: {}, 返回值: {}, 参数列表: {}", name, value, point.proceed(), point.getArgs());
        // 返回切入方法的返回值
        return point.proceed();
    }
}

@Around、@Before、@After区别

  • @Before前置通知,是在所拦截方法执行之前执行一段逻辑,返回值类型为void。
  • @After 后置通知,是在所拦截方法执行之后执行一段逻辑,返回值类型为void。
  • @Around 环绕通知,是可以同时在所拦截方法的前后执行一段逻辑,用这个注解的方法入参传的是ProceedingJoinPoint,返回结果类型为Object,返回结果为ProceedingJoinPoint对象的.proceed();

3. @Pointcut

  • 使用within表达式匹配

​ 匹配com.leo.controller包下所有的类的方法

java 复制代码
@Pointcut("within(com.leo.controller..*)")
public void pointcutWithin(){
}
  • this匹配目标指定的方法,此处就是HelloController的方法
java 复制代码
@Pointcut("this(com.leo.controller.HelloController)")
public void pointcutThis(){
}
  • target匹配实现UserInfoService接口的目标对象
java 复制代码
@Pointcut("target(com.leo.service.UserInfoService)")
public void pointcutTarge(){
}
  • bean匹配所有以Service结尾的bean里面的方法

    注意:使用自动注入的时候默认实现类首字母小写为bean的id

java 复制代码
@Pointcut("bean(*ServiceImpl)")
public void pointcutBean(){
}
  • args匹配第一个入参是String类型的方法
java 复制代码
@Pointcut("args(String, ..)")
public void pointcutArgs(){
}
  • @annotation匹配是@Controller类型的方法
java 复制代码
@Pointcut("@annotation(org.springframework.stereotype.Controller)")
public void pointcutAnnocation(){
}
  • @within匹配@Controller注解下的方法,要求注解的@Controller级别为@Retention(RetentionPolicy.CLASS)
java 复制代码
@Pointcut("@within(org.springframework.stereotype.Controller)")
public void pointcutWithinAnno(){
}
  • @target匹配的是@Controller的类下面的方法,要求注解的@Controller级别为@Retention(RetentionPolicy.RUNTIME)
java 复制代码
@Pointcut("@target(org.springframework.stereotype.Controller)")
public void pointcutTargetAnno(){
}
  • @args匹配参数中标注为@Sevice的注解的方法
java 复制代码
@Pointcut("@args(org.springframework.stereotype.Service)")
public void pointcutArgsAnno(){
}
  • 使用excution表达式
java 复制代码
@Pointcut(value = "execution(public * com.leo.controller.HelloController.hello*(..))")
public void pointCut() {
}
相关推荐
考虑考虑18 小时前
Jpa使用union all
java·spring boot·后端
用户37215742613518 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊19 小时前
Java学习第22天 - 云原生与容器化
java
渣哥21 小时前
原来 Java 里线程安全集合有这么多种
java
间彧21 小时前
Spring Boot集成Spring Security完整指南
java
间彧1 天前
Spring Secutiy基本原理及工作流程
java
Java水解1 天前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆1 天前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学1 天前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole1 天前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端