您好,登錄后才能下訂單哦!
熟悉java web開發的同學都清楚,tomcat作為一款非常流行的servlet容器,開源,流行,配置簡單,不需要贅述。個人認為,web.xml作為webapp的入口,弄清楚該文件的底層解析過程,進而可以窺探tomcat的底層工作機制,搞明白tomcat對servlert規范的實現機理。
通過本文,可以知道以下部分內容
webapp部署3種部署方式
webapp web.xml解析流程
webapp Context對象信息的生成(不包括對象的生成)
總體來說,webapp部署有三種方式:XML文件描述符、WAR包、文件目錄。三種方式部署的總體流程很相似,都是一個web應用分配一個線程來處理,這里統一放到與Host內部的線程池對象中(startStopExecutor),所以有時會看到在默認配置下Tomcat啟動后可能有一個叫“-startStop-”的線程還會運行一段時間才結束。但瀏覽這三種部署方式的實現代碼,里面都是構建一個Context對象,并將構建好的Context對象與Host組件關聯起來。
1.三種部署方式
/** * 部署應用,該方法被org.apache.catalina.startup.HostConfig.start()調用 * 包含3種部署方式,每個部署方式分別使用內部類,分配一個線程處理 */ protected void deployApps() { File appBase = appBase(); File configBase = configBase(); String[] filteredAppPaths = filterAppPaths(appBase.list()); // Deploy XML descriptors from configBase deployDescriptors(configBase, configBase.list()); // Deploy WARs deployWARs(appBase, filteredAppPaths); // Deploy expanded folders deployDirectories(appBase, filteredAppPaths); }
3個部署的內部類
org.apache.catalina.startup.HostConfig.DeployWar
org.apache.catalina.startup.HostConfig.DeployDescriptor
org.apache.catalina.startup.HostConfig.DeployDirectory
2.從部署webapp到解析web.xml序列圖
找了很多網上資料,對從部署webapp開始,到開始解析web.xml這一段處理過程,沒找到相關資料。個人就花時間整理出這個序列圖,填補下這方面的空缺。通過該圖,可以很清楚的知道,這部分主要完成了2件事:
(1)啟動StandardContext,并將context對象添加到StandardHost對象中。
(2)通過觸發事件機制,開始Context的解析過程。
3.web.xml解析過程
接第二步驟中序列圖,開始分析web.xml的解析過程。從ContextConfig開始。
3.1 org.apache.catalina.startup.ContextConfig.configureStart()
/** * Process a "contextConfig" event for this Context. */ protected synchronized void configureStart() { // Called from StandardContext.start() webConfig(); if (ok) { validateSecurityRoles(); } // Configure an authenticator if we need one if (ok) authenticatorConfig(); // Make our application available if no problems were encountered if (ok) context.setConfigured(true); else { log.error(sm.getString("contextConfig.unavailable")); context.setConfigured(false); } }
該方法通過調用webConfig(),具體完成解析工作,此外完成了安全驗證相關內容。
3.2 org.apache.catalina.startup.ContextConfig.webConfig()方法
/** * Scan the web.xml files that apply to the web application and merge them * using the rules defined in the spec. For the global web.xml files, * where there is duplicate configuration, the most specific level wins. ie * an application's web.xml takes precedence over the host level or global * web.xml file. */ protected void webConfig() { 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(); } // 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(); } // Step 6. Merge web-fragment.xml files into the main web.xml // Step 7. Apply global defaults webXml.merge(defaults); // Step 8. Convert explicitly mentioned jsps to servlets convertJsps(webXml); // Step 9. Apply merged web.xml to Context webXml.configureContext(context); // Step 9a. Make the merged web.xml available to other // components, // Always need to look for static resources // Step 10. Look for static resources packaged in JARs proce***esourceJARs(resourceJars); // See also StandardContext.resourcesStart() for // WEB-INF/classes/META-INF/resources configuration // Step 11. Apply the ServletContainerInitializer config to the // context context.addServletContainerInitializer( entry.getKey(), entry.getValue()); }
通過源碼中的注釋,step1 到step11。主要工作包含:1.解析xml,2.合并xml,3.組裝Context,4.編譯JSP。具體步驟,參考說明
掃描應用打包的所有Jar來檢索Jar包里面的web.xml配置并解析,放入內存。
對這些已經檢索到的web配置進行排序。
基于SPI機制查找ServletContainerInitializer的實現,寫web中間件的同學注意了,了解SPI以及 ServletContainerInitializer機制這對于你來說可能是一個很好的知識點。
處理/WEB-INF/classes下面的類的注解,某個版本Servlet支持注解方式的配置,可以猜測相關事宜就是在這里干的。
處理Jar包中的注解類。
將web配置按照一定規則合并到一起。
應用全局默認配置,還記得Tomcat包下面的conf文件夾下面有個web.xml配置文件吧。
將JSP轉換為Servlet,這讓我想起了若干年前對JSP的理解。
將web配置應用到Servlet上下文,也即Servlet容器。
將配置信息保存起來以供其他組件訪問,使得其他組件不需要再次重復上面的步驟去獲取配置信息了。
檢索Jar包中的靜態資源。
將ServletContainerInitializer配置到上下文。
3.3 org.apache.catalina.startup.ContextConfig.parseWebXml方法
/** * Parses the given source and stores the parsed data in the given web.xml * representation. The byte stream will be closed at the end of the parse * operation. * * @param source Input source containing the XML data to be parsed * @param dest The object representation of common elements of web.xml and * web-fragment.xml * @param fragment Specifies whether the source is web-fragment.xml or * web.xml */ protected void parseWebXml(InputSource source, WebXml dest, boolean fragment) { XmlErrorHandler handler = new XmlErrorHandler(); Digester digester; WebRuleSet ruleSet; if (fragment) { digester = webFragmentDigester; ruleSet = webFragmentRuleSet; } else { digester = webDigester; ruleSet = webRuleSet; } digester.push(dest); digester.setErrorHandler(handler); digester.parse(source); }
使用Digester 對象即系web.xml,并將結果保存到WebXml對象中。
3.4 Digester的解析規則
(1)構造Digester.createWebXmlDigester
public void createWebXmlDigester(boolean namespaceAware, boolean validation) { boolean blockExternal = context.getXmlBlockExternal(); webRuleSet = new WebRuleSet(false); webDigester = DigesterFactory.newDigester(validation, namespaceAware, webRuleSet, blockExternal); webDigester.getParser(); webFragmentRuleSet = new WebRuleSet(true); webFragmentDigester = DigesterFactory.newDigester(validation, namespaceAware, webFragmentRuleSet, blockExternal); webFragmentDigester.getParser(); }
(2)配置解析規則 WebRuleSet.addRuleInstances
/** * <p>Add the set of Rule instances defined in this RuleSet to the * specified <code>Digester</code> instance, associating them with * our namespace URI (if any). This method should only be called * by a Digester instance.</p> * * @param digester Digester instance to which the new Rule instances * should be added. */ @Override public void addRuleInstances(Digester digester) { digester.addRule(fullPrefix, new SetPublicIdRule("setPublicId")); digester.addRule(fullPrefix, new IgnoreAnnotationsRule()); digester.addRule(fullPrefix, new VersionRule()); // Required for both fragments and non-fragments digester.addRule(fullPrefix + "/absolute-ordering", absoluteOrdering); digester.addRule(fullPrefix + "/ordering", relativeOrdering); if (fragment) { // web-fragment.xml digester.addRule(fullPrefix + "/name", name); digester.addCallMethod(fullPrefix + "/ordering/after/name", "addAfterOrdering", 0); digester.addCallMethod(fullPrefix + "/ordering/after/others", "addAfterOrderingOthers"); digester.addCallMethod(fullPrefix + "/ordering/before/name", "addBeforeOrdering", 0); digester.addCallMethod(fullPrefix + "/ordering/before/others", "addBeforeOrderingOthers"); } else { // web.xml digester.addCallMethod(fullPrefix + "/absolute-ordering/name", "addAbsoluteOrdering", 0); digester.addCallMethod(fullPrefix + "/absolute-ordering/others", "addAbsoluteOrderingOthers"); } digester.addCallMethod(fullPrefix + "/context-param", "addContextParam", 2); digester.addCallParam(fullPrefix + "/context-param/param-name", 0); digester.addCallParam(fullPrefix + "/context-param/param-value", 1); digester.addObjectCreate(fullPrefix + "/filter", "org.apache.catalina.deploy.FilterDef"); digester.addSetNext(fullPrefix + "/filter", "addFilter", "org.apache.catalina.deploy.FilterDef"); digester.addCallMethod(fullPrefix + "/filter/description", "setDescription", 0); digester.addCallMethod(fullPrefix + "/filter/display-name", "setDisplayName", 0); digester.addCallMethod(fullPrefix + "/filter/filter-class", "setFilterClass", 0); digester.addCallMethod(fullPrefix + "/filter/filter-name", "setFilterName", 0); digester.addCallMethod(fullPrefix + "/filter/icon/large-icon", "setLargeIcon", 0); digester.addCallMethod(fullPrefix + "/filter/icon/small-icon", "setSmallIcon", 0); digester.addCallMethod(fullPrefix + "/filter/async-supported", "setAsyncSupported", 0); digester.addCallMethod(fullPrefix + "/filter/init-param", "addInitParameter", 2); digester.addCallParam(fullPrefix + "/filter/init-param/param-name", 0); digester.addCallParam(fullPrefix + "/filter/init-param/param-value", 1); digester.addObjectCreate(fullPrefix + "/filter-mapping", "org.apache.catalina.deploy.FilterMap"); digester.addSetNext(fullPrefix + "/filter-mapping", "addFilterMapping", "org.apache.catalina.deploy.FilterMap"); digester.addCallMethod(fullPrefix + "/filter-mapping/filter-name", "setFilterName", 0); digester.addCallMethod(fullPrefix + "/filter-mapping/servlet-name", "addServletName", 0); digester.addCallMethod(fullPrefix + "/filter-mapping/url-pattern", "addURLPattern", 0); digester.addCallMethod(fullPrefix + "/filter-mapping/dispatcher", "setDispatcher", 0); digester.addCallMethod(fullPrefix + "/listener/listener-class", "addListener", 0); ... }
在這個方法里,可以看到熟悉的“/servlet/servlet-name”,"/listener/listener-class"等等。稍微懂點Digester解析語法的基礎的朋友,立刻可以知道這兒就是解析規則所在,Digester解析web.xml規則都是在此配置的。進一步梳理下,可以弄明白servlet,filter等重要對象的數據載體。
標簽 | 數據載體類 |
/filter | org.apache.catalina.deploy.FilterDef |
/error-page | org.apache.catalina.deploy.ErrorPage |
/servlet | org.apache.catalina.deploy.ServletDef |
/filter-mapping | org.apache.catalina.deploy.FilterMap |
/login-config | org.apache.catalina.deploy.LoginConfig |
/session-config | org.apache.catalina.deploy.SessionConfig |
... | ... |
部分私有內部Rule列表
通過分析,可以知道web.xml通過解析之后,配置信息都保存在WebXml對象中了。
WebXml中持有FilterMap,ServletDef,FilterDef等等對象的聚集信息。接下來tomcat就可以按照servlet規范初始化里面的組件了,有空將進一步介紹。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。