AOP概念
面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑中抽离出来。
-
主要用途:日志记录,事务控制,异常统计等。
-
主要思想:将这些额外的工作剥离正常的业务,进行统一操作,而不影响真正的业务。
下面使用的版本:
- spring-aop 2.5.6
- aspectjweaver 1.9.7
AOP 简单demo
概述
简单 demo 就是单纯使用 AOP ,指定需要被拦截的代码进行增强。只涉及 AOP 这一个知识点。
代码
@Aspect
@Component
public class AspectClassTest1 {
private static final Logger log = LoggerFactory.getLogger(AspectClassTest1.class);
//切入点:表示拦截 controller 包下的所有类的所有方法
@Pointcut("execution(* com.practice.springdemo.controller.*.*(..))")
public void cut() {
}
//Around 通知:环绕
@Around(value = "cut()")
public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] objs = joinPoint.getArgs();
log.info("@Around前 入参的个数:" + objs.length);
joinPoint.proceed();
log.info("@Aroud 后面");
}
//Before 通知
@Before(value = "cut()")
public void befor(){
log.info("before");
}
//AfterReturning 通知
@AfterReturning(value = "cut()")
public void AfterReturning(){
log.info("AfterReturning");
}
//After 通知
@After(value = "cut()")
public void After(){
log.info("After");
}
}
- 切面 Aspect :把增强应用到具体方法上
- 切入点 Pointcut :需要被拦截的具体方法
- 通知/增强:
-
- Before 在方法执行之前
- After 在方法执行之后
- Around 环绕,在方法执行前后都行
- AfterReturning 在方法执行之后,可以拿到方法的返回值
- AfterThrowing 方法异常会执行
这是上面代码的执行顺序:
[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : @Around前 入参的个数:1
[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : before
[nio-8080-exec-1] c.p.springdemo.controller.AspectDemo : 进入 AspectDemo1 的 controller
[nio-8080-exec-1] c.p.springdemo.controller.AspectDemo : 传入的值:123
[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : AfterReturning
[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : After
[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : @Aroud 后面
AOP + 注解实现
概述
使用注解+AOP实现增强,使用更加灵活。
代码
Target:是 Java 元注解之一:指定使用的地方,比如这里可以用在形式参数和方法上面
Retention:是 Java 元注解之一:描述注解的保留策略,这里是运行期间起作用
Documented:是 Java 元注解之一:会被 javac 提取为文档
@Target( {ElementType.PARAMETER,ElementType.METHOD} )
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AspectAnnonationTest {
/**
* 模块
*/
public String moudleId() default "";
/**
* 方法
*/
public String methodId() default "";
}
@Aspect
@Component
public class AspectClassTest {
private static final Logger log = LoggerFactory.getLogger(AspectClassTest.class);
@Before(value = "@annotation(testLog)")
public Object before(JoinPoint joinPoint, AspectAnnonationTest testLog) {
log.info("@Before");
return "";
}
@After(value = "@annotation(testLog)")
public void doAfter(JoinPoint joinPoint, AspectAnnonationTest testLog) {
log.info("@After");
}
@AfterReturning(value = "@annotation(testLog)", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, AspectAnnonationTest testLog, Object result) {
log.info("@AfterReturning");
log.info("doAfterReturning: " + result.toString());
}
@AfterThrowing(value = "@annotation(testLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, AspectAnnonationTest testLog, Exception e) {
log.info("@AfterThrowing");
log.error("捕获异常:" + e.getMessage());
}
/**
*
* @param joinPoint
* @param testLog
* @throws Throwable
*/
@Around(value = "@annotation(testLog)")
public void doAround(ProceedingJoinPoint joinPoint, AspectAnnonationTest testLog) throws Throwable {
Object[] objs = joinPoint.getArgs();
log.info("@Around前 入参的个数:" + objs.length);
joinPoint.proceed();
log.info("@Aroud 后面");
}
}
执行顺序和上面的保持一致
实战分析
日志
一般使用 注解 + AOP 实现:
- 灵活,可以在方法上灵活使用,比如一般的查询是不需要操作日志的。
- 使用 AfterThrowing 和 AfterReturning,可以拿到返回值
- 真正保存数据库的时候使用异步保存
redis
背景:项目的一些页面在最高权限使用的时候,由于数据量太大,需要保存到 redis 。
使用注解+AOP(Around)。这里只能使用 Around:因为在执行方法前后,我们都需要操作:
- 方法执行前:查询数据是否最新、redis 是否有数据。没有才执行具体方法
- 方法执行后:把数据更新到 redis
总结
上面的代码应该是很清晰了,我在本地都能跑。
只是五个通知,执行顺序问题,在我看来 After 、AfterReturning 两者的执行顺序不是那么重要,实际开发中都是选用合适的。
- AfterReturning 能获取到返回值,After 则不行
- 还有其他很多实用用法,比如防重提交,比如限流,都是同样的思路