SpringBoot使用AOP
在springboot项目中,如果有需要拦截的请求,可以使用拦截器,但是拦截器的粒度在我看来还是太粗糙了一些,多数用在一些登陆控制上,如果想要将粒度掌握的更加细致,就需要使用AOP(Aspect Oriented Programming)了
aop可以随意掌握粒度,大到类,小到方法。
下面介绍在springBoot项目中使用aop
导入Maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
准备Aspect类
需要在自己书写的Aspect类上带上@Aspect跟@Component注解
@Aspect:将该类变成切面类
@Component:将该类注册到容器中
@Aspect @Component public class AspectAnnotation {}
Aspect类中各种注解使用
@Before
前置通知,在目标方法执行之前执行,标注在方法上
@Before("execution(public * com.jx.aop.aopdemo.controller.*.*(..))") public void before(JoinPoint point) { // do someing }
可以看到,在前置方法中有一个JoinPoint point参数,那么这个参数是做什么的呢?
原来使用point可以获取目标方法的各种信息,如下表所列
方法 |
说明 |
joinPoint.getSignature() |
获取目标方法的信息 |
joinPoint.getArgs() |
获取目标方法传入的参数 |
joinPoint.getTarget() |
获取被代理的对象 |
joinPoint.getThis() |
获取代理对象 |
具体使用如下:
// 获取目标方法的信息 System.out.println(joinPoint.getSignature().getName()); // 执行的方法名 System.out.println(joinPoint.getSignature().getDeclaringType()); // 方法所在类 Class
System.out.println(joinPoint.getSignature().getDeclaringTypeName()); // 方法所在类的类型String System.out.println(joinPoint.getSignature().getModifiers()); // 方法修饰符对应的数字:1表示PUBLIC // 获取目标方法传入的参数 System.out.println(joinPoint.getArgs()); // 获取被代理的对象 System.out.println(joinPoint.getTarget()); // 获取代理对象 System.out.println(joinPoint.getThis());
@After
后置通知,在目标方法正常执行之后执行,如果出现异常不会执行
@After("execution(public * com.jx.aop.aopdemo.controller.*.*(..))") public void after(JoinPoint point) { // do someing }
@Around
环绕通知,在目标方法执行前后都会执行
@Around("execution(public * com.jx.aop.aopdemo.controller.*.*(..))") public void around(ProceedingJoinPoint point) { // do someing }
这里有一个参数需要说明一下
ProceedingJoinPoint point:
在环绕方法中需要写上point.proceed(),然后返回出去,继续执行被拦截的方法,当然这样是按照被拦截的方法本来的样子去执行的,这没有什么好说的,这里需要说的是point还有一个proceed方法,这个方法里面可以带上参数,当然了,参数不能随便带,必须是被拦截的方法里面的形参。那么怎么一个带法呢?
例如,现在要拦截一个controller方法,方法如下:
@RequestMapping("/index") public String index(String name, int num, ExtendedModelMap map) { map.put("name", name); map.put("num", num); return "index"; }
现在在执行proceed()的时候如果想要改变参数,那么需要按照顺序传递一个String类型的参数,然后是一个int类型的参数,最后是一个ExtendedModelMap
示例如下:
@Around("execution(* *(..))") public Object Around(ProceedingJoinPoint point) throws Throwable { // 获取ExtendedModelMap // point.getArgs()获取拦截方法的参数 List<Object> collect = Arrays.stream(point.getArgs()) .filter(e -> e instanceof ExtendedModelMap) .collect(Collectors.toList()); Object[] objects = {"dd", 123, collect.get(0)}; return point.proceed(objects); // 传一个Object数据进去即可 }
由上面的例子可以知道,只需要按照被拦截方法的参数顺序封装一个Object数组传入即可
@AferReturning
后置通知,在目标方法执行之后执行,不管是否产生异常都会执行
@AferReturning("execution(public * com.jx.aop.aopdemo.controller.*.*(..))") public void AferReturning(JoinPoint point) { // do someing }
@AfterThrowing
异常通知,在目标方法执行异常的时候执行
@AferThrowing("execution(public * com.jx.aop.aopdemo.controller.*.*(..))") public void AferThrowing(JoinPoint point) { // do someing }
Pintcut中注解使用
可以看出几种 Aspect 的执行顺序依次为 Before After Around AfterReturning(AfterThrowing)
上面说明了切面中各种注解的使用,但是在切面中,要指定切入那个方法,得使用execution或者其他,但是如果有多个切面拦截同一个切点的情况下,就得在每一个切面上声明一下,这样会很麻烦,那么我们可以使用@Pointcut来统一指定横切点,这样别的切面要使用的时候,引入这个切点即可
新建一个类,定义切点
public class PointCut { // 为controller包下所有的类配置切入点 @Pointcut("execution(public * com.jx.aop.aopdemo.controller.*.*(..))") public void pointcut(){} }
这样在切面中可以这样引入切点
@After("com.jx.aop.aopdemo.aspect.pointcut.PointCut.pointcut()") public void after(JoinPoint joinPoint) { System.out.println("后置通知" + joinPoint); }
@Pointcut详解
@pointcut能定义切点,下面介绍定义规则:
通配符(wildcards):
符号 |
作用 |
* |
匹配任意数量字符 |
+ |
匹配自定类及其子类 |
.. |
匹配任意数的子包或参数 |
指示器(指示器)
execution
这个是最常用的, 用于指定切点函数的,参数是匹配串
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
例如:
- public * com.jx.* target(..)
表示匹配com.jx下所有类的的target()方法,参数不限,返回值不限,但是得是public方法
- public * com.jx.* target(String, int)
匹配target()方法,而且第一个参数必须是String类型,第二个参数必须是int类型
那么在匹配多个规则的时候怎么办呢?
在多个表达式之间:
或的表示方法: ||, or
与的表示方法: &&, and
非的表示方法: !
例如:
@Pointcut("(execution(public * com.jx.aop.aopdemo.controller.*.*(..))) || (execution(private * *(..)))") public void pointcut(){}
@annotation()
切入标注了特定注解的方法,括号里面写注解类的全类名,也可以将注解写在切面方法里面作为形参,然后括号里面写上形参名就可以了
@Around("@annotation(annotation)") public Object Around(ProceedingJoinPoint point, FirstAnnotation annotation) throws Throwable {}
args()
表示入参必须是括号里面指定的类型
例如args(com.jx.Writer)表示运行时入参是Writer类型的方法,相当于execution(* *(com.jx.Writer+)),加号表示匹配Writer及其所有实现类为类型的类型参数
@args()
括号里面写一个注解类型,表示匹配任何这样一个方法,方法的入参对象的类必须标注上这个注解,例如:@args(com.jx.FirstAnnotation),假设匹配target(Writer writer),这个Writer类必须标注上@FirstAnnotation注解才行
within()
括号里面写某个类的全类名,表示匹配这个类下的所有方法,例如:
within(com.jx.*Service)表示匹配com.jx包下所有类名以Service结尾的类里面的所有方法
target()
括号里面填写匹配类,可以匹配该类以及其实现类里面所有的方法
如:target(com.jx.Writer)可以匹配writer以及其实现类里面的所有连接点
@within()
括号里面写注解类,用于匹配标注了该注解的所有类及其子孙类里面的方法
例如:@within(com.jx.FirstAnnotation)匹配标注了@FirstAnnotation的所有类以及其子孙类
@target
括号里面写注解类,里面匹配标注了该注解的所有类,与@within()的区别就是不会匹配子孙类
this()
匹配的是经过aop代理之后的类 通过introduction动态添加的方法也可以匹配到
Bean
bean匹配的是spring托管的bean实例。例如bean(authService) 匹配authService实例对象