Spring Aop
2021-03-23 本文已影响0人
lclandld
因为项目中有使用AOP,所有就整理一个出来,相当于自己也再学习一遍。
Controller上的一个切面:是否需要记录日志
项目中的具体需求是,对每个接口发生异常的情况的数据,要详细的记录到数据库中的日志表中,日志表中的字段如下
- description 接口功能描述
- actionArgs 方法参数
- className类名称
- methodName方法名称
- ip ip地址
- modelName 模块名称
- action操作
- succeed 是否成功 1:成功 2异常
- message 异常堆栈信息
1、pom.xml文件引入
实际项目中是多模块的,所有将关于aop的引入,放到到对应的模块中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1、pom.xml文件引入
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo1</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 要用Aop必须加上这个依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、写一个Controller
@RestController
@RequestMapping("/course")
public class TestController {
@GetMapping("/findById")
public String findById() {
return "调用接口成功";
}
}
运行起来之后,浏览器调用http://localhost:8080/course/findById能打印出“调用接口成功”即可
3、定义一个日志annotation
/**
* 在Controller方法上加入改注解会自动记录日志
*/
@Target( { ElementType.METHOD } )
@Retention( RetentionPolicy.RUNTIME )
@Documented
public @interface Log {
/**
* 模块名称
*/
String modelName() default "";
/**
* 操作
*/
String action()default "";
/**
* 描述.
*/
String description() default "";
/**
* 是否保存返回参数
*/
boolean saveResult() default false;
}
4、定义一个ControllerAspect
/**
* @author lichunlan
* @description 控制器的切面:记录log
* @since 2021-03-22
*/
@Aspect
@Configuration
public class ControllerAspect {
/**
* 连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强
* 连接点表示具体要拦截的方法,上面切点是定义一个范围,而连接点是具体到某个方法
*/
/**
* 切点(PointCut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点--数据库的记录,切点--查询条件
* 切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围
*/
@Pointcut("execution(* com.example.demo1.controller..*(..)) ")
public void aspect() {
}
@Around(value = "aspect()")
public Object validationPoint(ProceedingJoinPoint pjp)throws Throwable{
Method method = currentMethod(pjp,pjp.getSignature().getName());
if (method != null && method.isAnnotationPresent(Log.class)){
return new RecordLogAspect().doHandlerAspect(pjp,method);
}
return pjp.proceed(pjp.getArgs());
}
/**
* 获取目标类的所有方法,找到当前要执行的方法
*/
private Method currentMethod (ProceedingJoinPoint joinPoint , String methodName ) {
Method[] methods = joinPoint.getTarget().getClass().getMethods();
Method resultMethod = null;
for ( Method method : methods ) {
if ( method.getName().equals( methodName ) ) {
resultMethod = method;
break;
}
}
return resultMethod;
}
}
这里的主要能执行到日志记录的切面方法是RecordLogAspect
if (method != null && method.isAnnotationPresent(Log.class)){
return new RecordLogAspect().doHandlerAspect(pjp,method);
}
5、定义一个RecordLogAspect
/**
* @author lichunlan
* @description 日志处理切面
* @since 2021-03-22
*/
public class RecordLogAspect {
public Object doHandlerAspect(ProceedingJoinPoint pjp, Method method) throws Throwable {
return execute(pjp, method);
}
@Async
protected Object execute(ProceedingJoinPoint pjp, Method method) throws Throwable {
try{
Object result = null;
result = pjp.proceed(pjp.getArgs());
return result;
}catch (Throwable throwable){
return throwable;
}finally {
Log log = method.getAnnotation(Log.class);
if (log != null) {
pjp.getTarget().getClass().getName();
System.out.println("开始处理切面日志信息 "+log.modelName()+"\n"+log.action()+"\n"+log.description());
}
}
}
}
- 运行代码,并访问http://localhost:8080/course/findById 可以看到
image.png - 然后我又定义了一个Controller
/**
* @author lichunlan
* @description 测试另外一个Controller
* @since 2021-03-22
*/
@RestController
@RequestMapping("/info")
public class Test2Controller {
@Log(action = "add", modelName = "Test2Controller", description = "添加用户信息")
@GetMapping("/add")
public String add() {
return "调用添加用户信息接口成功";
}
}
运行代码并访问http://localhost:8080/info/add 可以看到
image.png至此我的AOP切面模块就能跑起来了,至于对日志做哪些处理就在打印日志这里做详细的逻辑处理
image.png
6、在我们项目中的一些其他切面(这个后续还会整理一遍)
-
限流、拦截xss攻击
image.png
可以看到ParamXssPass AccessLimit这两个注解,其实和Log是一样的,所有这里面又用到了一个设计模式,装饰者模式。
-
对某个service中的方法执行完之后的一切处理
image.png