深入理解Tomcat系列之六 Servlet工作原理
前言
Servlet是Web開發中的核心技術,作為一名合格的開發人員,就必須清楚Servlet的工作原理。本章沒有對Servlet技術本身進行詳細的說明,只是針對開發過程中一次Servlet的請求的處理過程進行分析的。Servlet實際上就是一個java類,只不過可以和瀏覽器進行一些資料的交換。有Servlet類就有管理Servlet的容器,種類有很多,這裡主要針對Tomcat對Servlet的工作原理進行說明。為了說清楚Servlet工作原理,需要知道Servlet的工作過程大致可以分為以下幾個階段: 1. 啟動Tomcat容器 2. Web應用初始化 3. 建立Servlet例項 4. 初始化Servlet 5. 執行Servlet的service方法
Tomcat的啟動過程
這部分可以用下圖進行簡化:
Web應用初始化
下面分析Web應用的初始化,初始化工作是由ContextConfig類的configureStart方法完成的,該方法的主要任務是完成web.xml配置檔案的解析。下面解析的關鍵程式碼:
程式碼清單5-1:
Set<WebXml> defaults = new HashSet<WebXml>(); defaults.add(getDefaultWebXmlFragment()); WebXml webXml = createWebXml(); // Parse context level web.xml InputSource contextWebXml = getContextWebXmlSource(); parseWebXml(contextWebXml, webXml, false); ServletContext sContext = context.getServletContext(); // Ordering is important here // Step 1. Identify all the JARs packaged with the application // If the JARs have a web-fragment.xml it will be parsed at this // point. Map<String,WebXml> fragments = processJarsForWebFragments(webXml); // Step 2. Order the fragments. Set<WebXml> orderedFragments = null; orderedFragments = WebXml.orderWebFragments(webXml, fragments, sContext); // Step 3. Look for ServletContainerInitializer implementations if (ok) { processServletContainerInitializers(context.getServletContext()); } if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) { // Step 4. Process /WEB-INF/classes for annotations if (ok) { // Hack required by Eclipse's "serve modules without // publishing" feature since this backs WEB-INF/classes by // multiple locations rather than one. NamingEnumeration<Binding> listBindings = null; try { try { listBindings = context.getResources().listBindings( "/WEB-INF/classes"); } catch (NameNotFoundException ignore) { // Safe to ignore } while (listBindings != null && listBindings.hasMoreElements()) { Binding binding = listBindings.nextElement(); if (binding.getObject() instanceof FileDirContext) { File webInfClassDir = new File( ((FileDirContext) binding.getObject()).getDocBase()); processAnnotationsFile(webInfClassDir, webXml, webXml.isMetadataComplete()); } else { String resource = "/WEB-INF/classes/" + binding.getName(); try { URL url = sContext.getResource(resource); processAnnotationsUrl(url, webXml, webXml.isMetadataComplete()); } catch (MalformedURLException e) { log.error(sm.getString( "contextConfig.webinfClassesUrl", resource), e); } } } } catch (NamingException e) { log.error(sm.getString( "contextConfig.webinfClassesUrl", "/WEB-INF/classes"), e); } } // Step 5. Process JARs for annotations - only need to process // those fragments we are going to use if (ok) { processAnnotations( orderedFragments, webXml.isMetadataComplete()); } // Cache, if used, is no longer required so clear it javaClassCache.clear(); } if (!webXml.isMetadataComplete()) { // Step 6. Merge web-fragment.xml files into the main web.xml // file. if (ok) { ok = webXml.merge(orderedFragments); } // Step 7. Apply global defaults // Have to merge defaults before JSP conversion since defaults // provide JSP servlet definition. webXml.merge(defaults); // Step 8. Convert explicitly mentioned jsps to servlets if (ok) { convertJsps(webXml); } // Step 9. Apply merged web.xml to Context if (ok) { webXml.configureContext(context); } } else { webXml.merge(defaults); convertJsps(webXml); webXml.configureContext(context); } // Step 9a. Make the merged web.xml available to other // components, specifically Jasper, to save those components // from having to re-generate it. // TODO Use a ServletContainerInitializer for Jasper String mergedWebXml = webXml.toXml(); sContext.setAttribute( org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML, mergedWebXml); if (context.getLogEffectiveWebXml()) { log.info("web.xml:\n" + mergedWebXml); } // Always need to look for static resources // Step 10. Look for static resources packaged in JARs if (ok) { // Spec does not define an order. // Use ordered JARs followed by remaining JARs Set<WebXml> resourceJars = new LinkedHashSet<WebXml>(); if (orderedFragments != null) { for (WebXml fragment : orderedFragments) { resourceJars.add(fragment); } } for (WebXml fragment : fragments.values()) { if (!resourceJars.contains(fragment)) { resourceJars.add(fragment); } } processResourceJARs(resourceJars); // See also StandardContext.resourcesStart() for // WEB-INF/classes/META-INF/resources configuration } // Step 11. Apply the ServletContainerInitializer config to the // context if (ok) { for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializerClassMap.entrySet()) { if (entry.getValue().isEmpty()) { context.addServletContainerInitializer( entry.getKey(), null); } else { context.addServletContainerInitializer( entry.getKey(), entry.getValue()); } } }
- 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
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
在程式碼已經清晰地說明了Web應用的初始化過程,由於在Tomcat7中增加了對註解(annotation)的支援,所以會對Servlet中的註解進行解析。首先查詢jar包中的web-fragment.xml,並對其進行解析,接下來將對/WEB-INF/classes目錄下的class進行註解的解析。之後,把web-fragment.xml檔案合併到web.xml中,被解析後的web.xml檔案的配置項將儲存到WebXml物件中,然後把WebXml物件中的屬性設定到Context容器中,這個過程是由configureContext方法來完成的。下面是其部分原始碼——完成Servlet的解析:
程式碼清單5-2:
for (ServletDef servlet : servlets.values()) { Wrapper wrapper = context.createWrapper(); // Description is ignored // Display name is ignored // Icons are ignored // jsp-file gets passed to the JSP Servlet as an init-param if (servlet.getLoadOnStartup() != null) { wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); } if (servlet.getEnabled() != null) { wrapper.setEnabled(servlet.getEnabled().booleanValue()); } wrapper.setName(servlet.getServletName()); Map<String,String> params = servlet.getParameterMap(); for (Entry<String, String> entry : params.entrySet()) { wrapper.addInitParameter(entry.getKey(), entry.getValue()); } wrapper.setRunAs(servlet.getRunAs()); Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); for (SecurityRoleRef roleRef : roleRefs) { wrapper.addSecurityReference( roleRef.getName(), roleRef.getLink()); } wrapper.setServletClass(servlet.getServletClass()); MultipartDef multipartdef = servlet.getMultipartDef(); if (multipartdef != null) { if (multipartdef.getMaxFileSize() != null && multipartdef.getMaxRequestSize()!= null && multipartdef.getFileSizeThreshold() != null) { wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation(), Long.parseLong(multipartdef.getMaxFileSize()), Long.parseLong(multipartdef.getMaxRequestSize()), Integer.parseInt( multipartdef.getFileSizeThreshold()))); } else { wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation())); } } if (servlet.getAsyncSupported() != null) { wrapper.setAsyncSupported( servlet.getAsyncSupported().booleanValue()); } wrapper.setOverridable(servlet.isOverridable()); context.addChild(wrapper); }
- 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
這段程式碼說明了把Servlet包裝成StandardWrapper的過程,並把這個Wrapper例項設定到Context容器中。之所以要把Servlet封裝成一個Wrapper,主要為了解耦,因為Wrapper是一個容器而Servlet是一個具體的類。
到目前為止,已經完成了web應用的初始化,其中將web.xml進行了解析並完成了註解的解析,還把配置的Servlet類封裝成了一個Wrapper容器,接下來就是根據這個Wrapper建立Servlet例項了。
建立Servlet例項 建立Servlet例項要回到我們分析Wrapper容器中提到的loadServlet方法了,這個方法是由Wrapper容器的標準實現類StandardWrapper完成的,通過loadServlet方法獲取servletClass,然後把這個servletClass交給例項管理器(InstanceManager)完成servletClass.class的物件建立。
初始化Servlet 初始化的工作是由StandardWrapper的initServlet方法完成的,這個方法主要就是呼叫Servlet的init方法,然後把包裝了StandardWrapper的StandardWrapperFacade交給Servlet。下面是這個方法的原始碼:
程式碼清單5-3:
private synchronized void initServlet(Servlet servlet) throws ServletException { if (instanceInitialized && !singleThreadModel) return; // Call the initialization method of this servlet try { instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT, servlet); if( Globals.IS_SECURITY_ENABLED) { boolean success = false; try { Object[] args = new Object[] { facade }; SecurityUtil.doAsPrivilege("init", servlet, classType, args); success = true; } finally { if (!success) { // destroy() will not be called, thus clear the reference now SecurityUtil.remove(servlet); } } } else { servlet.init(facade); } instanceInitialized = true; instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet); } catch (UnavailableException f) { instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet, f); unavailable(f); throw f; } catch (ServletException f) { instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet, f); // If the servlet wanted to be unavailable it would have // said so, so do not call unavailable(null). throw f; } catch (Throwable f) { ExceptionUtils.handleThrowable(f); getServletContext().log("StandardWrapper.Throwable", f ); instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet, f); // If the servlet wanted to be unavailable it would have // said so, so do not call unavailable(null). throw new ServletException (sm.getString("standardWrapper.initException", getName()), f); } }
- 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
這樣就完成了Servlet的初始化,下面就是執行Servlet的service方法了。
執行service方法
在分析Wrapper方法中提到,StandardWrapper會呼叫allocate方法從例項池棧中彈出一個Servlet處理請求,這個Servlet例項在完成初始化後,並經過一系列過濾器的過濾後就到達Servlet例項的service方法,這個過程就是過濾器執行的過程。這樣Servlet例項就順利呼叫到了service方法,之後發生的過程就是我們熟悉的request獲取引數並用response進行響應的過程了。