当我们进行全链路trace、监控接口性能、日志记录时,可能需要大量的代码堆积到已有的逻辑中,为了避免这种冗余的代码,我们可以使用spring的aop功能,动态织入目标方法来增强逻辑。
它的基本概念我这里就不陈述了。
我这里用到了两种方式来进行实现aop,1、注解形式(功能实现后可通过注解直接作用在目标方法上),2、通过类全限定名-方法名匹配 两种方式可以交叉使用
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PerfAnnotation {
String name() default "";
}
@Component
@Aspect
@Slf4j
@Order(1)
public class PerfAnnotationAop {
// *************************************************
/* 注解形式 */
// *************************************************
// 定义一个切入点
@Pointcut(value = "@annotation(com.example.demo_fengyifan.annotation.PerfAnnotation)")
public void perfAnnotationMethod() {
// 仅作为一个切入点,perfAnnotationMethod方法里面的内容不会执行
}
// 环绕增强 可以在目标方法执行前后 织入一些逻辑
@Around("perfAnnotationMethod()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
try {
System.out.println("目标方法执行前======");
Object proceed = pjp.proceed(); // 目标方法执行
System.out.println("目标方法执行后======");
// 通过反射获取该类的所有方法
for (Method method : pjp.getTarget().getClass().getMethods()) {
System.out.println("method.getName() = " + method.getName());
}
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
String name = method.getName(); // 目标方法名
System.out.println("name = " + name); // 这种方式获取的方法名和pjp.getSignature().getName()相同
System.out.println("pjp.getSignature().getName() = " + pjp.getSignature().getName()); // 具体目标方法名
System.out.println("pjp.getSignature().getDeclaringTypeName() = " + pjp.getSignature().getDeclaringTypeName());// 类名全路径(类的全限定名)
System.out.println("proceed = " + proceed); // 目标方法执行后的返回值,如果方法是void修饰,proceed就是null
return proceed;
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
String msg = pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName();
System.out.println("msg = " + msg);
}
}
// *************************************************
/* 类的全限定名形式 */
// *************************************************
@Pointcut("execution(* com.example.demo_fengyifan.controller.UserController.show2(..))")
public void checkpoint() {
}
// 目标方法执行前 在执行该方法
@Before("checkpoint()")
public void before(JoinPoint point) {
// args是请求参数
Object[] args = point.getArgs();
if (args.length == 0) return;
BBB bbb = (BBB) args[0]; //执行前
System.out.println("执行前 bbb = " + bbb);
}
// 目标方法执行后 在执行该方法
@AfterReturning("checkpoint()")
public void after(JoinPoint point) {
// args是请求参数,这个请求参数是被加工过的,如果目标方法对请求参数重新赋值,这里获取到的是赋值后的参数
Object[] args = point.getArgs();
if (args.length == 0) return;
BBB bbb = (BBB) args[0];
System.out.println("bbb = " + bbb);
}
}
controller层
@RestController
@Api(tags = "用户的Controller", hidden = true)
public class UserController {
@Resource
UserService userService;
@PostMapping("/test111")
public void showwww(@RequestBody AAA aaa) {
List<BBB> tests = new LinkedList<>();
List<CCC> cccs = aaa.getCccs();
tests = cccs.stream().map(CCC::toBBB).collect(Collectors.toList());
System.out.println("collect = " + tests);
System.out.println("aaa = " + aaa);
}
@PostMapping("/test_bbb")
public void show2(@RequestBody BBB bbb) {
System.out.println("bbb = " + JSONUtils.write(bbb));
System.out.println(bbb.getB());
BBB bbb1 = new BBB();
bbb1.setId(111);
System.out.println("bbb1 = " + bbb1);
bbb.setId(88888);
}
@GetMapping("/test/runAsync")
@PerfAnnotation
public String showRunAsync() {
System.out.println(";;;;;");
CompletableFuture.runAsync(() -> {
try {
userService.show1();
userService.show2();
userService.show3();
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println("异步处理了!!!!!");
return "ok";
}
}
@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonIgnoreProperties(ignoreUnknown = true)
@NoArgsConstructor
@AllArgsConstructor
public class BBB {
private Integer id;
private Boolean b = false;
}
补充下
如何获取自定义注解内的属性值?
@PerfAnnotation(name = “showRunAsync”) 自定义注解内 name = “showRunAsync”
@GetMapping("/test/runAsync")
@PerfAnnotation(name = "showRunAsync")
public String showRunAsync() {
return "ok";
}
在进行aop增强功能时 这样来做
@Around("perfAnnotationMethod()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
try {
// 获取目标方法的类对象
Class<?> aClass = pjp.getTarget().getClass();
// pjp.getSignature().getName() 具体目标方法名
System.out.println("pjp.getSignature().getName() = " + pjp.getSignature().getName());
// 根据类对象 获取到目标方法的信息
Method declaredMethod = aClass.getDeclaredMethod(pjp.getSignature().getName());
// 根据目标方法 找到自定义注解对象
PerfAnnotation annotation = declaredMethod.getAnnotation(PerfAnnotation.class);
// 打印注解内的信息
System.out.println("annotation name = " + annotation.name());
}
}
获取自定义注解内的参数
注意的地方:如果两个方法都被@PerfAnnotation注解修饰了,并且这两个方法在同一个类中,然后在方法一内调用方法二时,方法二的注解会失效,解决方法:把方法二放到别的类中 或者指明当前类然后调用方法二 看下图
@PostMapping("/test_bbb")
@PerfAnnotation(name = "fff")
public void showyifan(@RequestBody BBB bbb) {
System.out.println("bbb = " + JSONUtils.write(bbb));
System.out.println(bbb.getB());
BBB bbb1 = new BBB();
bbb1.setId(111);
System.out.println("bbb1 = " + bbb1);
bbb.setId(88888);
userController.showyifanhaha(99999);
}
@PerfAnnotation(name = "DDD")
public void showyifanhaha(Integer id) {
System.out.println("id = " + id);
}
@Around("perfAnnotationMethod()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
try {
Thread.sleep(2000);
// 通过反射获取该类的所有方法
for (Method method : pjp.getTarget().getClass().getMethods()) {
System.out.println("method.getName() = " + method.getName());
}
// 获取目标方法的类对象
Class<?> aClass = pjp.getTarget().getClass();
// pjp.getSignature().getName() 具体目标方法名
System.out.println("pjp.getSignature().getName() = " + pjp.getSignature().getName());
// ==========================================================================================
// 获取自定义注解内的参数 方式一:
MethodSignature signature = (MethodSignature) pjp.getSignature();
Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
Class[] classes = new Class[pjp.getArgs().length];
for (int i = 0; i < pjp.getArgs().length; i++) {
Class<?> parameterType = parameterTypes[i];
System.out.println("parameterType = " + parameterType);
classes[i]=parameterType;
}
// 根据类对象 获取到目标方法的信息
Method declaredMethod = aClass.getDeclaredMethod(pjp.getSignature().getName(), classes);
// 根据目标方法 找到自定义注解对象
PerfAnnotation annotation = declaredMethod.getAnnotation(PerfAnnotation.class);
// 打印注解内的信息
System.out.println("annotation name = " + annotation.name());
// ==========================================================================================
System.out.println("目标方法执行前======");
Object proceed = pjp.proceed(); // 目标方法执行
System.out.println("目标方法执行后======");
// ==========================================================================================
// 获取自定义注解内的参数 方式二:这种方式看起来比上面的要好一些
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
PerfAnnotation annotation1 = method.getAnnotation(PerfAnnotation.class);
System.out.println("annotation1 name = " + annotation1.name());
String name = method.getName(); // 目标方法名
System.out.println("name = " + name); // 这种方式获取的方法名和pjp.getSignature().getName()相同
// ==========================================================================================
System.out.println("pjp.getSignature().getName() = " + pjp.getSignature().getName()); // 具体目标方法名
System.out.println("pjp.getSignature().getDeclaringTypeName() = " + pjp.getSignature().getDeclaringTypeName());// 类名全路径(类的全限定名)
System.out.println("proceed = " + proceed); // 目标方法执行后的返回值,如果方法是void修饰,proceed就是null
return proceed;
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
String msg = pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName();
// System.out.println("msg = " + msg);
}
}```