在了解springmvc的工作流程之前,我们需要了解servlet以及servlet容器。
1. 为什么有servlet
互联网技术的发展,使得基于HTTP和HTML的web应用急速增长。用户页不满足于仅浏览静态页面,需要一些交互操作,获取一些动态结果。如果基于HTTP协议实现服务器端软件增强功能太过 复杂,所以需要一些扩展机制来实现用户想要的功能。早期使用的Web服务器扩展机制是CGI(Common Gateway Interface,公共网关接口)。使用这种方法,用户单击某个链接或输入网址来访问CGI程序,web服务器收到请求后,运行该CGI程序,对用户请 求进行处理,紧接着将处理结果并产生一个响应,该响应被返回给web服务器,web服务器对响应进行包装,以HTTP响应的方式返回给浏览器。
CGI程序在一定程度上解决了用户需求。不过还存在一些不足之处,如CGI程序编写困难,响应时间较长,以进程方式运行导致性能受限。于是1997年,sun公司推出了Servlet技术,作为java阵营的CGI解决方案。
2. 什么是servlet
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。
servlet接口定义的是一套处理网络请求的规范,所有实现servlet类的服务都需要实现规范定义的方法。定义如下:
public interface Servlet {
// 初始化方法
public void init(ServletConfig config) throws ServletException;
// 获取配置信息
public ServletConfig getServletConfig();
// 服务方法,处理逻辑
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
// 返回servlet创建者信息,比如author等
public String getServletInfo();
// 销毁
public void destroy();
}
public interface ServletConfig {
// servlet名字
public String getServletName();
// servlet的上下文信息
public ServletContext getServletContext();
// 初始化参数
public String getInitParameter(String name);
/**
* @return an <code>Enumeration</code> of <code>String</code> objects
* containing the names of the servlet's initialization parameters
*/
public Enumeration<String> getInitParameterNames();
}
3. 怎么使用servlet
servlet作为一种规范,没有main方法,不能独立运行,它必须被部署到Servlet容器中,由容器来实例化和调用 Servlet的方法。
Servlet容器也叫做Servlet引擎,是Web服务器或应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务,解码基于 MIME的请求,格式化基于MIME的响应。最常见的servlet容器是tomcat。在tomcat中servlet的生命周期如图:
关于web容器,可参见:
4. springmvc和servlet有什么关系
springmvc是对servlet的封装,核心的DispatcherServlet最终继承自HttpServlet。同时,springmvc做为spring家族一员,将和web相关的处理器托付给springmvc容器,使得对象的管理和spring保持一致。
5. SpringMVC的启动过程
在介绍springmvc的启动过程之前,先了解一下几个基本概念。
Java规范定义 | ServletContext | Servlet规范中定义的,是所有Servlet的上下文 |
tomcat定义 | StandardContext | Tomcat中定义的容器,一个Web应用对应一个StandardContext |
ServletContextListener | 同属于Tomcatjar包,实现该接口的类可以监听到 StandardContext初始化完成事实和销毁完成事件 | |
spring | ApplicationContext | Spring中定义的容器接口,MVC使用的实现类是WebApplicationContext |
springmvc | DispatcherServlet | Spring定义的Servlet,负责处理所有请求,并分配到达对Controller |
通过web.xml配置启动springMVC的web项目
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml</param-value>
</context-param>
<!-- 字符集 过滤器 -->
<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>
<!-- 容器启动监听 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Spring view分发器 -->
<servlet>
<servlet-name>servletDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servletDispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
web容器启动的时候,会创建ServletContext,这个过程中,如果实现了ServletContextListener接口,会被调用contextInitialized方法。
public interface ServletContextListener extends EventListener {
// context创建的时候执行
public default void contextInitialized(ServletContextEvent sce) {
}
// context销毁的时候执行
public default void contextDestroyed(ServletContextEvent sce) {
}
}
在配置web.xml的时候,会配置监听器ContextLoaderListener, 它实现了 ServletContextListener 接口, 主要用来加载根容器。ContextLoaderListener类图结构有:
查看ContextLoaderListener的实现,
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
// 调用父类的初始化方法
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
执行初始化方法,会调用initWebApplicationContext方法,
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!");
}
long startTime = System.currentTimeMillis();
try {
// 这个时候是没有容器的,所以创建一个容器。如果是java config,可以传入一个context
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
// 将容器设置到servletcontext中,
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 这里会做refresh操作
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);
}
return this.context;
}
catch (RuntimeException | Error ex) {
throw ex;
}
}
因为web.xml配置,还没有WebApplicationContext,所以会创建
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
创建时,需要判断根Context的类别,web.xml是通过initparam方式传递进来
protected Class<?> determineContextClass(ServletContext servletContext) {
// 会从web.xml中获取初始化参数contextClass
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
// 如果没有配置contextClass,从DefaultStrategies属性文件中获取
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
如果没有配置,则会采用默认的方式获取根容器
public class ContextLoader {
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
// 加载默认的属性数据
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
}
回到initWebApplicationContext方法中,this.context 并没有处于active状态,设置根的父容器为null,然后做容器的初始化过程configureAndRefreshWebApplicationContext。具体的方法实现有:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 设置容器的Id
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 设置容器中的上下文信息
wac.setServletContext(sc);
// 这里获取了contextConfigLocation
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// 及早的获取到环境变量中的初始化参数
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
这里有一个小插曲,customizeContext方法中,会做ApplicationContextInitializer的加载,这一块内容在后面会有讲到,这里web.xml并没有涉及到,所以此方法会执行为空。接着做容器的刷新操作。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
到这里,contextLoaderListener的工作完成了,跟容器WebApplicationContext也创建完毕。
6. 通过ServletContainerInitializer方式启动springmvc
web容器启动的时候,为第三方组件提供了做一些初始化操作的功能,servlet规范中通过ServletContainerInitializer
实现此功能。每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。
Tomcat容器的ServletContainerInitializer,主要由Context容器和ContextConfig监听器共同实现,ContextConfig监听器负责在容器启动时读取每个web应用的WEB-INF/lib目录下包含的jar包的META-INF/services/javax.servlet.ServletContainerInitializer,以及web根目录下的META-INF/services/javax.servlet.ServletContainerInitializer,通过反射完成这些ServletContainerInitializer的实例化,然后再设置到Context容器中,最后Context容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法。
ServletContainerInitializer是一个接口,在javax.servlet-api中有定义
public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}
在spring-web 的jar包中,META-INF/services/javax.servlet.ServletContainerInitializer=SpringServletContainerInitializer。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@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);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
Springweb中,WebApplicationInitializer的实现有:
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
protected final Log logger = LogFactory.getLog(getClass());
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
}
}
@Nullable
protected abstract WebApplicationContext createRootApplicationContext();
@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
// 这里会注册dispatcherServlet
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// 这里很重要,已经传入了跟容器信息
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());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
// 做一些初始化处理
customizeRegistration(registration);
}
...
}
接着,再看一个Initializer类
public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
// 定义了根容器类
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
}
// 定义了dispatcherServlet容器
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
@Nullable
protected abstract Class<?>[] getRootConfigClasses();
@Nullable
protected abstract Class<?>[] getServletConfigClasses();
}
所以,可以自定义实现WebApplicationContextInitializer ,作为springmvc启动入口:
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
// 创建根容器
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootConfig.class);
// 创建 ContextLoaderListener
// 用来管理 root WebApplicationContext 的生命周期:加载、初始化、销毁
container.addListener(new ContextLoaderListener(rootContext));
// 创建 dispatcher 持有的上下文容器
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(WebConfig.class);
// 注册、配置 dispatcher servlet
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
也可以继承AbstractAnnotationConfigDispatcherServletInitializer,
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
}
@Configuration
@EnableWebMvc
@ComponentScan("com.xm.web")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
configurer.enable();
}
}
@Configuration
@ComponentScan(basePackages={"xm.web"},
excludeFilters={
@Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)
})
public class RootConfig {}