目录
面向切面(AOP) 是spring重要特性,在功能上切面编程是面向对象编程的很好的补充,面向对象强调封装和开闭原则,如果多个对象有通用行为和方法,将造成很多冗余代码,AOP将通用功能作为切面插入到业务逻辑中(如通用的日志打印,异常处理,license判断等)抽取通用逻辑,减少代码冗余,并且无侵入地修改代码,在实现层面上都是通过动态代理实现,默认cglib方式,SpringBoot简化了它的使用,常用的2种方式:直接使用aspect注解扫描或基于自定义的注解。
首先在springBoot的依赖管理基础上,引入aop的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
直接使用@Aspect
定义切面逻辑
使用@Aspect实现一个通用的业务逻辑,比如日志打印,提供了几种切面增强方式:前置增强@Before,后置增强@After,环绕返回@AfterReturning,环绕增强@Around(前后都有),异常抛出增强AfterThrowing,执行顺序是:
- 正常情况:@Around-->@Before-->目标方法执行-->@AfterReturning-->@After-->@AfterReturning-->@Around
- 异常情况:@Around-->@Before-->目标方法执行-->@AfterThrowing-->@After-->AfterThrowing-->@Around
如下:
@Component
@Aspect
@Slf4j
public class LoggingAspect {
@Pointcut("execution(public * com.example.hello.controller.*.*())")
public void LogAspect(){}
@Before("LogAspect()")
public void doBefore(JoinPoint joinPoint){
log.info(" -------------------------> this is before.");
}
@After("LogAspect()")
public void doAfter(JoinPoint joinPoint){
log.info(" -------------------------> this is after.");
}
@AfterReturning("LogAspect()")
public void doAfterReturning(JoinPoint joinPoint){
log.info(" -------------------------> this is afterReturning.");
}
@AfterThrowing("LogAspect()")
public void deAfterThrowing(JoinPoint joinPoint){
log.info(" -------------------------> this is deAfterThrowing.");
}
@Around("LogAspect()")
public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable{
log.info(" -------------------------> this is deAround.");
return joinPoint.proceed();
}
}
模拟业务代码
这里的切入点是controller类中方法,controller的方法定义如下,其中helloService模拟的是真实的业务代码,这个controller不需要做任何修改,测试正常输入和异常输出;
@GetMapping("/hello")
public String sayHello(){
log.info("---- 模拟基于标准注解的切面过程 ---------- >running hello");
return helloService.sayHello();
}
@GetMapping("/hello2")
@LogAnnotation(param = "HelloController")
public String sayHello2(){
log.info("---- 模拟异常后切面过程 ---------- >running hello2");
throw new RuntimeException();
}
测试输出
在正常输出业务代码的前后,及方法的全局都被注入了自定义的逻辑:
异常输出:
自定义注解方式
上面的方式可以无侵入地为我们的业务代码增加某些通用逻辑,同时我们也可以自定义注解方式,然后将自定义注解增加到部分业务代码中;
自定义切面注解
定义注解LogAannotation,能够在方法和类上标注,有个参数param默认空;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface LogAnnotation {
String param() default "";
}
定义切入点逻辑
LoggingAspect2实现了切入点和自定义注解@LogAnnotation的逻辑,在切入点前后执行时间打印;
@Aspect
@Component
@Slf4j
public class LoggingAspect2 {
@Around("@annotation(logAnnotation)")
public Object around(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable {
log.info(String.format("time now :%s",new Date()));
Object o = joinPoint.proceed();
log.info(String.format("time now :%s",new Date()));
return o;
}
}
模拟业务代码
这种方式需要修改业务代码,增加对于自定义注解的传参;
@GetMapping("/hello3")
@LogAnnotation(param = "HelloController")
public String sayHello3(){
log.info("---- 模拟基于自定义注解的切面 ---------- >running hello3");
return helloService.sayHello();
}
测试输出
这里因为上面的@aspect注解增加了环绕增强,因此hello3的上下日期打印是自定义注解的实现。