淺談JSP是如何編譯成servlet并提供服務(wù)的
概述
服務(wù)端對外提供JSP請求服務(wù)的是JspServlet,繼承自HttpServlet。核心服務(wù)入口在service方法,大體流程如下:
- 首先獲取請求的jspUri,如果客戶端發(fā)起請求:https://xxx.xx.com/jsp/test.jsp,那么獲取到的jspUri為:/jsp/test.jsp
- 然后查看緩存(Map結(jié)構(gòu))中是否包含該jspUri的JspServletWrapper,如果沒有就需要創(chuàng)建一個JspServletWrapper并且緩存起來,并調(diào)用JspServletWrapper的service方法
- 如果為development模式,或者首次請求,那么就需要執(zhí)行JspCompilationContext.compile() 方法
- 在JspCompilationContext.compile方法中,會根據(jù)jsp文件的lastModified判斷文件是否已經(jīng)被更新(out dated),如果被更新過了,就需要刪除之前生成的相關(guān)文件,然后將jspLoader置空(后面需要加載的時候如果jspLoader為空,就會創(chuàng)建一個新的jspLoader),調(diào)用Compiler.compile方法生成servlet,設(shè)置reload為true(默認為true),后面會根據(jù)reload參數(shù)判斷是否需要重新加載該servlet
- 調(diào)用JspServletWrapper.getServlet方法獲取最終提供服務(wù)的servlet,這個過程會根據(jù)reload參數(shù)看是否需要重載servlet,如果需要重載,那么就會獲取jspLoader實例化一個新的servlet(如果前面發(fā)現(xiàn)jsp文件過期,那么此時獲取的jspLoader為空,則會創(chuàng)建一個新的jspLoader),并且設(shè)置reload為false
- 調(diào)用servlet的service方法提供服務(wù),如果servlet實現(xiàn)了SingleThreadModel接口,那么會用synchronized做同步控制
源碼分析
首先看JspServlet的核心邏輯,主要是獲取jspUri和獲取JspServletWrapper,分別是入口service方法和serviceJspFile方法,代碼如下(部分):
/** * 獲取jspUri,然后調(diào)用serviceJspFile方法 */ public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String jspUri = this.jspFile; String pathInfo; if (jspUri == null) { pathInfo = (String)request.getAttribute(Constants.JSP_FILE); if (pathInfo != null) { jspUri = pathInfo; request.removeAttribute(Constants.JSP_FILE); } } if (jspUri == null) { jspUri = (String)request.getAttribute("javax.servlet.include.servlet_path"); if (jspUri != null) { pathInfo = (String)request.getAttribute("javax.servlet.include.path_info"); if (pathInfo != null) { jspUri = jspUri + pathInfo; } } else { jspUri = request.getServletPath(); pathInfo = request.getPathInfo(); if (pathInfo != null) { jspUri = jspUri + pathInfo; } } } boolean precompile = this.preCompile(request); this.serviceJspFile(request, response, jspUri, precompile); } /** * 主要獲取JspServletWrapper,然后調(diào)用JspServletWrapper.service方法 */ private void serviceJspFile(HttpServletRequest request, HttpServletResponse response, String jspUri, boolean precompile) throws ServletException, IOException { JspServletWrapper wrapper = this.rctxt.getWrapper(jspUri); if (wrapper == null) { synchronized(this) { wrapper = this.rctxt.getWrapper(jspUri); if (wrapper == null) { if (null == this.context.getResource(jspUri)) { this.handleMissingResource(request, response, jspUri); return; } wrapper = new JspServletWrapper(this.config, this.options, jspUri, this.rctxt); this.rctxt.addWrapper(jspUri, wrapper); } } } try { //核心服務(wù)方法 wrapper.service(request, response, precompile); } catch (FileNotFoundException var8) { this.handleMissingResource(request, response, jspUri); } }
然后進入JspServletWrapper.service方法(部分代碼):
//注意這個reload是volatile修飾的 private volatile boolean reload = true; public void service(HttpServletRequest request, HttpServletResponse response, boolean precompile) throws ServletException, IOException, FileNotFoundException { Servlet servlet; try { if (this.ctxt.isRemoved()) { throw new FileNotFoundException(this.jspUri); } //判斷development模式和firstTime(首次請求) if (!this.options.getDevelopment() && !this.firstTime) { if (this.compileException != null) { throw this.compileException; } } else { synchronized (this) { this.firstTime = false; //調(diào)用JspCompilationContext.compile方法 this.ctxt.compile(); } } //獲取最終提供服務(wù)的servlet servlet = this.getServlet(); if (precompile) { return; } } try { //根據(jù)是否實現(xiàn)SingleThreadModel決定是否需要做同步控制 if (servlet instanceof SingleThreadModel) { synchronized (this) { servlet.service(request, response); } } else { servlet.service(request, response); } } }
這里主要看JspCompilationContext.complie方法:
public void compile() throws JasperException, FileNotFoundException { this.createCompiler(); if (this.jspCompiler.isOutDated()) { if (this.isRemoved()) { throw new FileNotFoundException(this.jspUri); } try { //清楚文件數(shù)據(jù) this.jspCompiler.removeGeneratedFiles(); //置空jspLoader,現(xiàn)在置null,后面就會創(chuàng)建一個新的JspLoader this.jspLoader = null; //根據(jù)jsp生成servlet的邏輯,實現(xiàn)主要有AntCompiler和JDTCompiler,默認JDTCompiler this.jspCompiler.compile(); //設(shè)置reload為true,后面根據(jù)reload參數(shù)判斷是否需要重新加載 this.jsw.setReload(true); this.jsw.setCompilationException((JasperException) null); } } }
要注意對于isOutDated方法的判斷,并不是簡單地每次請求都檢查jsp文件是否更新,而是有一個間隔時間,如果此次檢查更新的時間在上一次檢查更新+間隔時間之內(nèi),也就是沒有超過間隔時間,那么就不會去檢查jsp文件的更新。這就是我們說的jsp熱更新延時生效,isOutDated是Compiler的方法,如下(部分代碼):
public boolean isOutDated(boolean checkClass) { if (this.jsw != null && this.ctxt.getOptions().getModificationTestInterval() > 0) { //getModificationTestInterval就是檢查最短間隔時間,單位為秒 if (this.jsw.getLastModificationTest() + (long)(this.ctxt.getOptions().getModificationTestInterval() * 1000) > System.currentTimeMillis()) { return false; } this.jsw.setLastModificationTest(System.currentTimeMillis()); } Long jspRealLastModified = this.ctxt.getLastModified(this.ctxt.getJspFile()); if (jspRealLastModified < 0L) { return true; } else { long targetLastModified = 0L; File targetFile; if (checkClass) { targetFile = new File(this.ctxt.getClassFileName()); } else { targetFile = new File(this.ctxt.getServletJavaFileName()); } if (!targetFile.exists()) { return true; } else { targetLastModified = targetFile.lastModified(); if (checkClass && this.jsw != null) { this.jsw.setServletClassLastModifiedTime(targetLastModified); } if (targetLastModified != jspRealLastModified) { if (this.log.isDebugEnabled()) { this.log.debug("Compiler: outdated: " + targetFile + " " + targetLastModified); } return true; } else if (this.jsw == null) { return false; } } }
另外,這里還涉及到JSP的編譯工作,編譯工作主要由org.apache.jasper.compiler.Compiler編譯器負責,Compiler是一個抽象類,apache-jsp中提供了兩種實現(xiàn):AntCompiler和JDTCompiler,默認使用的編譯器為JDTCompiler。
最后回到JspServletWrapper.getServlet方法:
private volatile boolean reload = true; public Servlet getServlet() throws ServletException { //reload是被volatile修飾的一個boolean變量 //這里進行雙重檢測 if (this.reload) { synchronized (this) { if (this.reload) { //需要重載 this.destroy(); Servlet servlet; try { InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(this.config); //創(chuàng)建一個新的serlvet實例對象,注意這里的getJspLoader方法 servlet = (Servlet) instanceManager.newInstance(this.ctxt.getFQCN(), this.ctxt.getJspLoader()); } catch (Exception var6) { Throwable t = ExceptionUtils.unwrapInvocationTargetException(var6); ExceptionUtils.handleThrowable(t); throw new JasperException(t); } servlet.init(this.config); if (!this.firstTime) { this.ctxt.getRuntimeContext().incrementJspReloadCount(); } this.theServlet = servlet; this.reload = false; } } } return this.theServlet; }
可以看到,方法中使用了雙重檢測機制判斷是否需要重載,reload參數(shù)由volatile修飾保證可見性。在創(chuàng)建新的servlet實例的時候,classLoader是通過JspCompilationContext.getJspLoader方法獲取的,看看這個方法的邏輯:
public ClassLoader getJspLoader() { if (this.jspLoader == null) { this.jspLoader = new JasperLoader(new URL[]{this.baseUrl}, this.getClassLoader(), this.rctxt.getPermissionCollection()); } return this.jspLoader; }
在前面JspCompilationContext.complie的邏輯中,如果檢測到j(luò)sp文件被更新過(過期),那么jspLoader會被設(shè)置為null,此時就會創(chuàng)建一個新的jspLoader(JasperLoader),然后使用新的loader加載新的servlet,以完成jsp的熱更新,老的classloader在之后會被GC直接回收。
到此這篇關(guān)于淺談JSP是如何編譯成servlet并提供服務(wù)的的文章就介紹到這了,更多相關(guān)JSP編譯成servlet內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot+vue實現(xiàn)Token自動續(xù)期(雙Token方案)
雙Token方案通過訪問令牌和刷新令牌提高用戶登錄安全性和體驗,訪問令牌有效期短,包含用戶信息,用于請求校驗,本文就來介紹一下springboot+vue實現(xiàn)Token自動續(xù)期(雙Token方案),感興趣的可以了解一下2024-10-10Java使用Thumbnailator實現(xiàn)圖片處理功能
Thumbnailator是一個簡單且功能強大的Java庫,用于生成縮略圖和執(zhí)行其他圖片處理任務(wù),在這篇博客中,我們將介紹如何使用Thumbnailator進行圖片的縮放、裁剪、旋轉(zhuǎn)等操作,需要的朋友可以參考下2024-07-07Java中的線程同步與ThreadLocal無鎖化線程封閉實現(xiàn)
這篇文章主要介紹了Java中的線程同步與ThreadLocal無鎖化線程封閉實現(xiàn),Synchronized關(guān)鍵字與ThreadLocal變量的使用是Java中線程控制的基礎(chǔ),需要的朋友可以參考下2016-03-03Java線程池隊列PriorityBlockingQueue原理分析
這篇文章主要介紹了Java線程池隊列PriorityBlockingQueue原理分析,PriorityBlockingQueue隊列是?JDK1.5?的時候出來的一個阻塞隊列,但是該隊列入隊的時候是不會阻塞的,永遠會加到隊尾,需要的朋友可以參考下2023-12-12spring學(xué)習(xí)之util:properties的使用
這篇文章主要介紹了spring學(xué)習(xí)之util:properties的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01Java數(shù)據(jù)結(jié)構(gòu)之集合框架與常用算法詳解
Java集合框架是Java中常用的數(shù)據(jù)結(jié)構(gòu)庫,包括List、Set、Map等多種數(shù)據(jù)結(jié)構(gòu),支持快速的元素添加、刪除、查找等操作,可以用于解決各種實際問題。Java中也有多種常用算法,如排序、查找、遞歸等,在數(shù)據(jù)處理和分析中有廣泛應(yīng)用2023-04-04Spring Data + Thymeleaf 3 + Bo
本篇文章主要介紹了Spring Data + Thymeleaf 3 + Bootstrap 4 實現(xiàn)分頁器實例代碼,非常具有實用價值,需要的朋友可以參考下2017-05-05