• Spring – 记录传入请求


    1. 简介

    在本快速教程中,我们将演示使用 Spring 的日志记录过滤器记录传入请求的基础知识。如果我们刚刚开始使用日志记录,我们可以查看此日志记录介绍文章以及SLF4J 文章

    2. Maven 依赖项

    日志记录依赖项将与介绍文章中的依赖项相同;我们在这里简单地添加 Spring:

    1. <dependency>
    2. <groupId>org.springframework</groupId>
    3. <artifactId>spring-core</artifactId>
    4. <version>5.2.2.RELEASE</version>
    5. </dependency>
    Copy

    最新版本可以在这里找到弹簧芯

    3. 基本网页控制器

    首先,我们将定义一个要在示例中使用的控制器:

    1. @RestController
    2. public class TaxiFareController {
    3. @GetMapping("/taxifare/get/")
    4. public RateCard getTaxiFare() {
    5. return new RateCard();
    6. }
    7. @PostMapping("/taxifare/calculate/")
    8. public String calculateTaxiFare(
    9. @RequestBody @Valid TaxiRide taxiRide) {
    10. // return the calculated fare
    11. }
    12. }
    Copy

    4. 自定义请求日志记录

    Spring 提供了一种机制,用于配置用户定义的拦截器以在 Web 请求之前和之后执行操作。

    在 Spring 请求拦截器中,其中一个值得注意的接口是HandlerInterceptor,我们可以通过实现以下方法来记录传入的请求:

    1. preHandle() – 我们在实际控制器服务方法之前执行此方法
    2. afterCompletion() – 我们在控制器准备好发送响应后执行此方法

    此外,Spring 以 HandlerInterceptorAdaptor 类的形式提供了HandlerInterceptor接口的默认实现,用户可以扩展该类。

    让我们通过将HandlerInterceptorAdaptor扩展为来创建我们自己的拦截器:

    1. @Component
    2. public class TaxiFareRequestInterceptor
    3. extends HandlerInterceptorAdapter {
    4. @Override
    5. public boolean preHandle(
    6. HttpServletRequest request,
    7. HttpServletResponse response,
    8. Object handler) {
    9. return true;
    10. }
    11. @Override
    12. public void afterCompletion(
    13. HttpServletRequest request,
    14. HttpServletResponse response,
    15. Object handler,
    16. Exception ex) {
    17. //
    18. }
    19. }
    Copy

    最后,我们将在 MVC 生命周期中配置TaxiRideRequestInterceptor,以捕获映射到TaxiFareController类中定义的路径/taxifare的控制器方法调用的预处理和后处理:

    1. @Configuration
    2. public class TaxiFareMVCConfig implements WebMvcConfigurer {
    3. @Autowired
    4. private TaxiFareRequestInterceptor taxiFareRequestInterceptor;
    5. @Override
    6. public void addInterceptors(InterceptorRegistry registry) {
    7. registry.addInterceptor(taxiFareRequestInterceptor)
    8. .addPathPatterns("/taxifare/*/");
    9. }
    10. }
    Copy

    总之,WebMvcConfigurer通过调用addInterceptors() 方法在 Spring MVC 生命周期中添加了TaxiFareRequestInterceptor

    最大的挑战是获取请求和响应有效负载的副本以进行日志记录,并且仍然将请求的有效负载留给 servlet 来处理它:

    读取请求的主要问题是,一旦首次读取输入流,它就会被标记为已使用,无法再次读取。

    应用程序将在读取请求流后引发异常:

    1. {
    2. "timestamp": 1500645243383,
    3. "status": 400,
    4. "error": "Bad Request",
    5. "exception": "org.springframework.http.converter
    6. .HttpMessageNotReadableException",
    7. "message": "Could not read document: Stream closed;
    8. nested exception is java.io.IOException: Stream closed",
    9. "path": "/rest-log/taxifare/calculate/"
    10. }
    Copy

    为了克服这个问题,我们可以利用缓存来存储请求流并将其用于日志记录。

    Spring 提供了一些有用的类,例如 ContentCachingRequestWrapper 和ContentCachingResponseWrapper,它们可用于缓存请求数据以进行日志记录。

    让我们调整TaxiRideRequestInterceptor类的preHandle(),以使用ContentCachingRequestWrapper类缓存请求对象:

    1. @Override
    2. public boolean preHandle(HttpServletRequest request,
    3. HttpServletResponse response, Object handler) {
    4. HttpServletRequest requestCacheWrapperObject
    5. = new ContentCachingRequestWrapper(request);
    6. requestCacheWrapperObject.getParameterMap();
    7. // Read inputStream from requestCacheWrapperObject and log it
    8. return true;
    9. }
    Copy

    如我们所见,我们使用ContentCachingRequestWrapper类缓存请求对象,我们可以使用它来读取有效负载数据以进行日志记录,而不会干扰实际的请求对象:

    requestCacheWrapperObject.getContentAsByteArray();Copy

    限度

    • 仅支持以下内容:
    1. Content-Type:application/x-www-form-urlencoded
    2. Method-Type:POST
    Copy
    • 我们必须调用以下方法,以确保请求数据在使用之前缓存在ContentCachingRequestWrapper中:
    requestCacheWrapperObject.getParameterMap();Copy

    5. 弹簧内置请求记录

    Spring 提供了一个内置的解决方案来记录有效负载。我们可以通过使用配置插入 Spring 应用程序来使用现成的过滤器。

    AbstractRequestLoggingFilter是一个提供日志记录基本功能的过滤器。子类应覆盖 beforeRequest() 和afterRequest() 方法,以围绕请求执行实际日志记录。

    Spring 框架提供了三个具体的实现类,我们可以用来记录传入的请求。这三个类是:

    • CommonsRequestLoggingFilter
    • Log4jNestedDiagnosticContextFilter(已弃用)
    • ServletContextRequestLoggingFilter

    现在让我们转到CommonsRequestLoggingFilter,并将其配置为捕获用于日志记录的传入请求。

    5.1. 配置 Spring 引导应用程序

    我们可以通过添加 Bean 定义来配置 Spring 引导应用程序以启用请求日志记录:

    1. @Configuration
    2. public class RequestLoggingFilterConfig {
    3. @Bean
    4. public CommonsRequestLoggingFilter logFilter() {
    5. CommonsRequestLoggingFilter filter
    6. = new CommonsRequestLoggingFilter();
    7. filter.setIncludeQueryString(true);
    8. filter.setIncludePayload(true);
    9. filter.setMaxPayloadLength(10000);
    10. filter.setIncludeHeaders(false);
    11. filter.setAfterMessagePrefix("REQUEST DATA : ");
    12. return filter;
    13. }
    14. }
    Copy

    此日志记录筛选器还要求我们将日志级别设置为 DEBUG。我们可以通过在logback中添加以下元素来启用DEBUG模式.xml:

    1. <logger name="org.springframework.web.filter.CommonsRequestLoggingFilter">
    2. <level value="DEBUG" />
    3. </logger>
    Copy

    启用 DEBUG 级别日志的另一种方法是在application.properties 中添加以下内容:

    1. logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=
    2. DEBUG
    Copy

    5.2. 配置传统 Web 应用程序

    在标准的Spring Web应用程序中,我们可以通过XML配置或Java配置来设置过滤器。因此,让我们使用传统的基于Java的配置来设置CommonsRequestLoggingFilter

    众所周知,默认情况下,CommonsRequestLoggingFilterincludePayload属性设置为 false。我们需要一个自定义类来覆盖属性的值,以便在使用 Java 配置注入容器之前启用includePayload

    1. public class CustomeRequestLoggingFilter
    2. extends CommonsRequestLoggingFilter {
    3. public CustomeRequestLoggingFilter() {
    4. super.setIncludeQueryString(true);
    5. super.setIncludePayload(true);
    6. super.setMaxPayloadLength(10000);
    7. }
    8. }
    Copy

    然后我们需要使用基于 Java 的 Web 初始值设定项注入CustomeRequestLoggingFilter

    1. public class CustomWebAppInitializer implements
    2. WebApplicationInitializer {
    3. public void onStartup(ServletContext container) {
    4. AnnotationConfigWebApplicationContext context
    5. = new AnnotationConfigWebApplicationContext();
    6. context.setConfigLocation("com.baeldung");
    7. container.addListener(new ContextLoaderListener(context));
    8. ServletRegistration.Dynamic dispatcher
    9. = container.addServlet("dispatcher",
    10. new DispatcherServlet(context));
    11. dispatcher.setLoadOnStartup(1);
    12. dispatcher.addMapping("/");
    13. container.addFilter("customRequestLoggingFilter",
    14. CustomeRequestLoggingFilter.class)
    15. .addMappingForServletNames(null, false, "dispatcher");
    16. }
    17. }
    Copy

    6. 行动中的例子

    最后,我们可以将 Spring Boot 与上下文连接起来,以查看传入请求的日志记录是否按预期工作:

    1. @Test
    2. public void givenRequest_whenFetchTaxiFareRateCard_thanOK() {
    3. TestRestTemplate testRestTemplate = new TestRestTemplate();
    4. TaxiRide taxiRide = new TaxiRide(true, 10l);
    5. String fare = testRestTemplate.postForObject(
    6. URL + "calculate/",
    7. taxiRide, String.class);
    8. assertThat(fare, equalTo("200"));
    9. }
    Copy

    7. 结论

    在本文中,我们学习了如何使用拦截器实现基本的 Web 请求日志记录。我们还探讨了此解决方案的局限性和挑战。

    然后,我们讨论了内置筛选器类,它提供了现成的简单日志记录机制。

    与往常一样,示例和代码片段的实现可在GitHub 上找到。

  • 相关阅读:
    HandlerAdapter具有什么功能呢?
    本机MySQL数据库安装
    认识xml文件
    数据持久化技术——MP
    数据网格和视图入门
    离散Hopfield神经网络分类——高校科研能力评价
    时间显示(蓝桥杯)
    给一百万张图片批量添加水印
    C现代方法(第7章)笔记——基本类型
    基于51单片机CO2二氧化碳气体浓度检测超限报警Proteus仿真
  • 原文地址:https://blog.csdn.net/allway2/article/details/127976452