面向切换编程。
比如记录controller日志等,都很有用。
spring-boot-starter-parent一般会包含它,所以不用引入:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
springboot是不用加这个注解的。
aop的配置项少的可怜,已知的就2个,一般采用默认就行,也不用配置:
# 是否使用aop自动代理,默认值为true。 设置为false表示关闭自动代理。
# 在boot中不用加@EnableAspectJAutoProxy 因为只要引入aop依赖,并且spring.aop.auto=true 也是默认值,就会自动使用代理
spring.aop.auto=true
# 是否使用cglib动态代理,默认为false
spring.aop.proxy-target-class=false
如果不是用的boot,那么需要添加 @EnableAspectJAutoProxy 注解,启用动态代理。
@Pointcut 定义切入点,以便后续调用。
@After(“captureUserFindAll()”) ,调用切入点,并增加逻辑。
代码:
@Aspect
@Component
public class UserAop {
// 定义切入点,这个方法名captureUserFindAll() 后续要用到
@Pointcut("execution (* com.example.accessingdatamysql.controller.UserService.findAll(..))")
public void captureUserFindAll(){}
// 在 captureUserFindAll() 这个切入点之后执行操作
@After("captureUserFindAll()")
public void doAfter() {
System.out.println("after UserService.findAll 成功");
}
}
然后用controller调用下 UserService.findAll()
方法,发现切入成功。
推荐设置切入点,这样逻辑清楚,而且可以复用,当然,也可以每个注解直接写表达式,不引入切入点。
@Aspect
@Configuration
public class TestAop{
@Before("execution(* com.example.accessingdatamysql.controller..*.*(..))")
public void before(JoinPoint joinPoint) throws Throwable {
System.out.println("------- this is before --------");
}
@After("execution(* com.example.accessingdatamysql.controller..*.*(..))")
public void after(JoinPoint joinPoint){
System.out.println("------- this is after --------");
}
@Around("execution(* com.example.accessingdatamysql.controller..*.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
try {
System.out.println("------- this is around before --------");
Object proceed = proceedingJoinPoint.proceed(); // 这个方法表示继续执行
// System.out.println(JSON.toJSON(proceedingJoinPoint));
System.out.println("------- this is around after --------");
return null;
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
获取入参比较简单,Object[] args = joinPoint.getArgs(); 获取参数列表,然后强制转换即可。
需要用 @AfterReturning
注解,returning指定返回对象的名称,代码:
@AfterReturning(value="execution (* com.ssss.web.ServiceImpl.findAll(..))",
returning="user")
public void afterCollect(JoinPoint joinPoint,User user){
}
代码:
@Aspect
@Component
public class PayAop {
private static final Logger logger = LoggerFactory.getLogger(PayAop.class);
@Pointcut("execution (* com.example.PayServiceImpl.pay(..))")
public void proxy() {
}
@Before("proxy()")
public void before(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Double amount=(Double)args[0];
logger.info("开始支付,支付金额"+amount);
}
@After("proxy()")
public void after(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Double amount=(Double)args[0];
logger.info("支付完成,支付金额"+amount);
}
@AfterReturning(value="proxy()",returning="string")
public void afterRerurning(JoinPoint joinPoint,String string){
logger.info("支付结果:"+string);
}
// Around 相当于Before、AfterReturning、After之和
@Around("proxy()")
public void around(ProceedingJoinPoint proceedingJoinPoint){
Object[] args = proceedingJoinPoint.getArgs();
Double amount=(Double)args[0];
logger.info("开始支付,支付金额"+amount);
try {
String string = (String)proceedingJoinPoint.proceed();
logger.info("支付结果:"+string);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
logger.info("支付完成,支付金额"+amount);
}
// 异常处理
@AfterThrowing(value="proxy()",throwing="e")
public void afterThrowing(Exception e){
logger.info("出现异常了");
}
}
Around 相当于Before、AfterReturning、After之和
拦截器算是aop的一种实现方式。
拦截器基于url,aop更灵活,面向的范围更广,例如 类,方法等。
拦截器可以用来阻断后续的执行,但是aop不能,aop并不影响原来的逻辑。
实测不是,例如用controller调用,需要等aop方法执行完毕之后才会返回。
为此找bug还费了很多时间。
如果要异步,可以在aop新增的逻辑里面启动个线程,这样就不会阻塞了。
还有一种方式是调用的service方法上加 @Async
注解,表示异步。
这种其实有限制,因为要切入的service已经写好,aop没法改他。
实测2个都可以。
我这里用的是springboot,并且用的默认配置(没做任何设置)。
spring.aop.proxy-target-class=false
这个配置项就是设置选用哪种代理的。
中间的知识点很多,不详述。