• 中文
    • English
  • 注册
  • 查看作者
  • 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] 薄暮凉年

  • 0
  • 0
  • 0
  • 2.3k
  • umuhm

    请登录之后再进行评论

    登录
    单栏布局 侧栏位置: