@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,也就不能達到我們動態除錯的效果了。