1、AOP简介:
AOP为开发者提供一种进行横切关注点分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而无耦合的完成了我们的功能。
使用楔子,完成方法前后的自定义功能
2、AOP学习目的:
- SpringBoot使用AOP统一处理:Web请求日志
- SpringBoot使用AOP统一处理:事务处理
- SpringBoot使用AOP统一处理:用户登录拦截
- SpringBoot使用AOP统一处理:请求幂等级(防止重复提交)
- SpringBoot使用AOP统一处理:统计方法执行时长
3、参考网址:
4、核心代码讲解
4.1 使用AOP进行日志的拦截
- 进行日志AOP的编写:先定义一个切入点,然后编写这个切入点的前置、后置等方法,进行日志相关的记录,当前的切入点是所有的controller类的所有方法
@Aspect
@Component
public class LogWebAspect {
/**
* 指定切点
* 匹配 com.enzoism.controller包及其子包下的所有类的所有方法
*/
@Pointcut("execution(public * com.enzoism.controller.*.*(..))")
public void webLog(){
}
/**
* 前置通知,方法调用前被调用
* @param joinPoint
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint){
System.out.println("我是前置通知!!!");
//获取目标方法的参数信息
Object[] obj = joinPoint.getArgs();
System.out.println("请求参数:"+obj);
// 代理
Signature signature = joinPoint.getSignature();
//代理的是哪一个方法
System.out.println("方法:"+signature.getName());
//AOP代理类的名字
System.out.println("方法所在包:"+signature.getDeclaringTypeName());
//AOP代理类的类(class)信息
signature.getDeclaringType();
MethodSignature methodSignature = (MethodSignature) signature;
String[] strings = methodSignature.getParameterNames();
System.out.println("参数名:"+ Arrays.toString(strings));
System.out.println("参数值ARGS : " + Arrays.toString(joinPoint.getArgs()));
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest req = attributes.getRequest();
// 记录下请求内容
System.out.println("请求URL : " + req.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + req.getMethod());
System.out.println("IP : " + req.getRemoteAddr());
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
}
/**
* 处理完请求返回内容
* @param ret
* @throws Throwable
*/
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
System.out.println("方法的返回值 : " + ret);
}
/**
* 后置异常通知
* @param jp
*/
@AfterThrowing("webLog()")
public void throwss(JoinPoint jp){
System.out.println("方法异常时执行.....");
}
/**
* 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
* @param jp
*/
@After("webLog()")
public void after(JoinPoint jp){
System.out.println();
}
/**
* 环绕通知,环绕增强,相当于MethodInterceptor
* @param pjp
* @return
*/
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) {
try {
Object o = pjp.proceed();
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
- 进行方法请求:任何的请求都会触发日志的拦截,所有我们可以自定义多个AOP,然后针对不对的业务,对不同的类和方法进行拦截,或者【结合annotation进行指定的标记位插入】
4.2 进行annotation的使用
- 定义annotation:后面可以获取该注解的参数,进行日志相关的打印
@Aspect
@Component
public class LogWebAspect {
//统计请求的处理时间
ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 指定切点
* 匹配 com.enzoism.controller包及其子包下的所有类的所有方法
*/
@Pointcut("execution(public * com.enzoism.controller.*.*(..))")
public void webLog(){
}
/**
* 前置通知,方法调用前被调用
* @param joinPoint
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint){
System.out.println("我是前置通知!!!");
startTime.set(System.currentTimeMillis());
//获取目标方法的参数信息
Object[] obj = joinPoint.getArgs();
System.out.println("请求参数:"+obj);
// 代理
Signature signature = joinPoint.getSignature();
//代理的是哪一个方法
System.out.println("方法:"+signature.getName());
//AOP代理类的名字
System.out.println("方法所在包:"+signature.getDeclaringTypeName());
//AOP代理类的类(class)信息
signature.getDeclaringType();
MethodSignature methodSignature = (MethodSignature) signature;
String[] strings = methodSignature.getParameterNames();
System.out.println("参数名:"+ Arrays.toString(strings));
System.out.println("参数值ARGS : " + Arrays.toString(joinPoint.getArgs()));
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest req = attributes.getRequest();
// 记录下请求内容
System.out.println("请求URL : " + req.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + req.getMethod());
System.out.println("IP : " + req.getRemoteAddr());
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
}
/**
* 处理完请求返回内容
* @param ret
* @throws Throwable
*/
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
System.out.println("方法的返回值 : " + ret);
}
/**
* 后置异常通知
* @param jp
*/
@AfterThrowing("webLog()")
public void throwss(JoinPoint jp){
System.out.println("方法异常时执行.....");
}
/**
* 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
* @param jp
*/
@After("webLog()")
public void after(JoinPoint jp){
System.out.println("方法执行时间:"+ (System.currentTimeMillis() - startTime.get()));
System.out.println();
}
/**
* 环绕通知,环绕增强,相当于MethodInterceptor
* @param pjp
* @return
*/
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) {
try {
Object o = pjp.proceed();
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
此时可以看出annotation的作用,可以进行自定义标记,然后在切面中获取该标记,进行后续相关操作,通过method.getAnnotation()获取指定的标记位即可,但是这个操作毕竟有点low,能不能使用AOP将Annotation直接对接起来
4.1 进行AOP将Annotation组合使用
@Aspect
@Component
public class LogAnnotationAspect {
/**
* 指定切点
* 匹配 被添加LogAnnotation标注的方法
*/
@Pointcut("@annotation(logAnnotation)")
public void pointCut(LogAnnotation logAnnotation) {
}
/**
* 环绕通知,环绕增强,相当于MethodInterceptor
* @param pjp
* @return
*/
@Around("pointCut(logAnnotation)")
public Object arround(ProceedingJoinPoint pjp, LogAnnotation logAnnotation) {
try {
Object o = pjp.proceed();
System.out.println("---------------直接指定到logAnnotation位置");
System.out.println("actionType:"+logAnnotation.actionType());
System.out.println("controllerName:"+logAnnotation.controllerName());
System.out.println("module:"+logAnnotation.module());
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
5、由AOP发起的思考
- 什么是代理?有几种方式,区别是什么?
- ThreadLocal在多线程中的使用(并发编程)
- ProceedingJoinPoint进行proceed()进程执行过程中加其他操作(锁判定/幂等级判定)
- Springboot测试用例CountDownLatch和ExecutorService使用