轻松搞定AOP
集成Spring AOP步骤
1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.实体
@Slf4j
@Aspect
@Order(3) // 有多个日志时,ORDER可以定义切面的执行顺序(数字越大,前置越后执行,后置越前执行)
@Component
public class LogAspect {
ThreadLocal<Long> startTime = new ThreadLocal<>();
@Pointcut("execution(public * cn.myframe.controller..*.*(..))")
public void log() {
}
@Before("log()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
startTime.set(System.currentTimeMillis());
// 接收到请求,记录请求内容
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
log.info("URL : " + request.getRequestURL().toString());
log.info("HTTP_METHOD : " + request.getMethod());
log.info("IP : " + request.getRemoteAddr());
log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName()
+ "." + joinPoint.getSignature().getName());
log.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "log()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
log.info("RESPONSE : " + ret);
log.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));
}
}
通知位置的切入内容
-
使用@Before在切入点开始处切入内容
-
使用@After在切入点结尾处切入内容
-
使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
-
使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
-
使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
AOP切面的优先级
由于通过AOP实现,程序得到了很好的解耦,但是也会带来一些问题,比如:我们可能会对Web层做多个切面,校验用户,校验头信息等等,这个时候经常会碰到切面的处理顺序问题。
@Order(i)注解来标识切面的优先级。i的值越小,优先级越高。假设我们还有一个切面是CheckNameAspect用来校验name必须为didi,我们为其设置@Order(10),而上文中WebLogAspect设置为@Order(5),所以WebLogAspect有更高的优先级,这个时候执行顺序是这样的:
在@Before中优先执行@Order(5)的内容,再执行@Order(10)的内容 在@After和@AfterReturning中优先执行@Order(10)的内容,再执行@Order(5)的内容 所以我们可以这样子总结: 在切入点前的操作,按order的值由小到大执行 在切入点后的操作,按order的值由大到小执行
定义切入点
切入点表达式的格式:execution([可见性] 返回类型 [声明类型].方法名(参数) [异常])
其中[]的为可选,其他的还支持通配符的使用:
-
*:匹配所有字符
-
..:一般用于匹配多个包,多个参数
-
+:表示类及其子类
运算符有:&&、||、!
@Pointcut("execution(public * cn.myframe.controller..*.*(..))") 上面这段注解的意思如下: 1) execution(): 表达式主体 2) 第一个public *号:表示返回类型, *号表示所有的类型。 3) 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.king.controller包、子孙包下所有类的方法。 4) 第二个*号:表示类名,*号表示所有的类。 5) *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
常用切入点表达式关键词
1)execution:用于匹配子表达式。
//匹配com.cjm.model包及其子包中所有类中的所有方法,返回类型任意,方法参数任意 @Pointcut("execution(* com.cjm.model...(..))") public void before(){}
2)within:用于匹配连接点所在的Java类或者包。
//匹配Person类中的所有方法 @Pointcut("within(com.cjm.model.Person)") public void before(){} //匹配com.cjm包及其子包中所有类中的所有方法 @Pointcut("within(com.cjm..*)") public void before(){}
3)@annotation :匹配连接点被它参数指定的Annotation注解的方法。也就是说,所有被指定注解标注的方法都将匹配。
@Pointcut("@annotation(com.cjm.annotation.AdviceAnnotation)") public void before(){}
4)target:用于向通知方法中传入目标对象的引用。
@Before("before() && target(target) public void beforeAdvide(JoinPoint point, Object proxy){ }
5)bean:通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。
@Pointcut("bean(person)") public void before(){}
6)args:用于将参数传入到通知方法中。
args
来绑定。如果在一个args表达式中应该使用类型名字的地方 使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。
@Before("before() && args(age,username)") public void beforeAdvide(JoinPoint point, int age, String username){ }
上一篇: SpringBoot日志配置