SpringMVC启动-源码跟踪

环境搭建

目前搭建一个标准的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 配置

<mvc:annotation-driven />
<context:component-scan base-package="cn.jhs.mvc"/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/html"/>
    <property name="suffix" value=".jsp"/>
</bean>

其中,<context:component-scan />还可以如下配置

<!--
    只扫描@Controller,其余的bean交给Spring的容器去管理,此时web.xml需要配置listener
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
-->
<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>

创建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方法主要做了三件事:

  1. 获取Spring根容器rootContext
  2. 设置webApplicationContext并根据情况调用onRefresh方法
  3. 将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配置

猜你喜欢

转载自blog.csdn.net/it_freshman/article/details/81322147