Springboot实战:AOP日志实现详细教程(附git源码下载)

Springboot-cli 开发脚手架系列



简介

  • AOP面向切面编程
    是通过预编译方式和运行期动态代理,实现在不修改源代码的情况下给程序动态统一添加功能的一种技术,同时是对OOP(面向对象编程)的补充和完善,常被用来在spring中实现日志记录、性能监控等功能。
    面向对象实现日志记录,性能监控这些功能时,需要在每个对象中都添加相同的方法,这样就产生了较大的重复工作量和大量的重复代码,不利于维护,使用AOP,可以大大减少代码数量,方便维护。

  • AOP实现原理
    Spring 实现AOP思想使⽤的是动态代理技术
    默认状况下, Spring会根据被代理对象是否实现接⼝来选择使⽤JDK仍是CGLIB。当被代理对象没有实现
    任何接⼝时, Spring会选择CGLIB。当被代理对象实现了接⼝, Spring会选择JDK官⽅的代理技术,不过能够经过配置的⽅式,让Spring强制使⽤CGLIB

  • 面向切面编程的步骤
    接下我们以实战案例讲解AOP的实现步骤

1. 依赖

  • pom.xml
      <!-- aop依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--    json 工具包 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
       </dependency>

2. 定义注解

  • 自定义一个注解CustomLog.java接口。
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface CustomLog {

    /**
     * value 描述
     */
    String value() default "";

}
  • @Retention注解的生命周期
    1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
    2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
    3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
  • @Target:注解的作用目标
    @Target(ElementType.TYPE)——接口、类、枚举、注解
    @Target(ElementType.FIELD)——字段、枚举的常量
    @Target(ElementType.METHOD)——方法
    @Target(ElementType.PARAMETER)——方法参数
    @Target(ElementType.CONSTRUCTOR) ——构造函数
    @Target(ElementType.LOCAL_VARIABLE)——局部变量
    @Target(ElementType.ANNOTATION_TYPE)——注解
    @Target(ElementType.PACKAGE)——包

3. 定义切面

  • 日记实体封装SysLog.java
@Data
@Accessors(chain = true)
public class SysLog implements Serializable {

    private static final long serialVersionUID = -6309732882044872298L;

    /**
     * 日记id
     */
    private Long logId;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 包名
     */
    private String packageName;

    /**
     * 执行时间
     */
    private Long executionTime;

    /**
     * 方法名
     */
    private String method;

    /**
     * 参数
     */
    private String params;

    /**
     * 说明
     */
    private String desc;

    /**
     * 创建日期
     */
    private Date createTime;
}
  • 切面
@Aspect
@Component
@Slf4j
public class LogAspect {

    /**
     * 植入触发条件
     */
    @Pointcut("@annotation(com.springboot.cli.annotation.CustomLog)")
    public void pointcut() {

    }
    
    /**
     * 环绕增强,相当于MethodInterceptor
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        SysLog sysLog = this.getMethodInfo(joinPoint);
        long start = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        long end = System.currentTimeMillis();
        sysLog.setCreateTime(new Date())
                .setExecutionTime(end - start);
        log.info(JSON.toJSONString(sysLog));
        return proceed;
    }
    
    /**
     * 获取方法执行信息
     */
    private SysLog getMethodInfo(ProceedingJoinPoint joinPoint) {
        SysLog sysLog = new SysLog();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        CustomLog customLog = method.getAnnotation(CustomLog.class);
        // 注解上的描述
        Optional.ofNullable(customLog).ifPresent(c -> sysLog.setDesc(c.value()));
        try {
            sysLog
                    .setMethod(joinPoint.getSignature().getName())
                    .setPackageName(joinPoint.getTarget().getClass().getName())
                    .setParams(JSON.toJSONString(this.getParameters(joinPoint)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sysLog;
    }

    /**
     * 获取请求参数
     */
    private Map<String, Object> getParameters(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] parameterValues = joinPoint.getArgs();
        Map<String, Object> paramsMap = new HashMap<>(2);
        for (int i = 0; i < parameterValues.length; i++) {
            try {
                Object s = parameterValues[i];
                paramsMap.put(parameterNames[i], s);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return paramsMap;
    }

    /**
     * 获取 Request
     */
    private HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
    }
}
  • @Aspect:作用是把当前类标识为一个切面供容器读取

  • @Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。

  • @Around:环绕增强,相当于MethodInterceptor

  • @AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行

  • @Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有

  • @AfterThrowing:异常抛出增强,相当于ThrowsAdvice

  • @After: final增强,不管是抛出异常或者正常退出都会执行

4. 效果演示

  • 编写测试接口IndexController.java
  • 在需要生成日记的方法上添加@CustomLog注解
@RestController
public class IndexController {

    /**
     * 基础web
     */
    @GetMapping("/test")
    @CustomLog("基础web")
    public String test(String msg) {
        return "欢迎使用 springboot-cli !";
    }

}
  • 浏览器输入http://localhost:9999/test?msg=hello
    在这里插入图片描述
  • 可以看到添加@CustomLog注解后,日记就自动生成了,这种规则的日记,在实际开发中对于日记的收集和分析是很有帮助的。

6. 源码分享

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
当然可以!以下是一个使用Spring Boot实现AOP的简单示例: 首先,在pom.xml文件中添加Spring AOPSpring Boot Starter依赖: ```xml <dependencies> <!-- Spring AOP --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies> ``` 接下来,创建一个切面类,用于定义切点和增强逻辑: ```java @Aspect @Component public class LoggingAspect { // 定义切点 @Pointcut("execution(* com.example.demo.service..*(..))") public void serviceMethods() {} // 在切点前执行 @Before("serviceMethods()") public void logBefore(JoinPoint joinPoint) { System.out.println("Before method: " + joinPoint.getSignature().getName()); } // 在切点后执行 @After("serviceMethods()") public void logAfter(JoinPoint joinPoint) { System.out.println("After method: " + joinPoint.getSignature().getName()); } } ``` 在上面的示例中,我们通过`@Pointcut`注解定义了一个切点,该切点匹配所有位于`com.example.demo.service`包及其子包下的方法。然后,我们使用`@Before`和`@After`注解分别定义了在切点前后执行的增强逻辑。 最后,在Spring Boot应用程序的入口类上添加`@EnableAspectJAutoProxy`注解,启用自动代理: ```java @SpringBootApplication @EnableAspectJAutoProxy public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` 这是一个简单的示例,它会在每次调用`com.example.demo.service`包及其子包下的方法之前和之后打印方法名。你可以根据自己的需求扩展和定制切面。 希望这可以帮助到你!如有更多问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈小定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值