Как зарегистрировать запрос JSON в приложении Spring?

После многих попыток я здесь, чтобы спросить, есть ли способ зарегистрировать входной запрос JSON перед его десериализацией в объект Java.

Мои попытки:

1: Я могу правильно зарегистрировать запрос перед десериализацией, но затем выдает «Ошибка ввода-вывода при чтении входного сообщения».

Код: RequestInterceptor (getRequestBody из HttpServletRequest)

@Slf4j
@Order(3)
@Component
public class RequestInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(final HttpServletRequest request, 
                             final HttpServletResponse response,
                             final Object handler) throws Exception {
        logRequestDetails(request);
        return true;
    }

    @SuppressWarnings("unchecked")
    private void logRequestDetails(HttpServletRequest request) throws IOException {
        String httpMethod = request.getMethod();
        String url = request.getRequestURL().toString();
        String queryString = request.getQueryString();
        Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        String requestBody = getRequestBody(request);
        
        log.info("REQUEST INFO: [ (Method: {}) (URL: {}) (Query String: {}) (Path Variables: {}) ]", httpMethod, url, queryString, pathVariables);
        log.info("REQUEST: {}", requestBody);
    
    }
    
    private String getRequestBody(HttpServletRequest request) {
        StringBuilder stringBuilder = new StringBuilder();
        try (BufferedReader bufferedReader = request.getReader()) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
        } catch (IOException e) {
            return "[Error Reading Body]";
        }
        return stringBuilder.toString();
    }
    
}

Журналы: HttpMessageNotReadableException: ошибка ввода-вывода при чтении входного сообщения

2024-07-10 00:43:00.523 | http-nio-9001-exec-1 | INFO  | i.g.p.dav.adap.inbou.rest.interceptor.RequestInterceptor | 90963499-0c12-43e3-bd03-f92392090e26 | 16c64a15-6383-4ca2-87ae-34516435f64f | REQUEST INFO: [ (Method: POST) (URL: http://localhost:9001/api/account/passengers/associate-passengers) (Query String: null) (Path Variables: {}) ]
2024-07-10 00:43:00.523 | http-nio-9001-exec-1 | INFO  | i.g.p.dav.adap.inbou.rest.interceptor.RequestInterceptor | 90963499-0c12-43e3-bd03-f92392090e26 | 16c64a15-6383-4ca2-87ae-34516435f64f | REQUEST: [  {    "accountId": 100,    "firstName": "David",    "lastName": "Guetta",    "dateOfBirth": "1967-11-07",    "nationalityId": 63,    "email": "[email protected]",    "phoneNumber": "+33 3334448899"  }]
2024-07-10 00:43:00.654 | http-nio-9001-exec-1 | ERROR | i.g.p.coa.rese.adapt.inbound.controller.rest.ExceptionRestController | 90963499-0c12-43e3-bd03-f92392090e26 | 16c64a15-6383-4ca2-87ae-34516435f64f | Exception 238abf96-75f5-47c2-88dc-93c4ef83e935
org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:200) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:159) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:134) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:228) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:182) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:920) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:830) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[tomcat-embed-core-10.1.19.jar:6.0]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.19.jar:6.0]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.4.jar:6.1.4]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.4.jar:6.1.4]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.4.jar:6.1.4]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.19.jar:10.1.19]

2: Я могу правильно зарегистрировать запрос перед десериализацией, но затем мне выдает сообщение «тело запроса отсутствует».

Код: RequestInterceptor (запрос, завернутый в ContentCachingRequestWrapper)

@Slf4j
@Order(3)
@Component
public class RequestInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(final HttpServletRequest request, 
                             final HttpServletResponse response,
                             final Object handler) throws Exception {
        ContentCachingRequestWrapper requestWrapper;
        
        if (request instanceof ContentCachingRequestWrapper contentCachingRequestWrapper) {
            requestWrapper = contentCachingRequestWrapper;
        } else {
            requestWrapper = new ContentCachingRequestWrapper(request);
        }
        
        logRequestDetails(requestWrapper);
        return true;
    }

    @SuppressWarnings("unchecked")
    private void logRequestDetails(ContentCachingRequestWrapper request) throws IOException {
        String httpMethod = request.getMethod();
        String url = request.getRequestURL().toString();
        String queryString = request.getQueryString();
        Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        String requestBody = getRequestBody(request);
        
        log.info("REQUEST INFO: [ (Method: {}) (URL: {}) (Query String: {}) (Path Variables: {}) ]", httpMethod, url, queryString, pathVariables);
        log.info("REQUEST: {}", requestBody);
    
    }
    
    private String getRequestBody(HttpServletRequest request) {
        StringBuilder stringBuilder = new StringBuilder();
        try (BufferedReader bufferedReader = request.getReader()) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
        } catch (IOException e) {
            return "[Error Reading Body]";
        }
        return stringBuilder.toString();
    }
    
}

Журналы: HttpMessageNotReadableException: отсутствует необходимое тело запроса.

2024-07-10 00:35:34.738 | http-nio-9001-exec-2 | INFO  | i.g.p.dav.adap.inbou.rest.interceptor.RequestInterceptor | 176cc195-73ec-4921-8d84-5d50f5853cf6 | 5d3d84a5-5330-4608-bc6b-e90916543c94 | REQUEST INFO: [ (Method: POST) (URL: http://localhost:9001/api/account/passengers/associate-passengers) (Query String: null) (Path Variables: {}) ]
2024-07-10 00:35:34.738 | http-nio-9001-exec-2 | INFO  | i.g.p.dav.adap.inbou.rest.interceptor.RequestInterceptor | 176cc195-73ec-4921-8d84-5d50f5853cf6 | 5d3d84a5-5330-4608-bc6b-e90916543c94 | REQUEST: [  {    "accountId": 100,    "firstName": "David",    "lastName": "Guetta",    "dateOfBirth": "1967-11-07",    "nationalityId": 63,    "email": "[email protected]",    "phoneNumber": "+33 3334448899"  }]
2024-07-10 00:35:34.758 | http-nio-9001-exec-2 | ERROR | i.g.p.coa.rese.adapt.inbound.controller.rest.ExceptionRestController | 176cc195-73ec-4921-8d84-5d50f5853cf6 | 5d3d84a5-5330-4608-bc6b-e90916543c94 | Exception 6e9a77ed-24bf-438d-934c-4c7981c6bb70
org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public io.github.paulmarcelinbejan.davinci.adapters.api.DavinciApiResponse<java.util.List<io.github.paulmarcelinbejan.coandaairlines.reservationsystem.ports.domain.Passenger>> io.github.paulmarcelinbejan.coandaairlines.reservationsystem.adapters.inbound.controller.rest.PassengerRestController.associatePassengers(java.util.List<io.github.paulmarcelinbejan.coandaairlines.reservationsystem.adapters.inbound.dto.request.PassengerRequest>)
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:162) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:134) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:228) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:182) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:920) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:830) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[tomcat-embed-core-10.1.19.jar:6.0]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.4.jar:6.1.4]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.19.jar:6.0]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.4.jar:6.1.4]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.4.jar:6.1.4]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.4.jar:6.1.4]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.4.jar:6.1.4]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.19.jar:10.1.19]

3: Используя LoggingFilterBean, я могу зарегистрировать запрос, но он регистрируется в конце API, а не при вызове API, и, более того, 2 идентификатора из MappedDiagnosticContext (| 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a |), записанные перехватчиками, не регистрируются. .

Код: LoggingFilterBean

@Slf4j
@Component
public class LoggingFilterBean extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ContentCachingRequestWrapper requestWrapper = requestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = responseWrapper(response);
        
        chain.doFilter(requestWrapper, responseWrapper);
        
        logRequest(requestWrapper);
        logResponse(responseWrapper);
    }

    private void logRequest(ContentCachingRequestWrapper request) {
        StringBuilder builder = new StringBuilder();
        final String jsonRequest = new String(request.getContentAsByteArray());
        builder.append(jsonRequest);
        log.info("Request: {}", builder);
    }

    private void logResponse(ContentCachingResponseWrapper response) throws IOException {
        StringBuilder builder = new StringBuilder();
        builder.append(new String(response.getContentAsByteArray()));
        log.info("Response: {}", builder);
        response.copyBodyToResponse();
    }

    private ContentCachingRequestWrapper requestWrapper(ServletRequest request) {
        if (request instanceof ContentCachingRequestWrapper requestWrapper) {
            return requestWrapper;
        }
        return new ContentCachingRequestWrapper((HttpServletRequest) request);
    }

    private ContentCachingResponseWrapper responseWrapper(ServletResponse response) {
        if (response instanceof ContentCachingResponseWrapper responseWrapper) {
            return responseWrapper;
        }
        return new ContentCachingResponseWrapper((HttpServletResponse) response);
    }
    
}

Журналы:

2024-07-10 01:00:21.665 | http-nio-9001-exec-1 | INFO  | i.g.p.dav.adap.inbou.rest.interceptor.RequestInterceptor | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | REQUEST INFO: [ (Method: POST) (URL: http://localhost:9001/api/account/passengers/associate-passengers) (Query String: null) (Path Variables: {}) ]
2024-07-10 01:00:21.745 | http-nio-9001-exec-1 | DEBUG | o.h.SQL | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | select nextval('id_passenger_seq')
2024-07-10 01:00:21.768 | http-nio-9001-exec-1 | DEBUG | o.h.SQL | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | insert into passenger (fk_account,date_of_birth,email,first_name,is_primary,last_name,fk_nationality,phone_number,id_passenger) values (?,?,?,?,?,?,?,?,?)
2024-07-10 01:00:21.770 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | binding parameter (1:BIGINT) <- [9]
2024-07-10 01:00:21.771 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | binding parameter (2:VARCHAR) <- [1967-11-07]
2024-07-10 01:00:21.771 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | binding parameter (3:VARCHAR) <- [[email protected]]
2024-07-10 01:00:21.771 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | binding parameter (4:VARCHAR) <- [David]
2024-07-10 01:00:21.771 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | binding parameter (5:BOOLEAN) <- [false]
2024-07-10 01:00:21.771 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | binding parameter (6:VARCHAR) <- [Guetta]
2024-07-10 01:00:21.771 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | binding parameter (7:INTEGER) <- [63]
2024-07-10 01:00:21.771 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | binding parameter (8:VARCHAR) <- [+33 3334448899]
2024-07-10 01:00:21.771 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 99d30bc4-4102-482a-a78e-614f2c0f5876 | 31b3b078-737f-4c60-a429-0596a0736b7a | binding parameter (9:BIGINT) <- [23]
2024-07-10 01:00:21.797 | http-nio-9001-exec-1 | INFO  | i.g.p.dav.adap.inbou.rest.filter.LoggingFilterBean |  |  | Request: [
  {
    "accountId": 9,
    "firstName": "David",
    "lastName": "Guetta",
    "dateOfBirth": "1967-11-07",
    "nationalityId": 63,
    "email": "[email protected]",
    "phoneNumber": "+33 3334448899"
  }
]
2024-07-10 01:00:21.797 | http-nio-9001-exec-1 | INFO  | i.g.p.dav.adap.inbou.rest.filter.LoggingFilterBean |  |  | Response: {"status":"OK","response":[{"id":23,"accountId":9,"isPrimary":false,"firstName":"David","lastName":"Guetta","dateOfBirth":"1967-11-07","nationalityId":63,"email":"[email protected]","phoneNumber":"+33 3334448899"}]}

Одним из решений, позволяющих войти в систему до вызова API и без исключений, является использование ControllerAdvice:

4: Запрос уже десериализован, поэтому, чтобы зарегистрировать json как строку, мне нужно сериализовать запрос.

Код: RequestBodyControllerAdvice

@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class RequestBodyControllerAdvice extends RequestBodyAdviceAdapter {
    
    private final HttpServletRequest httpServletRequest;
    
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, 
                            Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }
    
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
                                MethodParameter parameter, Type targetType,
            Class<? extends HttpMessageConverter<?>> converterType) {
        
        String json;

        if (body instanceof JsonSerializer object) {
            json = object.toJSON();
        } else {
            try {
                json = OBJECT_MAPPER.writeValueAsString(body);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        
        log.info("REQUEST: {}", json);
        
        return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
    }
    
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    
}

Журналы:

2024-07-10 01:31:55.879 | http-nio-9001-exec-1 | INFO  | i.g.p.dav.adap.inbou.rest.interceptor.RequestInterceptor | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | REQUEST INFO: [ (Method: POST) (URL: http://localhost:9001/api/account/passengers/associate-passengers) (Query String: null) (Path Variables: {}) ]
2024-07-10 01:31:55.902 | http-nio-9001-exec-1 | INFO  | i.g.p.dav.adap.inbou.rest.filter.RequestBodyControllerAdvice | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | REQUEST: [{"accountId":9,"firstName":"David","lastName":"Guetta","dateOfBirth":"1967-11-07","nationalityId":63,"email":"[email protected]","phoneNumber":"+33 3334448899"}]
2024-07-10 01:31:55.966 | http-nio-9001-exec-1 | DEBUG | o.h.SQL | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | select nextval('id_passenger_seq')
2024-07-10 01:31:55.988 | http-nio-9001-exec-1 | DEBUG | o.h.SQL | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | insert into passenger (fk_account,date_of_birth,email,first_name,is_primary,last_name,fk_nationality,phone_number,id_passenger) values (?,?,?,?,?,?,?,?,?)
2024-07-10 01:31:55.989 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | binding parameter (1:BIGINT) <- [9]
2024-07-10 01:31:55.990 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | binding parameter (2:VARCHAR) <- [1967-11-07]
2024-07-10 01:31:55.990 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | binding parameter (3:VARCHAR) <- [[email protected]]
2024-07-10 01:31:55.990 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | binding parameter (4:VARCHAR) <- [David]
2024-07-10 01:31:55.990 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | binding parameter (5:BOOLEAN) <- [false]
2024-07-10 01:31:55.990 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | binding parameter (6:VARCHAR) <- [Guetta]
2024-07-10 01:31:55.990 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | binding parameter (7:INTEGER) <- [63]
2024-07-10 01:31:55.990 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | binding parameter (8:VARCHAR) <- [+33 3334448899]
2024-07-10 01:31:55.990 | http-nio-9001-exec-1 | TRACE | o.h.o.jdb.bind | 0137fbd6-a64c-4b7c-9930-4225a6651051 | 7ed2fa14-ffcf-468e-82ff-a7f5a5bfddae | binding parameter (9:BIGINT) <- [25]

Я ищу решение:

  1. Войдите в начале, прежде чем войти в метод контроллера отдыха
  2. Зарегистрируйте запрос json без необходимости сериализации объекта запроса, который он только что десериализовал весной.
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Помню, у меня была такая же проблема. По сути, проблема в том, что если вы используете входной поток, который собираетесь получить: Required request body is missing after reading request поскольку входной поток можно использовать только один раз.

Решение 1. Создание фильтра журналирования, который кэширует/сохраняет запрос, чтобы его можно было прочитать несколько раз.

1. Создайте фильтр журналирования.

Этот фильтр сохраняется в нашем собственном CachedBodyHttpServletRequest, чтобы иметь возможность использовать его как для регистрации, так и для дальнейшей обработки.

@Slf4j
@Component
public class LoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        var cachedRequest = new CachedBodyHttpServletRequest(request);
        log.info("PROCESSING REQUEST: " + request.getMethod() + " " + request.getRequestURI()
                + getParameters(request));
        filterChain.doFilter(cachedRequest, response);
    }

    @SneakyThrows
    private String getParameters(HttpServletRequest request) {
        String body = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
        return body.isEmpty() ? "" : ", with following body: " + body;
    }
}

2. Создайте кэшируемую реализацию HttpServletRequestWrapper.

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

    private final byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedBodyServletInputStream(this.cachedBody);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
        return new BufferedReader(new InputStreamReader(byteArrayInputStream));
    }
}

Относительно простой класс, который сохраняет inputStream в массив байтов и позволяет вам получать InputStream несколько раз, возвращая реализацию ServletInputStream нашего CachedBodyServletInputStream.

3. Создайте реализацию ServletInputStream.

public class CachedBodyServletInputStream extends ServletInputStream {
    private final InputStream cachedBodyInputStream;

    public CachedBodyServletInputStream(byte[] cachedBody) {
        this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
    }

    @Override
    public int read() throws IOException {
        return cachedBodyInputStream.read();
    }

    @SneakyThrows
    @Override
    public boolean isFinished() {
        return cachedBodyInputStream.available() == 0;
    }

    @Override
    public boolean isReady() {
        return true;
    }

    @Override
    public void setReadListener(ReadListener readListener) {
        throw new RuntimeException("Breaks Liskov`s substitution principle :(");
    }
}

За этим классом также относительно легко следить. Он переопределяет все необходимые методы, но также должен переопределять setReadListener, который в нашем простом примере нам не нужен. Хотя я следовал ярлыку создания исключения во время выполнения, я должен предупредить вас, что это плохой стиль, поскольку он нарушает принцип подстановки Лискова... (в таком простом случае использования, как ведение журнала, незначительно, но все же)


Мое решение было основано на этой статье из Baeldung . Я первым опубликовал это решение, потому что именно его я в конечном итоге использовал в своем проекте, альтернативное и, возможно, более простое решение из этой статьи, которое следует использовать CommonsRequestLoggingFilter (не работает надежно для весенней загрузки < 2.0):

Решение 2 Компонент CommonsRequestLoggingFilter

  @Bean
  CommonsRequestLoggingFilter logFilter() {
      CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
        
      filter.setIncludeQueryString(true);
      filter.setIncludePayload(true);
      filter.setMaxPayloadLength(10000);
      filter.setIncludeHeaders(false);
      filter.setAfterMessagePrefix("REQUEST DATA : ");
        
      return filter;
  }

Не забудьте также настроить уровень ведения журнала DEBUG:

logging:
  level:
    org.springframework.web.filter: DEBUG

Обратите внимание, что запрос регистрируется в конце процесса API.


Моим последним предложением также было бы @ControllerAdvice, но, как вы правильно сказали, запрос уже обработан и десериализован...

Другие вопросы по теме