AOP=Aspect Oriented Programming,面向切面编程。
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容。
原理
通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。
代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean,当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
注意:
Spring只支持方法级别的连接点,不支持对字段和构造器连接点的支持,因此无法获取字段修改或bean创建时的应用通知,如需要字段和构造器连接点拦截功能,可以使用Aspect来补充。
常见的使用场景:
打开数据库连接/关闭数据库连接、打开事务/关闭事务、记录日志等。
基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
下面主要讲两个内容,一个是如何在Spring Boot中引入Aop功能,二是如何使用Aop做切面去统一处理Web请求的日志。
准备工作
因为需要对web请求做切面来记录日志,所以先引入web模块,并创建一个简单的hello请求的处理。
- pom.xml中引入web模块
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 实现一个简单请求处理:通过传入name参数,返回“hello xxx”的功能。
@RestController
public class TestController{
@Autowired
UserService userService;
private static Logger log = LoggerFactory.getLogger(TestController.class);
@RequestMapping("/aop")
public String test() {
log.info("Test AOP function");
userService.callService("test");
return "Test aop";
}
}
下面,我们可以对上面的/aop请求,进行切面日志记录。
引入AOP依赖
在Spring Boot中引入AOP就跟引入其他模块一样,非常简单,只需要在pom.xml中加入如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在完成了引入AOP依赖包后,一般来说并不需要去做其他配置。也许在Spring中使用过注解配置方式的人会问是否需要在程序主类中增加@EnableAspectJAutoProxy来启用,实际并不需要。
可以看下面关于AOP的默认配置属性,其中spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。
# AOP
spring.aop.auto=true # Add @EnableAspectJAutoProxy.
spring.aop.proxy-target-class=false # Whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false).
而当我们需要使用CGLIB来实现AOP的时候,需要配置spring.aop.proxy-target-class=true,不然默认使用的是标准Java的实现。
实现Web层的日志切面
实现AOP的切面主要有以下几个要素:
- 使用@Aspect注解将一个java类定义为切面类
- 使用@Pointcut定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。
- 根据需要在切入点不同位置的切入内容
-
- 使用@Before在切入点开始处切入内容
- 使用@After在切入点结尾处切入内容
- 使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
- 使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
- 使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
/**
* @author vincent
* AOP切面定义示例
*/
@Component
@Aspect
public class LogAopAdviseDefine {
private Logger logger = LoggerFactory.getLogger(getClass());
// 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
//@Pointcut("within(com.blue.module.service.UserService)")
@Pointcut("execution(* com.blue.module.service.UserService.*(*))")
public void pointcut() {
}
// 定义 advise
@Before("pointcut()")
public void logMethodInvokeParam(JoinPoint point) {
logger.info("Before method {} invoke, param: {}", point.getSignature().toShortString(), point.getArgs());
}
@AfterReturning(pointcut = "pointcut()", returning = "retVal")
public void logMethodInvokeResult(JoinPoint point, Object retVal) {
logger.info("After method {} invoke, result: {}", point.getSignature().toShortString(), point.getArgs());
}
@AfterThrowing(pointcut = "pointcut()", throwing = "exception")
public void logMethodInvokeException(JoinPoint point, Exception exception) {
logger.info("method {} invoke exception: {}", point.getSignature().toShortString(), exception.getMessage());
}
}
可以看上面的例子,通过@Pointcut定义的切入点为com.blue.module.service.UserService类下的所有函数(对web层所有请求处理做切入点),然后通过@Before实现,对请求内容的日志记录,最后通过@AfterReturning记录请求返回的对象。
通过运行程序并访问:http://localhost:8080/aop,可以获得下面的日志输出
Test AOP function
Before method UserService.callService(..) invoke, param: [test]
UserService: invoked, param: test
After method UserService.callService(..) invoke, result: [test]
源码示例链接:
https://download.csdn.net/download/vincent_yuan89/10865005
如有问题欢迎指正
参考资料: