Java 23, SpringBoot 3.3.4:API流程与日志记录——第4部分
在上一节(第三部分),我们讨论了 Logback 配置,它能提供对 Spring Boot 应用程序日志记录的精确控制。这样可以确保在必要时捕获详细的日志信息,而不会因为过多的日志信息而淹没应用程序的日志。
现在,我们将重点转移到使用Spring中的AOP(面向切面编程)来处理特别是在日志记录方面的横切关注点的好处。在深入探讨之前,让我们先了解一下Servlet是如何处理传入请求的。
以下图表展示了Spring Boot MVC(模型-视图-控制器)架构。它展示了数据流动及各个组件间在Spring Boot框架内的互动。以下是对主要元素的说明:
SpringBoot MVC 的运行流程
1. 调度Servlet:负责接收客户端请求并分发到相应的处理器。- 这是Spring MVC架构的核心。所有进来的HTTP请求(例如:/cart/customer/{customerId})首先到达“Dispatcher Servlet”。该组件负责将请求路由到合适的处理器(Controller类)。
- 它执行诸如URL映射等任务,确定将请求发送给哪个控制器,并处理响应结果。
- 处理器映射器帮助调度Servlet根据URL模式选择合适的控制器来处理请求。
- Controller,标记为 @RestController,处理接收到的HTTP请求。它与Service层交互以执行业务逻辑,并响应应用中的操作。
- 控制器定义了应用的端点(例如,/cart/customer/{customerId})。它使用依赖注入(通过 @Autowired)调用服务并获取所需的数据。
- 控制器处理请求并以JSON或XML格式返回数据(因为它是一个RestController)。
- 服务 层是处理核心业务逻辑的地方。它被标记为 @Service。
- 它与 Repository 交互以获取和处理数据。服务还通过依赖注入(@Autowired)来访问数据仓库。
- 这一层作为控制器和数据仓库之间的桥梁,确保在获取或向用户发送数据前,业务规则已正确应用。
- 仓储层 被标记为 @Repository,并负责与 数据存储系统(如关系数据库)进行交互。
- 它简化了对数据执行简单的 CRUD 操作(创建、读取、更新、删除)。
- DTO 用于在各层之间传递数据。它提供了一种将模型数据封装并以简化或格式化的方式传递给客户端的方法。
- DTO 常用于避免直接暴露内部模型结构给外界。
- 实体模型 通过 @Entity 注解表示,这个注解将 Java 对象映射到数据库中的实体。它代表了应用的数据架构,通常由 Repository 用来与数据库进行交互。
- 最右侧是 数据仓库 ,它代表应用的持久层,如数据库或其他存储系统。
- 这里就是所有数据最终存储和检索的地方。
- 控制器、服务和仓库之间的箭头表示 依赖注入,每一层均依赖于下一层来完成操作,从而实现操作的完成。
- Spring 使用 控制反转(IoC) 来管理这些依赖关系,这在 Spring 框架中是一个核心概念。这确保组件更加松耦合,便于测试。
如有继续,请在此处添加简要介绍或总结,使中文语境更加自然流畅。
- 用户通过各种设备发送HTTP(S)请求,例如:/cart/customer/{customerId}。
- 请求通过防火墙后传送到Dispatcher Servlet。
- Handler Mapping 将请求映射到正确的Controller(使用@RestController注解)。
- Controller 处理请求,通常将业务逻辑委派给Service层。
- Service 层与Repository交互,从Data Store中读取或写入数据。
- 数据被格式化后通过DTOs发送回给用户。
每一层(控制器、服务和存储库)都需要异常处理机制、日志记录和安全功能。我们已经探讨了如何使用AOP来管理异常处理,现在来看一下如何用AOP来做日志记录。
时间追踪工具@Before 前置通知
@Before
注解用于在方法执行之前执行前置通知。这意味着它会在实际方法执行前运行,即在方法被调用之前被调用。
- @Before: 该通知在目标方法执行前执行。
- execution( com.example.service..*(..)): 此切入点表达式指定了通知应应用于 com.example.service 包中的任何方法。
- logStatementBefore(JoinPoint joinPoint): 通知接受一个 JoinPoint 参数,该参数包含了正在调用的方法的详细信息(如方法名、参数等)。
@Aspect
@Configuration
public class TimeTrackerAspect {
// 设置日志 -> 自动查找类名。
private static final Logger log = getLogger(lookup().lookupClass());
/**
* 执行前记录消息
* 对于所有类 = "execution(* io.fusion.air.microservice.adapters.controllers.*.*(..))")
* 包括子包 = "execution(* io.fusion.air.microservice.adapters.controllers..*.*(..))")
* @param joinPoint
*/
@Before(value = "execution(* io.fusion.air.microservice.adapters.controllers..*.*(..))")
public void logStatementBefore(JoinPoint joinPoint) {
log.debug("1|TA|TIME=|STATUS=开始|CLASS={}",joinPoint);
}
// ... 仅显示相关代码...
}
应用场景:
- 日志记录:捕获方法的入口点。
- 身份验证与权限控制:在方法执行前验证权限。
- 验证输入:确保方法参数有效后再继续。
@After 注解用于在方法执行之后执行逻辑,无论方法是抛出异常还是正常返回。这在需要在方法执行之后执行某些任务(例如释放资源)时非常实用。
- @After: 此注解在目标方法执行完成后运行(不论是成功还是抛出异常)。
- execution( com.example.service..*(..)): 再次,这是指定要应用通知的方法的切入点表达式。
- logStatementAfter(JoinPoint joinPoint): 此通知在执行后记录方法名,以供日志使用。
@Aspect
@Configuration
public class TimeTrackerAspect {
// ...
/**
* 方法执行后记录日志
* 针对所有类 = "execution(* io.fusion.air.microservice.adapters.controllers.*.*(..))")
* 包含子包及其下的类 = "execution(* io.fusion.air.microservice.adapters.controllers..*.*(..))")
* @param joinPoint
*/
@After(value = "execution(* io.fusion.air.microservice.adapters.controllers..*.*(..))")
public void logStatementAfter(JoinPoint joinPoint) {
log.debug("9|TA|TIME=|STATUS=END|CLASS={}",joinPoint);
}
// ... 仅展示相关代码...
}
使用案例:
- 日志记录:记录方法的退出点。
- 资源管理:管理资源。
- 审计记录:跟踪方法是否完成,成功或失败。
@After 和 @AfterReturning 或 @AfterThrowing 的区别是什么?
- @After:在方法执行后执行,无论方法是否成功完成,也不管是否抛出异常。
- @AfterReturning:仅当方法正常完成且没有抛出任何异常时运行。
- @AfterThrowing:仅当方法抛出异常时执行。
@Around
注解更加强大,因为它能控制整个方法的执行。你可以定义在方法执行前和执行后的自定义行为,甚至可以修改其返回值或阻止方法调用。
- @Around : 这个注解允许在方法执行前后都执行相应的行为。
- ProceedingJoinPoint joinPoint : 与 JoinPoint 不同,ProceedingJoinPoint 用来实际执行方法。
- joinPoint.proceed() : 此方法调用执行目标方法的过程。你可以在调用之前或之后操作返回值或方法参数,甚至可以选择不调用。
- 自定义的前置和后置逻辑 : 你可以在方法执行前后执行自定义的前置和后置逻辑,使其比 @Before 或 @After 更加灵活。
@Aspect
@Configuration
public class TimeTrackerAspect {
// ...
/**
* 捕获控制器方法执行的整体时间
* 匹配所有类方法 = "execution(* io.fusion.air.microservice.adapters.controllers.*.*(..))")
* 包括子包中的方法 = "execution(* io.fusion.air.microservice.adapters.controllers..*.*(..))")
* @param joinPoint
* @return
* @throws Throwable
*/
@Around(value = "execution(* io.fusion.air.microservice.adapters.controllers..*.*(..))")
public Object timeTrackerRest(ProceedingJoinPoint joinPoint) throws Throwable {
// 查看trackTime方法中的业务处理逻辑
return trackTime("WS", joinPoint);
}
// ... 仅显示相关代码...
}
示例用法:
- 事务管理:在调用方法之前启动事务,在方法执行结束后提交或回滚事务。
- 性能监控:记录方法的开始和结束时间以计算持续时长。
- 自定义逻辑:您可以覆盖、跳过或完全修改方法的执行过程。
@Aspect
@Configuration
public class TimeTrackerAspect {
// ... 只显示相关代码,省略其他内容...
/**
* 跟踪时间
* @param _method
* @param joinPoint
* @return
* @throws Throwable
*/
private Object trackTime(String _method, ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String status = "STATUS=SUCCESS";
try {
return joinPoint.proceed();
}catch(Throwable e) {
status = "STATUS=ERROR:" + e.getMessage(); // 状态设置为错误
throw e;
} finally {
logTime(_method, startTime, status, joinPoint);
}
}
/**
* 记录函数执行耗时
* @param _method
* @param _startTime (开始时间)
* @param _status
* @param joinPoint
*/
private void logTime(String _method, long _startTime, String _status, ProceedingJoinPoint joinPoint) {
long timeTaken = System.currentTimeMillis() - _startTime;
log.info("3|{}|时间={}毫秒|{}|类={}|", _method, timeTaken, _status, joinPoint);
}
}
使用总结:
- @Before: 在方法执行前运行前置通知。适用于日志记录、安全检查、输入验证等。
- @After: 在方法执行后运行后置通知,无论方法执行是否成功。通常用于清理操作。
- @Around: 环绕通知,控制整个方法的执行。它是最灵活的建议类型,提供了对方法调用的完全控制。
这些注解有助于以干净、模块化的方式处理事务,比如日志记录、事务管理、安全性和性能监视,从而将这些功能从核心业务逻辑中抽离出来。
我们现在来看看API请求的整个流程,包括Java Servlet和Spring内部处理过程,以及你在Controller(控制层)、Business Service(业务服务层)和Repository(存储层)各层编写的代码。
REST API 流该图概述了Spring Boot MVC应用程序中的API流,展示了(HTTP(s)请求如何通过不同的上下文(Servlet、Spring和应用上下文)进行处理)。让我们一一拆解各个阶段及其对应组件,以及每个部分的上下文(用不同颜色标出)。
Spring Boot MVC — REST API 流
HTTP 请求流程:上部分 1. Servlet 请求监听- 上下文 : Servlet 上下文环境
- 目的 : 它监听请求的初始化和终止。这充当传入 HTTP 请求的生命周期钩子。
- 上下文:Servlet 上下文(紫色)
- 目的:HTTP 安全墙在请求进一步传递之前执行安全检查。例如,它会检查协议是否合法,检查 URL 中的特殊字符,并确保请求满足安全策略的要求。
(此处可添加解释或上下文,以保持一致性和流畅性)
- Servlet上下文:Servlet上下文(紫)
- 目的:请求通过Servlet过滤器链,应用多种Servlet过滤器,例如日志记录、安全检查或速率限制。这些过滤器会对请求执行预处理逻辑。例如:
- 记录过滤器记录传入的请求。
- 安全过滤器检查身份验证令牌。
- 情境:Spring上下文(蓝色)
- 作用:Dispatcher Servlet 是 Spring MVC 的前端控制中心。它接收来自 Servlet 过滤器链的传入请求,并协调整个处理流程。它根据 URL 模式将请求发送到正确的控制器。
- 背景:Spring上下文(蓝色)
- 目的:处理器映射器负责将传入的请求分配给适当的控制器方法。它根据URL和请求方法(如GET、POST等)将请求路由到合适的处理器。
- 上下文:Spring上下文(蓝色)
- 目的:拦截器(Interceptor)是Spring的一种机制,在请求到达控制器之前或之后处理逻辑。例如,可以在这里应用前置处理器,如JWT令牌验证等。
(拦截器)标注已经根据专家建议进行调整,术语更加明确,表述更加自然和技术化。
7. AOP (面向切面的编程)- 上下文:应用上下文(绿色)
- 目的:在此阶段可以使用类似
@Before
的前置通知,以在方法调用前执行代码(比如,日志记录、JWT 验证)。这使得横切关注点能与业务逻辑清晰地分开。
- 上下文 : 应用上下文(绿色)
- 目的 : 控制器 包含处理传入的请求的业务逻辑,并与服务层通信。处理完请求后,它会向客户端返回响应。控制器通常会用
@RestController
或@Controller
进行标注。
- 上下文 : 应用程序上下文(绿色)
- 目的 : 业务处理服务 层处理核心业务逻辑,并与数据仓库或其他服务交互以完成必要的任务。控制器会把业务逻辑交给服务层处理。
在上述流程中,请求到达数据库层(仓库层),在那里它要么检索数据,要么处理保存或更新操作。接下来,在 HTTP 响应流程中,我们将按相反的顺序进行——从检索数据或处理保存/更新的状态开始,最终返回 HTTP 响应给最终用户(或调用 API 的客户端)。
7. 消息的转换- 背景信息:应用上下文(绿色)
- 目的:当控制器返回一个对象时,它需要被转换成 JSON 或 XML(根据内容类型)。消息转换这个部分会处理这个任务,会将 Java 对象转换成合适的格式并发送响应给客户端。
- 上下文:应用上下文(绿色)
- 目的:AOP After通知(后置通知)可以在控制器逻辑执行完毕后运行。例如,这种通知可以记录响应,清理资源,或者在出现异常时进行错误处理和日志记录。
- 上下文:Spring上下文(蓝色)
- 目的:在响应生成之后(或者在REST情况下跳过),可以使用后置拦截器来记录响应或执行进一步的处理。
- 情境:Spring 上下文(蓝色)
- 目的:在传统 MVC(非 REST API)的情况下,渲染视图 在此阶段进行。视图解析器使用控制器提供的数据渲染视图(例如 HTML、Thymeleaf 等)。对于 REST API,这一步通常会被省略,因为响应通常是 JSON 或 XML。
- 上下文:Spring上下文(蓝色)
- 目的:拦截器中的后置处理逻辑在控制器处理完请求之后执行,例如计算响应时间或增加日志数据。
- Context : Servlet上下文(紫色)
- 用途 :Servlet过滤器链 在响应被发送回客户端之前对其进行处理。例如,过滤器可以向响应添加头,比如缓存头、安全相关的头(如CSP策略)。
- 上下文:Servlet上下文
- 目的:类似于请求流程,HTTP安全过滤器会对响应进行最终检查,确保不会向客户端回传任何恶意内容。<!--注释:紫色-->
- 这是 web 应用程序中的基础上下文,处理较低级别的 web 请求和响应处理周期,比如防火墙和过滤器。
- Spring Web MVC 框架的核心,管理路由、处理请求和渲染视图。它将应用特定的逻辑与底层的 Servlet 系统相连。
- 这里存放的是业务逻辑。它包括处理核心应用流程的控制器和服务,这些流程经常通过面向切面编程和依赖注入来应对横切关注点。
- HTTP 请求 从客户端发起,经过防火墙、过滤器、拦截器和 AOP 通知等几个阶段,最终到达负责处理业务逻辑的控制器。
- 在处理请求之后,HTTP 响应 也会走同样的流程,经过过滤器、后置处理器和消息转换器等处理后,最终返回客户端。
这种分层的方法能够促进各个关注点的清晰分离,符合六边形架构的理念,并利用了Spring的依赖注入。这种方法提供了灵活性和易于维护性,特别是在大规模应用或微服务架构中尤为突出。
在接下来的部分里,我们将聊聊服务监控的那些事儿,重点介绍Micrometer、Prometheus和Spring Actuator提供的特性,更好地了解服务和应用的健康和表现。
在 Java 周里,享受 SpringBoot 和 AOP,就像喝了一杯 Java 咖啡一样……
Java 23, SpringBoot 3.3.4 & Jakarta 10 系列版本源代码:GitHub 仓库:ms-springboot-334-vanilla — 使用 SpringBoot 3.3.4 和 Java 23 版本以及 Jakarta EE 10 的微服务模板,主要技术栈包括:面向切面编程(AOP)、异常处理、日志记录、Micrometer、加密与安全(包括 JSON Web Token,JWT)。非功能特性包括:AOP、异常处理、日志记录、Micrometer、加密与安全。
- Java 23, SpringBoot 3.3.4 & Jakarta 10 — Part 1
- Java 23, SpringBoot 3.3.4: AOP 异常管理 — Part 2
- Java 23, SpringBoot 3.3.4: Logback 设置 — Part 3
- Java 23, SpringBoot 3.3.4: API 流程和日志记录 — Part 4 (这篇文章)
- Java 23, SpringBoot 3.3.4: 监控: Micrometer、Prometheus — Part 5 即将发布
- Java 23, SpringBoot 3.3.4: AOP 安全性 — Part 6 即将发布
- Java 23, SpringBoot 3.3.4: CRUD — Part 7 即将发布
- Spring Boot 特性:日志记录
- Spring Boot 日志:指南
- Logback:第一章:介绍 https://logback.qos.ch/manual/introduction.html
- Logback:第二章:架构 https://logback.qos.ch/manual/architecture.html
- Logback:第四章:追加器 https://logback.qos.ch/manual/appenders.html
- Spring Boot:分发 Servlet
共同学习,写下你的评论
评论加载中...
作者其他优质文章