SpringBoot使用Aspect AOP注解实现日志管理(一)
回顾AOP
aop,又叫面向切面编程,通俗理解就是,将那些与业务无关,却为业务模块所共同调用的逻辑代码封装起来,形成一个切面,减少重复代码,降低模块间的耦合度,方便后期操作和维护。
Spring AOP和Aspect AOP
Spring AOP属于运行时的增强,而Aspect AOP属于编译时的增强。SpringAOP是基于代理(Proxying),而AspectAOP是基于字节码操作。如果切面比较少,两者差不多,如果切面太多,最好使用AspectAOP,它比SpringAOP快很多。
使用spring-boot-starter-aop启动器默认帮我们引入了对Aspectj的实现
常用注解使用
@Aspect -- 作用是把当前类标识为一个切面供容器读取
@Pointcut -- (切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
@Before -- 标识一个前置增强方法,相当于BeforeAdvice的功能
@AfterReturning -- 后置增强,相当于AfterReturningAdvice,方法退出时执行
@AfterThrowing -- 异常抛出增强,相当于ThrowsAdvice
@After -- final增强,不管是抛出异常或者正常退出都会执行
@Around -- 环绕增强,相当于MethodInterceptor
@Aspect
@Aspect
– 作用是把当前类标识为一个切面供容器读取
@Aspect
@Component
@Slf4j
public class LogAspect {
}
@Pointcut
@Pointcut -- (切入点)
:就是带有通知的连接点,在程序中主要体现为书写切入点表达式或者基于注解的切入点
基于注解的切入点
@Pointcut("@annotation(cn.zysheep.annotation.Log)")
//@annotation(cn.zysheep.annotation.Log),为自定义注解
public void Pointcut() {}
基于切入点表达式execution()
是最常用的切点函数
@Pointcut("execution(* cn.zysheep.springaop.service.impl..*.*(..))")
public void Pointcut() {}
整个表达式可以分为四个部分:
1. 第一个*号:表示返回类型, *号表示所有的类型
2. 包名: 表示需要拦截的包名,后面的..表示当前包和当前包的所有子包,cn.zysheep.springaop.service.impl包、子孙包下所有类的方法。
3. 第二个*号: 表示类名,*号表示所有的类。
4. *(..):最后这个星号表示方法名, *号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
// cn.zysheep.controller包中所有的类的所有方法切面
// @Pointcut("execution(public * cn.zysheep.controller.*.*(..))")
// 只针对 UserController 类切面
// @Pointcut("execution(public * cn.zysheep.controller.UserController.*(..))")
// 统一切点,对cn.zysheep及其子包中所有的类的所有方法切面
// @Pointcut("execution(* cn.zysheep.controller.*.*(..))")
个人认为基于注解的方式可以实现更加细粒度的操作,比如日志管理,只要自己声明一个自定义的注解@Log,可以在任意三层中定义到不同的方法中
@Before
标识一个前置增强方法,相当于BeforeAdvice的功能
//@Before: 前置通知 ,Pointcut()定义的切入点函数
@Before("Pointcut()")
public void beforeMethod() {
log.info("调用了前置通知");
}
@AfterReturning
后置增强,相当于AfterReturningAdvice
,方法退出时执行
//@AfterRunning: 返回通知 result为切入点返回内容
@AfterReturning(value="Pointcut()",returning="result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
log.info("调用了返回通知,result :{}",result);
}
@AfterThrowing
异常抛出增强,相当于ThrowsAdvice
//@AfterThrowing: 异常通知 e为切入点执行异常的信息
@AfterThrowing(value="Pointcut()",throwing="e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e){
log.info("调用了异常通知 joinPoint:{},e :{}",joinPoint, e.getStackTrace());
}
@After
final增强,不管是抛出异常或者正常退出都会执行
//@After: 后置通知
@After("Pointcut()")
public void afterMethod(JoinPoint joinPoint){
//joinPoint: 当前的连接点,即执行的切入点
log.info("调用了后置通知,joinPoint:{}",joinPoint);
}
@Around
环绕增强,相当于MethodInterceptor
//@Around: 环绕通知
@Around("Pointcut()")
public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around执行方法之前");
// 执行方法
Object object = proceedingJoinPoint.proceed();
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
log.info("around执行方法之后--返回值: " +object);
return object;
}
基于注解的切入点测试
pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
//模块名
String value() default "";
}
定义一个切面
@Aspect
@Component
@Slf4j
public class LogAspect {
long beginTime;
@Pointcut("@annotation(cn.zysheep.annotation.Log)")
public void Pointcut() {}
@Before("Pointcut()")
public void beforeMethod() {
log.info("调用了前置通知@Before");
beginTime = System.currentTimeMillis();
}
@AfterReturning(value="Pointcut()",returning="result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
log.info("调用了返回通知@AfterReturning,result :{}",result);
}
@AfterThrowing(value="Pointcut()",throwing="e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e){
log.info("调用了异常通知@AfterThrowing joinPoint:{},e :{}",joinPoint, e.getMessage());
}
@After("Pointcut()")
public void afterMethod(JoinPoint joinPoint){
//joinPoint: 当前的连接点,即执行的切入点
log.info("调用了后置通知@After,joinPoint:{}",joinPoint);
}
@Around("Pointcut()")
public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("@Around执行方法之前");
// 执行方法
Object object = proceedingJoinPoint.proceed();
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
log.info("@Around执行方法之后--返回值: " +object);
return object;
}
}
controller
@RestController
@Slf4j
public class TestAopController {
@GetMapping("/save")
@Log("保存数据")
public String save() {
log.info("执行了controller中的save方法");
return "保存数据";
}
}
启动类
@SpringBootApplication
public class AOPApplication {
public static void main(String[] args) {
SpringApplication.run(AOPApplication.class,args);
}
}
执行流程:
- @Around前置增强
- @Before前置通知
- 执行了
controller
中的save方法 - @AfterReturning返回通知
- @After后置通知
- @Around后置增强
如果切入点出现异常:
执行流程:
- @Around前置增强
- @Before前置通知
- 执行了controller中的save方法,如果异常在之前执行,不会输出
- @AfterThrowing异常通知
- @After后置通知