@Java web程序員,在保留現場,服務不重啓的情況下,執行我們的調試代碼(JSP 方式)

一、前言

類加載器實戰系列的第六篇(悄悄跟你說,這篇比較水),前面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>
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,也就不能達到我們動態調試的效果了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章