概述
服務端對外提供JSP請求服務的是JspServlet,繼承自HttpServlet。核心服務入口在service方法,大體流程如下:
- 首先獲取請求的jspUri,如果客戶端發起請求:https://xxx.xx.com/jsp/test.jsp,那么獲取到的jspUri為:/jsp/test.jsp
- 然后查看緩存(Map結構)中是否包含該jspUri的JspServletWrapper,如果沒有就需要創建一個JspServletWrapper并且緩存起來,并調用JspServletWrapper的service方法
- 如果為development模式,或者首次請求,那么就需要執行JspCompilationContext.compile() 方法
- 在JspCompilationContext.compile方法中,會根據jsp文件的lastModified判斷文件是否已經被更新(out dated),如果被更新過了,就需要刪除之前生成的相關文件,然后將jspLoader置空(后面需要加載的時候如果jspLoader為空,就會創建一個新的jspLoader),調用Compiler.compile方法生成servlet,設置reload為true(默認為true),后面會根據reload參數判斷是否需要重新加載該servlet
- 調用JspServletWrapper.getServlet方法獲取最終提供服務的servlet,這個過程會根據reload參數看是否需要重載servlet,如果需要重載,那么就會獲取jspLoader實例化一個新的servlet(如果前面發現jsp文件過期,那么此時獲取的jspLoader為空,則會創建一個新的jspLoader),并且設置reload為false
- 調用servlet的service方法提供服務,如果servlet實現了SingleThreadModel接口,那么會用synchronized做同步控制
源碼分析
首先看JspServlet的核心邏輯,主要是獲取jspUri和獲取JspServletWrapper,分別是入口service方法和serviceJspFile方法,代碼如下(部分):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
/** * 獲取jspUri,然后調用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,然后調用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 { //核心服務方法 wrapper.service(request, response, precompile); } catch (FileNotFoundException var8) { this .handleMissingResource(request, response, jspUri); } } |
然后進入JspServletWrapper.service方法(部分代碼):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
//注意這個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 ; //調用JspCompilationContext.compile方法 this .ctxt.compile(); } } //獲取最終提供服務的servlet servlet = this .getServlet(); if (precompile) { return ; } } try { //根據是否實現SingleThreadModel決定是否需要做同步控制 if (servlet instanceof SingleThreadModel) { synchronized ( this ) { servlet.service(request, response); } } else { servlet.service(request, response); } } } |
這里主要看JspCompilationContext.complie方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public void compile() throws JasperException, FileNotFoundException { this .createCompiler(); if ( this .jspCompiler.isOutDated()) { if ( this .isRemoved()) { throw new FileNotFoundException( this .jspUri); } try { //清楚文件數據 this .jspCompiler.removeGeneratedFiles(); //置空jspLoader,現在置null,后面就會創建一個新的JspLoader this .jspLoader = null ; //根據jsp生成servlet的邏輯,實現主要有AntCompiler和JDTCompiler,默認JDTCompiler this .jspCompiler.compile(); //設置reload為true,后面根據reload參數判斷是否需要重新加載 this .jsw.setReload( true ); this .jsw.setCompilationException((JasperException) null ); } } } |
要注意對于isOutDated方法的判斷,并不是簡單地每次請求都檢查jsp文件是否更新,而是有一個間隔時間,如果此次檢查更新的時間在上一次檢查更新+間隔時間之內,也就是沒有超過間隔時間,那么就不會去檢查jsp文件的更新。這就是我們說的jsp熱更新延時生效,isOutDated是Compiler的方法,如下(部分代碼):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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中提供了兩種實現:AntCompiler和JDTCompiler,默認使用的編譯器為JDTCompiler。
最后回到JspServletWrapper.getServlet方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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); //創建一個新的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參數由volatile修飾保證可見性。在創建新的servlet實例的時候,classLoader是通過JspCompilationContext.getJspLoader方法獲取的,看看這個方法的邏輯:
1
2
3
4
5
6
7
|
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的邏輯中,如果檢測到jsp文件被更新過(過期),那么jspLoader會被設置為null,此時就會創建一個新的jspLoader(JasperLoader),然后使用新的loader加載新的servlet,以完成jsp的熱更新,老的classloader在之后會被GC直接回收。
到此這篇關于淺談JSP是如何編譯成servlet并提供服務的的文章就介紹到這了,更多相關JSP編譯成servlet內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/huangzhilin2015/article/details/114893129