@Java web程序员,在保留现场,服务不重启的情况下,执行我们的调试代码(JSP 方式)

一、前言

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

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

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

了不得,我可能发现了Jar 包冲突的秘密

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

@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:

 1 <%@ page import="com.alibaba.fastjson.JSONArray" %>
 2 <%@ page import="com.*.base.common.exception.IllegalParameterException" %>
 3 <%@ page import="com.*.base.common.utilities.JsonUtils" %>
 4 <%@ page import="com.*.base.common.utilities.SpringContextUtils" %>
 5 <%@ page import="com.*.dao.interfaces.IncidentInformationDao" %>
 6 <%@ page import="com.*.model.IncidentAppealInformation" %>
 7 <%@ page import="com.*.model.IncidentInformation" %>
 8 <%@ page import="com.*.service.impls.GisIncidentInformationServiceImpl" %>
 9 <%@ page import="com.*.service.interfaces.IGisService" %>
10 <%@ page import="com.*.service.interfaces.IncidentAppealService" %>
11 <%@ page import="com.*.service.interfaces.IncidentInformationService" %>
12 <%@ page import="com.*.utils.GisUtils" %>
13 <%@ page import="com.*.vo.GisAccessAlarm" %>
14 <%@ page import="org.apache.commons.collections.CollectionUtils" %>
15 <%@ page import="org.apache.commons.lang3.StringUtils" %>
16 <%@ page import="org.slf4j.Logger" %>
17 <%@ page import="org.slf4j.LoggerFactory" %>
18 <%@ page import="java.util.Calendar" %>
19 <%@ page import="java.util.Date" %>
20 <%@ page import="java.util.List" %>
21 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
22 <%@page contentType="text/html;charset=UTF-8"%>
23 <html xmlns="http://www.w3.org/1999/xhtml">
24
25 <head>
27 <meta charset="UTF-8">
29     <meta http-equiv="pragma" content="no-cache">
30     <meta http-equiv="cache-control" content="no-cache">
31     <meta http-equiv="expires" content="0">
32     <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
33     <meta http-equiv="description" content="This is my page">
34 </head>
35
36 <%
37     Logger logger = LoggerFactory.getLogger(GisIncidentInformationServiceImpl.class);
38     IncidentInformationService incidentInformationService = SpringContextUtils.getBean(IncidentInformationService.class);
39     IncidentAppealService incidentAppealService = SpringContextUtils.getBean(IncidentAppealService.class);
40     IncidentInformationDao incidentInformationDao = SpringContextUtils.getBean(IncidentInformationDao.class);
41     IGisService gisCommonService = SpringContextUtils.getBean(IGisService.class);
42
43     String incidentInformationId = "B06BBE52-E85F-450C-A8C6-EB45D2634EED";
44     Integer radius = 2000;
45     Integer startTime = 10;
46     Integer endTime = 10;
47     if (StringUtils.isEmpty(incidentInformationId)) {
48         throw new IllegalParameterException("IncidentinformationID is null or empty.");
49     }
50     IncidentInformation incidentInfo = incidentInformationService.get(incidentInformationId);
51     IncidentAppealInformation incidentAppealInformation = incidentAppealService.get(incidentInformationId);
52     if (incidentInfo == null || null == incidentAppealInformation) {
53         throw new IllegalParameterException("incidentinformationID is not found in db. IncidentinformationID = " + incidentInformationId);
54     }
55
56     String type = incidentInfo.getIncidentTypeId();
57     type = StringUtils.substring(type, 0, 2);
58     if (StringUtils.isEmpty(type)) {
59         throw new IllegalParameterException("incidentinformation type is empty.");
60     }
61
62     String lonStr = incidentInfo.getLongitude();
63     String latStr = incidentInfo.getLatitude();
64     GisUtils.checkLonLat(lonStr,latStr);
65
66     Date appealTime = incidentAppealInformation.getIncidentTime();
67     Calendar cal = Calendar.getInstance();
68     cal.setTime(appealTime);
69     cal.add(Calendar.MINUTE, -1 * startTime);
70     Date startDate = cal.getTime();
71
72     Calendar cal1 = Calendar.getInstance();
73     cal1.setTime(appealTime);
74     cal1.add(Calendar.MINUTE, 1 * endTime);
75     cal1.add(Calendar.SECOND, 1);
76     Date endDate = cal1.getTime();
77     List<GisAccessAlarm> resultList = incidentInformationDao.queryAllIncidentInformation(incidentInformationId, type, startDate, endDate);
78     if (null != resultList && CollectionUtils.isNotEmpty(resultList)) {
79         resultList = gisCommonService.getCoordinatesWithinCircle(resultList, Double.valueOf(lonStr), Double.valueOf(latStr), radius.doubleValue());
80     }
81     JSONArray array = JsonUtils.toFormatDateJSONArray(resultList);
82     logger.info("array:{}",array);
83 %>
84 <body>
85
86 </body>
87 </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,也就不能达到我们动态调试的效果了。

原文地址:https://www.cnblogs.com/grey-wolf/p/11052495.html

时间: 2024-12-28 18:26:33

@Java web程序员,在保留现场,服务不重启的情况下,执行我们的调试代码(JSP 方式)的相关文章

Java Web 程序员的职业进阶之路

啥也不说了,都在图里了.希望可以给大家的职业规划一些提示,尤其是写了几年程序,却越来越迷茫的同学. Java Web 程序员的职业进阶之路,布布扣,bubuko.com

java web程序员微信群

关注微信公众号"程序员成长日志",回复关键字"java"扫码进群 本群主要为大家解决工作中遇到的问题遇到的问题发到群里大家集思广益平时可以瞎扯不定期红包 原文地址:https://www.cnblogs.com/czrz1024/p/12707515.html

10本Java精选书籍助你快速进阶Java顶尖程序员

书是人类进步的阶梯,从某种意义上讲,一个人读书多少,跟这个人将来能有多大成就取得多大成功有着必然的联系,然而读书不仅仅是求量的过程,还需要精读.有选择的读,前面的文章给大家介绍过从零基础学习java编程到精通之路的五本书籍,但是Java学习入门之后,想要往更高层次的Java方向发展,如果能有几本好书的辅助,可以使我们在Java进阶之路上事半功倍,那么下面亦是美网络小编再给大家推荐10本Java精选书籍助你进阶Java顶尖程序员. 1.<深入理解Java虚拟机:JVM高级特性与最佳实践> 本书从

【转】成为Java顶尖程序员 ,看这11本书就够了

成为Java顶尖程序员 ,看这11本书就够了 转自:http://developer.51cto.com/art/201512/503095.htm 以下是我推荐给Java开发者们的一些值得一看的好书.但是这些书里面并没有Java基础.Java教程之类的书,不是我不推荐,而是离我自己学习 Java基础技术也过去好几年了,我学习的时候看的什么也忘了,所以我不能不负责任地推荐一些我自己都没有看过的书给大家. 作者:来源:攻城狮之家|2015-12-31 09:55 收藏 分享 “学习的最好途径就是看

【Web探索之旅】第四部分:Web程序员

内容简介 1.第四部分第一课:什么是Web程序员? 2.第四部分第二课:如何成为Web程序员? 3.第四部分第三课:成为优秀Web程序员的秘诀 第四部分:Web程序员(完结篇) 大家好.终于来到了[Web探索之旅]的最后一部分. 我们的第二和第三部分都略有难度,不过这第四部分和第一部分一样,都会比较轻松愉快. 这一课我们来聊一聊Web程序员,Web的文化,如何成为一名优秀的Web程序员. 或许看完这部分,你会跃跃欲试,想要成为一名Web程序员也不一定.毕竟现在Web程序员还是比较吃香的. 第四部

P8架构师倾情讲述,看源码是Java高级程序员内功修炼必经之路

笔者前两天总结了一些Java程序员进阶必备的技术要点,并且做出了分享,可以参见前篇文章< Java程序员年薪40W,他1年走了别人5年的路(技术提炼建议收藏) >. 今天笔者在这里给大家介绍一下程序员的内功,内功修炼的内容就与之前文章中的技术内容大致相似,但是,那些技术不是说你想学就能学会的,如果你只是写了几年的业务代码,不对源码加以分析,那么你永远只是停留在码农阶段.你必须要学会看懂代码,才能尝试在架构技术上加深造诣,架构师之所以称之为架构师,源于他们对源码透彻的理解. 首先先为大家介绍一下

如何成为一名Java初级程序员

目前,JAVA是开发人员的热宠,很多论坛都有不少热爱JAVA的开发人员,也有不少想成为JAVA程序员,但苦于不知道该如何学习,也不清楚该学些什么知识才能成为一个JAVA程序员.本人在这里抛砖引玉,和大家讨论成为一个JAVA初级程序员应该具有的知识,与大家共享. 个人认为想成为一个合格的JAVA初级程序员应该具备如下知识: 一.面向对象的知识:JAVA是一个面向对象的开发语言,因此熟悉面向对象对学习JAVA很有必要,您要了解:什么是对象,什么是类;什么是封装,什么是多态,什么是继承;什么是抽象类,

工作2-5年java的程序员,这六个技术栈让你轻松涨薪50%

工作多年以及在面试中,我经常能体会到,有些面试者确实是认真努力工作,但坦白说表现出的能力水平却不足以通过面试,通常是两方面的原因: 1."知其然不知其所以然".做了多年技术,开发了很多业务应用,但似乎并未思考过种种技术选择背后的逻辑.坦白说,我并不放心把具有一定深度的任务交给他. 2.知识碎片化,不成系统.在面试中,面试者似乎无法完整.清晰地描述自己所开发的系统,或者使用的相关技术.平时可能埋头苦干,或者过于死磕某个实现细节,并没有抬头审视这些技术. 前人已经掉过的坑,后来的同学就别再

java web 程序---购物车选商品,购买,付款

虚函数使用的时机 为什么虚函数不总是适用? 1. 虚函数有事会带来很大的消耗: 2. 虚函数不总是提供所需的行为: 3. 当我们不考虑继承当前类时,不必使用虚函数. 必须使用虚函数的情况: 1. 当你想删除一个表面上指向基类对象,实际却是指向派生类对象的指针,就需要虚析构函数. java web 程序---购物车选商品,购买,付款,布布扣,bubuko.com