十分钟通过一个实际问题,真正教会大家如何解决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
- 通过远程debug的断点,将问题范围确定在应用服务之前,进而判断大概率在tomcat(当然也可能是在应用服务前的nginx等。不过在当前场景优先排查tomcat)。
- 通过400错误页面的样式,联想到tomcat其他错误页面,猜测问题发生在tomcat。
- 通过资料查询,网站搜索,找到初步解决方案(这里需要重视搜索关键词,关键词的准确性决定了查询效率)
SpringBoot版本冲突问题
- 通过日志中的错误信息,配合搜索引擎,猜测错误是由于版本冲突造成。
- 通过代码的增量,判断问题发生在SpringBoot2.x的依赖上。
- [可选] 这里可以通过demo,确定问题是否版本冲突造成(这里的demo必须精准,否则就多准备几个)。
- 通过maven查看项目依赖树,确定是由于SpringBoot2.x与SpringBoot1.x的版本冲突造成。
- 查询资料,没有明确解决方案直接指向。
- 通过提示的tomcat配置信息,找到ServerProperties类
- 在ServerProperties类中,寻找Tomcat相关的类。
- 最终找到TomcatEmbeddedServletContainerFactory,两者父类与接口吻合,并且可以直接替换原有的TomcatServletWebServerFactory
事后思考了一下,发现上述5-8可以更加简单。那就是直接查询SpringBoot1.x中类似TomcatServletWebServerFactory的类。
具体方法就是明确TomcatServletWebServerFactory的父类AbstractEmbeddedServletContainerFactory(实际功能)与接口ResourceLoaderAware(资源注入)。
在SpringBoot1.x中直接查找AbstractEmbeddedServletContainerFactory子类即可(发现有三个,分别对应Tomcat,Netty与Undertow,都实现了ResourceLoaderAware接口)。不过需要确认是否可以直接使用,还是需要再进行转化等。
小结
再将上述步骤浓缩一下,就是大家常见的:
- 查询资料
- debug,打断点
- 代码跳转
- 联想
- 单一变量进行筛选
- ...
相信上面这类总结,大家见得多了。但是真正落实后,效率确实差别很大的。
这篇文章,从实际问题 → 问题解决步骤 → 问题解决思路,帮助大家能够真正明白如何在程序中发现问题,定位问题,解决问题,理解问题解决思路。如果可以的话,希望大家更进一步,学到如何进行这样的总结。
愿与诸君共进步。
十分钟通过一个实际问题,真正教会大家如何解决Bug的更多相关文章
- 十分钟开发一个调用Activity的PhoneGap插件
在HybridApp开发中,非常多业务我们是没有办法通过HTML5+js实现的,比方调用第三方的包括Activity的jar包,一些必须使用原生代码才干实现的功能,比方复杂的UI的效果,调用通讯相关的 ...
- 十分钟通过 NPM 创建一个命令行工具
大过年的,要不要写点代码压压惊?来花十分钟学一下怎么通过 NPM 构建一个命令行工具. 写了一个小 demo,用于代替 touch 的创建文件命令 touchme ,可以创建自带“佛祖保佑”注释的文件 ...
- 【NLP】十分钟快览自然语言处理学习总结
十分钟学习自然语言处理概述 作者:白宁超 2016年9月23日00:24:12 摘要:近来自然语言处理行业发展朝气蓬勃,市场应用广泛.笔者学习以来写了不少文章,文章深度层次不一,今天因为某种需要,将文 ...
- 十分钟轻松让你认识ASP.NET MVC6
这篇文章说明下如何在普通编辑器下面开发mvc6应用程序. 上篇文章: 十分钟轻松让你认识ASP.NET 5(MVC6) 首先安装mvc6的nuget包: 可以看到在project.json文件中添加了 ...
- 十分钟轻松让你认识ASP.NET 5(MVC6)
ASP.NET 5差不多快发布了.自己也学习了有两个月了.今天给没有接触asp.net 5的同学写一个简单地十分钟教程,教你认识一下asp.net 5. 1.安装kvm 首先,你需要以管理员权限打开c ...
- 十分钟了解分布式计算:Google Dataflow
介绍 Google Cloud Dataflow是一种构建.管理和优化复杂数据处理流水线的方法,集成了许多内部技术,如用于数据高效并行化处理的Flume和具有良好容错机制流处理的MillWheel.D ...
- 十分钟了解分布式计算:GraphX
GraphX原型论文 GraphX是Spark中用于图(e.g., Web-Graphs and Social Networks)和图并行计算(e.g., PageRank and Collabora ...
- 快速入门:十分钟学会Python
初试牛刀 假设你希望学习Python这门语言,却苦于找不到一个简短而全面的入门教程.那么本教程将花费十分钟的时间带你走入Python的大门.本文的内容介于教程(Toturial)和速查手册(Cheat ...
- 十分钟入门less(翻译自:Learn lESS in 10 Minutes(or less))
十分钟入门less(翻译自:Learn lESS in 10 Minutes(or less)) 注:本文为翻译文章,因翻译水平有限,难免有缺漏不足之处,可查看原文. 我们知道写css代码是非常枯燥的 ...
随机推荐
- SpringCloud服务的注册发现--------zookeeper实现服务与发现 + Ribbon实现客户端负载均衡
1,Eureka 闭源了,但是我们可以通过zookeeper实现注册中心的功能. zookeeper 是一个分布式协调工具,可以实现服务的注册和发现,配置中心,注册中心,消息中间件的功能 2,工具准备 ...
- 已知IP地址和子网掩码求出网络地址、广播地址、地址范围和主机数(转载https://blog.csdn.net/qq_39026548/article/details/78959089)
假设IP地址为128.11.67.31,子网掩码是255.255.240.0.请算出网络地址.广播地址.地址范围.主机数.方法:将IP地址和子网掩码转化成二进制形式,然后进行后续操作. IP地址和子网 ...
- Effective Java要点笔记
第一章: 创建和销毁对象 类可以通过静态工厂方法来提供客户端,而不是通过构造器 优点: 自定义工厂名称,提高可读性 可以工厂里搞单例 控制实例类是哪种子类 总之是更加灵活,可读性更高 缺点: 有可能会 ...
- Spring中常用注解的介绍
spring中使用注解时配置文件的写法: <?xml version="1.0" encoding="UTF-8"?> <span style ...
- C++ 简单信息的表示和基本运算
一.算术运算和自增自减运算 二.关系运算 三.逻辑运算 四.位运算 五.特殊运算符 六.混合运算中的类型转换
- Python Tkinter Grid布局管理器详解
Grid(网格)布局管理器会将控件放置到一个二维的表格里.主控件被分割成一系列的行和列,表格中的每个单元(cell)都可以放置一个控件. 注意:不要试图在一个主窗口中混合使用pack和grid (1) ...
- Java是未来的第一编程语言吗?
目录 一.前言 二.Java帝国的今天 2.1 依然霸占TIOBE热门编程语言的榜首 2.2 曾经想扼杀Java的微软宣布加入OpenJDK 2.3 Oracle发布开源全栈虚拟机GraalVM 三. ...
- Reface.NPI 方法名称解析规则详解
在上次的文章中简单介绍了 Reface.NPI 中的功能. 本期,将对这方法名称解析规则进行详细的解释和说明, 以便开发者可以完整的使用 Reface.NPI 中的各种功能. 基本规则 方法名称以 I ...
- PTA数据结构与算法题目集(中文) 7-25
PTA数据结构与算法题目集(中文) 7-25 7-25 朋友圈 (25 分) 某学校有N个学生,形成M个俱乐部.每个俱乐部里的学生有着一定相似的兴趣爱好,形成一个朋友圈.一个学生可以同时属于若干 ...
- TC1.6SourceCode java课程表
/** * @version 2.0 * @author sharks */ /** * Instruction * this version will use IO * apply file to ...