title: 12.SpringMVC与Spring是如何联系的
tag: 笔记 手写SSM Web
z实现了IOC、AOP和ORM之后我们开始实现Web开发支持。
分析
我们知道SpringMVC是一个Spring内置的基于Servlet的MVC框架。而Spring的核心就是它的IOC容器,那么SpringMVC作为一个web层框架是如何与Spring的IOC去连接起来的呢?
想要将它们连接起来应当需要解决下面的问题:
- 如何在tomcat服务器启动Servlet容器时启动IOC容器。
在这之前我们首先需要复习一下JavaWeb的内容。
Java Web应用程序
我们首先回顾下Java Web应用程序到底有几方参与。
首先,Java Web应用一般遵循**Servlet
标准**,这个标准定义了应用程序可以按接口编写哪些组件:Servlet
、Filter
和Listener
,也规定了一个服务器(如Tomcat、Jetty、JBoss等)应该提供什么样的服务,按什么顺序加载应用程序的组件,最后才能跑起来处理来自用户的HTTP请求。
Servlet规范定义的组件有3类:
- Servlet:处理HTTP请求,然后输出响应;
- Filter:对HTTP请求进行过滤,可以有多个Filter形成过滤器链,实现权限检查、限流、缓存等逻辑;
- Listener:用来监听Web应用程序产生的事件,包括启动、停止、Session有修改等。
这些组件均由应用程序实现。
而服务器为一个应用程序提供一个“容器”,即Servlet Container
,一个Server可以同时跑多个Container,不同的Container可以按URL、域名等区分,Container才是用来管理Servlet、Filter、Listener这些组件的:
所以我们要捋清楚这些组件的创建顺序,以及谁创建谁。
对于一个Web应用程序来说,启动时,应用程序本身只是一个war
包,并没有main()
方法,因此,启动时执行的是Server的main()
方法。以Tomcat服务器为例:
- 启动服务器,即执行Tomcat的
main()
方法; - Tomcat根据配置或自动检测到一个
xyz.war
包后,为这个xyz.war
应用程序创建Servlet容器; - Tomcat继续查找
xyz.war
定义的Servlet、Filter和Listener组件,按顺序实例化每个组件(Listener最先被实例化,然后是Filter,最后是Servlet); - 用户发送HTTP请求,Tomcat收到请求后,转发给Servlet容器,容器根据应用程序定义的映射,把请求发送个若干Filter和一个Servlet处理;
- 处理期间产生的事件则由Servlet容器自动调用Listener。
其中,第3步实例化又有很多方式:
- 通过在
web.xml
配置文件中定义,这也是早期Servlet规范唯一的配置方式; - 通过注解
@WebServlet
、@WebFilter
和@WebListener
定义,由Servlet容器自动扫描所有class后创建组件,这和我们用Annotation配置Bean,由IoC容器自动扫描创建Bean非常类似; - 先配置一个
Listener
,由Servlet容器创建Listener
,然后,Listener
自己调用相关接口,手动创建Servlet
和Filter
。
到底用哪种方式,取决于Web应用程序自己如何编写。对于使用Spring框架的Web应用程序来说,Servlet、Filter和Listener数量少,而且是固定的,应用程序自身编写的Controller数量不定,但由IoC容器管理,因此,采用方式3最合适。
web.xml和代码方式实现
而servlet容器的初始化我们可以用这两种方式来实现:
web.xml
:tomcat启动时会自动读取web.xml中的配置信息后创建Servlet容器ServletContainerInitialize
:它通过SPI机制,当web容器启动时它会找到注解@HandlesTypes
中标注类的实现类去执行类中的onStarup
方法,免去了web.xml
的编写。
我们这里主要介绍第二种通过代码的方式实现。
代码实现容器初始化
目前实际使用中已经很少去使用xml这样的方式进行配置了,我们通常都更加倾向于去使用Java编写配置类来实现。而ServletContainerInitializer
就是提供的一种用于取代web.xml的方式,通过这种方式我们可以向servlet容器中添加servlet,listener等容器初始化操作。
ServletContainerInitializer
:
public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}
- 这个接口仅有一个方法
onStartup
,听方法名可以知道,当servlet启动时调用的就是实现类的onStartup
方法来进行初始化。
下面我们来看Spring中实现这个接口的类SpringServletContainerInitializer
:
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if(webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if(!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)waiClass.newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if(initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
代码意思:
- 判断
webAppInitializerClasses
这个Set集合是否为空。- 如果不为空的话,找到这个set中不是接口,不是抽象类,并且是
WebApplicationInitializer
接口实现类的类,将它们保存到list
中。
- 如果不为空的话,找到这个set中不是接口,不是抽象类,并且是
- 对
list
按一定规则排序后,遍历list调用其onStartup
。
从上面逻辑我们可以知道:实际调用的是接口WebApplicationInitializer
的实现类的onStartup
,而这个接口是由@HandlesTypes({WebApplicationInitializer.class})
注解指定的。
因此我们再看接口WebApplicationInitializer
:
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
- 该接口和
ServletContainerInitializer
相同也有一个onStartup
但这里仅有一个servletContext上下文的参数。
总结:SpringMVC初始化servlet容器时实际上是调用WebApplicationInitializer
实现类的onStartup
来进行初始化的。
容器初始化过程
从SpringMVC的功能实现来说,初始化过程应该需要两个东西:
- 启动IOC容器。
- 创建一个Servlet来处理分发请求给Controller,SpringMVC称之为
DispatcherServlet
。
在之前JavaWeb的介绍中我们知道,我们可以先创建一个Listener
组件来完成上面两个东西的创建,我们来看SpringMVC的源码。
下面是SpringMVC提供的初始化类的继承层次图:
这里使用了很多的模板方法设计模式,将每个继承层次的职责划分得很清楚:
AbstractContextLoaderInitializer
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
protected final Log logger = LogFactory.getLog(this.getClass());
public AbstractContextLoaderInitializer() {
}
public void onStartup(ServletContext servletContext) throws ServletException {
this.registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = this.createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(this.getRootApplicationContextInitializers());
servletContext.addListener(listener);
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
@Nullable
protected abstract WebApplicationContext createRootApplicationContext();
@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
- 这个层次的职责是创建根IOC容器,也就是除Controller层的IOC容器。
- 该类在
onStartup
调用了registerContextLoaderListener
,并且又在其中调用了两个抽象方法- createRootApplicationContext:创建根IOC容器。
- getRootApplicationContextInitializers:
AbstractDispatcherServletInitializer
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
public AbstractDispatcherServletInitializer() {
}
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
this.registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = this.getServletName();
Assert.state(StringUtils.hasLength(servletName), "getServletName() must not return null or empty");
WebApplicationContext servletAppContext = this.createServletApplicationContext();
Assert.state(servletAppContext != null, "createServletApplicationContext() must not return null");
FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);
Assert.state(dispatcherServlet != null, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(this.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.");
} else {
registration.setLoadOnStartup(1);
registration.addMapping(this.getServletMappings());
registration.setAsyncSupported(this.isAsyncSupported());
Filter[] filters = this.getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
Filter[] var7 = filters;
int var8 = filters.length;
for(int var9 = 0; var9 < var8; ++var9) {
Filter filter = var7[var9];
this.registerServletFilter(servletContext, filter);
}
}
this.customizeRegistration(registration);
}
}
protected abstract WebApplicationContext createServletApplicationContext();
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
//....省略
-
这个层次的职责是创建
DispatcherServlet
,并且DispatcherServlet
也需要一个IOC容器,这个容器是管理Controller Bean
的容器。 -
这个类同样将创建IOC容器的逻辑定义为抽象方法给子类去实现。
AbstractAnnotationConfigDispatcherServletInitializer
现在上面的抽象类剩下了创建根IOC容器和webIOC容器的逻辑:
public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
public AbstractAnnotationConfigDispatcherServletInitializer() {
}
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = this.getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
} else {
return null;
}
}
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = this.getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
@Nullable
protected abstract Class<?>[] getRootConfigClasses();
@Nullable
protected abstract Class<?>[] getServletConfigClasses();
}
createRootApplicationContext
:创建根IOC容器createServletApplicationContext
:创建Servlet的IOC容器
我们这里只看注解配置,因此我们用户需要继承该类并实现两个抽象方法,提供创建容器的JavaConfig类。
总结
SpringMVC通过web.xml的方式或者JavaConfig的方式创建一个Listener完成IOC容器的创建和DispatcherServlet的创建,它们分别由下面两个类完成:
- ContextLoaderListener:一个监听器,会将传入的IOC容器存储到ServletContext中,实现根IOC容器的保存。
- DispatcherServlet:用于分发请求给Controller
etApplicationContext`:创建Servlet的IOC容器
我们这里只看注解配置,因此我们用户需要继承该类并实现两个抽象方法,提供创建容器的JavaConfig类。
总结
SpringMVC通过web.xml的方式或者JavaConfig的方式创建一个Listener完成IOC容器的创建和DispatcherServlet的创建,它们分别由下面两个类完成:
- ContextLoaderListener:一个监听器,会将传入的IOC容器存储到ServletContext中,实现根IOC容器的保存。
- DispatcherServlet:用于分发请求给Controller