前言:
Spring 三大核心思想是啥,还记得不?IOC(控制反转),DI(依赖注入),AOP(面向切面编程)。回顾一下这三个东西:
IOC:不考虑使用 Spring,纯手写 java 程序时,对象之间的使用和依赖完全是由程序员手写代码控制的,何时创建对象完全在代码中标明。IOC 是一种编程思想,它的核心作用就是把对象的创建、管理由人工负责转成 Spring 托管。
DI:IOC的核心思想很好,那么怎么实现呢?DI就是实现IOC的方案,例如常见的 @Value、@Resource、@Autowired 注解、配置文件都是最具体的使用样例。
AOP:简单点说就是,把某个核心方法切开看看能不能加点其他处理逻辑。
一、相关注解
@Aspect | 描述这是一个切面类 |
@Pointcut | 定义一个切面,所关注事件入口 |
@Before | 切入方法执行前干什么 |
@Around | 切入方法执行时的增强处理,慎用 |
@After | 切入方法执行后后干什么 |
@AfterReturning | 对切入方法返回数据的增强处理 |
@AfterThrowing | 切入方法抛出异常时的处理 |
二、常用注解说明
常用注解三个:Before、After、Pointcut
切面类:
@Slf4j
@Aspect
@Component
public class AopTest {
@Pointcut("execution(* quancheng.demo.service..TestService.test(..))")
public void pointFun() {
}
@Before("pointFun()")
public void doB() {
log.info("Before doB...");
}
@After("pointFun()")
public void doA() {
log.info("After doA...");
}
}
切入的类和方法:
@Slf4j
@Service
public class TestService {
public String test(String str) {
log.info("test service......{}", str);
return str;
}
}
运行时日志截图:
PointCut 定义了一个切面,该注解有两个表达式:
1. execution 表达式
execution(* quancheng.demo.service..TestService.test(..))
* :表示返回值类型,* 表示所有类型;
quancheng.demo.service:这是一个包名,标识需要拦截的包;
包名后的 ..:表示当前包和所有其子包,在本例中指 quancheng.demo.service 包和其子包下所有类;
TestService :表示具体的类名,如果是 * 表示所有类;
test(..) :test是具体方法名,可以用 * 表示所有的方法;后面括弧里面表示方法的参数,双点表示任何参数;
2.@annotation 表达式(推荐使用)
@annotation(quancheng.demo.aop.anno.ServiceDeal)
@annotation 的参数是一个注解的全类限定名,可以自定义也可以使用现有的注解;
quancheng.demo.aop.anno.ServiceDeal 是自定义的注解;
切面类样例如下:
@Slf4j
@Aspect
@Component
public class AopTest {
@Pointcut("@annotation(quancheng.demo.aop.anno.ServiceDeal)")
public void pointFan() {
}
@Before("pointFan()")
public void doB() {
log.info("Before doB...");
}
@After("pointFan()")
public void doA() {
log.info("After doA...");
}
}
切面类的自定义注解:
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceDeal {
String value() default "";
}
使用样例:
@Slf4j
@Service
public class TestService {
@ServiceDeal
public String test1(String str) {
log.info("test1 service......{}", str);
return str;
}
}
三、增强处理注解
增强处理注解:AfterReturning、AfterThrowing、Around
1.AfterReturning
这个只能加些处理逻辑,对修改返回无能为力。
@Slf4j
@Aspect
@Component
public class AopTest {
@Pointcut("@annotation(quancheng.demo.aop.anno.ServiceDeal)")
public void pointFan() {
}
@Before("pointFan()")
public void doB() {
log.info("Before doB...");
}
@After("pointFan()")
public void doA() {
log.info("After doA...");
}
@AfterReturning(pointcut = "pointFan()", returning = "result")
public void dealReturn(String result) {
log.info("打印返回值:{}", result);
}
}
输出截图:
注意:returning 的值要跟参数的属性保持一致,要不会报错。
2.AfterThrowing
service 加个会抛出异常的方法:
@ServiceDeal
public String test1(String str) {
Integer i = Integer.valueOf(str);
log.info("test1 service......{}", str);
return str;
}
切面方法处理异常:
@AfterThrowing(pointcut = "pointFan()", throwing = "e")
public void dealException(Throwable e) {
log.info("打印报错:{}", e.getStackTrace());
}
输出截图:
3.Around
当定义一个 Around 增强处理方法时,该方法的第一个形参必须是 ProceedingJoinPoint 类型。在增强处理方法体内,调用 ProceedingJoinPoint 的 proceed 方法才会执行目标方法;调用 ProceedingJoinPoint 的 proceed 方法时,还可以传入一个 Object[] 对象,该数组中的值将被传入目标方法作为实参,这就是 Around 增强处理方法可以改变目标方法参数值的关键,当然传参类型和数量是必须跟实际方法一致。
@Around("pointFan()")
public Object process(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
log.info("原始传参 args:{}", args);
Object result = null;
try {
//修改传参
result = joinPoint.proceed(new Object[]{"newArgs"});
} catch (Throwable throwable) {
throwable.printStackTrace();
}
log.info("数据返回:{},这块可以加些处理逻辑", result);
return result;
}
输出截图:
传参和结果都可随意修改,所以这块慎用。