【JavaEE学习贴3】分析spring webmvc启动流程——MVC

一、什么是Spring MVC?

Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。

在这里插入图片描述

- M 代表 模型(Model)
模型是什么呢? 模型就是数据,即dao,bean,包含了数据与行为

- V 代表 视图(View)
视图是什么呢? 就是直观可看到的网页, JSP,用来展示模型中的数据

- C 代表 控制器(controller)
控制器是什么? 控制器的作用就是把不同的数据(Model),显示在不同的视图(View)上,Servlet 扮演的就是这样的角色。它接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,
由视图负责展示。也就是说控制器做了个调度员的工作。

特点:

  • 结构松散,几乎可以在 Spring MVC 中使用各类视图
  • 松耦合,各个模块分离
  • 与 Spring 无缝集成

二、核心架构的流程步骤概述

1、 首先用户发送请求——>DispatcherServlet
前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;

2、DispatcherServlet——>HandlerMapping
HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;

3、 DispatcherServlet——>HandlerAdapter
HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

4、 HandlerAdapter——>处理器功能处理方法的调用
HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);

5、 ModelAndView的逻辑视图名——> ViewResolver
ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;

6、 View——>渲染
View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;

7、返回控制权给DispatcherServlet
由DispatcherServlet返回响应给用户,到此一个流程结束。

名词解释:

  • DispatcherServlet:前端控制器
    是整个流程控制的中心,由它调用其它组件处理用户的请求,ispatcherServlet的存在降低了组件之间的耦合性。

  • HandlerMapping:处理器映射器
    负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

  • Handler:处理器
    是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。

  • HandlAdapter:处理器适配器
    通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

  • View Resolver:视图解析器
    View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。

  • View:视图
    springmvc框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。

三、启动流程

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <!-- 设置编码格式 -->
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/<l-pattern>
  </filter-mapping>
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>


</web-app>

Spring的启动其实就是IOC容器的启动过程,通过context-param初始化上下文。
Tomcat启动的时候会依次加载web.xml中配置的Listener、Filter和Servlet。首先加载listerner中ContextLoaderListener这个上下文监听器。这个类继承了ContextLoader,用来初始化Spring根上下文,并将其放入ServletContext中。在启动项目时会触发contextInitialized上下文初始化方法

public void contextInitialized(ServletContextEvent event) {
    
    
    this.initWebApplicationContext(event.getServletContext());
}

再去看看initWebApplicationContext方法

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    
    
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
    
    
        throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
    } else {
    
    
        servletContext.log("Initializing Spring root WebApplicationContext");
        Log logger = LogFactory.getLog(ContextLoader.class);
        if (logger.isInfoEnabled()) {
    
    
            logger.info("Root WebApplicationContext: initialization started");
        }

        long startTime = System.currentTimeMillis();

        try {
    
    
            if (this.context == null) {
    
    
                this.context = this.createWebApplicationContext(servletContext);
            }

            if (this.context instanceof ConfigurableWebApplicationContext) {
    
    
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                if (!cwac.isActive()) {
    
    
                    if (cwac.getParent() == null) {
    
    
                        ApplicationContext parent = this.loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }

                    this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }

            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);
            }

            if (logger.isInfoEnabled()) {
    
    
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
            }

            return this.context;
        } catch (Error | RuntimeException var8) {
    
    
            logger.error("Context initialization failed", var8);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
            throw var8;
        }
    }
}

总结来说这个方法主要做了三件事:

  • 创建WebApplicationContext。
  • 加载对应的spring配置文件中的Bean。
  • 将WebApplicationContext放入ServletContext(Java Web的全局变量)中。

完成ApplicationContext创建之后将其放入ServletContext中。

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);

子容器初始化

DispatcherServlet就是一个Servlet.

DispatcherServlet继承了FrameworkServlet;
FrameworkServlet又继承了HttpServletBean;
HttpServletBean又继承HttpServlet并且重写了init方法,所以创建子上下文时的入口就在这个init方法。

  • HttpServletBean的主要做一些初始化工作,将我们在web.xml中配置的参数书设置到Servlet中;
  • FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其和ServletContext关联;
  • DispatcherServlet:初始化各个功能的实现类。比如异常处理、视图处理、请求映射处理等。
<servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

看看HttpServletBean的init()。该初始化方法的主要作用:将Servlet初始化参数(init-param)设置到该组件上(如contextAttribute、contextClass、namespace、contextConfigLocation),通过BeanWrapper简化设值过程,方便后续使用;提供给子类初始化扩展点,initServletBean(),该方法由FrameworkServlet覆盖。

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

            throw var4;
        }
    }

    this.initServletBean();
}

再看看FrameWorkServlet这个类的initServletBean()方法。
FrameworkServlet继承HttpServletBean,通过initServletBean()进行Web上下文初始化,该方法主要覆盖一下两件事情:初始化web上下文;提供给子类初始化扩展点。

protected final void initServletBean() throws ServletException {
    
    
    this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
    
    
        this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
    }

    long startTime = System.currentTimeMillis();

    try {
    
    
        this.webApplicationContext = this.initWebApplicationContext();
        this.initFrameworkServlet();
    } catch (RuntimeException | ServletException var4) {
    
    
        this.logger.error("Context initialization failed", var4);
        throw var4;
    }

    if (this.logger.isDebugEnabled()) {
    
    
        String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
        this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
    }

    if (this.logger.isInfoEnabled()) {
    
    
        this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }

}

DispatcherServlet继承FrameworkServlet,并实现了onRefresh()方法提供一些前端控制器相关的配置。

protected void onRefresh(ApplicationContext context) {
    
    
    this.initStrategies(context);
}

它初始化DispatcherServlet使用的策略:

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

到此为止,SpringMVC的启动过程结束。

总结

  • 如果在web.xml中配置了org.springframework.web.context.ContextLoaderListener,那么Tomcat在启动的时候会先加载父容器,并将其放到ServletContext中;
  • 加载DispatcherServlet。DispatcherServlet实质是一个Servlet,会先执行它的init方法。这个init方法在HttpServletBean这个类中实现,其主要工作是做一些初始化工作,将我们在web.xml中配置的参数书设置到Servlet中,然后再触发FrameworkServlet的initServletBean()方法;
  • FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其放入ServletContext中;
  • FrameworkServlet在调用initServletBean()的过程中同时会触发DispatcherServlet的onRefresh()方法,这个方法会初始化Spring
    MVC的各个功能组件。比如异常处理器、视图处理器、请求映射处理等。

从以下链接进行的学习:
https://blog.csdn.net/weixin_43870026/article/details/86621409
https://www.cnblogs.com/54chensongxia/p/12522804.html?utm_source=tuicool&utm_medium=referral

猜你喜欢

转载自blog.csdn.net/Shadownow/article/details/105530024
今日推荐