按照servlet3.0規(guī)范的規(guī)定沉删,tomcat等web容器在啟動的時候需要查看META-INF/services目錄下的以javax.servlet.ServletContainerInitializer作為文件名的文件并加載文件中實現(xiàn)了ServletContainerInitializer接口的類
spring-web的實現(xiàn)為SpringServletContainerInitializer:
該類被@HandlesTypes(WebApplicationInitializer.class)所注解翻屈,所以該類的onStartup方法實現(xiàn)中可以以Set<Class<?>> webAppInitializerClasses參數(shù)接收到所有WebApplicationInitializer接口的實現(xiàn)類:
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
再看看WebApplicationInitializer的實現(xiàn)類有哪些:
下面看看SpringServletContainerInitializer.onStartup具體的邏輯(相關性不強的代碼我就不貼出來了照雁,主要看邏輯):
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
//拿到所有WebApplicationInitializer的實現(xiàn)類,如果該類不是接口宪萄、抽象類那么反射創(chuàng)建該類實例
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
//排序后依次執(zhí)行onStartup方法
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
根據(jù)上面代碼的分析SpringServletContainerInitializer并沒有執(zhí)行具體的springIOC容器的加載和DispatcherServlet的注冊州叠,而是把具體的加載邏輯委托給了WebApplicationInitializer的實現(xiàn)類旧困,但是WebApplicationInitializer的層級關系圖顯示印屁,spring框架除了提供了三個抽象實現(xiàn)外并沒有提供具體的實現(xiàn)類循捺,所以需要開發(fā)者自己提供斩例,我們先看最外層的封裝AbstractAnnotationConfigDispatcherServletInitializer的注釋(不喜歡英文的直接看后面翻譯):
/**
- Base class for {@link org.springframework.web.WebApplicationInitializer}
- implementations that register a
- {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
- configured with annotated classes, e.g. Spring's
- {@link org.springframework.context.annotation.Configuration @Configuration} classes.
- <p>Concrete implementations are required to implement {@link #getRootConfigClasses()}
- and {@link #getServletConfigClasses()} as well as {@link #getServletMappings()}.
- Further template and customization methods are provided by
- {@link AbstractDispatcherServletInitializer}.
- <p>This is the preferred approach for applications that use Java-based
- Spring configuration.
*/
大概意思是說:這個類是WebApplicationInitializer接口的基礎實現(xiàn)雄人,目的是通過使用諸如@Configuration注解的配置類來注冊DispatcherServlet(譯者:也包括root application context),繼承這個類的具體實現(xiàn)需要實現(xiàn)getRootConfigClasses念赶、getServletConfigClasses和getServletMappings三個方法础钠,并且繼承這個類是使用基于注解配置的推薦方式。
通過上面提供的三個方法名我們可以推斷叉谜,只需要通過類似
這種方式就可以配置spring的root 容器和DispatcherServlet使用的mvc容器并配置DispatcherServlet的路徑映射信息了旗吁,相當簡單。
下面分析原理,再次熟悉一下層級關系:
->WebApplicationInitializer
->AbstractContextLoaderInitializer
->AbstractDispatcherServletInitializer
->AbstractAnnotationConfigDispatcherServletInitializer
直接看onStartup方法停局,onStartup只在AbstractContextLoaderInitializer和AbstractDispatcherServletInitializer中實現(xiàn)很钓,我們從外往里看
AbstractDispatcherServletInitializer:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//調(diào)用父類的onStartup
super.onStartup(servletContext);
//注冊DispatcherServlet
registerDispatcherServlet(servletContext);
}
AbstractContextLoaderInitializer:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//注冊ContextLoaderListener
registerContextLoaderListener(servletContext);
}
其實從類的名字上我們就可以猜出來他們各自完成了什么工作了香府,AbstractContextLoaderInitializer的作用是向servletContext中注冊ContextLoaderListener,這和我們使用xml配置方式加載springIOC容器的方式是一樣的,AbstractDispatcherServletInitializer負責注冊DispatcherServlet加載mvc容器码倦。
protected void registerContextLoaderListener(ServletContext servletContext) {
//創(chuàng)建根容器rootAppContext
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
//實例化ContextLoaderListener
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
//必要的話傳入contextInitializers
listener.setContextInitializers(getRootApplicationContextInitializers());
//注冊到servletContext中
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
關于上面創(chuàng)建根容器rootAppContext的方法createRootApplicationContext:
protected WebApplicationContext createRootApplicationContext() {
//獲取用戶定義的根容器配置類企孩,在我們的例子中是AppConfig
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
//僅僅將配置類注冊到根IOC容器中以供ContextLoaderListener使用,具體容器加載過程由ContextLoaderListener完成
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
ContextLoaderListener加載IOC容器的原理這里就不再贅述了袁稽。下面看DispatcherServlet的注冊過程:
protected void registerDispatcherServlet(ServletContext servletContext) {
//獲得servlet名稱勿璃,固定值"dispatcher"
String servletName = getServletName();
//生成AnnotationConfigWebApplicationContext容器,并將配置類注冊進去(具體代碼看后面的代碼塊)
WebApplicationContext servletAppContext = createServletApplicationContext();
//實例化DispatcherServlet,傳入生成好的context容器
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
//必要的話傳入contextInitializers
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//將DispatcherServlet注冊到servletContext中
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
//設置LoadOnStartup
registration.setLoadOnStartup(1);
//設置mapping
registration.addMapping(getServletMappings());
//設置異步支持
registration.setAsyncSupported(isAsyncSupported());
//注冊過濾器
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
//自定義配置
customizeRegistration(registration);
}
createServletApplicationContext:
protected WebApplicationContext createServletApplicationContext() {
//實例化容器
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
//獲取配置類(webConfig)
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
//注冊配置類
servletAppContext.register(configClasses);
}
return servletAppContext;
}
配置好DispatcherServlet后推汽,服務器web容器啟動時根據(jù)LoadOnStartup屬性會自動開始springmvc容器的加載過程补疑。
總結(jié)
web容器啟動->
注冊contextloaderlistener(此監(jiān)聽器為servletcontextlistener,在servletcontext初始化之后執(zhí)行內(nèi)部方法完成root context的初始化工作)
向web容器注冊dispatcherservlet,設置loadonstartup參數(shù)為1歹撒,即啟動時執(zhí)行init方法莲组,在init方法中獲取之前初始化完成的root context 完成web context的初始化。