SpringAOP-Complete-Demo(5 types of notifications)

目录

一、导入依赖。

二、自定义注解。(精准标记切入点)

三、定义核心AOP切面类。(@Aspect)

四、controller、service层。

五、测试请求。


  • springboot3.4.9+openJdk17.0.16+maven。
  • 本篇博客案例:使用spring-aop完成日志记录为例。

一、导入依赖。

  • spring-web。请求实现。
  • spring-aop。切面实现。
XML 复制代码
   <dependencies>
        <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>
    </dependencies>

二、自定义注解。(精准标记切入点)

  • @Loggable。
java 复制代码
package com.hyl.bean;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 * 标记被AOP拦截
 */
@Target(ElementType.METHOD)  // 注解作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
public @interface Loggable {
    String description() default ""; // 操作描述
    boolean logParameters() default true;  // 是否记录参数
}

三、定义核心AOP切面类。(@Aspect)

  • @Pointcut。标记切入点,即注解@Loggable。

  • @Before。前置通知。
  • @After。后置通知。
  • @AfterReturning。返回通知。
  • @AfterThrowing。异常通知。
  • @Around。环绕通知。
java 复制代码
package com.hyl.aop;

import com.hyl.bean.Loggable;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

@Aspect
@Component
public class LogAspect {

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    //使用ConcurrentHashMap缓存SimpleDateFormat
    private static final  ConcurrentHashMap<String,ThreadLocal<SimpleDateFormat>> formatCache = new ConcurrentHashMap<>();

    //获取指定格式的线程安全的SimpleDateFormat
    private static SimpleDateFormat getSimpleDateFormat(String pattern){
        //获取新建或缓存的SimpleDateFormat
        //get+put(先 get 判断是否为null,再put)
        ThreadLocal<SimpleDateFormat> sdfThreadLocal = formatCache.computeIfAbsent(pattern, k -> ThreadLocal.withInitial(() -> new SimpleDateFormat(pattern)));
        return sdfThreadLocal.get();  //返回当前线程的SimpleDateFormat实例
    }

    /**
     * 切入点:匹配所有被@Loggable注解标记的方法
     */
    @Pointcut("@annotation(loggable)")
    public void logPointcut(Loggable loggable){}

    /**
     * 前置通知
     * @param joinPoint 切入点
     * @param loggable 自定义注解
     */
    @Before(value = "logPointcut(loggable)", argNames = "joinPoint,loggable") //argNames明确参数的名称顺序
    public void beforeAdvice(JoinPoint joinPoint, Loggable loggable){
        String methodName = joinPoint.getSignature().toShortString();
        String agrs = Arrays.toString(joinPoint.getArgs());
        log.info("【前置通知】方法:{},操作描述:{}", methodName, loggable.description());
        if(loggable.logParameters()){  //可记录参数
            log.info("【前置通知】方法参数:{}", agrs);
        }
    }

    /**
     * 后置通知 目标方法执行后执行(无论是否发生异常)
     * @param joinPoint 切入点
     * @param loggable 自定义注解
     */
    @After(value = "logPointcut(loggable)", argNames = "joinPoint,loggable")
    public void afterAdvice(JoinPoint joinPoint, Loggable loggable){
        String methodName = joinPoint.getSignature().toShortString();
        log.info("【后置通知】方法:{},执行结束,操作描述:{}", methodName, loggable.description());
    }

    /**
     * 返回通知:目标方法正常返回时执行
     * @param joinPoint 切入点
     * @param loggable 自定义注解
     * @param result 目标方法返回值
     */
    @AfterReturning(pointcut = "logPointcut(loggable)",returning = "result", argNames = "joinPoint,loggable,result")
    public void afterReturningAdvice(JoinPoint joinPoint, Loggable loggable, Object result){
        String methodName = joinPoint.getSignature().toShortString();
        log.info("【返回通知】方法:{},正常返回,结果:{}", methodName, result);
    }

    /**
     * 异常通知:目标方法发生异常时执行
     * @param joinPoint 切入点
     * @param loggable 自定义注解
     * @param ex 目标方法抛出的异常
     */
    @AfterThrowing(pointcut = "logPointcut(loggable)",throwing = "ex", argNames = "joinPoint,loggable,ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Loggable loggable, Exception ex){
        String methodName = joinPoint.getSignature().toShortString();
        log.error("【异常通知】方法:{},执行异常,异常信息:{}", methodName, ex.getMessage(), ex);
    }

    /**
     * 环绕通知:包围目标方法执行
     * @param proceedingJoinPoint 切入点(joinPoint子类)
     * @param loggable 自定义注解
     * @return 目标方法返回值
     * @throws Throwable proceed()
     */
    @Around(value = "logPointcut(loggable)", argNames = "proceedingJoinPoint,loggable")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, Loggable loggable) throws Throwable {
        String methodName = proceedingJoinPoint.getSignature().toShortString();
        long startTime = System.currentTimeMillis();  //记录开始时间
        String startTimeText = getSimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(startTime);
        log.info("【环绕通知】方法:{},开始执行,当前时间:{}", methodName, startTimeText);
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
        }catch (Exception e){
            log.error("【环绕通知】方法:{},执行异常:", methodName, e);
        }finally {
            long endTime = System.currentTimeMillis(); //记录结束时间
            log.info("【环绕通知】方法:{},执行结束,耗时:{}ms", methodName, (endTime - startTime));
        }
        return result;
    }
}

四、controller、service层。

  • TestController。
java 复制代码
package com.hyl.controller;

import com.hyl.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("aop")
public class TestController {

    @Autowired
    private TestService testService;

    /**
     * 测试正常逻辑请求
     * @param name 姓名
     * @param age 年龄
     * @return 响应结果
     */
    @GetMapping("/normal")
    public String normalMethod(@RequestParam String name,@RequestParam int age){
        return testService.normalMethod(name, age);
    }

    /**
     * 测试可能出现异常的请求
     * @param a 参数a
     * @param b 参数b
     * @return 响应结果
     */
    @GetMapping("/exception")
    public String exceptionMethod(@RequestParam int a,@RequestParam int b){
        try {
            int result = testService.exceptionMethod(a, b);
            return "请求正常,计算结果:"+result;
        }catch (Exception e){
            return "请求异常:"+e.getMessage();
        }
    }

    /**
     * 测试不带参数的请求
     * @return 响应结果
     */
    @GetMapping("/noParam")
    public String noParamMethod(@RequestParam String str){
        return testService.noParamMethod(str);
    }

}
  • TestService。
java 复制代码
package com.hyl.service;

public interface TestService {
    String normalMethod(String name, int age);

    int exceptionMethod(int a, int b);

    String noParamMethod(String str);
}
  • TestServiceImpl。
java 复制代码
package com.hyl.service.impl;

import com.hyl.bean.Loggable;
import com.hyl.service.TestService;
import org.springframework.stereotype.Service;

@Service
public class TestServiceImpl implements TestService {

    @Loggable(description = "执行正常逻辑", logParameters = true)
    @Override
    public String normalMethod(String name, int age) {
        return "hello "+name+"!age is:"+age;
    }

    @Loggable(description = "执行可能抛出异常的业务逻辑", logParameters = true)
    @Override
    public int exceptionMethod(int a, int b) {
        if(b==0){
            throw new ArithmeticException("除数不能为0");
        }
        return a/b;
    }

    @Loggable(description = "执行不记录参数逻辑", logParameters = false)
    @Override
    public String noParamMethod(String str) {
        return "处理了敏感信息:"+ str;
    }
}

五、测试请求。