AOP Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
编写目标类与方法
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user/api/v1/")
public class UserController {
@RequestMapping("/seckill")
public String seckill(){
return "OK";
}
}
编写切面类,使得切面绑定目标类
import com.zanglikun.springdataredisdemo.aop.appendDescAop.annotation.AppendFieldDesc;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;;
import org.springframework.web.bind.annotation.RequestBody;
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.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
@Aspect // 声明切面类
@Component // 注册为Spring组件,不然切面类不生效,这里我是先关闭了,你使用请务必开启
@Slf4j
public class AopAdvice {
/**
* Pointcut 定义切入点,一般为方法级别。通过切点表达式体现。
* A原表达式:execution(public * com.zanglikun.springdataredisdemo.controller..*.*(..))
* A表达式是切入 com.zanglikun.springdataredisdemo.controller 下面所有的Public方法
* <p>
* 一般还有匹配注解的如:@annotation(com.zanglikun.springdataredisdemo.aop.fileLimitAop.FileLimit)
* 就是切入被FileLimit的注解
*/
@Pointcut("execution(public * com.zanglikun.springdataredisdemo.controller..*.*(..))")
public void onePointCut() {
}
/**
* 环绕通知
* 理解点1:joinPoint.proceed()作用是:执行被拦截的方法。
* 为什么叫环绕通知呢?我们一般在joinPoint.proceed()。上下位置放置我们要执行的代码,可以实现在方法前、后执行代码
*
* @param joinPoint 切点 注意Around的joinPoint 与其他的不一样
* @return
* @throws Throwable
* @Around("onePointCut()") 这种定义切入点方式和直接在@Around里面声明具体得切入点表达式一样
* 环绕通知,更灵活得自定义增强通知,可以注入ProceedingJoinPoint获取相关信息和执行方法
*/
@Around("onePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object object = null;
log.info("环绕通知开始:我在方法前面执行了");
object = joinPoint.proceed(); //调用被切入方法,并获取其返回值
log.info("环绕通知结束:我在方法执行后再执行");
return object;
}
/**
* 用于在切入点的流程执行前生效,可以在方法中注入JoinPoint用于获取相关信息
*
* @param joinPoint 切点对象
*/
@Before("onePointCut()")
public void before(JoinPoint joinPoint) {
log.info("我是前置通知:我在方法前被执行");
}
/**
* finally通知,这就意味着无论如何都会执行这个通知(不论是否发生异常),可以在方法中注入JoinPoint用于获取相关信息
*
* @param joinPoint 切点对象
*/
@After("onePointCut()")
public void after(JoinPoint joinPoint) {
log.info("我是finally通知:无论如何(只要不停电、强杀进程)我都会执行");
}
/**
* 异常通知,出现异常时执行,可以在方法中注入JoinPoint和Throwable用于获取相关信息
*
* @param joinPoint 切点对象
* @param e 异常对象。由throwing指定变量名!
*/
@AfterThrowing(pointcut = "onePointCut()", throwing = "e")
public void throwing(JoinPoint joinPoint, Exception e) {
log.info("我是异常通知:仅在出现异常时触发,本地异常:{}", e.getMessage());
}
/**
* 后置返回前通知 ,在finally通知之后,方法正常退出前执行,可以注入JoinPoint 和Object 获取相关信息和方法执行成功的返回结果
* 注意:returning的内容必须要与变量保持一致
*
* @param joinPoint 切点对象
* @param object 目标方法返回的对象,一般有returning执行变量名赋值
*/
@AfterReturning(pointcut = "onePointCut()", returning = "object")
public void returning(JoinPoint joinPoint, Object object) {
getJoinPointInfo(joinPoint, object);
log.info("我是后置通知,响应结果为:" + object);
}
}
测试
我看别人说不同版本的Spring版本输出结果不一样!
2023-06-07 00:42:08.075 INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice : 环绕通知开始:我在方法前面执行了
2023-06-07 00:42:08.075 INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice : 我是前置通知:我在方法前被执行
2023-06-07 00:42:08.075 INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice : 我是后置通知,响应结果为:OK
2023-06-07 00:42:08.075 INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice : 我是finally通知:无论如何(只要不停电、强杀进程)我都会执行
2023-06-07 00:42:08.075 INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice : 环绕通知结束:我在方法执行后再执行
如果你要拓展JoinPoint属性,请参考如下代码:
这代码的意思是给一个接口返回后,处理其返回对象的字段,如果有@AppendFieldDesc就会触发替换字典值的非生产代码。当然有些RequestBody在此处意义不大,仅仅做更加健全的demo使用哈。
/**
* 后置返回前通知 ,在finally通知之后,方法正常退出前执行,可以注入JoinPoint 和Object 获取相关信息和方法执行成功的返回结果
* 注意:returning的内容必须要与变量保持一致
*
* @param joinPoint 切点对象
* @param object 目标方法返回的对象,一般有returning执行变量名赋值
*/
@AfterReturning(pointcut = "onePointCut()", returning = "object")
public void returning(JoinPoint joinPoint, Object object) {
getJoinPointInfo(joinPoint, object);
log.info("我是后置通知,响应结果为:" + object);
}
/**
* @param joinPoint
* @return
*/
public Object getJoinPointInfo(JoinPoint joinPoint, Object returnRes) {
// 这串代码的含义是获取HttpServletRequest、HttpServletResponse获取的是本线程的哦
{
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
HttpServletResponse response = requestAttributes.getResponse();
}
Object[] args = joinPoint.getArgs(); // 获取方法的所有入参值,但是无法准确获取入参类型,但是要去修改属性值,必须得获取对象
for (Object arg : args) {
System.out.println(arg);
}
// 获取AOP签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod(); // 获取被调用的方法
Class<?> returnType = method.getReturnType(); // 获取返回值的类型
Parameter[] parameters = method.getParameters(); // 获取方法的入参
for (Parameter parameter : parameters) {
parameter.getName(); // 获取参数名称例如:User user 获取的是user
Annotation[] classAnnotations = parameter.getAnnotations(); // 获取该入参的所有注解
Class<?> type = parameter.getType(); // 获取入参的字节码文件以便于快速获得变量的属性
if (parameter.isAnnotationPresent(RequestBody.class)) { // 如果该变量被RequestBody修饰了,才会执行
Field[] declaredFields = type.getDeclaredFields(); // 获取该变量的所有属性列表
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(AppendFieldDesc.class)) { // 如果变量被AppendFieldDesc修饰后,才执行
// 开始替换为字典值
try {
declaredField.setAccessible(true);
Object o = declaredField.get(returnRes);
if (o instanceof String) {
getDictionary(); // 初始化数据
HashMap<String, Object> stringObjectHashMap = returnMap.get(declaredField.getName());
declaredField.set(returnRes, stringObjectHashMap.get(o));
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} finally {
declaredField.setAccessible(false);
}
}
}
}
}
return returnRes;
}
HashMap<String, HashMap<String, Object>> returnMap = new HashMap<>();
// 自己主动去缓存Cache Code=Name oldValue NewValue
HashMap<String, HashMap<String, Object>> getDictionary() {
HashMap<String, Object> dbReturn = new HashMap<>();
dbReturn.put("1", "张三");
dbReturn.put("2", "李四");
returnMap.put("name", dbReturn);
return returnMap;
}
特殊说明: 上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤