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


    一、前言

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

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

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

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

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

     

    下面会给出详细代码,这个空指针不是那么好一眼看出来,不过最后,该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,也就不能达到我们动态调试的效果了。

     

  • 相关阅读:
    Eclipse快速上手指南之使用CVS
    数据备份的13种最佳做法
    用asp.net来回收IIS6.0应用程序池
    构建插件式的应用程序框架(五)-管理插件
    安全组织评出25个最危险软件编程错误
    在ASP.NET里轻松实现缩略图
    构建插件式的应用程序框架(六)-通讯机制
    快照复制,SQL Server保障数据一致性的法宝
    DataGridView新特色、常用操作
    如何在GridView中一次性批量更新多行数据
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/11052495.html
Copyright © 2020-2023  润新知