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 定义了一个切面,该注解有两个表达式:
execution(* quancheng.demo.service..TestService.test(..))
* :表示返回值类型,* 表示所有类型;
quancheng.demo.service:这是一个包名,标识需要拦截的包;
包名后的 ..:表示当前包和所有其子包,在本例中指 quancheng.demo.service 包和其子包下所有类;
TestService :表示具体的类名,如果是 * 表示所有类;
test(..) :test是具体方法名,可以用 * 表示所有的方法;后面括弧里面表示方法的参数,双点表示任何参数;
@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
这个只能加些处理逻辑,对修改返回无能为力。
@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 的值要跟参数的属性保持一致,要不会报错。
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());
}
输出截图:
当定义一个 Around 增强处理方法时,该方法的第一个形参必须是 ProceedingJoinPoint 类型。在增强处理方法体内,调用 ProceedingJoinPoint 的 proceed 方法才会执行目标方法;调用 ProceedingJoinPoint 的 proceed 方法时,还可以传入一个 Object[] 对象,该数组中的值将被传入目标方法作为实参,这就是 Around 增强处理方法可以改变目标方法参数值的关键,当然传参类型和数量是必须跟实际方法一致。
这里完全重新写一个注解实例:
1)定义注解,作用是为了校验age参数是否合法:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AgeCheck {
String message() default "数据格式错误";
int min() default 1;
int max() default 100;
}
2)使用注解:
@Slf4j
@Service
public class TestService {
@AgeCheck(min = 18, max = 65,message = "年龄范围错误")
public String test(Integer age) {
return age + "";
}
}
3)定义切面处理逻辑:
@Slf4j
@Aspect
@Component
public class AgeCheckAop {
@Pointcut("@annotation(quancheng.demo.aop.anno.AgeCheck)")
public void pointFan() {
}
@Before("pointFan()")
public void doB() {
log.info("Before doB...");
}
@After("pointFan()")
public void doA() {
log.info("After doA...");
}
@Around(value = "pointFan()")
public Object process(ProceedingJoinPoint joinPoint) {
log.info("进入Around,此时未执行函数方法");
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
AgeCheck ageCheck = methodSignature.getMethod().getAnnotation(AgeCheck.class);
Integer min = ageCheck.min();
Integer max = ageCheck.max();
String message = ageCheck.message();
log.info("min:{},max:{}", min, max);
Object[] args = joinPoint.getArgs();
log.info("原始传参 args:{}", args);
Integer age = (Integer) args[0];
if (age < min || age > max) {
log.info("不满足条件直接返回,message:{}", message);
return message;
}
Object result = null;
try {
//可以在 proceed() 方法里面修改传参
result = joinPoint.proceed(args);
log.info("在Around,此时已执行完函数方法");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
log.info("数据返回:{},这块可以加些处理逻辑,甚至修改返回", result);
return result;
}
}
输入age=19时(此时满足校验逻辑,程序会一直执行到最后),截图:
输入age=17时(此时不满足校验逻辑,程序会提前返回),截图:
传参和结果都可随意修改,所以这块慎用。