• 中文
    • English
  • 注册
  • 赞助本站

    • 支付宝
    • 微信
    • QQ

    感谢一直支持本站的所有人!

    • 查看作者
    • SpringBoot:面向切面编程

      一.  面向切面编程

      面向切面编程简称AOP,在讲解面向切面编程前,我们首先要搞懂什么是面向切面编程?简单说,面向切面编程就是在运行时,动态地将代码切入到类的指定方法、指定位置上,可以在不修改原代码的情况下添加新功能。[1]

      《SpringBoot和Mybatis的整合》一文中的数据为例,假如我们想在BookController的五个方法执行之前输出请求地址、请求方式等数据,应该怎么做呢?

      方法一:修改每个方法,完成对应的功能

      方法二:采用AOP编程,将要完成的功能直接切入这五个方法中。

      方法一是最笨的方法,不仅繁琐,且存在各种问题,而使用AOP编程,我们便可巧妙的为每个方法添加新功能,关于上述功能的实现,将在下文的前置通知中实现。

      二.  添加依赖

      在SpringBoot中使用AOP,需要先在pom.xml中添加以下依赖:

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>

      三.  前置通知

      所谓前置通知就是执行目标方法前拦截到的方法。只需要一个连接点JoinPoint,即可获取拦截目标方法以及请求参数。[2]

      还是以BookController中的五个方法为例,首先新建aspect包,在该包下新建LoggingAspect.java,并为其添加@Aspect、@Component注解

      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      import org.slf4j.Logger; //注意必须是slf4j包
      import org.slf4j.LoggerFactory;//注意必须是slf4j包
      import org.springframework.stereotype.Component;
      import org.springframework.web.context.request.RequestContextHolder;
      import org.springframework.web.context.request.ServletRequestAttributes;
      
      import javax.servlet.http.HttpServletRequest;
      import java.util.Arrays;
      
      @Aspect
      @Component
      public class LoggingAspect {
      
          private Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
      
          @Before("execution(public *  io.zhangjia.springboot06.controller.BookController.*(..))")
          //JoinPoint :连接点,可以获取到目标方法的相关信息
          public void before(JoinPoint joinPoint) {
              ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
              HttpServletRequest request = requestAttributes.getRequest();
              //获取请求地址
              StringBuffer requestURL = request.getRequestURL();
              logger.debug("request url : {} " ,requestURL);
              //获取请求方式
              String method = request.getMethod();
              logger.debug("request method : {} ",method);
              //获取目标方法的方法名
              String name = joinPoint.getSignature().getName();
              logger.debug("method name : {} ",name);
              //获取目标方法的参数
              Object[] args = joinPoint.getArgs();
              logger.debug("method args : {} ", Arrays.toString(args));
          }
      }

      关于@Before("execution(public *  io.zhangjia.springboot06.controller.BookController.*(..))") 注解

      • @Before:前置通知

      • "execution(xxx)" : 通过execution规定before()方法在哪些方法执行之前执行

      • public *  io.zhangjia.springboot06.controller.BookController.*(..):

        • public * 意为任何返回值

        • xxx.BookController.*()意为该项目下的BookController中的所有无参方法

        • 而(..) 意为该方法的任意参数

        • 合起来xxx.BookController.*(..) 就是BookController中的所有的方法

      接下来执行BookController中的任意方法,都会在执行指定的方法前输出before中的相关信息,以http://localhost:8888/book/2为例,控制台输出:

      2019-07-29 22:36:42.786 DEBUG 32096 --- [nio-8888-exec-8] i.z.springboot06.aspect.LoggingAspect    : request url : http://localhost:8888/book/2 
      2019-07-29 22:36:42.786 DEBUG 32096 --- [nio-8888-exec-8] i.z.springboot06.aspect.LoggingAspect    : request method : GET 
      2019-07-29 22:36:42.786 DEBUG 32096 --- [nio-8888-exec-8] i.z.springboot06.aspect.LoggingAspect    : method name : book 
      2019-07-29 22:36:42.786 DEBUG 32096 --- [nio-8888-exec-8] i.z.springboot06.aspect.LoggingAspect    : method args : [2] 
      2019-07-29 22:36:42.787 DEBUG 32096 --- [nio-8888-exec-8] i.z.s.mapper.BookMapper.queryById        : ==>  Preparing: select * from book WHERE book_id = ?; 
      2019-07-29 22:36:42.787 DEBUG 32096 --- [nio-8888-exec-8] i.z.s.mapper.BookMapper.queryById        : ==> Parameters: 2(Integer)
      2019-07-29 22:36:42.788 DEBUG 32096 --- [nio-8888-exec-8] i.z.s.mapper.BookMapper.queryById        : <==      Total: 1
      2019-07-29 22:36:42.789  INFO 32096 --- [nio-8888-exec-8] i.z.s.controller.BookController          : Book{bookId=2, name='我的自传', author='孙著杰', price=25.0}

      可以看到前四条即为before中获取的相关信息。

      四.  返回通知

      所谓返回通知就是在方法正常执行通过之后执行的通知。

      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Aspect;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class LoggingAspect {
      
          private Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
      
      
          @AfterReturning(pointcut = "execution(public * io.zhangjia.springboot06.controller.BookController.*(..))", returning = "object")
          public void afterReturning(Object object) {
              logger.debug("returning : {}", object);
          }
      }

      其中returning = "object") 该方法的哪一个参数用来保存方法的返回值,也就是afterReturning方法中的Object参数,如果目标方法方法是void,那么返回类型为Null[3]

      接下来访问http://localhost:8888/book/2,控制台输出:

      2019-07-29 22:52:29.547 DEBUG 33396 --- [nio-8888-exec-3] i.z.s.mapper.BookMapper.queryById        : ==>  Preparing: select * from book WHERE book_id = ?; 
      2019-07-29 22:52:29.548 DEBUG 33396 --- [nio-8888-exec-3] i.z.s.mapper.BookMapper.queryById        : ==> Parameters: 2(Integer)
      2019-07-29 22:52:29.549 TRACE 33396 --- [nio-8888-exec-3] i.z.s.mapper.BookMapper.queryById        : <==    Columns: BOOK_ID, NAME, AUTHOR, PRICE
      2019-07-29 22:52:29.549 TRACE 33396 --- [nio-8888-exec-3] i.z.s.mapper.BookMapper.queryById        : <==        Row: 2, 傻逼自传, 孙著杰, 25.00
      2019-07-29 22:52:29.549 DEBUG 33396 --- [nio-8888-exec-3] i.z.s.mapper.BookMapper.queryById        : <==      Total: 1
      2019-07-29 22:52:29.550  INFO 33396 --- [nio-8888-exec-3] i.z.s.controller.BookController          : Book{bookId=2, name='傻逼自传', author='孙著杰', price=25.0}
      2019-07-29 22:52:29.550 DEBUG 33396 --- [nio-8888-exec-3] i.z.springboot06.aspect.LoggingAspect    : returning : {data=Book{bookId=2, name='傻逼自传', author='孙著杰', price=25.0}, success=true}

      五.  后置通知

      所谓后置通知就是在目标方法执行之后执行,而且不管方法是否抛出异常,都会执行。注意,后置通知在返回通知之前执行

      //后置通知,在目标方法执行之后执行
      @After("execution(public * io.zhangjia.springboot06.controller.BookController.*(..))")
      public  void after(JoinPoint joinPoint) {
          String methoName = joinPoint.getSignature().getName();
          logger.debug("mehod {} end",methoName);
      }

      六.  异常通知

      所谓异常通知就是在目标方法出现异常时执行,执行异常通知后,返回通知不会被执行。

      //异常通知,在目标出现异常时执行
      @AfterThrowing(pointcut = "execution(public * io.zhangjia.springboot06.controller.BookController.*(..))", throwing = "e")
      public void afterThrowing(Exception e) {
          logger.error("exception : {}", e.toString());
      }

      七. 设置通用的切面表达式

      在上述通知中,所有的切面表达式内容都相同,而且写多次,我们便可以将其单独提取出来:

      package io.zhangjia.springboot06.aspect;
      
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.*;
      import org.slf4j.Logger; //注意必须是slf4j包
      import org.slf4j.LoggerFactory;//注意必须是slf4j包
      import org.springframework.stereotype.Component;
      import org.springframework.web.context.request.RequestContextHolder;
      import org.springframework.web.context.request.ServletRequestAttributes;
      
      import javax.servlet.http.HttpServletRequest;
      import java.util.Arrays;
      
      @Aspect
      @Component
      public class LoggingAspect {
      
          private Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
      
          @Pointcut("execution(public *  io.zhangjia.springboot06.controller.BookController.*(..))")
          public void log(){}
      
          @Before("log()")
          //JoinPoint :连接点,可以获取到目标方法的相关信息
          public void before(JoinPoint joinPoint) {
              ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
              HttpServletRequest request = requestAttributes.getRequest();
              //获取请求地址
              StringBuffer requestURL = request.getRequestURL();
              logger.debug("request url : {} ", requestURL);
              //获取请求方式
              String method = request.getMethod();
              logger.debug("request method : {} ", method);
              //获取目标方法的方法名
              String name = joinPoint.getSignature().getName();
              logger.debug("method name : {} ", name);
              //获取目标方法的参数
              Object[] args = joinPoint.getArgs();
              logger.debug("method args : {} ", Arrays.toString(args));
          }
      
          @AfterReturning(pointcut = "log()", returning = "object")
          public void afterReturning(Object object) {
              logger.debug("returning : {}", object);
          }
          //后置通知,在目标方法执行之后执行
          @After("log()")
          public  void after(JoinPoint joinPoint) {
              String methoName = joinPoint.getSignature().getName();
              logger.debug("mehod {} end",methoName);
          }
          //异常通知,在目标出现异常时执行
          @AfterThrowing(pointcut = "log()", throwing = "e")
          public void afterThrowing(Exception e) {
              logger.error("exception : {}", e.toString());
          }
      }

      参考资料

      [1] 知乎:欲眼熊猫 

      [2] 薄暮凉年

    • 1
    • 0
    • 0
    • 734
    • umuhm

      请登录之后再进行评论

      登录
    • 做任务
    • 实时动态
    • 偏好设置
    • 返回顶部
    • 单栏布局 侧栏位置: