SpringBoot 自定义注解异步记录复杂日志详解

目录
  • 1、背景
  • 2、技术方案-自定义注解
    • 2.1 注解介绍
    • 2.2 元注解
    • 2.3 实现自定义注解
  • 3、技术方案-AOP切面
    • 3.1 AOP术语解析
    • 3.2 切入点表达式
    • 3.3 ADVICE通知类型
    • 3.4 技术实现
    • 3.5 相关操作
  • 4、高级操作

1、背景

最近接手一个任务,需要给当前项目加一个较为复杂的日志。有多复杂呢? 要有日志类型、不同日志类型要有不同的操作和备注等。作为小白的我最开始的做法是在业务层写代码记录日志,好处就是方便,坏处就是这种做法直接侵袭Service层,Service层无法做到职责单一了。

经导师点拨,改用自定义注解+AOP切面异步日志

2、技术方案-自定义注解

注解(Annotation),也叫元数据。

2.1 注解介绍

注解其实就是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。

2.2 元注解

元注解用于修饰其他注解的注解,在JDK5.0中提供了四种元注解:Retention、Target、Documented、Inherited

(1) Retention介绍: 用于修饰注解,用于指定修饰的哪个注解的声明周期。@Rentention包含一个RetentionPolicy枚举类型的成员变量,使用@Rentention时必须为该value成员变量指定值

  • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释,在.class文件中不会保留注解信息
  • RetentionPolicy.CLASS:在class文件中有效(即class保留),保留在.class文件中,但是当运行java程序时,他就不会继续加载了,不会保留在内存中,JVM不会保留注解。如果注解没有加Retention元注解,那么相当于默认的注解就是这种状态。
  • RetentionPolicy.RUNTIME:在运行有效(即运行时保留),当运行Java程序时,JVM会保留注释,加载在内存中,那么程序可以通过反射获取该注释。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}

(2)Target 介绍: 用于修饰注解的注解,定义当前注解能够修饰程序中的哪些元素(类、属性、方法,构造器等等)

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

(3)Documented 介绍: 用于指定被该元注解修饰的注解类将被javadoc工具提取成文档。默认情况下,javadoc是不包括注解的,但是加上这个注解生成的文档中就会带着注解了

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

(4)Inherited 介绍:被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

注解的基础知识基本介绍完毕,我们开始写起来吧!!!

2.3 实现自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Log {
    // 日志类型
    LogType logType() ;
    // 操作类型
    OperateType operate();
    // 备注
    String note() default "";
}

3、技术方案-AOP切面

AOP切面编程一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展, 往往用于实现 日志处理,权限控制,性能检测,事务控制等。AOP实现的原理就是动态代理。在有接口的情况下,使用JDK动态代理,在没有接口的情况下使用cglib动态代理。

3.1 AOP术语解析

(1) Joint point:类里面那些可以被增强的方法,这些方法被称之为连接点

(2) PointCut:实际被增强的方法,这些方法被称之为连接点

(3) Advice:实际增加的逻辑部分称为通知

(4) Target:被增强功能的对象(被代理的对象)

(5) Aspect:Aspect 声明类似与Java中的类声明,在Aspect中会包含着一些PointCut以及相应的Advice.

(6) Weaving:创建代理对象并实现功能增强的声明并运行过程将Aspect和其他对象连接起来,并创建Adviced object的过程

3.2 切入点表达式

切入点表达式:通过一个表达式来确定AOP要增强的是哪个或者哪些方法.

3.3 ADVICE通知类型

(1)前置通知:@Before 执行前置通知,并通过JointPoint获取方法中的参数

@Aspect
@Component
public class DaoAspect {
	/*
	前置通知:在执行addUser方法之前,执行前置通知,并通过JointPoint获取addUser()方法中的参数
	*/
    @Before("execution(* com.xzit.dao.Impl.UserDaoImpl.addUser(..))")
    public void methodBefore(JoinPoint joinPoint){
        System.out.println("methodBefore invoked ... ...");
        Object[] args = joinPoint.getArgs();
        System.out.println(Arrays.toString(args));
    }
}

(2)后置通知:@After 切点方法是否出现异常,后置通知都会执行

(3)返回通知:@AfterReturning 切点出现异常,返回通知不会执行

(4)异常通知:@AfterThrowing 切点方法出现异常就执行,不出现异常就不执行

(5)环绕通知:@Around 在切点方法之前和之后执行。是@Before和@AfterReturing 相结合

3.4 技术实现

根据任务背景,我选择了返回通知@AfterReturning。使用返回通知一定要注意的是:由于我需要用到返回值,所以会用@AfterReturning注解中的returning属性来获取方法的返回值

  • returning指定的名称必须和切面方法参数中的名称相同。例如在下面代码中指定的"cId"都是相同的
@AfterReturning(pointcut = "@annotation(com.xxx.xxx.xxx.Log)",
        returning = "cId")
public void handleRdLogs(JoinPoint joinPoint, int cId)
  • 切面方法参数中的参数类型必须和方法返回类型一致
@AfterReturning(pointcut = "@annotation(com.xxx.xxx.xxx.Log)",
        returning = "cId")
public void handleRdLogs(JoinPoint joinPoint, int cId) {
    // 获取方法签名
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    // 获取@Log注解内容
    if (!methodSignature.getMethod().isAnnotationPresent(Log.class)) {
        log.error("获取注解@Log失败");
        throw new Exception("获取注解@Log失败");
    }
    Log log = methodSignature.getMethod().getAnnotation(Log.class);
    // 输出日志的备注
    System.out.println(log.note())
}

3.5 相关操作

(1) 获取方法签名

MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();`

(2)根据方法签名获取自定义注解

Log log = methodSignature.getMethod().getAnnotation(Log.class);

(3)根据自定义注解获取,注解内部的参数

System.out.println(log.note())

4、高级操作

场景:不仅需要获取返回值,还得知道方法参数的值,而且方法参数的值不能作为返回值,这个该怎么办呢?

秀操作开始:

第一步: 在注解上写 "#" + "方法参数的名"

@Log(note = "#note")
public int rdAuditReturn(String note) {
     System.out.println(note)
     xxxx.....
     xxxx.....
     业务代码.....
     xxxx.....
     xxxx....
    return cId;
}

第二步: 实现切面,只要调用这个方法,就可以得到note的值了

private final ExpressionParser parser = new SpelExpressionParser();
private final EvaluationContext evaluationContext = new StandardEvaluationContext();
private void getNote(JoinPoint joinPoint, StringBuilder noteBuilder, String note) throws NoSuchMethodException {
    if (!StringUtils.isBlank(note)) {
        Class<?> targetCls = joinPoint.getTarget().getClass();
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        String[] parameterNames = pnd.getParameterNames(targetMethod);
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; ++i) {
            int index = i;
            Optional.ofNullable(args[i]).ifPresent(param -> {
                String paramName = parameterNames[index];
                this.evaluationContext.setVariable(paramName, param);
            });
        }
        Optional.ofNullable(this.parser.parseExpression(note).getValue(this.evaluationContext)).ifPresent(k ->
                noteBuilder.append((String) k)
        );
    }
}

以上就是SpringBoot 自定义注解异步记录复杂日志详解的详细内容,更多关于SpringBoot注解异步日志记录的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringBoot用@Async注解实现异步任务

    什么是异步调用? 异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行. 如何实现异步调用? 多线程,这是很多人第一眼想到的关键词,没错,多线程就是一种实现异步调用的方式. 在非spring目项目中我们要实现异步调用的就是使用多线程方式,可以自己实现Runable接口或者集成Thread类,或者使用jdk1.5以上提供了的Executors线程池. StrngBoot中则提供了很方便的方式执行异步调

  • springboot @Async 注解如何实现方法异步

    目录 @Async注解如何实现方法异步 一.springboot的App类需要的注解 二.service层的注解 三.调用层 异步注解@Async的使用以及注意事项 第一步开启异步 下面显示配置线程的代码实现 使用@Async导致异步不成功的情况 @Async注解如何实现方法异步 处理大批量数据的时候,效率很慢.所以考虑一下使用多线程. 刚开始自己手写的一套,用了线程池启动固定的线程数进行跑批.但是后来老大考虑到自己手写的风险不好控制,所以使用spring的方法. 这里没有详细介绍,只有简单的d

  • 详解springboot使用异步注解@Async获取执行结果的坑

    目录 一.引言 二.获取异步执行结果 1.环境介绍 2.错误的方式 3.正确方式 三.异步执行@Async注解 四.总结 一.引言 在java后端开发中经常会碰到处理多个任务的情况,比如一个方法中要调用多个请求,然后把多个请求的结果合并后统一返回,一般情况下调用其他的请求一般都是同步的,也就是每个请求都是阻塞的,那么这个处理时间必定是很长的,有没有一种方法可以让多个请求异步处理那,答案是有的. springboot中提供了很便利的方式可以解决上面的问题,那就是异步注解@Async.正确的使用该注

  • 详解springboot通过Async注解实现异步任务及回调的方法

    目录 前言 什么是异步调用? 1. 环境准备 2. 同步调用 3. 异步调用 4. 异步回调 前言 什么是异步调用? 异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行.异步调用可以减少程序执行时间. 1. 环境准备 在 Spring Boot 入口类上配置 @EnableAsync 注解开启异步处理.创建任务抽象类 AbstractTask,并实现三个任务方法 doTaskOne(),doTas

  • SpringBoot使用Async注解失效原因分析及解决(spring异步回调)

    目录 Async注解失效原因分析及解决(spring异步回调) Spring中@Async 有时候在使用的过程中@Async注解会失效 解决方式一 解决方式二 springboot @Async 失效可能原因 Async注解失效原因分析及解决(spring异步回调) Spring中@Async 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的:但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在spring 3.x之后,就已

  • SpringBoot 自定义注解异步记录复杂日志详解

    目录 1.背景 2.技术方案-自定义注解 2.1 注解介绍 2.2 元注解 2.3 实现自定义注解 3.技术方案-AOP切面 3.1 AOP术语解析 3.2 切入点表达式 3.3 ADVICE通知类型 3.4 技术实现 3.5 相关操作 4.高级操作 1.背景 最近接手一个任务,需要给当前项目加一个较为复杂的日志.有多复杂呢? 要有日志类型.不同日志类型要有不同的操作和备注等.作为小白的我最开始的做法是在业务层写代码记录日志,好处就是方便,坏处就是这种做法直接侵袭Service层,Service

  • JSP 自定义注解及记录操作日志

    JSP 自定义注解及记录操作日志 Spring的配置文件 <aop:aspectj-autoproxy /> 日志拦截器 package com.vem.interceptor; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.anno

  • Springboot任务之异步任务的使用详解

    一.SpringBoot--异步任务 1.1 什么是同步和异步 同步是阻塞模式,异步是非阻塞模式. 同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会-直等待下去,知道收到返回信息才继续执行下去 异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态.当有消息返回式系统会通知进程进行处理,这样可以提高执行的效率. 1.2 Java模拟一个异步请求(线程休眠) AsyncService.java package com.tian.async

  • Java中自定义注解介绍与使用场景详解

    注解的概念及分类 1.首先我们来看一下什么是注解: 注解就是某种注解类型的一个实例,我们可以用它在某个类上进行标注,这样编译器在编译我们的文件时,会根据我们自己设定的方法来编译类. 2.注解的分类 注解大体上分为三种:标记注解,一般注解,元注解,@Override用于标识,该方法是继承自超类的.这样,当超类的方法修改后,实现类就可以直接看到了.而@Deprecated注解,则是标识当前方法或者类已经不推荐使用,如果用户还是要使用,会生成编译的警告. 本文主要介绍的是关于Java自定义注解,下面话

  • springboot通过注解、接口创建定时任务详解

    目录 springboot中定时任务的创建 springboot通过注解创建定时任务 首先引入pom 直接上代码来一个栗子 @Scheduled注解的各个参数 springboot通过注接口创建定时任务 实现接口SchedulingConfigurer 主要方法 总结 项目中经常会用到定时任务,有的人在用quartz,有的人可能自己搭建了一套调度平台,springboot对于定任务的支持,让定时任务的创建变得简单,今天来说说springboot中定时任务的创建. springboot中定时任务的

  • SpringBoot应用自定义logback日志详解

    目录 概述 logback配置详解 配置内容概念介绍 配置介绍 SpringBoot中自定义logback 多环境输出日志文件 读取配置文件配置 概述 默认情况下,SpringBoot内部使用logback作为系统日志实现的框架,将日志输出到控制台,不会写到日志文件.如果在application.properties或application.yml配置,这样只能配置简单的场景,保存路径.日志格式等.复杂的场景(区分 info 和 error 的日志.每天产生一个日志文件等)满足不了,只能自定义配

  • springboot使用自定义注解实现aop切面日志

    平时我们在开发过程中,代码出现bug时为了更好的在服务器日志中寻找问题根源,会在接口的首尾打印日志,看下参数和返回值是否有问题.但是手动的logger.info() 去编写时工作量较大,这时我们可以使用AOP切面,为所有接口的首尾打印日志. 实现AOP切面日志一般有两种方式: 1.拦截所有接口controller,在首尾打印日志2.拦截指定注解的接口,为有该注解的接口首尾打印日志 我们尝试用自定义注解来实现AOP日志的打印,这样拥有更高的灵活性.废话不多说,我们开始 1. 导入切面需要的依赖包

  • springboot扫描自定义的servlet和filter代码详解

    这几天使用spring boot编写公司一个应用,在编写了一个filter,用于指定编码的filter,如下: /** * Created by xiaxuan on 16/11/1. */ @WebFilter(urlPatterns = "/*",filterName="CharacterEncodeFilter", initParams={ @WebInitParam(name="encoding",value="UTF-8&qu

  • 使用Springboot自定义注解,支持SPEL表达式

    目录 Springboot自定义注解,支持SPEL表达式 1.自定义注解 2.使用AOP拦截方法,解析注解参数 自定义注解结合切面和spel表达式 自定义一个注解 自定义一个service类,在需要拦截的方法上加上@Log注解 写一个自定义切面 pom文件的依赖 测试 增加内容 Springboot自定义注解,支持SPEL表达式 举例,自定义redis模糊删除注解 1.自定义注解 import java.lang.annotation.ElementType; import java.lang.

  • SpringBoot自定义注解开发指南

    目录 一.Java注解(Annotation) 1.JDK基本注解 2.JDK元注解 二.自定义注解开发 1.含义 2.演示 三.完成切面日志操作 四.完成前端响应反应 总结 一.Java注解(Annotation) 含义:Java注解是附加在代码中的一些元信息,用于一些工具在编译. 运行时进行解析和使用,起到说明.配置的功能. 1.JDK基本注解 @Override ——>重写 @Deprecated ——>已过时 @SuppressWarnings(value = "unchec

随机推荐