天天看点

Spring AOP面向切面编程注解

Spring AOP

    • AOP概念
      • 相关术语
        • 切入点设置
        • 通知
    • AOP之JoinPoint和ProceedingJoinPoint
      • getSignature()方法
      • getArgs();
      • getTarget与getThis
      • ProceedingJoinPoint 特有

AOP概念

aop的本质就是动态代理, 在程序运行期间, 不修改源码对已有的方法进行增强

相关术语

  • 连接点(Joinpoint):在程序中我们可以进行aop的点, 通常是指的方法, 在使用aop时并不需要定义。
  • 切入点(Pointcut):被增强过的方法, (切入点一定是连接点, 但连接点并不一定是切入点), 切入点表示已经被增强过的, 连接点表示可以被增强的, 可以增强不表示被增强, 但是增强了的一定是可以被增强的。
  • 通知(Advice):就是我们进行增强的过程, 使我们具体逻辑过程, 可以理解为我们写的增强方法。
  • 切面(Aspect):切入点和通知组合就是一个切面, 就是我们通常用来进行切面的那个类, 可以使用注解 @Aspect 进行定义。

切入点设置

/**
	 * 权限 返回值 包.类.方法(参数。。。)
	 */
	@Pointcut(value = "execution(public * com.zqh.demo.controller.Demo.*(..)))")
    public void BrokerAspect(){
	}
           

定义切入点我们使用Pointcut注解, 使用的是execution表达式, 其中 * 号表示任意, 两点 表示任意参数。

上述execution表示, 在com.zqh.demo.controller.Demo类下权限为public的任意方法```

这个表示任意public方法
这个表示任意一To结尾的方法

通知

  • 前置通知
    /**
         * 定义切入点,切入点为 public 返回值 com.zqh.demo.controller.Demo.方法(参数) 中的所有函数
         *通过@Pointcut注解声明频繁使用的切点表达式
         */
        @Pointcut(value = "execution(public * com.zqh.demo.controller.Demo.*(..)))")
        public void BrokerAspect(){
        }
    	/**
         * 前置通知,在方法运行前通知
         */
        @Before("BrokerAspect()")
        //@Before("execution(public * com.zqh.demo.controller.Demo.*(..)))")
        public void doBeforeGame(){
            logger.info("前置通知");
        }
               
    前置通知使用@Before进行配置,里面值为切入点,可以手动写一个execution表达式也可以使用前面已经定义好的切入点, 这个方法将在切入点方法执行前执行
    /**
     * 这是执行方法
     */
     @GetMapping("/aop1")
    public String aop1(String msg) {
        logger.info("运行了方法");
        //int i = 1/0;
        return msg;
    }
               
    Spring AOP面向切面编程注解
    运行前置通知后再运行方法
  • 后置通知
    /**
         * 后置通知, 方法运行后运行, 发生异常后不通知
         */
        @AfterReturning("BrokerAspect()")
        //@AfterReturning("execution(public * com.zqh.demo.controller.Demo.*(..)))")
        public void doAfterReturningGame(){
            logger.info("后置通知");
        }
               
  • 异常通知
    /**
         * 异常通知, 在方法发生异常时通知
         */
        @AfterThrowing("BrokerAspect()")
        //@AfterThrowing("execution(public * com.zqh.demo.controller.Demo.*(..)))")
        public void doAfterThrowingGame(){
            logger.info("异常通知");
        }
               
后置通知和异常通知只有一个会执行,当方法没有抛出异常时将执行后置通知,抛出异常后只会执行异常通知

在没有异常的情况下, 并没有执行异常通知

Spring AOP面向切面编程注解

这时我们制造一个异常, 只执行了异常通知并没有执行后置通知

@GetMapping("/aop1")
    public String aop1(String msg) {
        logger.info("运行了方法");
        int i = 1/0;
        return msg;
    }
           
Spring AOP面向切面编程注解
  • 最终通知
    /**
         * 最终通知, 方法运行最后通知, 不管是否产生异常,都会通知
         */
        @After("BrokerAspect()")
        //@After("execution(public * com.zqh.demo.controller.Demo.*(..)))")
        public void doAfterGame(){
            logger.info("最终通知");
        }
               
Spring AOP面向切面编程注解
Spring AOP面向切面编程注解
  • 环绕通知

    这是功能最强的一个通知,我们可以使用这个通知完成其他通知的功能

    /**
     * 环绕通知
     */
    @Around("BrokerAspect()")
    public void doAroundGame(ProceedingJoinPoint pjp) {
        try {
            logger.info("环绕前置通知");
            pjp.proceed();
            logger.info("环绕后置通知");
        }  catch (Throwable throwable) {
            logger.info("环绕异常通知");
        } finally {
            logger.info("环绕最终通知");
        }
    }
               
    ProceedingJoinPoint 表示切入点,可以通过它的proceed控制方法什么时候执行。并在方法执行前后进行通知。
    现在我们将前面几个通知注释之后看看环绕通知效果
    Spring AOP面向切面编程注解
    如果有异常的情况
    Spring AOP面向切面编程注解
    和上面的效果是相似的,将环绕通知和其它通知同时存在是他的执行顺序又是如何呢?
    Spring AOP面向切面编程注解
    Spring AOP面向切面编程注解

    可以看到环绕通知比其它通知更早开始, 更晚结束。其实这并不难理解。

    前置通知,后置通知, 异常通知, 最终通知都是在方法执行时才会开始,在环绕通知时只有执行到pjp.proceed();方法时, 原来的方法才算是真正的开始执行,前置通知,后置通知, 异常通知, 最终通知相当于都在或者方法里面。

package com.zqh.demo.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * @author zhuangqinghui
 * @version 1.0
 * @date 2020/11/19 10:10
 */
@RestController
@RequestMapping("/demo")
public class Demo {


    public String a = "111";
    private static final Logger logger = LoggerFactory.getLogger(Demo.class);

    @GetMapping("/aop1")
    public String aop1(String msg) {
        logger.info("运行了方法");
        //int i = 1/0;
        return msg;
    }
    @GetMapping("/aop2")
    public String aop2(String msg) {
        logger.info("运行了方法2");
        //int i = 1/0;
        return msg;
    }

    @Override
    public String toString() {
        return "Demo{}";
    }

    public String aaa() {
        logger.info("hjdfhdhfhdfhdh");
        return "sss";
    }
}

           
package com.zqh.demo.aop;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zqh.demo.controller.Demo;
import com.zqh.demo.pojo.User;
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;

/**
 * @author zhuangqinghui
 * @version 1.0
 * @date 2020/11/19 10:16
 */
@Aspect
@Component
public class TestAspect {

    private static final Logger logger = LoggerFactory.getLogger(TestAspect.class);
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    
    /**
     * 定义切入点,切入点为 public 返回值 com.zqh.demo.controller.Demo.方法(参数) 中的所有函数
     *通过@Pointcut注解声明频繁使用的切点表达式
     */
    @Pointcut(value = "execution(public * com.zqh.demo.controller.Demo.aop1(..)))")
    public void BrokerAspect(){
    }

    /**
     * 前置通知,在方法运行前通知
     */
    @Before("BrokerAspect()")
    //@Before("execution(public * com.zqh.demo.controller.Demo.*(..)))")
    public void doBeforeGame() {
        logger.info("前置通知");
    }

    /**
     * 最终通知, 方法运行最后通知, 不管是否产生异常,都会通知
     */
    @After("BrokerAspect()")
    //@After("execution(public * com.zqh.demo.controller.Demo.*(..)))")
    public void doAfterGame(){
        logger.info("最终通知");
    }

    /**
     * 后置通知, 方法运行后运行, 发生异常后不通知
     */
    @AfterReturning("BrokerAspect()")
    //@AfterReturning("execution(public * com.zqh.demo.controller.Demo.*(..)))")
    public void doAfterReturningGame(){
        logger.info("后置通知");
    }

    /**
     * 异常通知, 在方法发生异常时通知
     */
    @AfterThrowing("BrokerAspect()")
    //@AfterThrowing("execution(public * com.zqh.demo.controller.Demo.*(..)))")
    public void doAfterThrowingGame(){
        logger.info("异常通知");
    }

    /**
     * 环绕通知
     */
    @Around("BrokerAspect()")
    public void doAroundGame(ProceedingJoinPoint pjp) {
        try {
            logger.info("环绕前置通知");
            pjp.proceed();
            logger.info("环绕后置通知");
        }  catch (Throwable throwable) {
            logger.info("环绕异常通知");
        } finally {
            logger.info("环绕最终通知");
        }
    }
}

           

AOP之JoinPoint和ProceedingJoinPoint

在之前的案例中我们执行了各种通知, 但似乎这些通知和原来的方法没有任何联系, 我们也获取不到被代理方法的任何信息, 只是两个独立的方法分前后运行. 建立通知和被代理对象关系的对象正是JoinPoint

方法名 返回值 功能
getSignature(); Signature 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
getArgs(); Object[] 获取传入目标方法的参数对象
getTarget(); Object 获取未被代理的被代理对象
getThis(); Object 获取已经被代理的被代理对象

getSignature()方法

这个方法可以得到被代理对象和方法的一些信息, 常用的有:

  1. getName(); 获取被代理的方法名.
  2. getDeclaringType().getSimpleName(); 获取类名
  3. getDeclaringTypeName(); 带有包名的类名
/**
     * 前置通知,在方法运行前通知
     */
    @Before("BrokerAspect()")
    //@Before("execution(public * com.zqh.demo.controller.Demo.*(..)))")
    public void doBeforeGame(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        logger.info("被代理方法名:{}", signature.getName());
        logger.info("类名:{}", signature.getDeclaringType().getSimpleName());
        logger.info("带包名类名:{}", signature.getDeclaringTypeName());
        logger.info("前置通知");
    }

           
Spring AOP面向切面编程注解
// 从切面植入点出通过反射机制获取织入点处的方法
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    // 获取切入点所在的方法
    Method method = signature.getMethod();
    //也可以通过这样的方式获取方法的反射对象
           

getArgs();

这个方法可以获取被代理方法的所有参数以Object[]的形式返回.

我们多设一个int参数

@GetMapping("/aop1")
    public String aop1(String msg, Integer i) {
        logger.info("运行了方法: msg={}, i={}", msg, i);
        return msg;
    }
           
/**
     * 前置通知,在方法运行前通知
     */
    @Before("BrokerAspect()")
    public void doBeforeGame(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            logger.info("参数类型为: {}, 值为: {}", arg.getClass().getName(), arg);
        }
        logger.info("前置通知");
    }
           
Spring AOP面向切面编程注解
Spring AOP面向切面编程注解

getTarget与getThis

getTarget和getThis有些相似, 一个是获取未被代理的, 一个是获取已被代理的. 我们简单看一下例子

  1. 在Demo中新建一个方法
public void printMsg(String msg) {
        logger.info("printMsg方法 msg:{}", msg);
    }
           
  1. 设置通知
@Pointcut(value = "execution(public * com.zqh.demo.controller.Demo.aop1(..)))")
    public void BrokerAspect(){
    }

    @Pointcut(value = "execution(public * com.zqh.demo.controller.Demo.printMsg(..)))")
    public void printMsgAspect(){
    }
    
	/**
     * 前置通知,在方法运行前通知
     */
    @Before("printMsgAspect()")
    public void doBeforePrintMsg() {
        logger.info("PrintMsg前置通知");
    }
           
  1. 使用getTarget
/**
     * 前置通知,在方法运行前通知
     */
    @Before("BrokerAspect()")
    public void doBeforeGame(JoinPoint joinPoint) {
        Demo target = (Demo)joinPoint.getTarget();
        target.printMsg("使用 target");
        logger.info("前置通知");
    }
           
  1. 运行结果如下
    Spring AOP面向切面编程注解
  2. 使用getThis
/**
     * 前置通知,在方法运行前通知
     */
    @Before("BrokerAspect()")
    public void doBeforeGame(JoinPoint joinPoint) {
        Demo aThis = (Demo)joinPoint.getThis();
        aThis.printMsg("使用 aThis");
        logger.info("前置通知");
    }
           
  1. 运行结果如下
    Spring AOP面向切面编程注解
    我们发现在getThis中获取的对象有使用到通知,而getTarget获得的对象没有使用到通知.

ProceedingJoinPoint 特有

proceed(); 方法, 运行方法, 也可以传入新的参数以新参数运行方法