一. 面向切面编程
面向切面编程简称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()); } }
请登录之后再进行评论