环境搭建
目前搭建一个标准的maven-webapp工程,结构如下:
依赖包
SpringMvc的环境搭建,用maven构建,只需要引入如下maven依赖,spring版本3.1.0.RELEASE(公司老项目,很多都是3.x版本 orz)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
<!--tomcat 默认使用3.1,Tomcat7默认使用3.0 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
web.xml
web.xml配置也十分简单,只需要配置org.springframework.web.servlet.DispatcherServlet
即可。
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring/application-*.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
xml 配置
ContextLoaderListener 和 DispatcherServlet 个各创建一个WebApplicationContext;
ContextLoaderListener 默认加载 context-param为contextConfigLocation中的配置文件信息,它是DispatcherServlet创建容器的父容器
。 为了防止交叉定义bean:
ContextLoaderListener
: 配置所有非MVC组件:Service,dao…DispatcherServlet
: 配置所有MVC相关组件 Controller,Handler,Resovler…
application-context.xml
<!-- use-default-filters默认为true, 表示配置扫描所有bean ...
exclude-filter 排除所有controller -->
<context:component-scan base-package="cn.jhs.mvc" >
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
application-mvc.xml
<mvc:annotation-driven> </mvc:annotation-driven>
<!-- include-filter ,仅扫描 Controller-->
<context:component-scan base-package="cn.jhs.mvc" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/html"/>
<property name="suffix" value=".jsp"/>
</bean>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<!-- 默认paramName为theme-->
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
创建Controller,view等
public class HiController {
@RequestMapping("/index")
public String index(){
return "/hi/index";
}
@RequestMapping("/say/{msg}")
@ResponseBody
public String say(@PathVariable("msg") String msg){
return "hi:"+msg;
}
}
完成如上步骤,环境搭建完成。
SpringMVC启动
web容器启动的过程,首先加载web.xml中listener -> filter -> servlet.init()
,由现有配置可知,程序的“入口”,是org.springframework.web.servlet.DispatcherServlet.init(ServletConfig config)
,但是观察DispatcherServlet并没有发现它有init方法,这是为什么呢?
当web容器未找到目标方法时,会向DispatcherServlet的父类中寻找该方法。
SpringMVC servlet结构图
代码结构如下:
public class DispatcherServlet extends FrameworkServlet {}
public abstract class FrameworkServlet extends HttpServletBean {}
public abstract class HttpServletBean extends HttpServlet implements EnvironmentAware {}
public abstract class HttpServlet extends GenericServlet{}
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable{}
其中EnvironmentAware接口的代码如下:
public interface EnvironmentAware extends Aware {
void setEnvironment(Environment var1);
}
常见的还有ApplicationContextAware
同样的它有setApplicationContext(ApplicationContext var1)接口
,XXXAware
都会通过setXXX方法为实现类提供XXX的使用能力。
Servlet
javax.servlet.Servlet,是Serve+Applet的缩写,表示一个服务器应用的接口规范。
public interface Servlet {
/**
* init方法在容器启动时被调用,`当load-on-setup设置为负数或者未设置时,
* 是在Servlet第一次访问时才会被调用`,且只调用一次。
*
* @Param config:contextConfigLocation等参数就是封装才此
*/
public void init(ServletConfig config) throws ServletException;
//service方法用来处理具体的请求
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
}
GenericServlet
GenericServlet 实现了javax.servlet.Servlet.init(ServletConfig config)
方法,并提供了无参的init(),模板方法供子类实现
,以及提供了新方法getServletContext()
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
//模板方法,供子类实现
public void init() throws ServletException {
}
public ServletContext getServletContext() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletContext();
}
}
HttpServlet
HttpServlet是用HTTP协议实现的Servlet基类,它主要关心的如何处理请求,所以它主要重写了service(ServletRequest req, ServletResponse res)
public abstract class HttpServlet extends GenericServlet
{
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
//....省略
//转换,调用service(HttpServletRequest req, HttpServletResponse resp)
service((HttpServletRequest) req, (HttpServletResponse) res);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
doGet(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} //....省略
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
//....省略
}
}
HttpServlet主要关注请求的处理,所以在启动时,无需过于关注此类。
HttpServletBean
由//GenericServlet.init()
可知,下一步需要调用DispatcherServlet无参的init()方法
,此类在HttpServletBean定义实现
。
public abstract class HttpServletBean extends HttpServlet implements EnvironmentAware {
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
//将Servlet配置的参数封装到pvs中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//this即DispatcherServlet
//通过PropertyAccessorFactory封装成一个BeanWrapper对象,通过BeanWrapper对DispatcherServlet进行属性设置等操作。
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
//模板方法,可在子类实现调用。做一些初始化工作
initBeanWrapper(bw);
//将pv配置,即servletConfig包含web.xml中配置的init-param给设置到DispatcherServlet中
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
throw ex;
}
//模板方法,子类初始化入口
initServletBean();
}
}
FrameworkServlet
由上可知,FrameworkServlet方法入口是initServletBean();
public abstract class FrameworkServlet extends HttpServletBean {
@Override
protected final void initServletBean() throws ServletException {
//log,try-catch省略.....
//a.初始化webApplicationContext
this.webApplicationContext = initWebApplicationContext();
//b.初始化FrameworkServlet模板方法---当前为空
initFrameworkServlet();
}
}
可以得知FrameworkServlet的构建过程中,主要的工作是初始化webApplicationContext :initWebApplicationContext()
。
protected WebApplicationContext initWebApplicationContext() {
//a1.获取spring的根容器rootContext
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
//如果context已经通过构造方法设置了,直接使用;
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
//如果context没有被refreshed,按照如下操作进行;如设置parent context
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
//a2.通过构造函数中的webApplicationContext做一些设置
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//a3 当WebApplicationContext已经存在于ServletContext中,通过配置在Servlet中Attribute获取
wac = findWebApplicationContext();
}
if (wac == null) {
//a4 如果context还没有创建,创建一个
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 当refreshEventReceived没有被触发,调用此方法。模板方法,由子类即DispatcherServlet实现。
onRefresh(wac);
}
if (this.publishContext) {
//a6 把WebApplicationContext作为ServletContext的一个属性
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
initWebApplicationContext方法主要做了三件事:
- 获取Spring根容器rootContext
- 设置webApplicationContext并根据情况调用onRefresh方法
- 将webApplicationContext设置到ServletContext中。
获取spring的根容器rootContext
默认情况下spring会将自己的容器设置成ServletContext的属性。
//org.springframework.web.context.support.WebApplicationContextUtils
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
//WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE =WebApplicationContext.class.getName()+".ROOT"
//即“org.springframework.web.context.WebApplicationContext.ROOT”
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
//省略assert,try-catch...
Object attr = sc.getAttribute(attrName);
return (WebApplicationContext) attr;
}
设置webApplicationContext并根据情况调用onRefresh方法
设置webApplicationContext一共有三种方法。
- 第一种是在构造方法中已经传递了webApplicationContext参数,此时只需要进行一些设置即可。如
configureAndRefreshWebApplicationContext(cwac);
这种方法主要用于Servlet3.0以后的环境中,Servlet3.0之后可以在程序中使用ServletContext.addServlet方式注册Servlet,此时就可以在新建FrameworkServlet和其子类的时候通过构造方法传递已经准备好的webApplicationContext。
- 第二种方法是
findWebApplicationContext()
:此时webApplicationContext已经存在于ServletContext中了。只需要在配置Servlet的时候,将ServletContext中的webApplicationContext中name配置到contextAttribute
属性即可。通常不这样做,会报错。
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
return wac;
}
- 第三种方式是在前两种方法都无效的情况下,自己创建一个。
正常情况下就是使用这种方式。
创建过程是在createWebApplicationContext
方法中,方法内部同第一种方式也调用了configureAndRefreshWebApplicationContext(cwac);
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//获取创建类型:默认值 XmlWebApplicationContext.class
Class<?> contextClass = getContextClass();
//检查类型
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException();
//省略..... throw内容
}
//创建
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setParent(parent);
//将web.xml设置的conextConfigLocation设置进wac,默认值为"/WEB-INF/"+namespace+".xml" ,即 /WEB-INF/[servleName]-servlet.xml
//此段逻辑见:XmlWebApplicationContext.getDefaultConfigLocations()
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
//添加监听ContextRefreshListener()监听器,监听ContextRefreshEvent事件。
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
//模板方法---此处为空
postProcessWebApplicationContext(wac);
//执行配置在web.xml <servlet><init-param>contextInitializerClasses<init-param>的类,这些类必须实现ApplicationContextInitializer接口,的initialize()方法。
applyInitializers(wac);
//springcontext-refresh,默认值为XmlApplicationContext
wac.refresh();
}
其中ContextRefreshListener
是FrameworkServletd的一个子类:
//org.springframework.web.servlet.FrameworkServlet
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
public void onApplicationEvent(ContextRefreshedEvent event) {
//当事件触发时,调用父类的onApplicationEvent方法
FrameworkServlet.this.onApplicationEvent(event);
}
}
public void onApplicationEvent(ContextRefreshedEvent event) {
//将refreshEventReceived设为true,表示已经refresh过。
this.refreshEventReceived = true;
//调用子类的onRefresh方法,即DispatcherServlet中该方法。
onRefresh(event.getApplicationContext());
}
第三种方法初始化时已经refresh,不再调用后续的onRefresh方法。同样第一种方法也调用了configureAndRefreshWebApplicationContext()
所以也不再onRefresh了,只有第二种方法初始化时才会调用。
b
if (!this.refreshEventReceived) {
//调用子类的onRefresh方法,即DispatcherServlet中该方法。
onRefresh(wac);
}
将webApplicationContext设置到ServletContext中
最后根据publishContext标识来判断是否将创建出来的webApplicationContext设置到ServletContext属性中。
if (this.publishContext) {
// FrameworkServlet.class.getName() + ".CONTEXT."+getServletName();
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
DispatcherServlet
由上可知onRefresh()
是DispatcherServlet 的入口。onRefresh简单的调用了initStrategies()
方法。
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
//初始化供servlet时候用的策略对象。 可以被子类覆盖以实现其他策略。
protected void initStrategies(ApplicationContext context) {
//初始化MultipartResolver处理文件上传。 默认没有配置,即不处理。
initMultipartResolver(context);
//初始化LocaleResolver 配置国际化的i18n,默认AcceptHeaderLocaleResolver
initLocaleResolver(context);
//初始化ThemeResolver 通过界面主题
initThemeResolver(context);
//初始化HandlerMappings:处理映射器,为用户发送的请求找到合适的Handler Adapter
initHandlerMappings(context);
//初始化HandlerAdapters:处理器适配器,为请求寻找实际调用处理函数
initHandlerAdapters(context);
//初始化HandlerExceptionResolvers 异常处理器:根据异常设置ModelAndView,并通过render方法渲染。
initHandlerExceptionResolvers(context);
//初始化RequestToViewNameTranslator,从request中获取viewName
initRequestToViewNameTranslator(context);
//初始化ViewResolvers,视图解析,通过Stirng类型viewName找到View
initViewResolvers(context);
//初始化FlashMapManager
initFlashMapManager(context);
}
除了MultipartResolver没有设置默认值,其他配置全部都设有默认值,这些默认值是存放在DispatcherServlet.class同目录下的DispatcherServlet.properties中的。
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.DefaultFlashMapManager
以initThemeResolver()为例
private void initThemeResolver(ApplicationContext context) {
try {
//加载context中配置的ThemeResolver
this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
}
catch (NoSuchBeanDefinitionException ex) {
//如果没有配置,则使用默认配置
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
}
}
//获取默认配置方法
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
List<T> strategies = getDefaultStrategies(context, strategyInterface);
//throw 省略.....
return strategies.get(0);
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
//获取默认配置,defaultStrategies在DispacherServlet static{}即<clinit>方法中,读取了上面的DispatcherServlet.properties中的配置信息。
String value = defaultStrategies.getProperty(key);
//省略if,try-catch,throw ....
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
for (String className : classNames) {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
//通过context容器,创建默认配置bean.
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
return strategies;
}
自此SpringMVC容器的启动过程完成。
其中FrameworkServlet.configureAndRefreshWebApplicationContext()中调用
wac.refresh();
这段逻辑,参见Spring容器启动。
ContextLoaderListener
通常我们还有奖springMVC和spring的bean分用两个不同的容器管理bean,即
- SpringMVC容器管理controller,viewerResolver等
- Spring管理其他bean
这是需要使用ContextLoaderListener
,配置如下:
<web-app>
<display-name>springmvc-empty</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring/application-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring/application-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
类结构图
由类图可知ContextLoaderListener
实现了ServletContextListener
,方法的入口是:contextInitialized(ServletContextEvent sce)
。
//org.springframework.web.context.ContextLoaderListener
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
它主要逻辑都交由ContextLoader.initWebApplicationContext(ServletContext )
处理:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
/**
* 如果servlet已经存在WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE则抛出异常
*/
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("..........");
}
//try-catch,log.....略
// 创建WebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
//转换为ConfigurableWebApplicationContext
if (this.context instanceof ConfigurableWebApplicationContext) {
configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
}
// 设置到servletContext的WebApplicationContext.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);
}
return this.context;
}
上述代码最重要的一句是:servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
,
在后续的DispatcherServlet
初始化过程中,出示过程中,通过调用FrameworkServlet#initWebApplicationContext()
获取spring-Root容器逻辑中,便可以获取Spring容器,并设为springmvc容器的parentContext;
此时创建的Spring容器,默认类型为XmlWebApplicationContext
,由ContextLoader同级的ContextLoader.properties
配置:
也可以由web.xml参数context-param : contextClass
自主配置。
XmlWebApplicationContext默认加载配置文件位置是:
/WEB-INF/applicationContext.xml
,
可以通过context-param : contextConfigLocation
配置