springboot AOP操作

一、AOP

  • AOP(Aspect Oriented Programming)面向切面变成,程序开发中主要用来解决一些系统层面的问题。比如日志,事务,权限等
  • 使用场景:
    • 权限控制
    • 缓存控制
    • 审计日志
    • 性能监控
    • 异常处理
    • 分布式追踪

二、AOP专业术语

  1. target:目标类,即需要被代理的类。例如:UserService
  2. Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
  3. PointCut(切入点):已经被增强的连接点。例如:addUser()
  4. advice(通知、增强):增强代码。例如:after、before 一共五种
  5. Weaving(织入):指把增强advice应用到目标对象target来创建新的代理对象Proxy的过程
  6. proxy:代理类
  7. Aspect(切面):是切入点pointcut和通知advice的结合

三、五种Advice

  • 前置通知:在我们执行目标方法之前运行 (Before)
  • 后置通知:在我们执行目标方法之后,不管有没有异常(@After)
  • 返回通知:在我们执行目标方法正常返回值后运行(@AfterReturning)
  • 异常通知:在我们执行目标方法出现异常后运行(@AfterThrowing)
  • 环绕通知:动态代理,需要手动执行joinPoint.procced() (@Around)
    • 其实就是执行我们的目标方法执行之前相当于前置通知,执行之后相当于后置通知

四、SpringBoot整合AOP

4.1 导入Maven

4.2 创建Controller

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Description:
 */
@RestController
public class UserController {
    @RequestMapping("/first")
    public Object first() {
        return "first controller";
    }

    @ReuqestMapping("/doError")
    public Object error() {
        return 1 / 0;
    }
}

4.3 创建切面类

/**
 * 日志切面
 */

@Aspect
@Comment
public class LogAspect {
    @Pointcut("execution(public * com.xncoding.aop.controller.*.*(..))")
    public void webLog(){}

    @Before("webLog()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        System.out.println("URL : " + request.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + request.getMethod());
        System.out.println("IP : " + request.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));

    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("方法的返回值 : " + ret);
    }

    //后置异常通知
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }

    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
    @After("webLog()")
    public void after(JoinPoint jp){
        System.out.println("方法最后执行.....");
    }

    //环绕通知,环绕增强,相当于MethodInterceptor
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) {
        System.out.println("方法环绕start.....");
        try {
            Object o =  pjp.proceed();
            System.out.println("方法环绕proceed,结果是 :" + o);
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

4.4 测试效果

方法环绕start.....
URL : http://localhost:8092/first
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.xncoding.aop.controller.UserController.first
ARGS : []
方法环绕proceed,结果是 :first controller
方法最后执行.....
方法的返回值 : first controller

4.5 切面注解说明

@Aspect 作用是把当前类标识为一个切面供容器读取

@Pointcut 定义切点,切点方法不用任何代码,返回值是void,重要的是条件表达式

@Before 标识一个前置增强方法,相当于BeforeAdvice的功能

@AfterReturning 后置增强,相当于AfterReturningAdvice,方法退出时执行

@AfterThrowing 异常抛出增强,相当于ThrowsAdvice

@After final增强,不管是抛出异常或者正常退出都会执行

@Around 环绕增强,相当于MethodInterceptor

方法参数说明

  1. 除了@Around外,每个方法里都可以加或者不加参数JoinPoint。

  2. JoinPoint:里包含了类名、被切面的方法名,参数等属性,可供读取使用。

  3. @Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。

  4. @AfterReturning方法里,可以加returning = “xxx”,xxx即为在controller里方法的返回值,本例中的返回值是”first controller”。

  5. @AfterThrowing方法里,可以加throwing = “XXX”,读取异常信息,如本例中可以改为:

    //后置异常通知
    @AfterThrowing(throwing = "ex", pointcut = "webLog()")
    public void throwss(JoinPoint jp, Exception ex){
        System.out.println("方法异常时执行.....");
    }
    

    一般常用的有before和afterReturn组合,或者单独使用Around,即可获取方法开始前和结束后的切面。

4.6 关于切点PointCut

execution函数用于匹配方法执行的连接点,语法为:

execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选))

Spring中还支持其他多个函数,这里列出名称和简单介绍

  1. @annotation() 表示标注了指定注解的目标类方法
  2. args() 通过目标类方法的参数类型指定切点
  3. @args() 通过目标类参数的对象类型是否标注了指定注解指定切点
  4. within() 通过类名指定,它将匹配指定类型
  5. target() 通过类名指定,同时包含所有子类
  6. @within() 匹配标注了指定注解的类及其子类,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
  7. @target() 匹配标注了指定注解的类,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

逻辑运算符

表达式可由多个切点函数通过逻辑运算组成,与(&&)、 或(||)、 非(!)

比如execution(* chop(..)) && target(Horseman) 表示Horseman及其子类的chop方法

五、自定义注解

一般多用于某些特定的功能,我们来自定义一个注解:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
    String desc() default "无信息";
}

在Controller里加个方法:

@RequestMapping("/second")
@UserAccess(desc = "second")
public Object second() {
    return "second controller";
}

创建一个新的切面类

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


@Aspect
@Component
public class UserAccessAspect {

    @Pointcut(value = "@annotation(com.xncoding.aop.aspect.UserAccess)")
    public void access() {

    }

    @Before("access()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("second before");
    }

    @Around("@annotation(userAccess)")
    public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
        //获取注解里的值
        System.out.println("second around:" + userAccess.desc());
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }
}

主要看一下@Around注解这里,如果需要获取在controller注解中赋给UserAccess的desc里的值,就需要这种写法,这样UserAccess参数就有值了。

方法环绕start.....
URL : http://localhost:8092/second
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.xncoding.aop.controller.UserController.second
ARGS : []
second around:second
second before
方法环绕proceed,结果是 :second controller
方法最后执行.....
方法的返回值 : second controller

通知结果可以看到,两个aop切面类都工作了,顺序呢就是下面的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kqrF8dF5-1600334839540)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20200917172554888.png)]

spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。

对于上面的例子就是,先外层的就是对所有controller的切面,内层就是自定义注解的。 那不同的切面,顺序怎么决定呢,尤其是同格式的切面处理,譬如两个execution的情况,那spring就是随机决定哪个在外哪个在内了

所以大部分情况下,我们需要指定顺序,最简单的方式就是在Aspect切面类上加上@Order(1)注解即可

order越小最先执行,也就是位于最外层

像一些全局处理的就可以把order设小一点,具体到某个细节的就设大一点。

AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。

对于上面的例子就是,先外层的就是对所有controller的切面,内层就是自定义注解的。 那不同的切面,顺序怎么决定呢,尤其是同格式的切面处理,譬如两个execution的情况,那spring就是随机决定哪个在外哪个在内了

所以大部分情况下,我们需要指定顺序,最简单的方式就是在Aspect切面类上加上@Order(1)注解即可

order越小最先执行,也就是位于最外层

像一些全局处理的就可以把order设小一点,具体到某个细节的就设大一点。

你可能感兴趣的:(springboot,java,aop)