【小家Spring】从OncePerRequestFilter的源码解读去了解Spring内置的Filter的特别之处以及常见过滤器使用介绍(下)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【小家Spring】从OncePerRequestFilter的源码解读去了解Spring内置的Filter的特别之处以及常见过滤器使用介绍(下)


下面书写一个我们最实用的例子,也是我用在我们所有微服务项目中的一个例子,此处贴出来供以参考:



    @ResponseBody
    @PostMapping(value = "/hello")
    public String helloPost(@RequestBody Map<String, Object> object) {
        //System.out.println(object);
        return "hello...Post";
    }


get请求,不输出日志,来一个put请求如下:


image.png



日志输出如下:


image.png


日志输出里有个小细节,我们看到请求开始里没有payload,输出的时候却有了,怎么回事呢?原因其实是Spring提供的ContentCachingRequestWrapper具有懒加载的特性,所以才会出现这种现象。我个人觉得没什么影响,payload输出一遍反倒是我最希望的效果。至于根本原因,后面的博文会专门分析Spring给我们提供的一些web工具类里,会提到~


贴上我书写的代码,各位若有需要,只需要配置此Filter即可。这个Filter我放在我们公司的私服里,所有的服务都可以直接使用了,若有任何问题,随时留言交流~~

/**
 * 打印请求日志,默认打印get放,只打印POST、PUT、DELETE方法的请求日志哦~
 * 会计算出请求耗时、client请求的ip地址等等  contentType也会记录打印出来 信息比较全 方便查找问题~
 *
 * @author fangshixiang
 * @description
 * @date 2019-02-16 22:04
 */
@Slf4j
@WebFilter(urlPatterns = "/*")
public class RequestLogFilter extends AbstractRequestLoggingFilter {
    //这些配置都可以在init-param中进行设置,但是基于注解的,这里就不要这么麻烦了,统一在初始化的时候设置值吧
    //private boolean includeQueryString = false;
    //private boolean includeClientInfo = false;
    //private boolean includeHeaders = false;
    //private boolean includePayload = false;
    private static final String PROCESS_START_TIME_SUFFIX = ".PROCESS_START_TIME";
    @Override
    protected void initFilterBean() throws ServletException {
        super.setIncludeQueryString(true);
        super.setIncludeClientInfo(true);
        //因为headers里面的信息太多了 所以这里不输出了,否则过于干扰,只手动把content-type等关键信息输出即可
        super.setIncludeHeaders(false);
        super.setIncludePayload(true);
        super.setMaxPayloadLength(1000); //最大支持到1000个字符
        //头信息
        super.setBeforeMessagePrefix("请求开始 [");
        super.setBeforeMessageSuffix("]");
        super.setAfterMessagePrefix("请求结束 [");
        super.setAfterMessageSuffix("]");
    }
    @Override
    protected boolean shouldLog(HttpServletRequest request) {
        String method = request.getMethod();
        return HttpMethod.POST.matches(method) || HttpMethod.PUT.matches(method) || HttpMethod.DELETE.matches(method);
    }
    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        logger.info(calcRequestTime(request)
                .concat(getConfigTypeLog(request))
                .concat(getThreadId())
                .concat(message));
    }
    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        logger.info(calcRequestTime(request)
                .concat(getConfigTypeLog(request))
                .concat(getThreadId())
                .concat(message));
    }
    //拼装contentType
    private String getConfigTypeLog(HttpServletRequest request) {
        String contentType = request.getContentType();
        String method = request.getMethod();
        return "[contentType=" + contentType + "] " + method.toUpperCase() + " ";
    }
    //计算请求耗时
    private String calcRequestTime(HttpServletRequest request) {
        long mills = 0;
        String requestTimeUniqueName = getRequestTimeUniqueName();
        Object processStartTime = request.getAttribute(requestTimeUniqueName);
        if (processStartTime == null) { //首次 放置值
            request.setAttribute(requestTimeUniqueName, Instant.now());
        } else { //请求结束的处理  开始计算吧
            Instant start = (Instant) processStartTime;
            Instant now = Instant.now();
            mills = Duration.between(start, now).toMillis();
            request.removeAttribute(requestTimeUniqueName);
        }
        return mills == 0 ? "" : ("[耗时:" + mills + "ms] ");
    }
    private String getRequestTimeUniqueName() {
        return this.getClass().getName().concat(PROCESS_START_TIME_SUFFIX);
    }
    private String getThreadId() {
        return "[ThreadId:" + Thread.currentThread().getId() + "] ";
    }
}


备注:此Filter自己定制日志。若你结束请求时还想输出response里面的的内容。比如状态码,返回body里面的内容等等(其实我倒觉得还挺重要的),自己可以去加工实现。


这里比较坑爹的是,Spring提供给我们复写的两个方法,都没返回给我们response,所以若有需要,各位小伙伴自己实现哈。我在我项目中有实现,每个人实现方式不一样,这里就不贴出这部分源码了哈~


此请求日志Filter一般放在TokenFilter后面执行(如果业务特殊,放在前面执行也可)


拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调


最后


Spring作为当下实际的Java EE标准,非常的流行,设计也是非常的好。因此熟练的掌握Spring为我们提供的一些实用类,很多时候能够让我们事半功倍~

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
19天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
19天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
19天前
|
XML 缓存 Java
手写Spring源码(简化版)
Spring包下的类、手写@ComponentScan注解、@Component注解、@Autowired注解、@Scope注解、手写BeanDefinition、BeanNameAware、InitializingBean、BeanPostProcessor 、手写AnnotationConfigApplicationContext
手写Spring源码(简化版)
|
4天前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
13 4
|
9天前
|
XML 缓存 Java
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
38 10
|
5天前
|
Java 开发者 Spring
Spring Cloud Gateway 中,过滤器的分类有哪些?
Spring Cloud Gateway 中,过滤器的分类有哪些?
13 3
|
9天前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
9天前
|
XML 存储 Java
Spring-源码深入分析(二)
Spring-源码深入分析(二)
|
9天前
|
XML 设计模式 Java
Spring-源码深入分析(一)
Spring-源码深入分析(一)
|
2月前
|
人工智能 前端开发 Java
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
本文介绍了如何使用 **Spring Cloud Alibaba AI** 构建基于 Spring Boot 和 uni-app 的聊天机器人应用。主要内容包括:Spring Cloud Alibaba AI 的概念与功能,使用前的准备工作(如 JDK 17+、Spring Boot 3.0+ 及通义 API-KEY),详细实操步骤(涵盖前后端开发工具、组件选择、功能分析及关键代码示例)。最终展示了如何成功实现具备基本聊天功能的 AI 应用,帮助读者快速搭建智能聊天系统并探索更多高级功能。
584 2
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
下一篇
无影云桌面