SpringMVC作为MVC框架近年来被广泛地使用,其与Mybatis和Spring的组合,也成为许多公司开发web的套装。SpringMVC继承了Spring的优点,对业务代码的非侵入性,配置的便捷和灵活,再加上注解方式的简便与流行,SpringMVC自然成为web开发中MVC框架的首选。
SpringMVC的设计理念,简单来说,就是将Spring的IOC容器与Servlet结合起来,从而在IOC容器中维护Servlet相关对象的生命周期,同时将Spring的上下文注入到Servlet的上下文中。依靠Servlet的事件和监听机制来操作和维护外部请求,以及组装和执行请求对应的响应。
1.XML配置
SpringMVC想与Servlet相结合,首先得在Servlet容器中进行配置。以Tomcat为例,通常在web.xml文件中配置一个监听器和SpringMVC的核心Servlet。
监听器
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:app-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
核心Servlet
<servlet>
<servlet-name>dispatcher</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>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
当我准备研究SpringMVC源码时,我问出了一个早应该问的问题:为什么配置了DispatcherServlet,还需要一个监听器,而且都能加载配置文件?在context-param中的配置文件要不要在DispatcherServlet中的init-param再加上?相信很多刚用SpringMVC的人都闪现过这样的问题。
翻阅过源码后,明白了SpringMVC通过这种方式实现了父子上下文容器结构。
Tomcat启动时,监听器ContextLoaderListener创建一个XMLWebApplicationContext上下文容器,并加载context-param中的配置文件,完成容器的刷新后将上下文设置到ServletContext。当DispatcherServlet创建时,先进行初始化操作,从ServletContext中查询出监听器中创建的上下文对象,作为父类上下文来创建servlet的上下文容器,并加载Servlet配置中的init-param的配置文件(默认加载/WEB-INF/servletName-servlet.xml,servletName为DispatcherServlet配置的servlet-name),然后完成容器的刷新。子上下文可以访问父上下文中的bean,反之则不行。
父子上下文容器结构如下
通常是将业务操作及数据库相关的bean维护在Listener的父容器中,而在Servlet的子容器中只加载Controller相关的业务实现的bean。从而将业务实现和业务的具体操作分隔在两个上下文容器中,业务实现bean可以调用业务具体操作的bean。
2.编码配置
随着注解方式的流行,很多不喜欢xml配置的方式。SpringMVC提供了一种不基于传统的web.xml的编码配置方式。Servlet3.0提供了ServletContainerInitializer接口,支持web应用启动阶段动态注册servlet,filter和listener。SpringMVC实现了此接口,即SpringServletContainerInitializer类。通过Servlet3.0的注解@HandlesTypes,指定WebApplicationInitializer类型,在web应用启动时加载servlet,filter及listener。
SpringMVC提供了一个便捷的WebApplicationInitializer的抽象配置类:AbstractAnnotationConfigDispatcherServletInitializer。由它我们来实现一个基本的配置。
public class AnnotationConfigWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// 根上下文配置类配置
// 对应web.xml中的ContextLoaderListener
protected Class<?>[] getRootConfigClasses() {
return new Class[] { AppRootConfig.class };
}
// servlet上下文配置类配置
// 对应web.xml中的DispatcherServlet
protected Class<?>[] getServletConfigClasses() {
return new Class[] { AppServletConfig.class };
}
// DispatcherServlet的servlet-mapping配置
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
AppRootConfig中为基础服务和数据库实现等,使用@ComponentScan进行bean扫描,使用@Bean定义一些数据库配置。这里简单一点,不涉及数据库的配置。
@Configuration
@ComponentScan(basePackages="com.lcifn.springmvc.bean")
public class AppRootConfig {
}
而web上下文的配置,需要增加@EnableWebMvc注解来增加对MVC的支持。如果对MVC有自定义的配置,比如ViewResolver,也可以实现WebMvcConfigurationSupport配置支持类。
@Configuration
@ComponentScan(basePackages="com.lcifn.springmvc.controller")
public class AppServletConfig extends WebMvcConfigurationSupport{
// 设置视图解析器的前缀和后缀
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
super.configureViewResolvers(registry);
registry.viewResolver(new InternalResourceViewResolver("/WEB-INF/", ".jsp"));
}
}
另外可以重写WebMvcConfigurationSupport的方法来增加或修改Web相关的配置,比如addInterceptors(增加拦截器)及addReturnValueHandlers(增加返回值处理器)等。
编码方式让web.xml得以简化,但要求servlet-api的版本必须为3.0+。
3.ServletContext启动监听
ServletContextListener监听ServletContext的生命周期。每个web application对应一个ServletContext,用于servlet与servlet容器沟通的中介。它定义两个方法,context初始化和context销毁。
public interface ServletContextListener extends EventListener {
public void contextInitialized(ServletContextEvent sce);
public void contextDestroyed(ServletContextEvent sce);
}
SpringMVC的ContextLoaderListener实现了此接口,在web application启动时创建一个Spring的ROOT上下文。
4.Servlet的初始化
Servlet的生命周期从第一次访问Servlet开始,Servlet对象被创建并执行初始化操作。而每次请求则由servlet容器交给Servlet的service方法执行,最后在web application停止时调用destroy方法完成销毁前处理。
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public void destroy();
}
在web.xml的servlet配置选项中有一个load-on-startup,其值为整数,标识此Servlet是否在容器启动时的加载优先级。若值大于0,按从小到大的顺序被依次加载;若为0,则标识最大整数,最后被加载;若值小于0,表示不加载。默认load-on-startup的值为-1。servlet的加载是在加载完所有ServletContextListener后才执行。
SpringMVC的核心Servlet-DispatcherServlet初始化时,创建servlet上下文,并对web相关的配置进行初始化。
对于SpringMVC框架原理的理解,一个是父子上下文的创建以及M,V,C各组件配置的初始化,另一个就是外部请求在各个组件间进行流转和处理并最终转化为响应的过程。通常对一个框架的理解要先从其总架构设计去了解,然后沿着架构中组件间的流转再去看具体的实现,这样才能在宏观和微观综合理解。大家好,我介绍一下,这是我理解的SpringMVC,源码解析下一章见。