淺談JSP是如何編譯成servlet并提供服務(wù)的
概述
服務(wù)端對(duì)外提供JSP請(qǐng)求服務(wù)的是JspServlet,繼承自HttpServlet。核心服務(wù)入口在service方法,大體流程如下:
- 首先獲取請(qǐng)求的jspUri,如果客戶端發(fā)起請(qǐng)求:https://xxx.xx.com/jsp/test.jsp,那么獲取到的jspUri為:/jsp/test.jsp
- 然后查看緩存(Map結(jié)構(gòu))中是否包含該jspUri的JspServletWrapper,如果沒有就需要?jiǎng)?chuàng)建一個(gè)JspServletWrapper并且緩存起來,并調(diào)用JspServletWrapper的service方法
- 如果為development模式,或者首次請(qǐng)求,那么就需要執(zhí)行JspCompilationContext.compile() 方法
- 在JspCompilationContext.compile方法中,會(huì)根據(jù)jsp文件的lastModified判斷文件是否已經(jīng)被更新(out dated),如果被更新過了,就需要?jiǎng)h除之前生成的相關(guān)文件,然后將jspLoader置空(后面需要加載的時(shí)候如果jspLoader為空,就會(huì)創(chuàng)建一個(gè)新的jspLoader),調(diào)用Compiler.compile方法生成servlet,設(shè)置reload為true(默認(rèn)為true),后面會(huì)根據(jù)reload參數(shù)判斷是否需要重新加載該servlet
- 調(diào)用JspServletWrapper.getServlet方法獲取最終提供服務(wù)的servlet,這個(gè)過程會(huì)根據(jù)reload參數(shù)看是否需要重載servlet,如果需要重載,那么就會(huì)獲取jspLoader實(shí)例化一個(gè)新的servlet(如果前面發(fā)現(xiàn)jsp文件過期,那么此時(shí)獲取的jspLoader為空,則會(huì)創(chuàng)建一個(gè)新的jspLoader),并且設(shè)置reload為false
- 調(diào)用servlet的service方法提供服務(wù),如果servlet實(shí)現(xiàn)了SingleThreadModel接口,那么會(huì)用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); } }
然后進(jìn)入JspServletWrapper.service方法(部分代碼):
//注意這個(gè)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(首次請(qǐng)求) 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ù)是否實(shí)現(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,后面就會(huì)創(chuàng)建一個(gè)新的JspLoader this.jspLoader = null; //根據(jù)jsp生成servlet的邏輯,實(shí)現(xiàn)主要有AntCompiler和JDTCompiler,默認(rèn)JDTCompiler this.jspCompiler.compile(); //設(shè)置reload為true,后面根據(jù)reload參數(shù)判斷是否需要重新加載 this.jsw.setReload(true); this.jsw.setCompilationException((JasperException) null); } } }
要注意對(duì)于isOutDated方法的判斷,并不是簡(jiǎn)單地每次請(qǐng)求都檢查jsp文件是否更新,而是有一個(gè)間隔時(shí)間,如果此次檢查更新的時(shí)間在上一次檢查更新+間隔時(shí)間之內(nèi),也就是沒有超過間隔時(shí)間,那么就不會(huì)去檢查jsp文件的更新。這就是我們說的jsp熱更新延時(shí)生效,isOutDated是Compiler的方法,如下(部分代碼):
public boolean isOutDated(boolean checkClass) { if (this.jsw != null && this.ctxt.getOptions().getModificationTestInterval() > 0) { //getModificationTestInterval就是檢查最短間隔時(shí)間,單位為秒 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編譯器負(fù)責(zé),Compiler是一個(gè)抽象類,apache-jsp中提供了兩種實(shí)現(xiàn):AntCompiler和JDTCompiler,默認(rèn)使用的編譯器為JDTCompiler。
最后回到JspServletWrapper.getServlet方法:
private volatile boolean reload = true; public Servlet getServlet() throws ServletException { //reload是被volatile修飾的一個(gè)boolean變量 //這里進(jìn)行雙重檢測(cè) if (this.reload) { synchronized (this) { if (this.reload) { //需要重載 this.destroy(); Servlet servlet; try { InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(this.config); //創(chuàng)建一個(gè)新的serlvet實(shí)例對(duì)象,注意這里的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; }
可以看到,方法中使用了雙重檢測(cè)機(jī)制判斷是否需要重載,reload參數(shù)由volatile修飾保證可見性。在創(chuàng)建新的servlet實(shí)例的時(shí)候,classLoader是通過JspCompilationContext.getJspLoader方法獲取的,看看這個(gè)方法的邏輯:
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的邏輯中,如果檢測(cè)到j(luò)sp文件被更新過(過期),那么jspLoader會(huì)被設(shè)置為null,此時(shí)就會(huì)創(chuàng)建一個(gè)新的jspLoader(JasperLoader),然后使用新的loader加載新的servlet,以完成jsp的熱更新,老的classloader在之后會(huì)被GC直接回收。
到此這篇關(guān)于淺談JSP是如何編譯成servlet并提供服務(wù)的的文章就介紹到這了,更多相關(guān)JSP編譯成servlet內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot+vue實(shí)現(xiàn)Token自動(dòng)續(xù)期(雙Token方案)
雙Token方案通過訪問令牌和刷新令牌提高用戶登錄安全性和體驗(yàn),訪問令牌有效期短,包含用戶信息,用于請(qǐng)求校驗(yàn),本文就來介紹一下springboot+vue實(shí)現(xiàn)Token自動(dòng)續(xù)期(雙Token方案),感興趣的可以了解一下2024-10-10Java使用Thumbnailator實(shí)現(xiàn)圖片處理功能
Thumbnailator是一個(gè)簡(jiǎn)單且功能強(qiáng)大的Java庫(kù),用于生成縮略圖和執(zhí)行其他圖片處理任務(wù),在這篇博客中,我們將介紹如何使用Thumbnailator進(jìn)行圖片的縮放、裁剪、旋轉(zhuǎn)等操作,需要的朋友可以參考下2024-07-07Java中的線程同步與ThreadLocal無鎖化線程封閉實(shí)現(xiàn)
這篇文章主要介紹了Java中的線程同步與ThreadLocal無鎖化線程封閉實(shí)現(xiàn),Synchronized關(guān)鍵字與ThreadLocal變量的使用是Java中線程控制的基礎(chǔ),需要的朋友可以參考下2016-03-03Java線程池隊(duì)列PriorityBlockingQueue原理分析
這篇文章主要介紹了Java線程池隊(duì)列PriorityBlockingQueue原理分析,PriorityBlockingQueue隊(duì)列是?JDK1.5?的時(shí)候出來的一個(gè)阻塞隊(duì)列,但是該隊(duì)列入隊(duì)的時(shí)候是不會(huì)阻塞的,永遠(yuǎn)會(huì)加到隊(duì)尾,需要的朋友可以參考下2023-12-12spring學(xué)習(xí)之util:properties的使用
這篇文章主要介紹了spring學(xué)習(xí)之util:properties的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01Java數(shù)據(jù)結(jié)構(gòu)之集合框架與常用算法詳解
Java集合框架是Java中常用的數(shù)據(jù)結(jié)構(gòu)庫(kù),包括List、Set、Map等多種數(shù)據(jù)結(jié)構(gòu),支持快速的元素添加、刪除、查找等操作,可以用于解決各種實(shí)際問題。Java中也有多種常用算法,如排序、查找、遞歸等,在數(shù)據(jù)處理和分析中有廣泛應(yīng)用2023-04-04java swing 實(shí)現(xiàn)加載自定義的字體
這篇文章主要介紹了java swing 實(shí)現(xiàn)加載自定義的字體,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Spring Data + Thymeleaf 3 + Bo
本篇文章主要介紹了Spring Data + Thymeleaf 3 + Bootstrap 4 實(shí)現(xiàn)分頁(yè)器實(shí)例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05