zoukankan      html  css  js  c++  java
  • 十分钟通过一个实际问题,真正教会大家如何解决Bug

    前言

    这篇文章从实际问题 -> 问题解决步骤 -> 问题解决思路,帮助大家能够明白如何在程序中发现问题,定位问题,解决问题。并真正理解那些问题解决思路。

    首先说说这个实际问题是什么,又是怎么遇到的。

    我这边做了一个操作日志模块,需要提供独立查询页面。正好集团内部有一个xxx前端产品,可以简单配置就生成一个报表页面。

    但是由于该产品请求http接口时,会自动加入一个“sorts=[]”的参数,作为报表排序的依据。但是悲剧发生了。请求后,后端服务器返回一个400-bad request。

    而xxx前端产品的mock数据就没问题。说明是后端差异造成的。所以需要后端优先解决。

    定位问题

    断点定位

    通过远程debug(因为是部署到远程预发环境的),进行断点查看。
    首先将断点打在对应Controller上,发现没用。
    紧接着将断点打在了DispatcherServlet的doService方法上,发现还是没用。

    那么再往前就不是SpringBoot这样的应用服务范围了,而是属于Tomcat这样的Web服务范围了。
    于此同时,我们发现400请求的页面与tomcat的错误页面很相似。由于很久没有看到tomcat错误页面,所以并没有在第一时间察觉。

    资料查询

    这时候,已经很难通过断点方式进行查询了。但是我们已经有一定的把握将问题定位在tomcat这一web容器了。并且通过之前的接口测试,确定问题发生在“[”这样字符上。
    所以,直接通过百度查询“tomcat 非法字符 400“这样的关键字,找到了如下的博客:
    解决springboot项目请求出现非法字符问题 java.lang.IllegalArgumentException:Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986

    根据博客中给出的方法,加入TomcatConfiguration这一配置类:

    /**
     * @author: zw
     * @create: 2019-06-27 11:19
     **/
    @Configuration
    public class TomcatConfig {
    
        @Bean
        public TomcatServletWebServerFactory webServerFactory() {
            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            factory.addConnectorCustomizers((Connector connector) -> {
                    connector.setProperty("relaxedPathChars", ""<>[\]^`{|}");
                    connector.setProperty("relaxedQueryChars", ""<>[\]^`{|}");
            });
            return factory;
        }
    }
    

    这里说一下,为什么没有采用其他博客所说的修改tomcat的配置文件。因为这种偏底层的改变,在大公司里面实现是非常麻烦的。所以优先考虑通过应用服务自身的设置完成。

    但是,问题到这里就真的结束了嘛?
    当然没有。如果只是如此,我只需要转载上面那篇博客就OK了。

    版本冲突

    问题发生

    当时,通过上述方法,在本地的demo已经解决了问题。
    但是,集团内部的xxx平台无法成功将代码部署到日常环境。查看错误日志,并没有看到有效的信息。只是一些ClassNotFound等异常,但并没有具体信息。

    问题定位

    简单搜索一下相关异常,看到一个关键字眼-版本冲突。
    结合之前TomcatConfiguration引入的依赖,唯一可能存在问题的就是下面这条依赖:
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;

    该依赖,来自SpringBoot 2.x。之前引入该依赖时,比较担心的是系统采用的是集团内部的xxxboot,可能不兼容。但是后来发现系统有引入SpringBoot,所以就没有担心了。
    现在看来,这里还是存在问题。
    通过maven,发现:原项目采用的是SpringBoot1.x,而不是SpringBoot2.x,所以才会在启动时,产生版本冲突问题。

    问题解决

    确定问题位置后,接下来就是解决问题。
    但是比较尴尬的是,SpringBoot1.x没有TomcatConfiguration所需要的TomcatServletWebServerFactory。

    所以,接下来就是寻找SpringBoot1.x版本的解决方案。

    首先谷歌”SpringBoot1.x tomcat configuration“(这种偏原理的,优先考虑谷歌。尤其是有这个网络条件,并且英文看得懂),找到以下博客:

    How to Configure Spring Boot Tomcat

    但是并没有找到直接的解决方案(相信这也是大家经常遇到的情况)。

    这个时候,看到tomcat存在一个application.properties配置项:
    server.tomcat.accesslog.enabled=true

    因为TomcatConfiguration是@Configuration修饰的配置类,所以直接在实际项目代码(SpringBoot1.x)的application.properties增加该属性,通过属性跳转,跳到ServerProperties(位于org.springframework.boot.autoconfigure.web下)。

    直接搜索tomcat,发现了TomcatEmbeddedServletContainerFactory,而这与SpringBoot2.x所使用的TomcatServletWebServerFactory很类似。
    打开代码,发现两者都继承自AbstractEmbeddedServletContainerFactory,并实现了ResourceLoaderAware接口。

    所以针对之前的解决方案,修改成下面样子就OK了。

    package tech.jarry.learning.birdlog.birdlog.config;
    
    import org.apache.catalina.connector.Connector;
    //import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * Tomcat配置类:SpringBoot1.x下,解决Tomcat对非法字符返回400问题
     * @author: jarry
     **/
    @Configuration
    public class TomcatConfiguration {
    
      @Bean
      public TomcatEmbeddedServletContainerFactory webServerFactory() {
        TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
        factory.addConnectorCustomizers((Connector connector) -> {
          connector.setProperty("relaxedPathChars", ""<>[\]^`{|}");
          connector.setProperty("relaxedQueryChars", ""<>[\]^`{|}");
        });
        return factory;
      }
    }
    

    总结

    至此,tomcat非法字符请求直接返回400的问题,就在SpringBoot1.x版本下解决了。

    但是,如果只是到此为止,不再向前一步就太可惜了。就如同百米赛跑,跑到99米为止,放弃了。

    问题的解决固然重要,但是更重要的是解决问题的思路。因为问题千千万万,每个问题的解决方法可能都不一样。而解决问题的思路才是有限的,才有最为宝贵的。

    所以,让我们复盘一下上述问题的解决思路:

    tomcat对非法字符返回400

    1. 通过远程debug的断点,将问题范围确定在应用服务之前,进而判断大概率在tomcat(当然也可能是在应用服务前的nginx等。不过在当前场景优先排查tomcat)。
    2. 通过400错误页面的样式,联想到tomcat其他错误页面,猜测问题发生在tomcat。
    3. 通过资料查询,网站搜索,找到初步解决方案(这里需要重视搜索关键词,关键词的准确性决定了查询效率)

    SpringBoot版本冲突问题

    1. 通过日志中的错误信息,配合搜索引擎,猜测错误是由于版本冲突造成。
    2. 通过代码的增量,判断问题发生在SpringBoot2.x的依赖上。
    3. [可选] 这里可以通过demo,确定问题是否版本冲突造成(这里的demo必须精准,否则就多准备几个)。
    4. 通过maven查看项目依赖树,确定是由于SpringBoot2.x与SpringBoot1.x的版本冲突造成。
    5. 查询资料,没有明确解决方案直接指向。
    6. 通过提示的tomcat配置信息,找到ServerProperties类
    7. 在ServerProperties类中,寻找Tomcat相关的类。
    8. 最终找到TomcatEmbeddedServletContainerFactory,两者父类与接口吻合,并且可以直接替换原有的TomcatServletWebServerFactory

    事后思考了一下,发现上述5-8可以更加简单。那就是直接查询SpringBoot1.x中类似TomcatServletWebServerFactory的类。

    具体方法就是明确TomcatServletWebServerFactory的父类AbstractEmbeddedServletContainerFactory(实际功能)与接口ResourceLoaderAware(资源注入)。
    在SpringBoot1.x中直接查找AbstractEmbeddedServletContainerFactory子类即可(发现有三个,分别对应Tomcat,Netty与Undertow,都实现了ResourceLoaderAware接口)。不过需要确认是否可以直接使用,还是需要再进行转化等。

    小结

    再将上述步骤浓缩一下,就是大家常见的:

    • 查询资料
    • debug,打断点
    • 代码跳转
    • 联想
    • 单一变量进行筛选
    • ...

    相信上面这类总结,大家见得多了。但是真正落实后,效率确实差别很大的。

    这篇文章,从实际问题 -> 问题解决步骤 -> 问题解决思路,帮助大家能够真正明白如何在程序中发现问题,定位问题,解决问题,理解问题解决思路。如果可以的话,希望大家更进一步,学到如何进行这样的总结。

    愿与诸君共进步。

  • 相关阅读:
    C++ 函数返回局部变量的std::move()的适用场景(转)
    Android 内存泄漏总结(转)
    android JNI调用(Android Studio 3.0.1)(转)
    Linux c —— opendir函数和readdir函数内涵及用法(转)
    理解Android编译命令(转)
    Linux内存管理(转)
    Android内存分析命令(转)
    关于跳板机穿透的文章 (未验证)
    windows下 git配置ssh
    转: Git远程操作详解 (阮一峰)
  • 原文地址:https://www.cnblogs.com/Tiancheng-Duan/p/12791799.html
Copyright © 2011-2022 走看看