tomcat + spring mvc原理(七):spring mvc的Servlet和九大标准组件的静态结构与初始化

tomcat + spring mvc原理(七):spring mvc的Servlet和九大标准组件的静态结构与初始化

前言

    前六讲基本已经将tomcat中比较重要的部分讲清楚了,如果有关键内容遗漏或者大家有哪些感兴趣的内容,可以留言给我,我会看情况和大家讨论一下或者发补充的文章。当然如果有错误的地方,也请大家留言给我,我会及时更正。
    本文开始介绍spring mvc的基本原理。按照惯例,会先介绍tomcat中spring mvc的Servlet是如何加载的,然后到spring mvc中各种父Servlet的作用,最后是spring mvc中的标准Servlet实现DispatcherServlet和在DispatcherServlet中初始化和使用的各种组件的基本作用。

tomcat中Servlet的加载

    原理(六)有讲到,部署最后,当context的实例设置完成后,会调用host的addChild(context)。接下来addChild方法内部调用context的start()方法,实际调用的是父类ContainerBase的start()方法,在这个方法中依次执行context的initInternal()方法和startInternal()方法。在Context的startInternal()中有很多代码逻辑,加载Servlet的代码在:

// Load and initialize all "load on startup" servlets
if (ok) {
    if (!loadOnStartup(findChildren())){
        log.error(sm.getString("standardContext.servletFail"));
        ok = false;
    }
}

loadOnStartup(Container children[])入参是Context子容器的数组,即Wrapper的数组。

public boolean loadOnStartup(Container children[]) {
    TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
    //获取加载顺序
    for (int i = 0; i < children.length; i++) {
        Wrapper wrapper = (Wrapper) children[i];
        int loadOnStartup = wrapper.getLoadOnStartup();
        if (loadOnStartup < 0)
            continue;
        Integer key = Integer.valueOf(loadOnStartup);
        ArrayList<Wrapper> list = map.get(key);
        if (list == null) {
            list = new ArrayList<>();
            map.put(key, list);
        }
        list.add(wrapper);
    }

    //加载Servlet
    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } catch (ServletException e) {
                getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                      getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                if(getComputedFailCtxIfServletStartFails()) {
                    return false;
                }
            }
        }
    }
    return true;
}

    第一个循环获取所有Servlet的加载顺序值的大小,第二个根据获取的顺序值进行Wrapper加载。wrapper.load()中调用了Wrapper的loadServlet(),使用InstanceManager工具根据配置的servletName(如果是标准的servlet,这里servlet名就是org.springframework.web.servlet.DispatcherServlet)创建了一个实例,赋值给了Wrapper中持有的Servlet instance引用。

public synchronized Servlet loadServlet() throws ServletException {
    ......
    InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
     ......
       servlet = (Servlet)instanceManager.newInstance(servletClass);
    ......
}
//在方法load中将loadServlet返回的Servlet实例赋值给instance
instance = loadServlet();

然后在load方法中调用了initServlet(instance)。

private synchronized void initServlet(Servlet servlet)
        throws ServletException {
        ......
        servlet.init(facade);
        instanceInitialized = true;
        ......
}

initServlet中调用了servlet.init(facade),其中facade为StandardWrapperFacade,继承自ServletConfig,用来存储Servlet配置。

//StandardWrapper.java
protected final StandardWrapperFacade facade
            = new StandardWrapperFacade(this);

这样整个servlet的初始化就传递到了Servlet的init()方法中了。

Servlet的实现

    查看DispatcherServlet代码时,发现没有实现init()方法,第一反应是父类中实现了init()。在这里插入图片描述
继承树显示DispatcherServlet主要继承了两部分,Servlet系(蓝色箭头追溯)和环境系(接口)。环境系的几个接口,类名以Aware和Capable结尾,还是挺有意思的。这和ApplicationListener中的几个Event可以凑一块,单独开个环境变量获取的party,这里不深究。

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;

    public String getServletInfo();

    public void destroy();
}

init()用来初始化Servlet。service()方法在原理(五)也出现了,用来处理传入的请求和应答体,等讲spring mvc部分处理请求的流程时,这个方法的介绍会是整个流程的开始。getServletInfo可以获取Servlet作者、版权等信息,需要Servlet的作者自己实现。
    getServletConfig()和配置有关,这就要说到原理(一)跳过没讲的配置文件web.xml。在tomcat的安装目录下的conf文件夹下和普通spring mvc(非spring boot)项目的WEB-INF文件夹下一般都有web.xml配置文件。tomcat目录下web.xml主要是系统默认Servlet配置、filter配置和mapping配置(项目暴露的接口路由),配置的格式和项目中配置的web.xml格式一致。这里以spring mvc 项目WEB-INF目录下配置的web.xml为例:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>WEB-INF/myservlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    ......
</web-app>

contextConfigLocation标签定义了项目自己的servlet.xml配置文件的位置。ServletConfig包含了servlet标签和contextConfigLocation标签设定的配置文件的大部分配置内容。从ServletConfig中能获取servlet配置,甚至上一级Context的配置内容。对于ServletConfig类对象的初始化,可以看到在init(ServletConfig config)方法中有从Wrapper中传入这个参数。servlet.xml中可以配置的内容就比较多了,主要是一些bean,比如视图解析器的配置。这里贴一个idea自动生成的servlet.xml配置。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <!--视图解析-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--扫描-->
    <context:component-scan base-package="com.demo.controller"/>

</beans>

spring boot的伟大功绩之一就是让我们从这种繁琐的配置中解放了出来。

GenericServlet和HttpServlet

    GenericServlet是抽象类,继承Servlet, ServletConfig,包含了 ServletConfig类引用config,重载了Servlet和ServletConfig的几个方法,可以作为获取环境ServletConfig的默认实现。值得一提的是GenericServlet对init方法进行了封装,重载了带参数的init(ServletConfig config),对类中封装的ServletConfig引用进行了赋值,然后调用了不带参数的init方法,后者是的代码逻辑由子类实现。上文说到的Servlet的初始化servlet.init(facade),调用的就是GenericServlet的init(ServletConfig config)的方法。

@Override
public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}
public void init() throws ServletException {
    // NOOP by default
}

    HttpServlet继承GenericServlet,这个Servlet和协议相关,主要包括了一些用来处理http请求的方法,包括doHead、doGet、doPost、doPut、doDelete、doOptions、doTrace等,当然还有service方法。service()作为请求的通用入口,然后根据Http请求的类型不同分发给doXxx()方法。由于除了doHead方法,其他方法包括service()方法都被后续的子类FrameworkServlet重载了,HttpServlet相当于只是定义了一个标准,这里也就不再贴代码了。

HttpServletBean和FrameworkServlet

    HttpServletBean最重要的作用是重载了不带参数的init()方法。

@Override
public final void init() throws ServletException {

  PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
  if (!pvs.isEmpty()) {
    try {
      BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
      ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
      bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
      
      initBeanWrapper(bw);
      bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
      if (logger.isErrorEnabled()) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
      }
      throw ex;
    }
  }

  initServletBean();
}

其中,BeanWrapper是一个获取类对象,然后直接修改对象属性的工具。BeanWrapper bw获取了Servlet对象(实际就代表DispatcherServlet对象),然后直接设置ServletConfig相关的配置(“bw.setPropertyValues(pvs, true)”)到Servlet对象中。在init()方法中还提供了initBeanWrapper(bw)和initServletBean()两个模板方法供子类实现。initBeanWrapper直接传入了Servlet的BeanWrapper,提供了直接修改Servlet对象属性的能力。initServletBean()让子类可以初始化任何servlet bean。
    FrameworkServlet类中重载了initServletBean方法,主要只做了一件事:

......
try {
  this.webApplicationContext = initWebApplicationContext();
  initFrameworkServlet();
}
......

initWebApplicationContext()给FrameworkServlet类的应用环境配置的引用赋值。initFrameworkServlet方法和上面的initServletBean方法一样,都是模板方法供子类实现,但是这个方法子类(DispatcherServlet)并没有重载。所以重点还是initWebApplicationContext()方法。
    所谓的webApplicationContext包含了很多重要的东西,比如类加载的ClassLoader、Context配置的ServletContext、Servlet配置的ServletConfig、tomcat的webServer实例、注解相关的AnnotatedBeanDefinitionReader、BeanFactory实例、资源加载的resourceLoader等等。所以initWebApplicationContext()中使用了多重机制,确保能够将这个环境的配置获取。

protected WebApplicationContext initWebApplicationContext() {
  WebApplicationContext rootContext =
      WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  WebApplicationContext wac = null;
  //1.构造方法中已经配置了webApplicationContext,直接获取
  if (this.webApplicationContext != null) {
    if (wac instanceof ConfigurableWebApplicationContext) {
      ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
      if (!cwac.isActive()) {
        if (cwac.getParent() == null) {
          cwac.setParent(rootContext);
        }
        configureAndRefreshWebApplicationContext(cwac);
      }
    }
  }
  //2.ServletContext中已经有WebApplicationContext,
  //从ServletContext获取
  if (wac == null) {
    wac = findWebApplicationContext();
  }
  //3.以上都没有获取,直接创建一个然后配置内容
  if (wac == null) {
    wac = createWebApplicationContext(rootContext);
  }

  if (!this.refreshEventReceived) {
    synchronized (this.onRefreshMonitor) {
      onRefresh(wac);
    }
  }

  if (this.publishContext) {
    String attrName = getServletContextAttributeName();
    getServletContext().setAttribute(attrName, wac);
  }

  return wac;
}

不管是3种方式的哪一种,onRefresh()会有且只调用一次。而onRefresh方式正是由DispatcherServlet实现的。

DispatcherServlet
public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void onRefresh(ApplicationContext context) {
      initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
      initMultipartResolver(context);
      initLocaleResolver(context);
      initThemeResolver(context);
      initHandlerMappings(context);
      initHandlerAdapters(context);
      initHandlerExceptionResolvers(context);
      initRequestToViewNameTranslator(context);
      initViewResolvers(context);
      initFlashMapManager(context);
    }
    ......
}

    DispatcherServlet的onFresh方法调用initStrategies方法初始化了spring mvc的九大组件,分别是HandlerMapping(处理器映射,寻找对应Interceptor和Controller)、HandlerAdapter(处理器适配,使用处理器干活)、HandlerExceptionResolver(处理器错误解析,请求处理过程报错,设置ModelAndView)、ViewResolver(视图解析器,寻找对应视图)、RequestToViewNameTranslator(从请求获取view名,view名供视图解析器寻找对应视图)、LocaleResolver(从请求获取Locale,Locale供视图解析器使用)、ThemeResolver(主题解析器)、MultipartResolver(处理上传请求)、FlashMapManager(管理重定向相关的FlashMap)。这些组件后续有时间可以拆开详细介绍。
    组件初始化基本步骤是,先在根据名称或者类型在注册的容器bean里查找,自行定制的组件可以在context中注册,这样就能加载成功。如果在context中找不到,就需要加载默认的组件,默认组件的配置是在org.springframework.web.DispatcherServlet.properties文件中,通过调用ClassUtils.forName就能够通过className加载类。需要注意的是,spring boot的配置方法和标准spring mvc不一样,需要另行讨论。

本系列文章:
tomcat + spring mvc原理(一):tomcat原理综述和静态架构
tomcat + spring mvc原理(二):tomcat容器初始化加载和启动
tomcat + spring mvc原理(三):tomcat网络请求的监控与处理1
tomcat + spring mvc原理(四):tomcat网络请求的监控与处理2
tomcat + spring mvc原理(五):tomcat Filter组件实现原理
tomcat + spring mvc原理(六):tomcat WAR包的部署与加载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值