个人博客地址: http://www.chenguanting.top
引子:
有一个包中的所有方法都需要打印出方法的执行时间,并打印出参数和返回值。
方案
如果我们在每一个方法上都加上一套计算时间的逻辑,将会消耗大量的重复工作,并且等不需要用的时候有需要一个一个删除,这是很恶心人的事情。这时不妨试用下spring的aop
实现步骤
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 在启动类加上@EnableAspectJAutoProxy注解
@SpringBootApplication
@EnableAspectJAutoProxy
public class NoteApplication {
public static void main(String[] args) {
SpringApplication.run(NoteApplication.class, args);
}
}
- 创建一个需要被添加日志的类,以下以controller为例
package com.yuyu.learning.note.controller;
@RestController()
@RequestMapping("/testRest" )
public class TestRest {
@GetMapping("/test")
public String test(@RequestParam("name") String name) {
System.out.println("test:" + name);
return "result:" + name;
}
}
- 创建AOP类
package com.yuyu.learning.note.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
@Aspect
@Component
public class AopLog {
@Pointcut(value = "execution(* com.yuyu.learning.note.controller.TestRest.test(..)) && args(name)")
public void point(String name) {
}
/**
* 方法执行前
*
* @param joinPoint
* @param name 参数
*/
@Before(value = "point(name)")
public void beforeMethod(JoinPoint joinPoint, String name) {
System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
//获取传入目标方法的参数
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println("第" + (i + 1) + "个参数为:" + args[i]);
}
System.out.println("被代理的对象:" + joinPoint.getTarget());
System.out.println("代理对象自己:" + joinPoint.getThis());
System.out.println("beforeMethod\t" + name);
}
/**
* 方法执行后
*
* @param joinPoint
* @param name 参数
*/
@After("point(name)")
public void afterMethod(JoinPoint joinPoint, String name) {
System.out.println("调用了后置通知\t" + name);
}
/**
* 返回通知
*
* @param joinPoint
* @param result 返回内容
* @param name 传入参数
*/
@AfterReturning(value = "point(name)", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result, String name) {
System.out.println("调用了返回通知\t" + result);
}
/**
* 异常通知
*
* @param joinPoint
* @param e
* @param name
*/
@AfterThrowing(value = "point(name)", throwing = "e")
public void afterReturningMethod(JoinPoint joinPoint, Exception e, String name) {
System.out.println("调用了异常通知");
}
/**
* 环绕通知
*
* @param pjp
* @param name
* @return
* @throws Throwable
*/
@Around("point(name)")
public Object Around(ProceedingJoinPoint pjp, String name) throws Throwable {
System.out.println("around执行方法之前");
// Object object = pjp.proceed();
Object object = pjp.proceed(new Object[]{"新参数"});
System.out.println("around执行方法之后--返回值" + object);
return object;
}
}
详细解释
- @Aspect 声明这个类是切面类
- @Component 声明为组件,将类交给spring管理
- @Pointcut 声明切点
3.1 execution(* com.yuyu.learning.note.controller.TestRest.test(…)) 这个是声明方法的位置,可以使用正则表达式,我这里精确匹配到了这个test方法
3.2 args(name) 制定参数为name,如果有两个参数就是args(name1,name2);这时下面方法的参数需要个数/参数名称匹配才可以
- value = “point(name)” point(arg)指向了定义切点的方法,里面的参数必须和本方法中的参数一致
- @Around(“point(name)”) 特别说下around通知,在这个位置是可以修改传入方法的参数值的
@Around("point(name)")
public Object Around(ProceedingJoinPoint pjp, String name) throws Throwable {
System.out.println("around执行方法之前");
Object object = pjp.proceed(new Object[]{"新参数"});
System.out.println("around执行方法之后--返回值" + object);
return object;
}
// 这个位置调用了可以传入参数数组的方法
public Object proceed(Object[] args) throws Throwable;