一、前言

类加载器实战系列的第六篇(悄悄跟你说,这篇比较水),前面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:

  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>
  26. <meta charset="UTF-8">
  27. <meta http-equiv="pragma" content="no-cache">
  28. <meta http-equiv="cache-control" content="no-cache">
  29. <meta http-equiv="expires" content="0">
  30. <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
  31. <meta http-equiv="description" content="This is my page">
  32. </head>
  33.  
  34. <%
  35. Logger logger = LoggerFactory.getLogger(GisIncidentInformationServiceImpl.class);
  36. IncidentInformationService incidentInformationService = SpringContextUtils.getBean(IncidentInformationService.class);
  37. IncidentAppealService incidentAppealService = SpringContextUtils.getBean(IncidentAppealService.class);
  38. IncidentInformationDao incidentInformationDao = SpringContextUtils.getBean(IncidentInformationDao.class);
  39. IGisService gisCommonService = SpringContextUtils.getBean(IGisService.class);
  40.  
  41. String incidentInformationId = "B06BBE52-E85F-450C-A8C6-EB45D2634EED";
  42. Integer radius = 2000;
  43. Integer startTime = 10;
  44. Integer endTime = 10;
  45. if (StringUtils.isEmpty(incidentInformationId)) {
  46. throw new IllegalParameterException("IncidentinformationID is null or empty.");
  47. }
  48. IncidentInformation incidentInfo = incidentInformationService.get(incidentInformationId);
  49. IncidentAppealInformation incidentAppealInformation = incidentAppealService.get(incidentInformationId);
  50. if (incidentInfo == null || null == incidentAppealInformation) {
  51. throw new IllegalParameterException("incidentinformationID is not found in db. IncidentinformationID = " + incidentInformationId);
  52. }
  53.  
  54. String type = incidentInfo.getIncidentTypeId();
  55. type = StringUtils.substring(type, 0, 2);
  56. if (StringUtils.isEmpty(type)) {
  57. throw new IllegalParameterException("incidentinformation type is empty.");
  58. }
  59.  
  60. String lonStr = incidentInfo.getLongitude();
  61. String latStr = incidentInfo.getLatitude();
  62. GisUtils.checkLonLat(lonStr,latStr);
  63.  
  64. Date appealTime = incidentAppealInformation.getIncidentTime();
  65. Calendar cal = Calendar.getInstance();
  66. cal.setTime(appealTime);
  67. cal.add(Calendar.MINUTE, -1 * startTime);
  68. Date startDate = cal.getTime();
  69.  
  70. Calendar cal1 = Calendar.getInstance();
  71. cal1.setTime(appealTime);
  72. cal1.add(Calendar.MINUTE, 1 * endTime);
  73. cal1.add(Calendar.SECOND, 1);
  74. Date endDate = cal1.getTime();
  75. List<GisAccessAlarm> resultList = incidentInformationDao.queryAllIncidentInformation(incidentInformationId, type, startDate, endDate);
  76. if (null != resultList && CollectionUtils.isNotEmpty(resultList)) {
  77. resultList = gisCommonService.getCoordinatesWithinCircle(resultList, Double.valueOf(lonStr), Double.valueOf(latStr), radius.doubleValue());
  78. }
  79. JSONArray array = JsonUtils.toFormatDateJSONArray(resultList);
  80. 82 logger.info("array:{}",array);
  81. %>
  82. <body>
  83.  
  84. </body>
  85. </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,也就不能达到我们动态调试的效果了。

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

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

    前言 这篇其实是对一年前的一篇文章的补坑. @Java Web 程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码 当时,就是在spring mvc应用里定义一个 ...

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

    一.前言 这篇算是类加载器的实战第五篇,前面几篇在这里,后续会持续写这方面的一些东西. 实战分析Tomcat的类加载器结构(使用Eclipse MAT验证) 还是Tomcat,关于类加载器的趣味实验 ...

  3. java web程序员微信群

    关注微信公众号"程序员成长日志",回复关键字"java"扫码进群 本群主要为大家解决工作中遇到的问题遇到的问题发到群里大家集思广益平时可以瞎扯不定期红包

  4. Web程序员开发App系列 - 调试Android和IOS手机代码(补图)

    Web程序员开发App系列 Web程序员开发App系列 - 认识HBuilder Web程序员开发App系列 - 申请苹果开发者账号 Web程序员开发App系列 - 调试Android和iOS手机代码 ...

  5. [刘阳Java]_Java程序员的成长路线_第3讲

    按照Java从业人员的职位晋升来说,Java程序成长路线大致如下 Java程序员 JavaEE初级软件工程师 JavaEE中级软件工程师 JavaEE高级软件工程师 Java架构师 按照职业发展方向, ...

  6. 深圳尚学堂:Web程序员应该会的知识

    互联网的行业里涌入了很多的程序员, 都在为互联网的发展添砖加瓦.程序员可以分为很多种,像Unix程序员.Windows程序员,或是C++程序员.Delphi程序员,等等.今天我们谈谈Web程序员,一名 ...

  7. Web程序员必备的CSS工具

    对于web开发来说,CSS是最有效的美化页面.设置页面布局的技术.但问题是,CSS是一种标记性语言,语法结构非常的松散.不严谨.WEB程序员会经常发现自己的或别人的CSS文件里有大量的冗余代码或错误或 ...

  8. 科普,想成为厉害的 Java 后端程序员,你需要懂这 13 个知识点

    老读者就请肆无忌惮地点赞吧,微信搜索[沉默王二]关注这个在九朝古都洛阳苟且偷生的程序员.本文 GitHub github.com/itwanger 已收录,里面还有我精心为你准备的一线大厂面试题. 站 ...

  9. 科普,想成为厉害的 Java 后端程序员,你需要懂这些

    站在运筹帷幄的角度来看,一名厉害的 Java 后端程序员都需要懂得哪些知识呢?我想,这也是很多读者迫切想知道的一个问题,因为如果不站在一个宏观的角度的话,所有学过的知识点都是零散的,就感觉像一只迷路的 ...

随机推荐

  1. The Django Book - 第四章 模板2

    模板(相应)使用的几种方式: 1.使用HttpResponse返回字符串HTML from django.http import HttpResponse def current_datetime(r ...

  2. PCL点云处理可视化——法向显示错误“no override found for vtk actor”解决方法

    转:https://blog.csdn.net/bflong/article/details/79137692 参照:https://blog.csdn.net/imsaws/article/deta ...

  3. 安装vc++6.0的步骤

    我们学习计算机,就必须要先将编程的c语言学好,打好基础,学习c语言最好的方法就是多上机联系,对于联系我们需要在自己的电脑上安装vc++6.0来进行平日里的联系.1.打开电脑进行联网,打开浏览器搜索vc ...

  4. 浅谈js的sort()方法

    如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码(字符串Unicode码点)的顺序进行排序.要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以 ...

  5. eclips配置

    新建空workspace import... configMathod:main:project:eFT-Debug@eFTSlnC/C++ Aplication /media/B/testspa2. ...

  6. 关于Java IO流学习总结

    一.IO流的三种分类方式 1.按流的方向分为:输入流和输出流 2.按流的数据单位不同分为:字节流和字符流 3.按流的功能不同分为:节点流和处理流     二.IO流的四大抽象类: 字符流:Reader ...

  7. ubuntu下如何对接斗鱼直播

    参考教程:https://www.cnblogs.com/liuxuzzz/p/5315998.html 大神写得挺细的,这里都不想再多说了! 为啥要做这个呢?可能真的只是为了好玩吧!!有兴趣直播的孩 ...

  8. python爬虫入门三:requests库

    urllib库在很多时候都比较繁琐,比如处理Cookies.因此,我们选择学习另一个更为简单易用的HTTP库:Requests. requests官方文档 1. 什么是Requests Request ...

  9. Hive 启动报错 URI

    Exception in thread "main"java.lang.RuntimeException: java.lang.IllegalArgumentException:j ...

  10. CEO的智力财富第12期-《股权激励》学习笔记

    卷首语---你现在走的第一步,都藏着你未来的样子 今天,又去参加天使岛举办的系列讲座之股权激励,由律大大律师事务所李刚律师主讲,走在路上,我就在想,我为什么要来参加这样的活动呢?我的本职工作和股权没有 ...