美文网首页
SpringMVC中的各种Servlet实现类

SpringMVC中的各种Servlet实现类

作者: 雅阁驸马 | 来源:发表于2023-08-02 14:30 被阅读0次

    一句话总结
    主角是这三个类:HttpServletBean、FrameworkServlet和DispatcherServlet,他们有继承关系,见下图,他们使用了模板方法的设计模式,所以一般都是父类会留下一个模板方法,子类去覆盖这个模板方法,以实现子类的特殊功能

    image.png

    可以看到在Servlet的继承结构中一共有5个类,GenericServlet和HttpServlet在java的servlet规范中,剩下的三个类HttpServletBean、FrameworkServlet和DispatcherServlet是Spring MVC提供的(前2者是抽象类,最后一个是非抽象类)

    这三个类直接实现三个接口:EnvironmentCapable、EnvironmentAware和Application-ContextAware。

    • XXXAware在spring里表示对XXX可以感知,通俗点解释就是:如果在某个类里面想要使用spring的一些东西,就可以通过实现XXXAware接口告诉spring,spring看到后就会给你送过来,而接收的方式是通过实现接口唯一的方法set-XXX。

    比如,有一个类想要使用当前的ApplicationContext,那么我们只需要让它实现ApplicationContextAware接口,然后实现接口中唯一的方法void setApplicationContext(ApplicationContext applicationContext)就可以了,spring会自动调用这个方法将applicationContext传给我们,我们只需要接收就可以了!很方便吧!

    • EnvironmentCapable,顾名思义,当然就是具有Environment的能力,也就是实现类可以提供Environment,所以EnvironmentCapable唯一的方法是Environment getEnvironment(),用于实现EnvironmentCapable接口的类,就是告诉spring,实现类它可以提供Environment,当spring需要Environment的时候就会调用实现类的getEnvironment方法跟它要。其实getEnvironment()函数里返回的Environment是从哪来的呢?答案是EnvironmentAware接口的setEnvironment函数,所以说要实现EnvironmentCapable提供Environment,那么你必须实现EnvironmentAware获取Environment

    1. HttpServletBean

    它实现了HttpServlet,HttpServlet中有一个无参数的init()模板方法,HttpServletBean需要覆盖这个模板方法,做一些定制操作。

    // org.springframework.web.servlet.HttpServletBean
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }
    
        try {
            //将Servlet中配置的参数封装到pvs变量中,requiredProperties为必需参数,如果没配置将报异常
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new esourceEditor(resourceLoader, getEnvironment()));
            //模板方法,可以在子类调用,做一些初始化工作。bw代表DispatcherServlet
            initBeanWrapper(bw);
            //将配置的初始化值(如contextConfigLocation)设置到DispatcherServlet
            bw.setPropertyValues(pvs, true);
        }catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }
    
        // 模板方法,子类初始化的入口方法
        initServletBean();
    
        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }
    

    BeanWrapper是干嘛的?
    它是Spring提供的一个用来操作JavaBean属性的工具,使用它可以直接修改一个对象的属性。举个例子:

    public class BeanWrapperTest {
        public static void main(String[] args) {
            User user = new User();
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(user);
            bw.setPropertyValue("userName", "张三");
            System.out.println(user.getUserName()); //输出张三
            PropertyValue value = new PropertyValue("userName", "李四");
            bw.setPropertyValue(value);
            System.out.println(user.getUserName()); //输出李四
        }
    }
    

    知道了BeanWrapper是干嘛的后,你一定会对这个注释有疑问:“bw代表DispatcherServlet”。创建bw的时候明明传的是this,这样它代表的应该是HttpServletBean,为什么注释说bw代表DispatcherServlet呢?
    因为,HttpServletBean是一个抽象类,DispatcherServlet是间接实现了HttpServletBean的,所以这里的this指的是DispatcherServlet,而不是HttpServletBean。

    解决了疑问以后,再来总结init()函数干了个啥:

    • 首先将Servlet中配置的参数使用BeanWrapper设置到DispatcherServle的相关属性,
    • 然后调用模板方法initServletBean,子类就通过这个方法初始化。

    所以,它跟HttpServlet一样,HttpServletBean也提供了一个模板方法initServletBean(),供子类做定制化

    2. FrameWorkServlet

    它继承了HttpServletBean,而HttpServletBean提供的模板方法是initServletBean,所以它的初始化入口方法应该是initServletBean。

    // org.springframework.web.servlet.FrameworkServlet
    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 {
            this.webApplicationContext = initWebApplicationContext();
            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,也就是创建spring容器。
    • 另一句用于初始化FrameworkServlet,而且initFrameworkServlet方法是模板方法,子类可以覆盖然后在里面做一些初始化的工作。

    下面来看一下initWebApplicationContext方法:

    // org.springframework.web.servlet.FrameworkServlet
    protected WebApplicationContext initWebApplicationContext() {
    //获取rootContext,rootContext早就在ContextLoaderListener已经被创建了,是属于Spring源码分析范畴的,这里就不分析它怎么来的了
        WebApplicationContext rootContext =
             WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
        //如果已经通过构造方法设置了webApplicationContext
        if (this.webApplicationContext != null) { 
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
           // 当webApplicationContext已经存在ServletContext中时,通过配置在Servlet中的contextAttribute参数获取
           wac = findWebApplicationContext();
       }
        if (wac == null) {
            // 如果webApplicationContext还没有创建,则创建一个
            wac = createWebApplicationContext(rootContext);
        }
    
        if (!this.refreshEventReceived) {
            // 当ContextRefreshedEvent事件没有触发时调用此方法,模板方法,可以在子类重写
               onRefresh(wac);
        }
    
        if (this.publishContext) {
            // 将ApplicationContext保存到ServletContext中
            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;
    }
    

    initWebApplicationContext方法做了三件事:

    • 获取spring的根容器rootContext。(rootContext早就在ContextLoaderListener被调用时已经被创建了,是属于Spring源码分析范畴的,这里就不分析它怎么来的了)

    • 获取或者创建webApplicationContext,并根据情况调用onRefresh方法。
      获取或者创建webApplicationContext一共有三种方法:

      1. 第一种方法是在构造方法中已经传递webApplicationContext参数,这时只需要对其进行一些设置即可。这种方法主要用于Servlet3.0以后的环境中,Servlet3.0之后可以通过代码添加ServletWebApplicationContext, 创建DispatcherServlet过程中会使用ServletContext.addServlet(new DispatcherServlet(servletAppContext))方式注册Servlet, 这种情况下new DispatcherServlet的时候带了参数, 而这个参数就是之前通过代码添加的ServletWebApplicationContext,所以构造函数里就有了webApplicationContext),这样就可以直接获取了。
      2. 第二种略过把
      3. 第三种方法是在前面两种方式都无效的情况下自己创建一个。常规SpringMVC项目就是使用的这种方式。创建过程在createWebApplicationContext方法中,createWebApplicationContext内部又调用了configureAndRefreshWebApplicationContext方法。
    • 将webApplicationContext设置到ServletContext中。

    上面说的第三种方法中的configureAndRefreshWebApplicationContext函数,代码如下:

    // org.springframework.web.servlet.FrameworkServlet
    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        //获取创建类型,通过读取web.xml中的contextClass属性,如果没配则默认使用org.springframework.web.context.support.XmlWebApplicationContext
        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");
        }
    
    //具体创建
        ConfigurableWebApplicationContext wac =
              (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    
        wac.setEnvironment(getEnvironment());
        // 将它的父Spring容器设置为Root WebApplicationContext
        wac.setParent(parent);
        //将web.xml中设置的contextConfigLocation参数传给wac,默认传入WEB-INFO/[ServletName]-Servlet.xml
        wac.setConfigLocation(getContextConfigLocation());
    
        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 {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
            }
        }
    
        wac.setServletContext(getServletContext());
        wac.setServletConfig(getServletConfig());
        wac.setNamespace(getNamespace());
        //添加监听ContextRefreshedEvent的监听器
        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);
        // 执行refresh
        wac.refresh();
    }
    

    3. DispatcherServlet

    DispatcherServlet虽然继承了FrameworkServlet,但是它并没有去重写FrameworkServlet提供的initFrameworkServlet()模板函数。它比较重要的函数是doService(...)函数(也是FrameworkServlet提供的模板函数,Servlet容器有请求进来会间接回调到这个函数)。

    所以,DispatcherServlet里面执行处理的入口方法应该是doService,不过doServic并没有直接进行处理,而是交给了doDispatch进行具体的处理。

    • doService:在这里主要是对request设置了一些属性,如果是include请求还会对request当前的属性做快照备份,并在处理结束后恢复。最后将请求转发给doDispatch方法。
    • doDispatch方法也非常简洁,从顶层设计了整个请求处理的过程。doDispatch中最核心的代码只要4句,它们的任务分别是:
      ①根据request从HandlerMapping找到Handler;
      ②根据Handler找到对应的HandlerAdapter;
      ③用HandlerAdapter处理Handler;
      ④调用processDispatchResult方法处理上面处理之后的结果(包含找到View并渲染输出给用户),对应的代码如下:
    mappedHandler = getHandler(processedRequest);
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    

    3.1 doDispatch函数的具体处理流程

    1. doDispatch中首先检查是不是上传请求,如果是上传请求,则将request转换为MultipartHttpServletRequest,并将multipartRequestParsed标志设置为true。其中使用到了Multipart-Resolver。
    2. 然后通过getHandler方法获取Handler处理器链,其中使用到了HandlerMapping,返回值为HandlerExecutionChain类型,HandlerExecutionChain中包含着与当前request相匹配的Interceptor和Handler。

    执行时先依次执行Interceptor的preHandle方法,最后执行Handler,返回的时候按相反的顺序执行Interceptor的postHandle方法。就好像要去一个地方,Interceptor是要经过的收费站,Handler是目的地,去的时候和返回的时候都要经过加油站,但两次所经过的顺序是相反的。

    getHandler代码如下:

    // org.springframework.web.servlet.DispatcherServlet
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                       "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }
    
    1. 接下来是处理GET、HEAD请求的Last-Modified。当浏览器第一次跟服务器请求资源(GET、Head请求)时,服务器在返回的请求头里面会包含一个Last-Modified的属性,代表本资源最后是什么时候修改的。在浏览器以后发送请求时会同时发送之前接收到的Last-Modified,服务器接收到带Last-Modified的请求后会用其值和自己实际资源的最后修改时间做对比,如果资源过期了则返回新的资源(同时返回新的Last-Modified),否则直接返回304状态码表示资源未过期,浏览器直接使用之前缓存的结果。

    2. 接下来依次调用相应Interceptor的preHandle

    3. 处理完Interceptor的preHandle后就到了此方法最关键的地方——让HandlerAdapter使用Handler处理请求,Controller就是在这个地方执行的。这里主要使用了HandlerAdapter.

    4. Handler处理完请求后,如果需要异步处理,则直接返回,如果不需要异步处理,当view为空时(如Handler返回值为void),设置默认view,然后执行相应Interceptor的postHandle。设置默认view的过程中使用到了ViewNameTranslator

    5. 接下来使用processDispatchResult方法处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容。

    整个过程的流程图大致如下,中间是doDispatcher的处理流程图,左边是Interceptor相关处理方法的调用位置,右边是doDispatcher方法处理过程中所涉及的组件。图中上半部分的处理请求对应着MVC中的Controller也就是C层,下半部分的processDispatchResult主要对应了MVC中的View也就是V层,M层也就是Model贯穿于整个过程中。

    image.png

    相关文章

      网友评论

          本文标题:SpringMVC中的各种Servlet实现类

          本文链接:https://www.haomeiwen.com/subject/dhkppdtx.html