Tomcat容器
spring整合Tomcat經(jīng)??吹降膚eb.xml
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
Servler3.0后,提供了注解+SPI的機(jī)制來配置servlet.
ServletContainerInitializer
允許庫(kù)/運(yùn)行時(shí)被通知 Web 應(yīng)用程序的啟動(dòng)階段并執(zhí)行任何必需的 servlet、過濾器和偵聽器的編程注冊(cè)以響應(yīng)它的接口。
該接口的實(shí)現(xiàn)可以使用HandlesTypes進(jìn)行注釋,以便(在它們的onStartup方法中)接收實(shí)現(xiàn)、擴(kuò)展或已使用注釋指定的類類型進(jìn)行注釋的應(yīng)用程序類集。
如果此接口的實(shí)現(xiàn)不使用HandlesTypes批注,或者沒有任何應(yīng)用程序類與批注指定的類匹配,則容器必須將空類集傳遞給onStartup 。
在檢查應(yīng)用程序的類以查看它們是否與ServletContainerInitializer的HandlesTypes注釋指定的任何條件匹配時(shí),如果缺少任何應(yīng)用程序的可選 JAR 文件,容器可能會(huì)遇到類加載問題。 因?yàn)槿萜鳠o法決定這些類型的類加載失敗是否會(huì)阻止應(yīng)用程序正常工作,所以它必須忽略它們,同時(shí)提供一個(gè)配置選項(xiàng)來記錄它們。
此接口的實(shí)現(xiàn)必須由位于META-INF/services目錄內(nèi)的 JAR 文件資源聲明,并以此接口的完全限定類名命名,并將使用運(yùn)行時(shí)的服務(wù)提供者查找機(jī)制或容器特定機(jī)制發(fā)現(xiàn)在語義上等同于它。 在任一情況下,必須忽略從絕對(duì)排序中排除的 Web 片段 JAR 文件中的ServletContainerInitializer服務(wù),并且發(fā)現(xiàn)這些服務(wù)的順序必須遵循應(yīng)用程序的類加載委托模型。
ServletContainerInitializer 是 Servlet 3.0 新增的一個(gè)接口,主要用于在容器啟動(dòng)階段通過編程風(fēng)格注冊(cè)Filter, Servlet以及Listener,以取代通過web.xml配置注冊(cè).
Tomcat啟動(dòng)的時(shí)候會(huì)通過JAR API來發(fā)現(xiàn)實(shí)現(xiàn)這類接口的類進(jìn)行配置加載
WebApplicationInitializer和SpringServletContainerInitializer
- org.springframework.web.SpringServletContainerInitializer#onStartup
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
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)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
// 激活WebApplicationInitializer#onStartup
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
SpringServletContainerInitializer實(shí)現(xiàn)了ServletContainerInitializer,在其onStartup方法中,通過@HandlesTypes(WebApplicationInitializer.class)注入WebApplicationInitializer到Tomcat容器.激活WebApplicationInitializer#onStartup.所以實(shí)現(xiàn)了WebApplicationInitializer的類都會(huì)被加載.
WebApplicationInitializer家族成員
Servlet WebApplicationContext 和 Root WebApplicationContext
- org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// 獲取配置類并且注入到容器中
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
- org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
}
Servlet WebApplicationContext:
將Controller、ViewResolver、HandleMapping相關(guān)的類掃描進(jìn)Servlet的web容器上下文中.
Root WebApplicationContext:
將Service、Repositories.這類對(duì)象交由Root WebApplicationContext管理.
總體來說,是做了業(yè)務(wù)對(duì)象的分層.
Spring MVC的大致流程
- 建立請(qǐng)求(RequestMapping)和Controller方法的映射集合的流程
- 根據(jù)請(qǐng)求(RequestURI)查找對(duì)應(yīng)的Controller方法的流程
- 請(qǐng)求參數(shù)綁定到方法形參,執(zhí)行方法處理請(qǐng)求,渲染視圖
流程圖
- 請(qǐng)求先經(jīng)過瀏覽器訪問tomcat容器,tomcat委派給線程池響應(yīng)當(dāng)前請(qǐng)求調(diào)用servlet進(jìn)行處理。
Spring MVC通過DispatcherServlet攔截了所有的請(qǐng)求,通過當(dāng)前請(qǐng)求的路徑與IoC提前解析好的HanlderMapping進(jìn)行對(duì)比,進(jìn)而定位到Controller的method進(jìn)行響應(yīng).- method經(jīng)過響應(yīng)后,會(huì)返回一個(gè)
ModelAndView實(shí)例.DispatcherServlet會(huì)委派給ViewResolver進(jìn)行視圖解析.- 返回
View,放入響應(yīng)流中響應(yīng)給瀏覽器.
注解配置的容器入口-AbstractDispatcherServletInitializer
注解的配置會(huì)優(yōu)于XML的配置執(zhí)行.SpringServletContainerInitializer實(shí)現(xiàn)了ServletContainerInitializer接口,通過@HandlesTypes(WebApplicationInitializer.class)的SPI發(fā)現(xiàn)機(jī)制,可以將實(shí)現(xiàn)該接口的Class對(duì)象傳遞到onStartup中.
- org.springframework.web.SpringServletContainerInitializer#onStartup
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
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)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
// 激活WebApplicationInitializer#onStartup
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
在對(duì)這些
initializers進(jìn)行排序后,最后就會(huì)依次激活所有WebApplicationInitializer的onStartup方法.
- org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
- org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#registerDispatcherServlet
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
// 創(chuàng)建spring web IoC 容器
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// 創(chuàng)建dispatcherServlet實(shí)例
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
// 路徑映射
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
// 請(qǐng)求攔截器,可以在此處控制編碼
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
這是一個(gè)模板方法,其中
createServletApplicationContext可以通過子類去實(shí)現(xiàn).支持注解形式的實(shí)現(xiàn)類是AbstractAnnotationConfigDispatcherServletInitializer
XML配置的容器入口-ContextLoaderListener
ContextLoaderListener實(shí)現(xiàn)了Servlet的ServletContextListener,Tomcat在啟動(dòng)的時(shí)候,會(huì)優(yōu)先加載Servlet的監(jiān)聽器組件,以確保在Servlet被創(chuàng)建之前,調(diào)用監(jiān)聽器的contextInitialized方法,Spring正是在ContextLoaderListener中進(jìn)行了initWebApplicationContext的調(diào)用,即啟動(dòng)的時(shí)候,進(jìn)行容器的refresh操作.
- org.springframework.web.context.ContextLoaderListener#contextInitialized
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
- org.springframework.web.context.ContextLoader#initWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 從servletContext中查找,是否存在以WebApplicationContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為key的值
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!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
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.
// 在AbstractContextLoaderInitializer#onStartup
// ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
// 進(jìn)行了初始化
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);
}
}
// 將WebApplicationContext放入servletContext中
// 其key為ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
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.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
- 先看看容器是否被創(chuàng)建出來了,如果沒有則調(diào)用
createWebApplicationContext來創(chuàng)建容器.- 容器是否屬于
ConfigurableWebApplicationContext類型,對(duì)容器進(jìn)行強(qiáng)轉(zhuǎn),然后查看是否處于激活的狀態(tài)(如果經(jīng)過了refresh,容器會(huì)進(jìn)入active狀態(tài)).- 是否存在父容器,如果有進(jìn)行
setParent- 配置并refresh容器
又是refresh
Spring會(huì)依次建立兩個(gè)上下文,一個(gè)是Root WebApplicationContext.另一個(gè)是WebApplicationContext For Dispatcher-servlet.無論哪個(gè),最終還是會(huì)回到org.springframework.context.support.AbstractApplicationContext#refresh這個(gè)方法中.
- 第一次refresh是Root WebApplicationContext
- 第二次refresh是Servlet WebApplicationContext
這里出現(xiàn)了幾個(gè)關(guān)鍵的對(duì)象:
FrameworkerServlet、HttpServletBean.
再?gòu)膱?zhí)行的調(diào)用棧中看看調(diào)用過程:
- org.springframework.web.servlet.HttpServletBean#init
- org.springframework.web.servlet.FrameworkServlet#initServletBean
- org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
- org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext
我們?cè)儆肬ML來梳理這些類之間的關(guān)系:
可以看到,
DispatcherServlet繼承自FrameworkServlet,而FrameworkServlet又繼承自HttpServletBean,最后HttpServletBean繼承了HttpServlet.
同時(shí),實(shí)現(xiàn)了Aware接口,就可以從容器中獲取到環(huán)境變量、容器上下文等資源.
這樣來看,調(diào)用順序就很好理解了.
HttpServletBean實(shí)現(xiàn)了HttpServlet接口,就可以重寫其init方法,在init方法里面調(diào)用一個(gè)鉤子方法initServletBean(HttpServletBean不負(fù)責(zé)實(shí)現(xiàn),由子類實(shí)現(xiàn)).
FrameworkServlet重寫了initServletBean方法,著手初始化容器的事項(xiàng).
注意,這里提到的FrameworkSerlvet和HttpSerlvetBean都是抽象類,真正的實(shí)例是-DispatcherSerlvet.
下一章,我們一起來看看
DispatcherSerlvet是如何初始化其他MVC組件的.