background

The system needs to report each request information and report the data to the monitoring platform.

question

Get interface return object

The system interface is RestController , and the returned results are all @ResponseBody objects. When reporting data, you need to parse the returned result object and extract 状态码 in the object. Obtain the returned result object from the response object, which was previously obtained in the filter method through the ContentCachingResponseWrapper method:

 public class AccessLogFilter extends OncePerRequestFilter implements Ordered {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        long biginTime = System.currentTimeMillis();
        HttpServletRequest httpServletRequest = request;
        HttpServletResponse httpServletResponse = response;
        if (!(httpServletRequest instanceof ContentCachingRequestWrapper)) {
            httpServletRequest = new ContentCachingRequestWrapper(request);
        }
        if (!(httpServletResponse instanceof ContentCachingResponseWrapper)) {
            httpServletResponse = new ContentCachingResponseWrapper(response);
        }

        try {
            …… // 其他操作
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            String responseBody = invokeHttpByteResponseData((ContentCachingResponseWrapper) httpServletResponse);
            …… // 上报等其他操作
        } finally {
            ((ContentCachingResponseWrapper) httpServletResponse).copyBodyToResponse();
            AccessLogEntityHolder.remove();
        }
    }   
        
    public String invokeHttpByteResponseData(ContentCachingResponseWrapper response) {
        try {
            String charset = getResponseCharset(response);
            return IOUtils.toString(response.getContentAsByteArray(), charset);
        } catch (IOException e) {
            throw new RuntimeException("访问日志解析器解析接口返回数据异常!", e);
        }
    }


    protected String getResponseCharset(ContentCachingResponseWrapper response) {
        if (response.getContentType() == null) {
            return StandardCharsets.UTF_8.name();
        }
        boolean isStream = response.getContentType()
                .equalsIgnoreCase(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        return isStream && StringUtils.isNotBlank(response.getCharacterEncoding()) ? response.getCharacterEncoding()
                : StandardCharsets.UTF_8.name();
    }    
    
}

Now, because of fear of conflict with the filter in a third plug-in package introduced, the Interceptor method is used instead. And spring's Interceptor can't build new request and response filter
这里的解决方案是, ControllerAdvice来获取存储对象, ControllerAdvice里的beforeBodyWrite方法,在执行时,将参数里的body Stored temporarily, the storage here adopts the ThreadLocal scheme.
The plan is as follows:

 @ControllerAdvice
public classXXXMetricInterceptor implements HandlerInterceptor, Ordered, ResponseBodyAdvice<Object> {

    private static final ThreadLocal<Object> resultBodyThreadLocal = new ThreadLocal();

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
            ServerHttpResponse response) {
        resultBodyThreadLocal.set(body);
        return body;
    }

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

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
    }
    
}

But there is a problem with the above. The developed Interceptor is a public component that allows other projects to expand. That is to say, the configuration method will be configured in @Configuration :

 @Bean
@ConditionalOnMissingBean(XXXMetricInterceptor.class)
public XXXMetricInterceptor getXXXMetricInterceptor() {
    return newXXXMetricInterceptor();
}

The above scheme cannot do @ConditionalOnMissingBean @ControllerAdvice because the annotation contains @Component , so the @ControllerAdvice is taken out independently, and then Inject in Interceptor :

 public class XXXMetricInterceptor implements HandlerInterceptor, Ordered {

    @Autowired
    private XXXResponseBodyStorage responseBodyStorage;
    
}

@ControllerAdvice
public class XXXResponseBodyStorage implements Ordered, ResponseBodyAdvice<Object> {

    private static final ThreadLocal<Object> resultBodyThreadLocal = new ThreadLocal();

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return enable;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
            ServerHttpResponse response) {
        resultBodyThreadLocal.set(body);
        return body;
    }

    public Object get() {
        return resultBodyThreadLocal.get();
    }

    public void remove() {
        resultBodyThreadLocal.remove();
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

Note: spring allows multiple ControllerAdvice objects exist, and the actual project already exists a ControllerAdvice object that converts the result.

get environment

Due to different environments (such as test , prod ), data needs to be reported to different places, so during initialization, it is necessary to judge the environment, which can be done by initialization (@PostConstruct) In the method, take applicationContext.getEnvironment().getActiveProfiles() to judge, but after testing, it is found that:

  1. If there is no right XXXMetricInterceptor to do inheritance extension ( XXXMetricInterceptor is placed in the public package, and is introduced in the way jar ---cfdcbba954c35b9ccff695ea92272e2a---), getActiveProfiles method value can be obtained.
  2. If an inheritance extension is made to XXXMetricInterceptor in the actual project, then the @PostConstruct method getActiveProfiles returns empty.

The solution is to adjust the time of initialization and change it to reinitialize when spring's application is available:

 public class XXXMetricInterceptor implements ApplicationListener<ApplicationReadyEvent>, HandlerInterceptor, Ordered {

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        String[] activeProfiles =
                SpringContextUtil.getApplicationContext() == null ? null : SpringContextUtil.getActiveProfile();
        ……
    }
    
}

data reporting

At the beginning, the data Shangbo was placed in the interceptor PostHandler method:

 public class XXXMetricInterceptor implements ApplicationListener<ApplicationReadyEvent>, HandlerInterceptor, Ordered {

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
            …… // 数据上报
    }
}

The test found that when the interface sends an exception, it will not enter the postHandle , and then change it to the afterCompletion method:

 public class XXXMetricInterceptor implements ApplicationListener<ApplicationReadyEvent>, HandlerInterceptor, Ordered {

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        try {
            …… //数据上报
        } catch (Exception e) {
            ……
        } finally {
            responseBodyStorage.remove();
        }
    }
    
}

If an exception occurs on the interface, it will first go through the processing of @ExceptionHandler 8cef41dff082fed1961abe1873eb062c---, then enter the ControllerAdvice link, and then enter the afterCompletion .


noname
317 声望51 粉丝

一只菜狗