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实例对象

 

posted @ 2019-07-26 15:20  Jin同学  阅读(318)  评论(0编辑  收藏  举报