启发
Spring AOP可以帮助我们Java在不修改源代码的前提下实现功能增强,其底层实现基于Java动态代理或者CGLIB
以往我们使用execution选定具体利用AOP扩展哪些类
execution(* com.itlaoqi…Service.(…))
这样使用非常不灵活,因为并不是Service中所有的方法都需要被增强
其实受到Spring声明式事务注解@Transactional的启发,我们在项目中利用自定义注解实现了大量共性需求。
应用场景
- 需要灵活使用共性需求的地方都可以使用该方案
- 收集上报指定关键方法的入参、执行时间、返回结果等关键信息,用作后期调优
- 关键方法在幂等性前置校验(基于本地消息表)
- 类似于Spring-Retry模块,提供关键方法多次调用重试机制
- 提供关键方法自定义的快速熔断、服务降级等职责
- 关键方法的共性入参校验
- 关键方法在执行后的扩展行为,例如记录日志、启动其他任务等
…
开发过程
新建Spring Boot 2.x工程,在pom.xml中增加aspectjweaver依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
新建自定义注解,利用@interface关键字定义注解
package com.itlaoqi.aopanno.annoation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//注解用在方法上
@Target(ElementType.METHOD)
//@Retention作用是定义被它所注解的注解保留多久,RUNTIME运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodExporter {
}
开发切面类
package com.itlaoqi.aopanno.aspect;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Date;
//关键代码1:说明当前对象是一个切面
@Aspect
//关键代码2:@Component允许在Spring IOC对当前对象实例化并管理
@Component
@Slf4j
public class MethodExporterAspect {
//关键代码3:说明切面的作用范围,任何增加@MethodExporter的目标方法都将在执行方法前先执行该切面方法
//@Around环绕通知,最强大的通知类型,可以控制方法入参、执行、返回结果等各方面细节
@Around("@annotation(com.itlaoqi.aopanno.annoation.MethodExporter)")
public Object methodExporter(ProceedingJoinPoint joinPoint) throws Throwable {
long st = new Date().getTime();
//执行目标方法,获取方法返回值
Object proceed = joinPoint.proceed();
long et = new Date().getTime();
ObjectMapper mapper = new ObjectMapper();
//将入参JSON序列化
String jsonParam = mapper.writeValueAsString(joinPoint.getArgs());
//将返回结果JSON序列化
String jsonResult = null;
if(proceed != null){
jsonResult = mapper.writeValueAsString(proceed);
}else{
jsonResult = "null";
}
//模拟上报过程
log.debug("正在上报服务器调用过程:\ntarget:{}.{}()\nexecution:{}ms,\nparameter:{}\nresult:{}"
,joinPoint.getTarget().getClass().getSimpleName()
,joinPoint.getSignature().getName()
,(et-st)
,jsonParam
,jsonResult);
return proceed;
}
}
目标方法上增加自定义注解
@RestController
public class SampleController {
@MethodExporter
@GetMapping("/list")
public Map list(int page , int rows){
Map result = new LinkedHashMap();
result.put("code", "0");
result.put("message", "success");
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}
DEBUG 1892 — [nio-8080-exec-1] c.i.aopanno.aspect.MethodExporterAspect : 正在上报服务器调用过程:
target:SampleController.insert
execution:602ms,
parameter:[]
result:{“code”:“404”,“message”:“NOT FOUND”}