一、前言

类加载器实战系列的第六篇(悄悄跟你说,这篇比较水),前面5篇在这里:

实战分析Tomcat的类加载器结构(使用Eclipse MAT验证)

还是Tomcat,关于类加载器的趣味实验

重写类加载器,实现简单的热替换

@Java Web 程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码

 
最近事不算多,所以有点时间写博客,昨天写着写着,测试的同学反馈说有一个bug。我看了下服务端日志,空指针了:

下面会给出详细代码,这个空指针不是那么好一眼看出来,不过最后,该bug就是在没有重启服务,也没在本地调试的情况下解决的,利用的方法就是 JSP。没错,就是这么古老的技术。现在很多90程序员已经慢慢成为主力了,对于JSP这类技术估计都不了解,尤其现在前后端分离后,互联网领域的公司,包括一些传统行业的新的项目,后端服务都只是简单的api 服务。下面演示下,怎么利用JSP来找BUG,一点不难,主要是提供一个思路吧。(不适用于打成 jar 包的spring boot应用,对于 打成 war包的spring boot项目是否支持,我还没实验,有兴趣同学可以试试)

二、问题描述

1、问题代码

如图所示,npe抛出的那行,暂时也确定不了到底是哪个空了。jsonObj来自于 array,array来自 resultList,resultList 来自 incidentInformationDao.queryAllIncidentInformation,然后又被  gisCommonService.getCoordinatesWithinCircle 处理了。

大概又看了一眼  gisCommonService.getCoordinatesWithinCircle 的代码:

看着这一坨坨的代码,而且不是我写的,而且没什么注释,而且bug还要我来看。。。哎。。。

我就想了,我要看看到底 在执行了 gisCommonService.getCoordinatesWithinCircle 后,要是可以直接把 List<GisAccessAlarm> resultList 打印出来,不是一下就清晰了吗?

说干就干,本来 @Java Web 程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码 这个博文里提供了一种方式,但是这个测试环境的工程还没搞上这个东东,无奈,用不了。

但是,emmm,等等,JSP 不是可以吗?

2、jsp文件代码

test.jsp:

 <%@ page import="com.alibaba.fastjson.JSONArray" %>
<%@ page import="com.*.base.common.exception.IllegalParameterException" %>
<%@ page import="com.*.base.common.utilities.JsonUtils" %>
<%@ page import="com.*.base.common.utilities.SpringContextUtils" %>
<%@ page import="com.*.dao.interfaces.IncidentInformationDao" %>
<%@ page import="com.*.model.IncidentAppealInformation" %>
<%@ page import="com.*.model.IncidentInformation" %>
<%@ page import="com.*.service.impls.GisIncidentInformationServiceImpl" %>
<%@ page import="com.*.service.interfaces.IGisService" %>
<%@ page import="com.*.service.interfaces.IncidentAppealService" %>
<%@ page import="com.*.service.interfaces.IncidentInformationService" %>
<%@ page import="com.*.utils.GisUtils" %>
<%@ page import="com.*.vo.GisAccessAlarm" %>
<%@ page import="org.apache.commons.collections.CollectionUtils" %>
<%@ page import="org.apache.commons.lang3.StringUtils" %>
<%@ page import="org.slf4j.Logger" %>
<%@ page import="org.slf4j.LoggerFactory" %>
<%@ page import="java.util.Calendar" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.util.List" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@page contentType="text/html;charset=UTF-8"%>
<html xmlns="http://www.w3.org/1999/xhtml"> <head>
<meta charset="UTF-8">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head> <%
Logger logger = LoggerFactory.getLogger(GisIncidentInformationServiceImpl.class);
IncidentInformationService incidentInformationService = SpringContextUtils.getBean(IncidentInformationService.class);
IncidentAppealService incidentAppealService = SpringContextUtils.getBean(IncidentAppealService.class);
IncidentInformationDao incidentInformationDao = SpringContextUtils.getBean(IncidentInformationDao.class);
IGisService gisCommonService = SpringContextUtils.getBean(IGisService.class); String incidentInformationId = "B06BBE52-E85F-450C-A8C6-EB45D2634EED";
Integer radius = 2000;
Integer startTime = 10;
Integer endTime = 10;
if (StringUtils.isEmpty(incidentInformationId)) {
throw new IllegalParameterException("IncidentinformationID is null or empty.");
}
IncidentInformation incidentInfo = incidentInformationService.get(incidentInformationId);
IncidentAppealInformation incidentAppealInformation = incidentAppealService.get(incidentInformationId);
if (incidentInfo == null || null == incidentAppealInformation) {
throw new IllegalParameterException("incidentinformationID is not found in db. IncidentinformationID = " + incidentInformationId);
} String type = incidentInfo.getIncidentTypeId();
type = StringUtils.substring(type, 0, 2);
if (StringUtils.isEmpty(type)) {
throw new IllegalParameterException("incidentinformation type is empty.");
} String lonStr = incidentInfo.getLongitude();
String latStr = incidentInfo.getLatitude();
GisUtils.checkLonLat(lonStr,latStr); Date appealTime = incidentAppealInformation.getIncidentTime();
Calendar cal = Calendar.getInstance();
cal.setTime(appealTime);
cal.add(Calendar.MINUTE, -1 * startTime);
Date startDate = cal.getTime(); Calendar cal1 = Calendar.getInstance();
cal1.setTime(appealTime);
cal1.add(Calendar.MINUTE, 1 * endTime);
cal1.add(Calendar.SECOND, 1);
Date endDate = cal1.getTime();
List<GisAccessAlarm> resultList = incidentInformationDao.queryAllIncidentInformation(incidentInformationId, type, startDate, endDate);
if (null != resultList && CollectionUtils.isNotEmpty(resultList)) {
resultList = gisCommonService.getCoordinatesWithinCircle(resultList, Double.valueOf(lonStr), Double.valueOf(latStr), radius.doubleValue());
}
JSONArray array = JsonUtils.toFormatDateJSONArray(resultList);
82 logger.info("array:{}",array);
%>
<body> </body>
</html>

你大概看到了,我写了这么大一坨,我这么懒,肯定是不可能手写,拷过来,然后把本来自动注入的那些,改成从 SpringContextUtils 静态工具中获取就行了。 我们这里,重点代码就一行,也就是标红的 82 行。

3、执行 jsp

然后我就把这个jsp 丢到了 web应用的根目录下:

然后从我的浏览器访问之:

http://192.168.19.97:8080/web应用上下文/test.jsp

执行后,去看看我们的日志文件:

把array 序列化之后,一看,原来是没有 userId 这个属性存在。。。

于是,下面这行红色处,肯定就空了:

jsonObj.put("userName", userService.getUserMap().getOrDefault(jsonObj.get("userId"), jsonObj.get("userId").toString()));

三、总结

JSP 这种方式,说起来还是挺方便的,可以马上修改,马上看到效果。但是背后的原理我们也需要了解,再看看 Tomcat 的类加载器图(来自于网络,侵删):

可以看到, JSP 的类加载器处于最下面一层,每次访问 JSP 时,如果JSP文件已经被修改过(通过文件的最近一次修改时间确定),都会生成一个新的 JSP 类加载器。 JSP 类加载器 加载的对象,为什么能够和 WebApp类加载器加载的类交互呢(比如我们上面例子中,JSP文件中引用了很多 java代码,甚至用了里面的spring 的 bean),这都是因为 JSP 类加载器的双亲加载器 就是 WebApp 类加载器,JSP 类加载器在遇到自己加载不了的那些类时,都委派给 WebApp 类加载器去加载了,所以 Jsp 文件中引用的那些类,最终是由 Webapp 类加载器加载的,所以才可以调用那些 java 代码。如果我们自己自定义个类加载器,这个类加载器除了加载 jsp 文件,也自己去 web-inf 下面加载想要的类,那么,肯定是会出错的,具体表现就是:

IncidentInformationService incidentInformationService = SpringContextUtils.getBean(IncidentInformationService.class);

这一句中,SpringContextUtils 如果由自己加载,那么 SpringContextUtils 里面是没有任何 bean 存在的,取出来的都为 null,也就不能达到我们动态调试的效果了。

最新文章

  1. [LeetCode] Contains Duplicate 包含重复值
  2. ModelAttribute注解
  3. firefox 最新版地址栏后没有生成二维码的工具
  4. E-Eating Together(POJ 3670)
  5. 《Java程序设计》第8周学习总结
  6. 利用FlashPaper实现类似百度文库功能
  7. Java面试试题
  8. 绝对定位的DIV绝对居中显示
  9. 图论(floyd算法):NOI2007 社交网络
  10. Swift 数组、字典
  11. session临时文件存储路径
  12. 深入研究React setState的工作机制
  13. 用node搭建简单的静态资源管理器
  14. foo的出现
  15. layui xtree 实现一级节点单选 ,子节点复选
  16. Windows Docker Toolbox 安装Redis等开发环境
  17. Linux的进程线程及调度
  18. mysql 备份数据库 mysqldump
  19. mysql 架构~MGR监控手段
  20. 教你一招:更新/替换系统 hosts,轻松访问国外站点

热门文章

  1. C语言二维数组作为函数参数
  2. Codeforces Round #318 (Div. 2) D Bear and Blocks (数学)
  3. GUI进化--数据与界面分离
  4. 第二次团队作业-PANTHER考勤系统需求分析
  5. 带二级目录的Nginx配置------目前找到的最简单的方法
  6. 关于img
  7. 【page-monitor 前端自动化 下篇】 实践应用
  8. 打印两个有序链表的公共部分 【题目】 给定两个有序链表的头指针head1和head2,打印两个 链表的公共部分
  9. Cscope的使用(领略Vim + Cscope的强大魅力)
  10. PAT (Advanced Level) Practise - 1099. Build A Binary Search Tree (30)