简介
AOP(Aspect Oriented Program)即面向切面编程,将程序抽象成各个切面。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
相关术语
(1)通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
(2)连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
(3)切点(PointCut): 可以插入增强处理的连接点。切入点表达式,也就是组成@Pointcut注解的值,是正规的AspectJ 5切入点表达式
(4)切面(Aspect): 切面是通知和切点的结合。拥有@Aspect注解的bean都将被Spring自动识别并用于配置Spring AOP
(5)引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。使用@DeclareParents注解来定义引入
(6)织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
通知类型
@Before 前置通知,在连接点方法前调用
@Around 环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法
@After 后置通知,在连接点方法后调用
@AfterReturning 返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常
@AfterThrowing 异常通知,当连接点方法异常时调用
SpringBoot中利用AOP实现日志记录
添加AOP的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
接下来实现个日志切面,当调用com.example.demo.controller包下面的方法时,记录对应日志。
package com.example.demo.aspect;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
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.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
@Component
public class WebLogAspect {
private Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution(public * com.example.demo.controller..*.*(..))")
public void webLog(){}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
logger.info("URL : " + request.getRequestURL().toString());
logger.info("请求方式 : " + request.getMethod());
logger.info("IP : " + request.getRemoteAddr());
logger.info("方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
logger.info("参数 : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
logger.info("返回内容 : " + ret);
}
}
controller包下面编写了一个测试controller,下面有个download方法,接下来我们测试访问对应接口,可以看到日志输出:
2021-11-08 10:42:56.837 INFO 52764 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : URL : http://127.0.0.1:8080/download
2021-11-08 10:42:56.837 INFO 52764 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : 请求方式 : GET
2021-11-08 10:42:56.837 INFO 52764 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : IP : 127.0.0.1
2021-11-08 10:42:56.838 INFO 52764 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : 方法 : com.example.demo.controller.TestContoller.downloadExampleExcel
2021-11-08 10:42:56.838 INFO 52764 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : 参数 : [org.apache.catalina.connector.ResponseFacade@285a7d26]
2021-11-08 10:42:56.872 INFO 52764 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : 返回内容 : null
切面优先级
有时我们实现了多个切面,这就牵涉到切面的执行顺序。解决方法是定义每个切面的优先级,@Order(i)注解来标识切面的优先级
在切入点前的操作,按order的值由小到大执行
在切入点后的操作,按order的值由大到小执行
WebLogAspect.java 我们设置优先级为3,再定义两个切面Test1Aspect.java
package com.example.demo.aspect;
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.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(1)
public class Test1Aspect {
private Logger logger = LoggerFactory.getLogger(Test1Aspect.class);
@Pointcut("execution(public * com.example.demo.controller..*.*(..))")
public void test1(){}
@Before("test1()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
logger.info("测试1 before方法执行......" );
}
@AfterReturning(returning = "ret", pointcut = "test1()")
public void doAfterReturning(Object ret) throws Throwable {
logger.info("测试1 after方法执行......");
}
}
Test2Aspect.java
package com.example.demo.aspect;
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.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(2)
public class Test2Aspect {
private Logger logger = LoggerFactory.getLogger(Test2Aspect.class);
@Pointcut("execution(public * com.example.demo.controller..*.*(..))")
public void test2(){}
@Before("test2()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
logger.info("测试2 before方法执行......" );
}
@AfterReturning(returning = "ret", pointcut = "test2()")
public void doAfterReturning(Object ret) throws Throwable {
logger.info("测试2 after方法执行......");
}
}
调用接口,测试结果跟预期一致
2021-11-08 11:05:08.065 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.Test1Aspect : 测试1 before方法执行......
2021-11-08 11:05:08.066 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.Test2Aspect : 测试2 before方法执行......
2021-11-08 11:05:08.066 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : URL : http://127.0.0.1:8080/download
2021-11-08 11:05:08.066 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : 请求方式 : GET
2021-11-08 11:05:08.066 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : IP : 127.0.0.1
2021-11-08 11:05:08.068 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : 方法 : com.example.demo.controller.TestContoller.downloadExampleExcel
2021-11-08 11:05:08.068 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : 参数 : [org.apache.catalina.connector.ResponseFacade@52a06205]
2021-11-08 11:05:08.092 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.WebLogAspect : 返回内容 : null
2021-11-08 11:05:08.092 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.Test2Aspect : 测试2 after方法执行......
2021-11-08 11:05:08.092 INFO 55112 --- [nio-8080-exec-1] com.example.demo.aspect.Test1Aspect : 测试1 after方法执行......