一、SpringMVC的整体架构
SpringMVC中核心Servlet的继承结构如下图所示:
从上图可以看出,SpringMVC与Web容器的衔接由HttpSevletBean完成。主要涉及五个核心类:GenericServlet属于java,HttpServletBean、FrameworkServlet、DispatcherServlet属于SpringMVC。
其中Environment用于存放ServletContext、ServletCofnig、JndiProperty、系统环境变量和系统属性。
二、HttpServletBean
HttpServletBean直接继承自HttpServlet,实现了与Servlet容器的衔接。同时,HttpServletBean重写了HttpServlet的init方法,对SpringMVC进行第一步的初始化工作。
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
//将ServletConfig中的参数封装到PropertiValues中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//创建BeanWraper对象
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//模板方法
initBeanWrapper(bw);
//将BeanWrapper设置到DispatcherServlet(木有看懂)
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
//模板方法,链接FrameworkServlet的入口
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
以上就是HttpServletBean在SpringMVC在初始化的过程中所做的,首先将Servlet配置的参数设置到DispatcherServlet,然后调用模板方法initServletBean();进入下一步的初始化。
其中,BeanWrapper是Spring提供了一个用来操作JavaBean属性的工具类,用法类似于下面所示:
public class BeanWrapperTest {
public static void main(String[] args) {
Person person = new Person();
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(person);
bw.setPropertyValue("name", "张三");
System.out.println(person.getName());
bw.setPropertyValue("name", "李四");
System.out.println(person.getName());
}
private static class Person{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
二、FrameworkServlet
HttpServletBean在init()方法中调用了模板方法initServletBean(),FrmeworkServlet覆盖了HttpServletBean的initServletBean()的实现,从此处开始,正式进入Spring环境的初始化。
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
//初始化WebApplicationContext
this.webApplicationContext = initWebApplicationContext();
//模板方法,衔接DispatcherServlet
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
在这个方法中的核心代码有两句:
//初始化WebApplicationContext
this.webApplicationContext = initWebApplicationContext();
//模板方法,衔接DispatcherServlet
initFrameworkServlet();
/**
* This method will be invoked after any bean properties have been set and
* the WebApplicationContext has been loaded. The default implementation is empty;
* subclasses may override this method to perform any initialization they require.
* @throws ServletException in case of an initialization exception
*/
protected void initFrameworkServlet() throws ServletException {
}
由于initFrameworkServlet()是模板方法,所以,FrameworkServlet的主要作用是初始化WebApplicationContext。
看一下initWebApplicationContext()的具体逻辑:
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
//根据固定的key从ServletContext中获取rootContext,通常是整个应用的Spring容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果已经通过构造方法设置了WebApplication
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
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
//将rootContext设到WebApplicationContext中 cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//如果没有设置WebApplicationContext
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
//从ServletContext中查找WebApplication,查找时使用的key是通过在DispatcherServlet
中配置contextAttribute参数指定的
wac = findWebApplicationContext();
}
//如果在ServletContext中没有WebApplicationContext实例
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
//新创建一个WebApplicationContext实例
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
通常情况下,这一步都会是创建一个WebApplicationContext,创建过程主要分为两步,看一下具体的创建逻辑:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//默认创建XmlWebApplicationContext
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//调用HttpServletBean的getEnvironment并设置到WebApplicationContext
wac.setEnvironment(getEnvironment());
//设置rootContext
wac.setParent(parent);
//将设置的contextConfigLocation参数传入wac,默认传入WEB-INF/[Servlet-Name]-Servlet.xml wac.setConfigLocation(getContextConfigLocation());
//进一步设置并调用refresh() configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
}
}
//设置ServletContext到WebApplicationContext
wac.setServletContext(getServletContext());
//将ServletConfig设置到WebApplicationContext
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
综上所述,initWebApplicationContext主要做了三件事:
1.获取Spring跟容器rootContext
2.设置WebApplicationContext并根据情况调用onRefresh方法
3.将webApplicationContext设置到Servlet
三、DispatcherServlet
在FrameworkServlet的initWebApplicationContext()中,完成对WebApplicationContext的初始化后,会调用模板方法onRefresh(),这个方法是FrameworkServlet和DispatcherServlet进行衔接的桥梁,在DispatcherServlet中重写了onRefresh()方法,并对DispatcherServlet进行了初始化。
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
从上面的代码可知, DispatcherServlet在SpringMVC初始化过程中的主要作用是初始化八种组件。
总结:
SpringMVC中Servlet的继承结构
Servlet
|-GenericServlet
|-HttpServlet
|-HttpServletBean
|-FrameworkServlet
|-DispatcherServlet
作用:
HttpServletBean:获取DispatcherServlet配置参数
FrameworkServlet:初始化WebApplicationContext
DispatcherServlet:初始化SpringMVC九大组件