S
随笔分类
SpringAOP
面向切面编程
怎么理解?
AOP可以说是OOP的补充和完善
OOP利用封装、继承和多态把一切事物都打造成对象结构,但是对于所有对象都存在着的一些公共行为,OOP就显得无能为力了
OOP中抽象和接口虽好,但是如果要对不相干的对象(散开的对象)进行公共行为的抽取(建立共同的接口或父类)未免显得有些生硬
运行流程
术语分析:
- 前置通知 Before:在目标方法被调用之前调用通知功能
- 后置通知 After:在目标方法完成之后调用通知,此时不会关心方法的输出是什么
- 环绕通知 Around:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后
- 返回通知 AfterReturning:在目标方法返回之后执行此方法
- 异常通知 AfterThrowing:在目标方法抛出异常后执行此方法
- 环绕通知 Around:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行
需要导入的依赖:
<!-- 引入AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
JoinPoint用法
JoinPoint对象分封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象
常用API:
方法名 | 功能 |
---|---|
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,参数名列表等(类型转换成MethodSignature) |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理对象 |
Object getThis(); | 获取代理对象 |
应用思路
- 利用SpringAop对实际业务方法进行切割,将众多分散的重用代码聚集到AOP切点上
- 在环绕通知里统一执行日志信息的拦截和存储
- 小业务量的日志使用普通的一步存储即可
- 大并发的数据量,使用中间件,如Kafka、RocketMQ、RabbitM
- 如果日志系统数据量、并发量非常庞大,需要读写分离的方案
代码测试:
@Aspect
@Component
public class LogAop {
@Pointcut("@annotation(LogAnnotation)")
public void pointCut(){
}
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around()开始执行");
Object proceed = joinPoint.proceed();
System.out.println("around()结束执行");
return proceed;
}
@After(value = "pointCut()")
public void after(){
System.out.println("after()...........");
}
}
/**
* 自定义注解
*/
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
String value() default "";
String desc() default "";
boolean flag() default false;
}
/**
* 需要进行横切的业务方法
* 简略版本
*/
/**
* 面向切面编程,定义一个切面
* 1.定义切面,说白了就是定义一个类
* 2.在这个类上加上两个注解@Aspect、@Component
* 3.定义切点
* 4.使用切点进行横向切入
*/
@Aspect
@Component
public class LogAop {
//寻找规则
/**
* 切点方法注解@Pointcut execution 规则:
* (1) 任意公共方法的执行:execution(public * *(..))
* (2) 任何一个以“set”开始的方法的执行:execution(* set*(..))
* (3) service 的任意方法的执行:execution(* com.abc.service.*(..))
* (4) service 包里的任意方法的执行: execution(* com.abc.service.*.*(..))
* (5) service 包和所有子包里的任意类的任意方法的执行:execution(* com.abc.service..*.*(..))
*/
//定义一个切点
//@Pointcut("execution(* com.aop.demo.service..*(..))")
//自定义注解
@Pointcut("@annotation(LogAnnotation)")
public void pointCut(){
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around开始执行");
//偷换参数
Object[] objects = {100,200};
//手动去执行真正的业务方法
Object res = joinPoint.proceed(objects);
System.out.println("Around执行结束");
return res;
}
@Before("pointCut()")
public void beforePointCut(JoinPoint joinPoint){ //JoinPoint对象封装了SpringAop中切面方法的
System.out.println("------before 执行到了LogAOP方法");
String className = joinPoint.getTarget().getClass().getName();
String funName = joinPoint.getSignature().getName();
System.out.println(className); //com.aop.demo.service.UserService
System.out.println(funName);
// System.out.println(joinPoint.getThis().getClass().getName());
//获取方法签名
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
//获取参数名
String[] parameterNames = methodSignature.getParameterNames();
//获取传入目标方法的参数对象
Object[] args = joinPoint.getArgs();
System.out.println("打印参数开始");
for (int i = 0; i < parameterNames.length; i++)
System.out.println(parameterNames[i]+": "+args[i]);
System.out.println("打印参数结束");
//接收注解中传递的参数
System.out.println("接收注解传递的参数");
LogAnnotation logAnnotation = methodSignature.getMethod().getDeclaredAnnotation(LogAnnotation.class);
String value = logAnnotation.value();
String desc = logAnnotation.desc();
boolean flag = logAnnotation.flag();
System.out.println("value:"+value);
System.out.println("desc:"+desc);
System.out.println("flag:"+flag);
System.out.println("注解传递的参数接收完毕");
}
@After("pointCut()")
public void afterPointCut(){
System.out.println("after...........");
}
@AfterReturning(pointcut = "pointCut()", returning = "rs")
public void afterReturning(String rs){
System.out.println("afterReturning.......");
System.out.println(rs);
}
@AfterThrowing(pointcut = "pointCut()", throwing = "throwable")
public void afterThrowing(Throwable throwable){
System.out.println("统一错误异常处理");
System.out.println(throwable.getMessage());
}
}
/**
* 测试类
*/
@SpringBootTest
class UserServiceTest {
@Autowired
UserService userService;
@Test
void funA() {
userService.funA();
}
@Test
void funB() {
userService.funB(15, 23);
}
@Test
void funC() {
userService.funC("liangye");
}
}
至于为什么在环绕通知里执行目标方法后,理论上目标方法执行完后去执行环绕通知后的剩余方法,但结果恰恰相反
经请教,是AOP里最后生成的拦截链和AOP对过滤的实现有关
惭愧,笔者还没细究其原因
之后如果笔者弄懂了之后会继续补充完该篇文章的.