文章目录
- 前言
- 一、Aop
- 1.基本概念
- 2.术语理解
- 3.通知类型
- 二、SpringBoot Aop 整合
- 1.引入依赖
- 2.注解
- 3.切面
- 4.使用
前言
本文基于Springboot2.x整合Aop时限日志记录
一、Aop
1.基本概念
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming),面向对象编程的补充和完善。面向切面编程是面向对象中的一种方式而已。在代码执行过程中,动态嵌入其他代码,叫做面向切面编程。面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如安全检查,事物,日志等。若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使业务逻辑变得混杂不清。常见的使用场景:
- 日志
- 事物
- 数据库操作
2.术语理解
target | 目标类 | 需要被代理的类 |
aspecct | 切面 | 切面泛指交叉业务逻辑。比如事物处理,日志处理就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强 |
weaving | 织入 | 织入是指将切面代码插入到目标对象的过程 |
joinpoint | 连接点 | 连接点指切面可以织入的位置 |
pointcut | 切入点 | 切入点指切面具体织入的位置 |
advice | 通知 增强 | 通知(Advice)是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。 |
advisor | 顾问 | 顾问(Advisor)是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。不仅指定了切入时间点,还可以指定具体的切入点。 |
3.通知类型
通知类型 | 说明 |
前置通知(MethodBeforeAdvice) | 目标方法执行之前调用 |
后置返回通知(AfterReturningAdvice) | 当连接点方法成功执行后,返回通知方法才会执行,如果连接点方法出现异常,则返回通知方法不执行。返回通知方法在目标方法执行成功后才会执行, 所以,返回通知方法可以拿到目标方法(连接点方法)执行后的结果 |
后置通知(AfterAdvice) | 目标方法执行完成之后调用 |
环绕通知(MethodInterceptor) | 目标方法执行前后都会调用方法,且能增强结果 |
异常处理通知(ThrowsAdvice) | 目标方法出现异常调用 |
二、SpringBoot Aop 整合
1.引入依赖
<!-- AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.注解
package com.dingwen.test.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
@Target:决定了你的注解可以使用的位置
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
*/
@Target(ElementType.METHOD)
/*
@Retention :
注解的注解,元注解
1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface SysLogAnnotation {
// 可以是Object 类型
String value() default "";
}
3.切面
package com.dingwen.test.aspect;
import com.dingwen.test.annotation.SysLogAnnotation;
import com.dingwen.test.entity.SysLog;
import com.dingwen.test.service.SysLogService;
import io.swagger.models.auth.In;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;
/**
* 日志记录切面类
*
* @author dingwen
* 2021.04.27 10:13
*/
// 切面类
@Aspect
// 对象交给Spring管理
@Component
public class SysLogAspect {
// 日志服务
private final SysLogService sysLogService;
@Autowired
SysLogAspect(SysLogService sysLogService){
this.sysLogService = sysLogService;
}
/*
*
* 定义切入点
*/
@Pointcut("@annotation(com.huitian.adapter.ferry.annotation.SysLogAnnotation))")
public void SysLogPointCut() {
}
/*
* 环绕通知
* 切入点指向切入点表达式
* @param proceedingJoinPoint
* @return Object
*/
@Around(value = "SysLogPointCut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) {
Object proceed = null;
// 获取方法签名
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
// 具体方法
Method method = methodSignature.getMethod();
// 方法上的日志切面注解
SysLogAnnotation sysLogAnnotation = method.getAnnotation(SysLogAnnotation.class);
// 注解上面的值 日志类型
Integer logType = getSysLogType(sysLogAnnotation.ferryType());
// 获取请求参数
Object[] args = proceedingJoinPoint.getArgs();
try {
proceed = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
sysLogService.save(sysLog);
return proceed;
}
/*
前置通知
*/
@Before(value = "SysLogPointCut()")
public void before(JoinPoint joinPoint){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("调用前连接点方法为:" + methodName + ",参数为:" + args);
System.out.println("前置通知");
}
/*
* 返回通知
* 当连接点方法成功执行后,返回通知方法才会执行,
* 如果连接点方法出现异常,则返回通知方法不执行。返回通知方法在目标方法执行成功后才会执行,
* 所以,返回通知方法可以拿到目标方法(连接点方法)执行后的结果
* @param joinPoint 连接点
* @param result 就是response
*
*/
@AfterReturning(pointcut = "SysLogPointCut()",returning = "result")
public void afterReturn(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",目标方法执行结果为:" + result);
}
/*
后置通知
*/
@After(value = "SysLogPointCut()")
public void after(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("调用后连接点方法为:" + methodName + ",参数为:" + args);
}
/*
后置返回通知
*/
@AfterThrowing(value = "SysLogPointCut()",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint,Exception exception){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + exception);
}
}
4.使用
/*
* 登录
* @return Result
*/
@PostMapping("/login")
@SysLogAnnotation(value= "登录")
public Result sendTextMsg(HttpServletRquest request,
@RequestBody User user) {
return ResultGenerator.genOkResult();
}