SpringBoot利用AOP记录系统日志

2024-03-10 02:59

本文主要是介绍SpringBoot利用AOP记录系统日志,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、AOP的基本概念

利用Spring框架中aop,我们可以实现业务代码与系统级服务进行解耦,例如日志记录、事务及其他安全业务等,可以使得我们的工程更加容易维护、优雅。
Advice(通知、切面): 某个连接点所采用的处理逻辑,也就是向连接点注入的代码, AOP在特定的切入点上执行的增强处理。
@Before: 标识一个前置增强方法,相当于BeforeAdvice的功能.
@After: final增强,不管是抛出异常或者正常退出都会执行.
@AfterReturning: 后置增强,似于AfterReturningAdvice, 方法正常退出时执行.
@AfterThrowing: 异常抛出增强,相当于ThrowsAdvice.
@Around: 环绕增强,相当于MethodInterceptor.
JointPoint(连接点):程序运行中的某个阶段点,比如方法的调用、异常的抛出等。
Pointcut(切入点): JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式。
Advisor(增强): 是PointCut和Advice的综合体,完整描述了一个advice将会在pointcut所定义的位置被触发。
@Aspect(切面): 通常是一个类的注解,里面可以定义切入点和通知
AOP Proxy:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。

二、什么是注解?

它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观、更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
  Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。

三、注解的用处:

生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
跟踪代码依赖性,实现替代配置文件功能。比如Dagger 2 依赖注入,未来java 开发,将大量注解配置,具有很大用处;
在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

四、注解的原理

注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法

4.1、元注解

所有元注解定义在java.lang.annotation包下面,其中Annotation是注解的基本接口,所有的注解都继承这个接口。
java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
1、@Documented:指定被标注的注解会包含在javadoc中。
2、@Retention: 指定注解的生命周期(源码、class文件、运行时),其参考值见类的定义:java.lang.annotation.RetentionPolicy
RetentionPolicy.SOURCE :注解只保留在源文件(.java文件))在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。(注解被保留到class文件,但jvm加载class文件时候被遗弃,默认值(.class文件))
RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
(注解被保存到class文件中,jvm加载class文件之后仍然存在(内存中的字节码))
生命周期长度 SOURCE < CLASS < RUNTIME
3、@Target:指定注解使用的目标范围(类、方法、字段等),其参考值见类的定义:java.lang.annotation.ElementType
ElementType.CONSTRUCTOR :用于描述构造器。
ElementType.FIELD :成员变量、对象、属性(包括enum实例)。
ElementType.LOCAL_VARIABLE: 用于描述局部变量。
ElementType.METHOD : 用于描述方法。
ElementType.PACKAGE :用于描述包。
ElementType.PARAMETER :用于描述参数。
ElementType.ANNOTATION_TYPE:用于描述参数
ElementType.TYPE :用于描述类、接口(包括注解类型) 或enum声明。
4、@Inherited:指定子类可以继承父类的注解,只能是类上的注解,方法和字段的注解不能继承。即如果父类上的注解是@Inherited修饰的就能被子类继承。
jdk1.8又提供了以下两个元注解
5、@Native:指定字段是一个常量,其值引用native code。

6、@Repeatable:注解上可以使用重复注解,即可以在一个地方可以重复使用同一个注解,像spring中的包扫描注解就使用了这个。

7、使用@interface关键词来定义注解。

4-2、自定义注解

自定义注解类编写的一些规则:
Annotation 类型定义为@interface, 所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口。
参数成员只能用public 或默认(default) 这两个访问权修饰。语法:类型 属性名() [default 默认值]; default表示默认值 ,也可以不编写默认值的.
参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法。
注解也可以没有定义成员,,不过这样注解就没啥用了。
注意: 自定义注解需要使用到元注解。
注解方法不能有参数。
注解方法的返回类型局限于原始类型,字符串,枚举,注解,或以上类型构成的数组。
注解方法可以包含默认值。

4-3、 自定义注解使用:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedAuth {String value() default "";
}

五、通过AOP实现日志记录

了解@AspectJ切点函数之execution()
execution()是最常用的切点函数,语法如下:

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

其中:返回类型模式、方法名模式、参数模式是必选项
通过execution()定义切点的不同方式
1.通过方法签名定义切点

execution(public * *(..)) 

匹配所有目标类的public方法。 第一个代表返回类型,第二个代表方法名,而…代表任意入参的方法

execution(* *To(..))

匹配目标类所有以To为后缀的方法。 第一个代表返回类型,而To代表任意以To为后缀的方法,而…代表任意入参的方法。
2.通过类定义切点

execution(* com.aop.spring.advisor.aspectJ.function.execution.classpoint.Cleaner.*(..))

3.通过类包定义切点

execution(* com.jiuyue.service.impl..*.*(..))
符号含义
execution()表达式的主体
第一个”*“符号表示返回值的类型任意
com.jiuyue.service.implAOP所切的服务的包名,即,我们的业务部分
包名后面的”…“表示当前包及子包
第二个”*“表示类名,*即所有类
.*(…)表示任何方法名,括号表示参数,两个点表示任何参数类型
在类名模式串中: 
.* 表示包下的所有类。
..*表示包、子孙包下的所有类。
execution(* com.test.*(..))

匹配com.test包下所有类的所有方法

execution(* com.test..*(..))

匹配com.test包、子孙包下所有类的所有方法.比如 com.jiuyue.dao ,com.test.dao.impl,com.test.service,com.test.service.impl包下所有类的所有方法都匹配。 当 …出现在类名中时,必须后面跟*表示子孙包下的所有类。

execution(* com..*Dao.find*(..))

匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀, 比如com.test.UserDao#findUserById()方法都是匹配切点。

通过方法入参定义切点
切点表达式中方法入参部分比较复杂,可以使用“”和“…”通配符,其中“”表示任意类型的参数,而“…”表示任意类型参数且参数个数不限。

execution(* joke(String,int))

匹配joke(String,int)方法,且joke方法的第一个入参是String,第二个入参是int。 比如 匹配 SmartSeller#joke(String ,int)方法。 如果方法中的入参类型是java.lang包下的,这可以直接使用类名,否则必须使用全限定类名,比如 joke(java.util.List,int)

execution(* joke(String,*))

匹配目标类中的joke()方法,该方法第一个入参为String,第二个入参为任意类型。 比如 joke(String s1, String s2)和joke(String s1,double d)都匹配,但是 joke(String s1, String s2,double d3)不匹配。

execution(* joke(String,..))

匹配目标类中的joke方法,该方法的第一个入参为String,后面可以有任意个入参且入参类型不限。 比如 joke(String s1),joke(String s1,String s2)和joke(String s1,double d2,String s3)都匹配。

execution(* joke(Object+))

匹配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该类的子类。 它匹配joke(String s1) 和joke(Client c) . 如果定义的切点是execution(* joke(Object)) ,则只匹配joke(Object object)而不匹配joke(String s1) 或者joke(Client c)。

六、具体代码实现

NeedAuth 自定义注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedAuth {String value() default "";
}

LogAspect.java

import groovy.util.logging.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Component
@Slf4j
@Aspect
public class LogAspect {/*** JointPoint(连接点):程序运行中的某个阶段点,比如方法的调用、异常的抛出等。* Pointcut(切入点):   JoinPoint的集合,是程序中需要注入Advice的位置的集合,* 指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式。*/
//    @Pointcut("@annotation(cn.test.dis.adapter.server.service.custom.annotation.NeedAuth)")@Pointcut("execution(* cn.test.dis.adapter.server.service.custom.test.service.SpringAopTestService.*(..))")public void logPoint(){}@Before("logPoint()")public void beforeNotify() {System.out.println("******** @Before我是前置通知MyAspect");}@After("logPoint()")public void afterNotify() {System.out.println("******** @After我是后置通知");}@AfterReturning("logPoint()")public void afterReturningNotify() {System.out.println("********@AfterReturning我是返回后通知");}@AfterThrowing("logPoint()")public void afterThrowingNotify() {System.out.println("********@AfterThrowing我是异常通知");}@Around("logPoint()")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {Object retValue = null;System.out.println("我是环绕通知之前AAA");retValue = proceedingJoinPoint.proceed();MethodSignature sign = (MethodSignature) proceedingJoinPoint.getSignature();Method method = sign.getMethod();System.out.println("method:" + method.getName());System.out.println("我是环绕通知之后BBB");return retValue;}
}

SpringAopTestController.java

@RestController
public class SpringAopTestController{@AutowiredSpringAopTestService springAopTestService;public void aopTest(){springAopTestService.message();}public void anoMessage(){springAopTestService.anoMessage();}
}
import cn.test.dis.adapter.server.common.config.DouyinConfig;
import cn.test.dis.adapter.server.service.custom.annotation.NeedAuth;
import org.springframework.boot.SpringBootVersion;
import org.springframework.core.SpringVersion;
import org.springframework.stereotype.Service;import javax.annotation.Resource;@Service
public class SpringAopTestService {@Resourceprivate DouyinConfig douyinConfig;//增加配置类public void message(){System.out.println("执行方法message");}@NeedAuthpublic void anoMessage(){System.out.println("spring版本:" + SpringVersion.getVersion() + "\t" + "SpringBoot版本:" + SpringBootVersion.getVersion());System.out.println("执行方法needAuth");int a = 1/0;}
}

执行结果:

在这里插入图片描述
在这里插入图片描述

Spring版本不一样,通知执行顺序可能也会存在差异

spring4的几种通知的执行顺序:

程序执行正常:1、环绕通知前
2@Before通知
3、程序逻辑
4、环绕通知后
5@After通知
6@AfterReturning通知程序执行异常:1、环绕通知前
2@Before通知
3@After通知
4@AfterThrowing异常通知
异常日志

spring5的几种通知的执行顺序:

程序执行正常:1、环绕通知前
2@Before通知
3、程序逻辑
4@AfterReturning通知
5@After通知
6、环绕通知后程序执行异常:1、环绕通知前
2@Before通知
3@AfterThrowing异常通知
4@After通知
异常日志

这篇关于SpringBoot利用AOP记录系统日志的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/792812

相关文章

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Spring Boot 配置文件之类型、加载顺序与最佳实践记录

《SpringBoot配置文件之类型、加载顺序与最佳实践记录》SpringBoot的配置文件是灵活且强大的工具,通过合理的配置管理,可以让应用开发和部署更加高效,无论是简单的属性配置,还是复杂... 目录Spring Boot 配置文件详解一、Spring Boot 配置文件类型1.1 applicatio

Java中StopWatch的使用示例详解

《Java中StopWatch的使用示例详解》stopWatch是org.springframework.util包下的一个工具类,使用它可直观的输出代码执行耗时,以及执行时间百分比,这篇文章主要介绍... 目录stopWatch 是org.springframework.util 包下的一个工具类,使用它

Java进行文件格式校验的方案详解

《Java进行文件格式校验的方案详解》这篇文章主要为大家详细介绍了Java中进行文件格式校验的相关方案,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、背景异常现象原因排查用户的无心之过二、解决方案Magandroidic Number判断主流检测库对比Tika的使用区分zip

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义