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

相关文章

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick

Spring WebClient从入门到精通

《SpringWebClient从入门到精通》本文详解SpringWebClient非阻塞响应式特性及优势,涵盖核心API、实战应用与性能优化,对比RestTemplate,为微服务通信提供高效解决... 目录一、WebClient 概述1.1 为什么选择 WebClient?1.2 WebClient 与

Java.lang.InterruptedException被中止异常的原因及解决方案

《Java.lang.InterruptedException被中止异常的原因及解决方案》Java.lang.InterruptedException是线程被中断时抛出的异常,用于协作停止执行,常见于... 目录报错问题报错原因解决方法Java.lang.InterruptedException 是 Jav