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注解异步日志记录的资料请关注Devmax其它相关文章!

SpringBoot 自定义注解异步记录复杂日志详解的更多相关文章

  1. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Ajax简单的异步交互及Ajax原生编写

    一提到异步交互大家就会说ajax,仿佛ajax这个技术已经成为了异步交互的代名词.那下面将研究ajax的核心对象

  3. 一种angular的方法级的缓存注解(装饰器)

    本篇文章主要介绍了一种angular的方法级的缓存注解(装饰器),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. SpringBoot本地磁盘映射问题

    这篇文章主要介绍了SpringBoot本地磁盘映射问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  5. java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源)

    这篇文章主要介绍了java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源),文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下

  6. SpringBoot整合Javamail实现邮件发送的详细过程

    日常开发过程中,我们经常需要使用到邮件发送任务,比方说验证码的发送、日常信息的通知等,下面这篇文章主要给大家介绍了关于SpringBoot整合Javamail实现邮件发送的详细过程,需要的朋友可以参考下

  7. SpringBoot详细讲解视图整合引擎thymeleaf

    这篇文章主要分享了Spring Boot整合使用Thymeleaf,Thymeleaf是新一代的Java模板引擎,类似于Velocity、FreeMarker等传统引擎,关于其更多相关内容,需要的小伙伴可以参考一下

  8. Spring详细讲解@Autowired注解

    @Autowired注解可以用在类属性,构造函数,setter方法和函数参数上,该注解可以准确地控制bean在何处如何自动装配的过程。在默认情况下,该注解是类型驱动的注入

  9. 使用AJAX完成用户名是否存在异步校验

    这篇文章主要介绍了使用AJAX完成用户名是否存在异步校验的相关资料,需要的朋友可以参考下

  10. Springboot集成mybatis实现多数据源配置详解流程

    在日常开发中,若遇到多个数据源的需求,怎么办呢?通过springboot集成mybatis实现多数据源配置,简单尝试一下,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

随机推荐

  1. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  3. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  4. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  5. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  6. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  7. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  8. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  9. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Spring JdbcTemplate执行数据库操作详解

    JdbcTemplate是Spring框架自带的对JDBC操作的封装,目的是提供统一的模板方法使对数据库的操作更加方便、友好,效率也不错,这篇文章主要介绍了Spring JdbcTemplate执行数据库操作,需要的朋友可以参考下

返回
顶部