寫本篇博客原由:
最近本人在學習Tomcat源碼,在研讀StandardContext的時候,其中有一個關鍵類ContextConfig,也就是下面代碼:
processServletContainerInitializers(); 方法的調用地方,發現其內部在加載處理 javax.servlet.ServletContainerInitializer,把對象實例保存到 ContextConfig 的 Map 中,待 Wrapper 子容器添加到 StandardContext 子容器中之後,再把 ServletContainerInitializer 加入 ServletContext 中。ServletContainerInitializer 是 servlet3.0 提供的一個 SPI,可以通過 HandlesTypes 篩選出相關的 servlet 類,並可以對 ServletContext 進行額外處理,實現了 ServletContainerInitializer 接口,和 jdk 提供的其它 SPI 一樣,需要在 META-INF/services/javax.servlet.ServletContainerInitializer 文件中指定該類名。
然後便想研究一下 SPI機制在servlet3.0的應用,之前也有一篇關於SPI機制的文章。
https://blog.csdn.net/gaohaicheng123/article/details/105824988
/**
* 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() {
/*
* Anything and everything can override the global and host defaults.
* This is implemented in two parts
* - Handle as a web fragment that gets added after everything else so
* everything else takes priority
* - Mark Servlets as overridable so SCI configuration can replace
* configuration from the defaults
*/
/*
* The rules for annotation scanning are not as clear-cut as one might
* think. Tomcat implements the following process:
* - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
* which Servlet spec version is declared in web.xml. The EG has
* confirmed this is the expected behaviour.
* - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
* web.xml is marked as metadata-complete, JARs are still processed
* for SCIs.
* - If metadata-complete=true and an absolute ordering is specified,
* JARs excluded from the ordering are also excluded from the SCI
* processing.
* - If an SCI has a @HandlesType annotation then all classes (except
* those in JARs excluded from an absolute ordering) need to be
* scanned to check if they match.
*/
WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
context.getXmlValidation(), context.getXmlBlockExternal());
Set<WebXml> defaults = new HashSet<>();
defaults.add(getDefaultWebXmlFragment(webXmlParser));
Set<WebXml> tomcatWebXml = new HashSet<>();
tomcatWebXml.add(getTomcatWebXmlFragment(webXmlParser));
// 創建 WebXml實例,並解析 web.xml 文件
WebXml webXml = createWebXml();
// Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
ok = false;
}
ServletContext sContext = context.getServletContext();
// Ordering is important here
// Step 1. Identify all the JARs packaged with the application and those
// provided by the container. If any of the application JARs have a
// web-fragment.xml it will be parsed at this point. web-fragment.xml
// files are ignored for container provided JARs.
Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);
// Step 2. Order the fragments.
Set<WebXml> orderedFragments = null;
orderedFragments =
WebXml.orderWebFragments(webXml, fragments, sContext);
// Step 3. Look for ServletContainerInitializer implementations
if (ok) {
// !!! 注意此處。。。
processServletContainerInitializers();
}
if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
// Step 4. Process /WEB-INF/classes for annotations and
// @HandlesTypes matches
Map<String,JavaClassCacheEntry> javaClassCache = new HashMap<>();
if (ok) {
WebResource[] webResources =
context.getResources().listResources("/WEB-INF/classes");
for (WebResource webResource : webResources) {
// Skip the META-INF directory from any JARs that have been
// expanded in to WEB-INF/classes (sometimes IDEs do this).
if ("META-INF".equals(webResource.getName())) {
continue;
}
processAnnotationsWebResource(webResource, webXml,
webXml.isMetadataComplete(), javaClassCache);
}
}
// Step 5. Process JARs for annotations and
// @HandlesTypes matches - only need to process those fragments we
// are going to use (remember orderedFragments includes any
// container fragments)
if (ok) {
processAnnotations(
orderedFragments, webXml.isMetadataComplete(), javaClassCache);
}
// 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 7a
// merge tomcat-web.xml
webXml.merge(tomcatWebXml);
// Step 7b. 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) {
configureContext(webXml);
}
} else {
webXml.merge(tomcatWebXml);
webXml.merge(defaults);
convertJsps(webXml);
configureContext(webXml);
}
if (context.getLogEffectiveWebXml()) {
log.info("web.xml:\n" + webXml.toXml());
}
// 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<>();
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());
}
}
}
}
此處是上面和核心部分, 尋找一個 ServletContainerInitializer 容器初始化的類對象:
// Step 3. Look for ServletContainerInitializer implementations
if (ok) {
// !!! 注意此處。。。
processServletContainerInitializers();
}
下面我們點入方法查看一下實現:
/**
* Scan JARs for ServletContainerInitializer implementations.
掃描包,尋找實現類
*/
protected void processServletContainerInitializers() {
List<ServletContainerInitializer> detectedScis;
try {
WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
// 此處是一個關鍵的地方。具體加載如何加載實現類。
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
log.error(sm.getString(
"contextConfig.servletContainerInitializerFail",
context.getName()),
e);
ok = false;
return;
}
for (ServletContainerInitializer sci : detectedScis) {
initializerClassMap.put(sci, new HashSet<Class<?>>());
HandlesTypes ht;
try {
ht = sci.getClass().getAnnotation(HandlesTypes.class);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.info(sm.getString("contextConfig.sci.debug",
sci.getClass().getName()),
e);
} else {
log.info(sm.getString("contextConfig.sci.info",
sci.getClass().getName()));
}
continue;
}
if (ht == null) {
continue;
}
Class<?>[] types = ht.value();
if (types == null) {
continue;
}
for (Class<?> type : types) {
if (type.isAnnotation()) {
handlesTypesAnnotations = true;
} else {
handlesTypesNonAnnotations = true;
}
Set<ServletContainerInitializer> scis =
typeInitializerMap.get(type);
if (scis == null) {
scis = new HashSet<>();
typeInitializerMap.put(type, scis);
}
scis.add(sci);
}
}
}
關鍵的地方就是這裏
List<ServletContainerInitializer> detectedScis;
try {
WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
// 此處是一個關鍵的地方。具體加載如何加載實現類。
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
log.error(sm.getString(
"contextConfig.servletContainerInitializerFail",
context.getName()),
e);
ok = false;
return;
}
下面我們繼續跟入代碼:
public List<T> load(Class<T> serviceType) throws IOException {
// String SERVICES = "META-INF/services/"; 這裏便是祕密。
String configFile = SERVICES + serviceType.getName();
LinkedHashSet<String> applicationServicesFound = new LinkedHashSet<>();
LinkedHashSet<String> containerServicesFound = new LinkedHashSet<>();
ClassLoader loader = servletContext.getClassLoader();
// if the ServletContext has ORDERED_LIBS, then use that to specify the
// set of JARs from WEB-INF/lib that should be used for loading services
@SuppressWarnings("unchecked")
List<String> orderedLibs =
(List<String>) servletContext.getAttribute(ServletContext.ORDERED_LIBS);
if (orderedLibs != null) {
// handle ordered libs directly, ...
for (String lib : orderedLibs) {
URL jarUrl = servletContext.getResource(LIB + lib);
if (jarUrl == null) {
// should not happen, just ignore
continue;
}
String base = jarUrl.toExternalForm();
URL url;
if (base.endsWith("/")) {
url = new URL(base + configFile);
} else {
url = JarFactory.getJarEntryURL(jarUrl, configFile);
}
try {
parseConfigFile(applicationServicesFound, url);
} catch (FileNotFoundException e) {
// no provider file found, this is OK
}
}
// and the parent ClassLoader for all others
loader = context.getParentClassLoader();
}
Enumeration<URL> resources;
if (loader == null) {
resources = ClassLoader.getSystemResources(configFile);
} else {
resources = loader.getResources(configFile);
}
while (resources.hasMoreElements()) {
parseConfigFile(containerServicesFound, resources.nextElement());
}
// Filter the discovered container SCIs if required
if (containerSciFilterPattern != null) {
Iterator<String> iter = containerServicesFound.iterator();
while (iter.hasNext()) {
if (containerSciFilterPattern.matcher(iter.next()).find()) {
iter.remove();
}
}
}
// Add the application services after the container services to ensure
// that the container services are loaded first
containerServicesFound.addAll(applicationServicesFound);
// load the discovered services
if (containerServicesFound.isEmpty()) {
return Collections.emptyList();
}
// 此處加載 路徑下的文件,然後通過反射創建對象返回。
return loadServices(serviceType, containerServicesFound);
}
最終創建的 ServletContainerInitializer 放入到 ContextConfig中的屬性中:Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap = new LinkedHashMap<>(); 等待後續被調用。
大致的調用順序:
tomcat啓動時webConfig() 的調用鏈:
Tomcat.start()->各種代理的start()->org.apache.catalina.core.StandardContext.startInternal->LifecycleBase.fireLifecycleEvent->org.apache.catalina.startup.ContextConfig.lifecycleEvent->configureStart->webConfig->processServletContainerInitializers()->load(Class<T> serviceType);
上面的文章主要是說明Tomcat啓動時,用到的SPI機制。
下面我們學習一下,tomcat啓動時如何通過SPI和SpringMVC結合到一起的。以及啓動方式。
java的類加載機制,servlet3.0新特性,java的spi機制,以及spring-mvc的初始化和加載過程。
到這裏不得不說一下tomcat啓動項目的兩種方式:
- web.xml方式。
- Servlet3.0後的註解形式。
SpringMVC初始化
之前我使用spring和springMVC的時候都是在web.xml裏面定義一個 listener org.springframework.web.context.ContextLoaderListener 用來初始化spring和一個 servlet org.springframework.web.servlet.DispatcherServlet 用來初始化springMVC。
我看了下DispatcherServlet的源碼初始化流程,如下圖DispatcherServlet初始化的時候,會創建一個WebApplicationInitializer。左邊三者是一個繼承關係。
WebApplicationContext和ApplicationContext有什麼不同呢? 前者實現了後者,我們可以配置多個servlet對應不同的mapping,每個servlet會對應一個WebApplicationContext(wac),但是ApplicationContext(ac)只有一個,wac之間可以共享ac裏面配置的bean,如共享數據源,緩存等,而且如果不需要這些配置,ac也不是必須的。ac和wc是一個parent-child的層級關係。
我們看一下 web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<!-- 在Spring框架中是如何解決從頁面傳來的字符串的編碼問題的呢?
下面我們來看看Spring框架給我們提供過濾器CharacterEncodingFilter
這個過濾器就是針對於每次瀏覽器請求進行過濾的,然後再其之上添加了父類沒有的功能即處理字符編碼。
其中encoding用來設置編碼格式,forceEncoding用來設置是否理會 request.getCharacterEncoding()方法,設置爲true則強制覆蓋之前的編碼格式。-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 項目中使用Spring 時,applicationContext.xml配置文件中並沒有BeanFactory,要想在業務層中的class 文件中直接引用Spring容器管理的bean可通過以下方式-->
<!--1、在web.xml配置監聽器ContextLoaderListener-->
<!--ContextLoaderListener的作用就是啓動Web容器時,自動裝配ApplicationContext的配置信息。因爲它實現了ServletContextListener這個接口,在web.xml配置這個監聽器,啓動容器時,就會默認執行它實現的方法。
在ContextLoaderListener中關聯了ContextLoader這個類,所以整個加載配置過程由ContextLoader來完成。
它的API說明
第一段說明ContextLoader可以由 ContextLoaderListener和ContextLoaderServlet生成。
如果查看ContextLoaderServlet的API,可以看到它也關聯了ContextLoader這個類而且它實現了HttpServlet這個接口
第二段,ContextLoader創建的是 XmlWebApplicationContext這樣一個類,它實現的接口是WebApplicationContext->ConfigurableWebApplicationContext->ApplicationContext->
BeanFactory這樣一來spring中的所有bean都由這個類來創建
IUploaddatafileManager uploadmanager = (IUploaddatafileManager) ContextLoaderListener.getCurrentWebApplicationContext().getBean("uploadManager");
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--2、部署applicationContext的xml文件-->
<!--如果在web.xml中不寫任何參數配置信息,默認的路徑是"/WEB-INF/applicationContext.xml,
在WEB-INF目錄下創建的xml文件的名稱必須是applicationContext.xml。
如果是要自定義文件名可以在web.xml里加入contextConfigLocation這個context參數:
在<param-value> </param-value>裏指定相應的xml文件名,如果有多個xml文件,可以寫在一起並以“,”號分隔。
也可以這樣applicationContext-*.xml採用通配符,比如這那個目錄下有applicationContext-ibatis-base.xml,
applicationContext-action.xml,applicationContext-ibatis-dao.xml等文件,都會一同被載入。
在ContextLoaderListener中關聯了ContextLoader這個類,所以整個加載配置過程由ContextLoader來完成。-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>
<!--如果你的DispatcherServlet攔截"/",爲了實現REST風格,攔截了所有的請求,那麼同時對*.js,*.jpg等靜態文件的訪問也就被攔截了。-->
<!--方案一:激活Tomcat的defaultServlet來處理靜態文件-->
<!--要寫在DispatcherServlet的前面, 讓 defaultServlet先攔截請求,這樣請求就不會進入Spring了,我想性能是最好的吧。-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.swf</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.xml</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.json</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.map</url-pattern>
</servlet-mapping>
<!--使用Spring MVC,配置DispatcherServlet是第一步。DispatcherServlet是一個Servlet,,所以可以配置多個DispatcherServlet-->
<!--DispatcherServlet是前置控制器,配置在web.xml文件中的。攔截匹配的請求,Servlet攔截匹配規則要自已定義,把攔截下來的請求,依據某某規則分發到目標Controller(我們寫的Action)來處理。-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name><!--在DispatcherServlet的初始化過程中,框架會在web應用的 WEB-INF文件夾下尋找名爲[servlet-name]-servlet.xml 的配置文件,生成文件中定義的bean。-->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指明瞭配置文件的文件名,不使用默認配置文件名,而使用dispatcher-servlet.xml配置文件。-->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--其中<param-value>**.xml</param-value> 這裏可以使用多種寫法-->
<!--1、不寫,使用默認值:/WEB-INF/<servlet-name>-servlet.xml-->
<!--2、<param-value>/WEB-INF/classes/dispatcher-servlet.xml</param-value>-->
<!--3、<param-value>classpath*:dispatcher-servlet.xml</param-value>-->
<!--4、多個值用逗號分隔-->
<param-value>classpath:spring/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup><!--是啓動順序,讓這個Servlet隨Servletp容器一起啓動。-->
</servlet>
<servlet-mapping>
<!--這個Servlet的名字是dispatcher,可以有多個DispatcherServlet,是通過名字來區分的。每一個DispatcherServlet有自己的WebApplicationContext上下文對象。同時保存的ServletContext中和Request對象中.-->
<!--ApplicationContext是Spring的核心,Context我們通常解釋爲上下文環境,我想用“容器”來表述它更容易理解一些,ApplicationContext則是“應用的容器”了:P,Spring把Bean放在這個容器中,在需要的時候,用getBean方法取出-->
<servlet-name>DispatcherServlet</servlet-name>
<!--Servlet攔截匹配規則可以自已定義,當映射爲@RequestMapping("/user/add")時,爲例,攔截哪種URL合適?-->
<!--1、攔截*.do、*.htm, 例如:/user/add.do,這是最傳統的方式,最簡單也最實用。不會導致靜態文件(jpg,js,css)被攔截。-->
<!--2、攔截/,例如:/user/add,可以實現現在很流行的REST風格。很多互聯網類型的應用很喜歡這種風格的URL。弊端:會導致靜態文件(jpg,js,css)被攔截後不能正常顯示。 -->
<url-pattern>/</url-pattern> <!--會攔截URL中帶“/”的請求。-->
</servlet-mapping>
<welcome-file-list><!--指定歡迎頁面-->
<welcome-file>login.html</welcome-file>
</welcome-file-list>
<error-page> <!--當系統出現404錯誤,跳轉到頁面nopage.html-->
<error-code>404</error-code>
<location>/nopage.html</location>
</error-page>
<error-page> <!--當系統出現java.lang.NullPointerException,跳轉到頁面error.html-->
<exception-type>java.lang.NullPointerException</exception-type>
<location>/error.html</location>
</error-page>
<session-config><!--會話超時配置,單位分鐘-->
<session-timeout>360</session-timeout>
</session-config>
</web-app>
1、spring 框架解決字符串編碼問題:過濾器 CharacterEncodingFilter(filter-name)
2、在web.xml配置監聽器ContextLoaderListener(listener-class)
ContextLoaderListener的作用就是啓動Web容器時,自動裝配ApplicationContext的配置信息。因爲它實現了ServletContextListener這個接口,在web.xml配置這個監聽器,啓動容器時,就會默認執行它實現的方法。
3、部署applicationContext的xml文件:contextConfigLocation(context-param下的param-name)
4、DispatcherServlet是前置控制器,配置在web.xml文件中的。攔截匹配的請求,Servlet攔截匹配規則要自已定義,把攔截下來的請求,依據某某規則分發到目標Controller(我們寫的Action)來處理。
DispatcherServlet(servlet-name、servlet-class、init-param、param-name(contextConfigLocation)、param-value)
在DispatcherServlet的初始化過程中,框架會在web應用的 WEB-INF文件夾下尋找名爲[servlet-name]-servlet.xml 的配置文件,生成文件中定義的bean
通過上面的瞭解,我們可以看出spring核心配置文件就是listener那塊。在監聽之前我們已經通過context-param將spring配置文件傳到上下文中了(application)。下面我們就來看看spring是如何工作的吧
第一步:
點開listener源碼,我們發現他有下面幾個方法。和繼承的關係。我們發現他實現了ContextLoaderListener這個接口,這個接口在參數設置好之後自動執行contextInitialized方法的。
那麼我們來看看contextInitialized方法
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
仔細研究官方解釋,就是在這裏初始化application,這裏會用到contextClass+contextConfigLocation兩個參數,如果contextClass在context-param提供了,我們就會根據這一個class去初始化application,很顯然我們正常配置都沒有配這個,而是配置了後者,配置了後者就會去根據contextConfigLocation中提供的配置文件去解析然後創建相關的bean和application操作,這個方法的最後會執行configureAndRefreshWebApplicationContext方法。這個方法就是在根據contextConfigLocation提供的配置文件中創建相關的bean。
springMVC 加載
springMVC其實和spring是一樣的,但是他不用再程序開始時訪問
<servlet>
<!-- 配置DispatcherServlet -->
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定spring mvc配置文件位置 不指定使用默認情況 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<!-- 設置啓動順序 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- ServLet 匹配映射 -->
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>*.zxh</url-pattern>
</servlet-mapping>
看DispatcherServlet源碼中對contextConfigLocation參數的解釋
上面明確指出我們這個參數給XmlWebApplicationContext類的,我們在進入XmlWebApplicationContext類看看究竟。
這樣我們很容易理解爲什麼springmvc默認的配置文件會在WEB-INF/application.xml中。
在dispatcherservlet中有一個初始化方法,這裏就初始化配置中一些東西,比如說文件上傳適配器的配置等
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
Servlet3.0後的註解形式。
隨着servlet3.0的到來,web.xml也不是必須的了,我們可以定義一個WebApplicationInitializer來初始化一個WebApplicationContext,下面我講講WebApplicationInitializer的加載機制
看下面介紹的<a href="#ServletContext的性能增強"">servlet3對ServletContext的增強可以知道,javaee容器在啓動的時候會通過spi機制來尋找javax.servlet.ServletContainerInitializer的實現類,在spring-web jar包,如下圖所示
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
...
}
SpringServletContainerInitializer
的onStartup()方法裏面有如下一段註釋
Because this class declares @HandlesTypes(WebApplicationInitializer.class), Servlet 3.0+ containers will automatically scan the classpath for implementations of Spring’s WebApplicationInitializer interface and provide the set of all such types to the webAppInitializerClasses parameter of this method.
因爲這個類@HandlesTypes註解的是WebApplicationInitializer.class,Servlet3.0容器會自動的掃描classpath下面WebApplicationInitializer接口的實現類,並提供給SpringServletContainerInitializer的onStartup()方法
spring-web中的具體應用
從servlet3.0開始,web容器啓動時爲提供給第三方組件機會做一些初始化的工作,例如註冊servlet或者filtes等,servlet規範中通過ServletContainerInitializer實現此功能。每個框架要使用ServletContainerInitializer就必須在對應的jar包的META-INF/services 目錄創建一個名爲javax.servlet.ServletContainerInitializer的文件,文件內容指定具體的ServletContainerInitializer實現類,那麼,當web容器啓動時就會運行這個初始化器做一些組件內的初始化工作。
一般伴隨着ServletContainerInitializer一起使用的還有HandlesTypes註解,通過HandlesTypes可以將感興趣的一些類注入到ServletContainerInitializerde的onStartup方法作爲參數傳入。
SpringServletContainerInitializer
通過源碼發現,配合註解@HandlesTypes它可以將其指定的Class對象作爲參數傳遞到onStartup方法中。進而在onStartup方法中獲取Class對象的具體實現類,進而調用實現類中的具體方法。SpringServletContainerInitializer類中@HandlesTypes指定的是Class對象是WebApplicationInitializer.Class。
利用這個機制,若實現WebApplicationInitializer這個接口,我們就可以自定義的注入Servlet,或者Filter,即可以不再依賴web.xml的配置。
Servlet3.0新特性
目前servlet4還正在開發過程中,目前最新的應該就是3了,最新的spring也用到了很多servlet3.0的特性,所以很有必要了解一下。
新特性概述:
- 異步處理支持,之前是servlet一直阻塞直到業務完成,現在可以將耗時任務委派給另外一個線程處理,自己不生成相應的情況下返回到容器
- 新增註解,簡化servlet、filter、listener,所以可以無需配置web.xml
- 可插件支持,類似於開發應用時,將jar包放入classpath
異步處理支持
在web.xml配置<async-supported>true</async-supported>或者在註解裏面添加asyncSupported = true
新增註解支持
@WebServlet
聲明一個類爲servlet,註解在部署時被容器自動處理
WebInitParam
通常配合@WebServlet和@WebFilter使用,類似於web.xml裏面的<init-param>標籤,指定初始化參數
WebFilter
聲明一個類爲過濾器
WebListener
聲明一個類爲監聽器,該類必須實現如下最少一個接口
ServletContextListener
ServletContextAttributeListener
ServletRequestListener
ServletRequestAttributeListener
HttpSessionListener
HttpSessionAttributeListener
@MultipartConfig
該註解主要是爲了輔助 Servlet 3.0 中 HttpServletRequest 提供的對上傳文件的支持。
可插性支持
以配置servlet爲例,有三種方式
- 最原始的在web.xml裏面配置servlet
- 使用servlet3.0的@WebServlet註解
- 利用可插性,將類繼承HttpServlet,然後打成jar包,在jar包的META-INF裏面放置一個 web-fragment文件,在文件中聲明Servlet配置
我覺得spring可能就是利用了可插性,等有時間驗證一下。
ServletContext的性能增強
支持運行時,動態的部署servlet、過濾器、監聽器,通過ServletContext的方法實現。
ServletContainerInitializer 也是 Servlet 3.0 新增的一個接口,容器在啓動時使用SPI來發現 ServletContainerInitializer 的實現類,並且容器將 WEB-INF/lib 目錄下 JAR 包中的類都交給該類的 onStartup() 方法處理,我們通常需要在該實現類上使用 @HandlesTypes 註解來指定希望被處理的類,過濾掉不希望給 onStartup() 處理的類。
Implementations of this interface must be declared by a JAR file resource located inside the META-INF/services directory and named for the fully qualified class name of this interface
這個接口的實現必須打包在一個jar文件裏面,並且需要在META-INF/services/通過spi來定義