1. Servlet與SpringMVC之間的關係
Spring的MVC是基於Servlet功能實現的,經過實現Servlet接口的DispatcherServlet來封裝其核心功能實現。
2. ServletContainerInitializer接口
在web容器啓動時會作一些初始化的工做,例如註冊servlet或者filtes等,servlet規範中經過ServletContainerInitializer實現此功能。
每一個框架, 比如Spring,要使用ServletContainerInitializer就必須在對應的jar包的META-INF/services 目錄建立一個名爲javax.servlet.ServletContainerInitializer的文件,文件內容指定具體的ServletContainerInitializer實現類。(JAVA的SPI特性)
案例演示:服務器
@HandlesTypes(value = MyHandlesType.class)//該註解聲明的類,會被注入到set中,若是沒有合適的類,set爲null,这个动作也是Servlet 容器做的(如tomcat)
public class MyServletContainerInitializer implements ServletContainerInitializer {
/**
* @param set 感興趣類型 也就是MyHandlesType 全部子類型
* @param servletContext
* @throws ServletException
*/
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
// 1.打印全部感興趣的類型
for (Class<?> c : set) {
System.out.println(c);
}
// 2.servletContext 手動註冊過濾器、servlet、監聽器
ServletRegistration.Dynamic payServlet = servletContext.addServlet("payServlet", new PayServlet());
payServlet.addMapping("/pay");
}
}
以前传统的Servlet开发,我也需要提供web.xml到Tomcat中,在web.xml中指定Servlet,以及servlet mapping,如:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- spring mvc配置开始 -->
<servlet>
<servlet-name>let'sGo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>let'sGo</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- spring mvc配置结束 -->
<welcome-file-list>
<welcome-file>index</welcome-file>
</welcome-file-list>
</web-app>
springmvc是如何實現不需要web.xml配置,靠的就是ServletContainerInitialize。
3. SpringServletContainerInitializer的作用
看下SpringServletContainerInitializer的源码:
@HandlesTypes(WebApplicationInitializer.class) // Servlet容器会根据@HandlesTypes找到所有注解中的类的实现,也就是所有WebApplicationInitializer.class的实现
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//所有WebApplicationInitializer.class的实现都会被放在这个Set中
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
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);
}
}
}
- @HandlesTypes(WebApplicationInitializer.class) // Servlet容器会根据@HandlesTypes找到所有注解中的类的实现,也就是所有WebApplicationInitializer.class的实现
- 所有WebApplicationInitializer.class的实现都会被放在这个Set中
- 调用所有WebApplicationInitializer实现类的onStartUp()函数,这样Spring框架就可以在WebApplicationInitializer实现类里做一些初始化等操作(如创建DispatcherServlet,添加Filter)
SpringServletContainerInitializer通过实现ServletContainerInitializer将自身并入到Servlet容器的生命周期中, 并通过自身定义的WebApplicationInitializer将依赖于Spring框架的系统初始化需求与Servlet容器解耦. 即依赖于spring的系统可以通过实现WebApplicationInitializer来实现自定义的初始化逻辑. 而不需要去实现ServletContainerInitializer
4. WebApplicationInitializer
SpringServletContainerInitializer会调用所有实现了WebApplicationInitializer接口的实现类。Spring提供了很多实现类用来做初始化操作。
5. AbstractAnnotationConfigDispatcherServletInitializer
SpringMVC提供的AbstractAnnotationConfigDispatcherServletInitializer这个抽象类间接实现了WebApplicationInitializer接口,如下图所示。所以我们只需要自己写一个类,继承AbstractAnnotationConfigDispatcherServletInitializer这个抽象类,servlet容器启动的时候就会调用我们写的实现类的父类里的onStartUp()函数。
具体其实就是AbstractDispatcherServletInitializer.class的onStartUp()函数:
//org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
// 创建Spring容器,ServletApplicationContext
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// 创建DispatcherServlet,并把Spring容器当做参数穿进去
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 将创建好的DispatcherServlet传进Servlet容器里
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());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter); }
}
customizeRegistration(registration);}
这样的话我们就通过代码的方式做了如下操作:
- 创建了Spring容器---ServletApplicationContext
- 创建了Servlet---DispatcherServlet,并把Spring容器放进了Servlet中
- 把Servlet添加到Servlet 容器中。
6. 上述操作,通过web.xml也能做到,不过Servlet规范3.0以后,支持了代码的方式, 我们看一下通过web.xml执行上述操作的例子:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- spring mvc配置开始 -->
<servlet>
<servlet-name>let'sGo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>let'sGo</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- spring mvc配置结束 -->
<welcome-file-list>
<welcome-file>index</welcome-file>
</welcome-file-list>
</web-app>
- Servlet容器会根据Servlet规范读取web.xml文件
- 读到<servlet>标签后就创建<servlet>里标记的Servlet——这里是DispatcherServlet,这里创建调用的是无参构造函数,跟前面通过代码实现的时候创建DispatcherServlet用的是带参数的构造函数不一样,因为这里没有事先创建好的Spring容器(Servlet WebApplicationContext)。
- 所以在new DispatcherServlet的过程中,判断当前没有现成的Spring容器(Servlet WebApplicationContext),就会自己创建一个Spring容器,代码如下
//org.springframework.web.servlet.FrameworkServlet,它是DispatcherServlet的父类
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
// 看下构造函数中是否传进来了 webApplicationContext,用web.xml的情况下,webApplicationContext是null
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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 -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
// 看下Servlet容器里有没有现成的 webApplicationContext
wac = findWebApplicationContext();
}
// 前面几步都没拿到,自己创建一个webApplicationContext,使用web.xml的情况走的就是这里
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
......
}
- 看下构造函数中是否传进来了 webApplicationContext,用web.xml的情况下,webApplicationContext是null
- 看下Servlet容器里有没有现成的 webApplicationContext
- 前面几步都没拿到,自己创建一个webApplicationContext,使用web.xml的情况走的就是这里
- 然后还是DispatcherServlet,它会把自己添加到Servlet容器中。
// org.springframework.web.servlet.FrameworkServlet
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}