Spring Boot中的切⾯AOP处理
AOP的概念
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它旨在通过将横切关注点与核心业务逻辑分离,来提高代码的模块化性、可维护性和可重用性。 在传统的面向对象编程中,业务逻辑往往分散在各个对象和方法中,同时还存在一些横切关注点(如日志记录、事务管理、安全控制等)会横跨多个对象和方法。AOP 的主要思想是将这些横切关注点从原始的业务逻辑中剥离出来,形成一个独立的模块,然后通过特定的手段将这些横切关注点织入到程序的特定位置,而不需要修改原始的业务逻辑代码。 AOP 的关键概念包括以下几个部分:
切面(Aspect):切面是横切关注点的模块化,它包含了一系列通知(Advice)和切点(Pointcut)。通知定义了在何时、何地执行横切逻辑,切点定义了在何处应用通知。 通知(Advice):通知是切面的具体行为,它定义了在程序的哪个时机执行横切逻辑。常见的通知类型包括前置通知(Before advice)、后置通知(After advice)、返回通知(After-returning advice)、异常通知(After-throwing advice)和环绕通知(Around advice)。 连接点(Join Point):连接点是在应用执行过程中能够插入切面的点,例如方法的调用、异常处理、字段访问等。 切点(Pointcut):切点是指定连接点的集合,它定义了切面将被织入的具体位置。切点可以使用表达式或者其他方式来指定连接点。 织入(Weaving):织入是将切面的通知应用到目标对象的过程。织入可以发生在编译期、类加载期、运行期等不同的阶段。 通过 AOP,我们可以将那些与核心业务逻辑无关但又散布其中的横切关注点模块化,并在需要的时候将其织入到程序中,而不需要修改原有的业务逻辑代码。这样可以提高代码的可维护性、可重用性,并且使得系统更容易扩展和修改。在 Java 生态中,Spring 框架广泛使用 AOP 来实现事务管理、日志记录、安全控制等功能。
SpringBoot中的AOP处理
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-aop</ artifactId>
</ dependency>
例子(实现日志记录):在引入上面的依赖之后,创建一个日志处理类LogAspectHandler,用来定义切面和处理方法。只要在类上加个@Aspect注解即可。@Aspect注解用来 描述一个切面,定义切面类的时候需要打上这个注解。@Component注解让该类交给Spring来管理。
@Aspect
@Component
public class LogAspectHandler {
}
常用的注解:
@Pointcut:定义一个切面 @Before:在处理相关业务之前加的逻辑 @After:在处理相关业务之后加的逻辑 @AfterReturning:在处理业务之后,对返回值进行加工处理 @AfterThrowing:在处理业务抛出异常时做的处理 @Pointcut:
@Pointcut注解用来定义一个切面,包含相应的切点和advice @Pointcut 注解指定⼀个切⾯,定义需要拦截的东西,这⾥介绍两个常⽤的表达式:⼀个是使⽤ execution(),另⼀个是使⽤ annotation()。
execution:
execution() 为表达式主体 第⼀个 * 号的位置:表⽰返回值类型,* 表⽰所有类型 包名:表⽰需要拦截的包名,后⾯的两个句点表⽰当前包和当前包的所有⼦包,com.itcodai.course09.controller 包、⼦包下所有类的 ⽅法 第⼆个 * 号的位置:表⽰类名,* 表⽰所有类 (…) :这个星号表⽰⽅法名, 表⽰所有的⽅法,后⾯括弧⾥⾯表⽰⽅法的参数,两个句点表⽰任何参数通过上面的表达式可以精确的定位某一个方法 annotation:
针对某个注解来定义切⾯,⽐如我们对具有@GetMapping注解的⽅法做切⾯,就可以在注解参数中传入对应的注解。 然后使⽤该切⾯的话,就会切⼊注解是 @GetMapping 的⽅法。因为在实际项⽬中,可能对于不同的注解有不同的逻辑处理,⽐ 如 @GetMapping、@PostMapping、@DeleteMapping 等。所以这种按照注解的切⼊⽅式在实际项⽬中也很常⽤。
public class LogAspectHandler {
@Pointcut ( "execution(* com.example.springboot_learn.controller..*.*(..))" )
public void pointCut ( ) { }
@Pointcut ( "execution(String com.example.springboot_learn.controller..StartController.helloWorld())" )
public void pointCut2 ( ) { }
@Pointcut ( "@annotation(org.springframework.web.bind.annotation.GetMapping)" )
public void annotationCut ( ) { }
}
package com. example. springboot_learn. controller ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
@RequestMapping ( "/start" )
public class StartController {
@RequestMapping ( "/SpringBoot" )
public String helloWorld ( ) {
return "HelloEWorld!" ;
}
}
@Before
@Before注解的方法在切面切入目标方法之前执行,可以做一些日志的处理,也可以做一些信息的统计,比如获取用户的请求URL以及用户的ip地址等等信息, 例子:在LogAspectHandler类中添加doBefore方法
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint){
logger.info("------------doBefore方法开始执行");
// 获取签名
Signature signature = joinPoint.getSignature();
// 获取切⼊的包名
String declaringTypeName = signature.getDeclaringTypeName();
// 获取即将执⾏的⽅法名
String funcName = signature.getName();
logger.info("即将执⾏⽅法为: {},属于{}包", funcName, declaringTypeName);
// 也可以⽤来记录⼀些信息,⽐如获取请求的url和ip
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取请求url
String url = request.getRequestURL().toString();
// 获取请求ip
String ip = request.getRemoteAddr();
logger.info("⽤户请求的url为:{},ip地址为:{}", url, ip);
}
// 请求com.example.springboot_learn.controller包中的controller的时候,控制台输出以下信息:
// ------------doBefore方法开始执行
// 即将执⾏⽅法为: helloWorld,属于com.example.springboot_learn.controller.StartController包
// ⽤户请求的url为:http://localhost:8001/start/SpringBoot,ip地址为:0:0:0:0:0:0:0:1
@After
@After 注解和 @Before 注解相对应,指定的⽅法在切⾯切⼊⽬标⽅法之后执⾏,也可以做⼀些完成某⽅法之后的 log 处理。 例子:在LogAspectHandler类中添加doAfter方法
@After("pointCut()")
public void doAfter(JoinPoint joinPoint){
logger.info("------------doAfter方法开始执行");
// 获取签名
Signature signature = joinPoint.getSignature();
// 获取切⼊的包名
String declaringTypeName = signature.getDeclaringTypeName();
// 获取即将执⾏的⽅法名
String funcName = signature.getName();
logger.info("执⾏完的⽅法为: {},属于{}包", funcName, declaringTypeName);
}
// 请求com.example.springboot_learn.controller包中的controller的时候,控制台输出以下信息:
// ------------doBefore方法开始执行
// 即将执⾏⽅法为: helloWorld,属于com.example.springboot_learn.controller.StartController包
// ⽤户请求的url为:http://localhost:8001/start/SpringBoot,ip地址为:0:0:0:0:0:0:0:1
// ------------doAfter方法开始执行
// 执⾏完的⽅法为: helloWorld,属于com.example.springboot_learn.controller.StartController包
@AfterReturning 注解
@AfterReturning 注解和 @After 有些类似,区别在于 @AfterReturning 注解可以⽤来捕获切⼊⽅法执⾏完之后的返回值,对返回值进⾏业 务逻辑上的增强处理. 例子:在LogAspectHandler类中添加doAfterReturning方法
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
logger.info("------------doAfterReturning方法开始执行");
Signature signature = joinPoint.getSignature();
String classMethod = signature.getName();
logger.info("⽅法{}执⾏完毕,返回参数为:{}", classMethod, result);
// 实际项⽬中可以根据业务做具体的返回值增强
logger.info("对返回参数进⾏业务上的增强:{}", result + "增强版");
}
// 请求com.example.springboot_learn.controller包中的controller的时候,控制台输出以下信息:
// ------------doBefore方法开始执行
// 即将执⾏⽅法为: helloWorld,属于com.example.springboot_learn.controller.StartController包
// ⽤户请求的url为:http://localhost:8001/start/SpringBoot,ip地址为:0:0:0:0:0:0:0:1
// ------------doAfterReturning方法开始执行
// ⽅法helloWorld执⾏完毕,返回参数为:HelloEWorld!
// 对返回参数进⾏业务上的增强:HelloEWorld!增强版
// ------------doAfter方法开始执行
// 执⾏完的⽅法为: helloWorld,属于com.example.springboot_learn.controller.StartController包
@AfterThrowing 注解 @AfterThrowing 注解是当被切⽅法执⾏时抛出异常时,会进⼊ @AfterThrowing 注解的⽅法中执⾏,在该⽅法中可以做⼀些异 常的处理逻辑。要注意的是 throwing 属性的值必须要和参数⼀致,否则会报错。该⽅法中的第⼆个⼊参即为抛出的异常。 例子:在LogAspectHandler类中添加doAfterReturning方法,修改StartController类,让方法抛出异常
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
logger.info("------------afterThrowing方法开始执行");
Signature signature = joinPoint.getSignature();
String method = signature.getName();
// 处理异常的逻辑
logger.info("执⾏⽅法{}出错,异常为:{}", method, ex);
}
// 控制台打印以下信息:
// ------------doAfter方法开始执行
// 执⾏完的⽅法为: helloWorld,属于com.example.springboot_learn.controller.StartController包
@RestController
@RequestMapping ( "/start" )
public class StartController {
@RequestMapping ( "/SpringBoot" )
public String helloWorld ( ) {
try {
String a = null ;
System . out. println ( a. toString ( ) ) ;
} catch ( Exception e) {
throw new BusinessErrorException ( BusinessMsgEnum . NULL_POINT_EXCEPTION ) ;
}
return "HelloEWorld!" ;
}
}