淺談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,如果沒(méi)有就需要?jiǎng)?chuàng)建一個(gè)JspServletWrapper并且緩存起來(lái),并調(diào)用JspServletWrapper的service方法
- 如果為development模式,或者首次請(qǐng)求,那么就需要執(zhí)行JspCompilationContext.compile() 方法
- 在JspCompilationContext.compile方法中,會(huì)根據(jù)jsp文件的lastModified判斷文件是否已經(jīng)被更新(out dated),如果被更新過(guò)了,就需要?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è)過(guò)程會(huì)根據(jù)reload參數(shù)看是否需要重載servlet,如果需要重載,那么就會(huì)獲取jspLoader實(shí)例化一個(gè)新的servlet(如果前面發(fā)現(xiàn)jsp文件過(guò)期,那么此時(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),也就是沒(méi)有超過(guò)間隔時(shí)間,那么就不會(huì)去檢查jsp文件的更新。這就是我們說(shuō)的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修飾保證可見(jiàn)性。在創(chuàng)建新的servlet實(shí)例的時(shí)候,classLoader是通過(guò)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文件被更新過(guò)(過(guò)期),那么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中的@CacheEvict 注解的實(shí)現(xiàn)
本文主要介紹了SpringBoot中的@CacheEvict注解的實(shí)現(xiàn),@CacheEvict 注解用于清空緩存,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-03-03
使用EasyExcel實(shí)現(xiàn)簡(jiǎn)單的Excel表格解析操作
這篇文章主要介紹了如何使用EasyExcel完成簡(jiǎn)單的表格解析操作,同時(shí)實(shí)現(xiàn)了大量數(shù)據(jù)情況下數(shù)據(jù)的分次批量入庫(kù),并記錄每條數(shù)據(jù)入庫(kù)的狀態(tài),感興趣的可以了解下2025-03-03
如何實(shí)現(xiàn)Java監(jiān)聽(tīng)器詳解
今天帶大家了解Java監(jiān)聽(tīng)器是如何實(shí)現(xiàn)的及實(shí)現(xiàn)原理是什么,文中有非常詳細(xì)的說(shuō)明,對(duì)正在學(xué)習(xí)的小伙伴們很有幫助,需要的朋友可以參考下2021-06-06
SpringBoot對(duì)Filter過(guò)濾器中的異常進(jìn)行全局處理方案詳解
這篇文章主要介紹了SpringBoot對(duì)Filter過(guò)濾器中的異常進(jìn)行全局處理,在SpringBoot中我們通過(guò) @ControllerAdvice 注解和 @ExceptionHandler注解注冊(cè)了全局異常處理器,需要的朋友可以參考下2023-09-09
java實(shí)現(xiàn)emqx設(shè)備上下線監(jiān)聽(tīng)詳解
這篇文章主要為大家介紹了java實(shí)現(xiàn)emqx設(shè)備上下線監(jiān)聽(tīng)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Maven的配置文件pom.xml詳解(含常用plugin)
pom.xml是Maven項(xiàng)目的核心配置文件,它是 項(xiàng)目對(duì)象模型 - Project Object Model(POM)的縮寫(xiě),本文我們將全面解析pom.xml,了解其結(jié)構(gòu)和屬性,以及如何使用它來(lái)管理項(xiàng)目,感興趣的朋友跟隨小編一起看看吧2024-08-08

