• SpringMVC源码分析(一)启动流程分析


    问题:SpringMVC 在启动过程中主要做了什么事情?

    SpringMVC在启动过程中是什么时候解析web.xml文件的,又是什么时候初始化9大内置对象的?

    <?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">
      <!--Spring配置文件-->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
      </context-param>
      <!--Spring监听器-->
      <listener>
        <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class>
      </listener>
    
      <!--SpringMVC前端控制器-->
      <servlet>
        <servlet-name> SpringMVC </servlet-name>
        <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
        <!-- 配置springMVC需要加载的配置文件-->
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:spring/springMVC.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
      </servlet>
      <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
    </web-app>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    补充:相关概念

    Spring MVC中,有两个容器:父容器(Root WebApplicationContext)和子容器(Servlet WebApplicationContext)。

    (1)父容器(Root WebApplicationContext)

    主要负责加载应用程序中的共享bean和配置。它通常包含以下内容:
    
    • 1
    1. 数据源和事务管理器:用于处理数据库操作和事务管理。
    2. 持久化层(DAO)和业务层(Service)的bean:用于处理数据访问和业务逻辑。
    3. 安全配置:用于配置安全相关的bean,如认证和授权配置。
    4. 缓存配置:用于配置缓存相关的bean,如缓存管理器和缓存注解处理器。
    5. 其他共享bean:例如,公共工具类、全局异常处理器等。

    (2)子容器(Servlet WebApplicationContext)

    主要负责加载与Web应用程序相关的bean和配置。它通常包含以下内容:
    
    • 1
    1. 控制器(Controller)和视图解析器(ViewResolver):用于处理请求和生成响应。
    2. Web相关的bean:例如,处理文件上传的MultipartResolver、处理静态资源的ResourceHandler等。
    3. Web安全配置:用于配置Web安全相关的bean,如Spring Security配置。
    4. 其他与Web应用程序相关的bean:例如,国际化资源处理器、消息转换器等。

    Spring本身的ApplicationContext属于父容器(Root WebApplicationContext),它通常包含整个应用程序范围内的bean和配置。这包括父容器中的共享bean,以及子容器中的bean。在Spring MVC中,通常使用ContextLoaderListener来加载父容器的ApplicationContext。

    总结起来,父容器(Root WebApplicationContext)包含应用程序范围内的共享bean和配置,子容器(Servlet WebApplicationContext)包含与Web应用程序相关的bean和配置,而Spring本身的ApplicationContext属于父容器,包含整个应用程序的bean和配置。
    Spring 父子容器是指 Spring 容器的层次结构,其中一个容器作为另一个容器的父容器。父容器可以提供资源和 bean 给子容器,子容器可以覆盖或扩展父容器的 bean 定义。

    这种父子关系在 Spring Web 应用中非常常见。通常情况下,在 Web 应用中会有两个 Spring 容器:Root WebApplicationContext 和 Servlet WebApplicationContext。

    (3)SpringMVC组件的配置方式

    1、在Spring和SpringMVC整合得时候通常需要进行Spring的MVC得配置,这个配置可以是XML得方式也可以是无配置化即注解得方式。即@EnableWebMvc和

    (4)SPI机制

    1、从 servlet 3.0 开始,Tomcat 启动时会自动加载实现了 ServletContainerInitializer 接口的类(需要在 META-INF/services 目录下新建配置文件),也称为 SPI 机制;
    在这里插入图片描述

    package javax.servlet;
    
    import java.util.Set;
    
    public interface ServletContainerInitializer {
        public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    实现类SpringServletContainerInitializer

    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    	/**
    	 * 该 onStartup() 方法会被 tomcat 服务启动调用
    	 * 这个方法主要是为 Web 容器
    	 * 1、添加 WebServlet 请求
    	 * 2、添加 WebFilter 过滤器
    	 * 3、添加 WebListener 监听器
    	 * 参数1:webAppInitializerClasses 表示加载被注解 @HandlesTypes(WebApplicationInitializer.class) 修饰的接口的所有子类实现,
    	 * 主要有一下三类重要的子类:
    	 * 1.AbstractContextLoaderInitializer ---> 对应 ContextLoaderListener
    	 * 2.AbstractDispatcherServletInitializer --> 对应 DispatcherServlet
    	 * 	2.1.AbstractAnnotationConfigDispatcherServletInitializer
    	 * 3.AbstractReactiveWebInitializer --> 对应 React
    	 * 
    	 * 参数2:servletContext servlet 容器上下文
    	 */
    	@Override
    	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
    			throws ServletException {
    
    		List<WebApplicationInitializer> initializers = Collections.emptyList();
    
    		if (webAppInitializerClasses != null) {
    			initializers = new ArrayList<>(webAppInitializerClasses.size());
    			for (Class<?> waiClass : webAppInitializerClasses) {
    				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
    						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
    					try {
    						initializers.add((WebApplicationInitializer)
    								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
    					}
    					catch (Throwable ex) {
    						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
    					}
    				}
    			}
    		}
    
    		if (initializers.isEmpty()) {
    			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
    			return;
    		}
    
    		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
    		AnnotationAwareOrderComparator.sort(initializers);
    		// initializers 里面会包含我们自己定义的 WebApplicationInitializer 子类实现
    		for (WebApplicationInitializer initializer : initializers) {
    		  
    			initializer.onStartup(servletContext);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    WebApplicationInitializer 接口

    @HandlesTypes 注解还是 Tomcat SPI 规范中的,会被 Tomcat 解析,也就是说会解析被注解 @HandlesTypes 修饰的 WebApplicationInitializer 接口的所有子类,注意此时的 WebApplicationInitializer 接口是由 Spring 提供的,这个接口就是 Spring 整合 SpringMVC 的核心入口, Spring 有三个较重要的实现子类:
    1.AbstractContextLoaderInitializer —> 对应 ContextLoaderListener
    2.AbstractDispatcherServletInitializer --> 对应 DispatcherServlet
    2.1.AbstractAnnotationConfigDispatcherServletInitializer --> 对应注解配置版的 DispatcherServlet
    3.AbstractReactiveWebInitializer --> 对应 React

    1、Tomcat 会扫描所有实现了 WebApplicationInitializer 接口的子类,然后放到 SpringServletContainerInitializer 类中的 onStartup() 方法中的第一个参数 Set 集合中;
    2、Tomcat 在启动时会调用 SpringServletContainerInitializer 的 onStartup() 方法(因为 SpringServletContainerInitializer 实现了 Tomcat ServletContainerInitializer 接口),然后再遍历调用 WebApplicationInitializer 接口的所有子类(上述的三类重要子类在这里就被回调了)的 onStartup() 方法;

    总结:实现了 WebApplicationInitializer 接口就会被 Tomcat 再调用 SpringServletContainerInitializer 类的 onStartup() 方法时遍历调用到每个 WebApplicationInitializer 子类的 onStartup() 方法。
    Spring 在整合 SpringMVC 时利用了 Tomcat SPI 可扩展接口,扩展了 ServletContainerInitializer 接口,在 WebApplicationInitializer 类中进行增强处理。

    0、启动过程中两个关键类

    ContextLoaderListener和DispatcherServlet

    Tomcat启动时候会先创建servlet容器,然后解析web.xml。
    1、首先加载conf/web.xml文件,然后加载web应用程序中的WEB-INF/web.xml文件。
    2、按照以下顺序读取和处理web.xml中的元素:context-param -> listener -> filter -> servlet。

    context-param元素用于向ServletContext提供键值对,即应用程序上下文信息,可以写在任意位置。
    servlet元素用于定义servlet的名字和类,servlet-mapping元素用于指定访问servlet的URL,servlet-mapping必须出现在servlet之后,servlet的初始化顺序按照load-on-startup元素指定的值,如果值为正数或零,则按照从小到大的顺序初始化,如果值为负数或未定义,则在第一次请求时初始化。

    先解析context-param,然后创建ServletContext对象,并将context-param转换为键值对交给ServletContext,然后再解析listener-class,并创建监听器实例。如果监听器类实现了ServletContextListener接口,那么它的contextInitialized(ServletContextEvent sce)方法和contextDestroyed(ServletContextEvent sce)方法会在ServletContext对象创建和销毁时被调用,这两个方法的参数sce可以通过getServletContext()方法获取ServletContext对象。

    1、ContextLoaderListener

    ContextLoaderListener实现了Servlet规范中的javax.servlet.ServletContextListener,WEB容器在启动过程中即ServletContext对象创建时会回调ServletContextListener.contextInitialized(), 同理在销毁的时候会回调contextDestroyed(ServletContextEvent sce)

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
     public ContextLoaderListener(WebApplicationContext context) {
      super(context);
     }
     @Override
     public void contextInitialized(ServletContextEvent event) {
      initWebApplicationContext(event.getServletContext());
     }
     @Override
     public void contextDestroyed(ServletContextEvent event) {
      closeWebApplicationContext(event.getServletContext());
      ContextCleanupListener.cleanupAttributes(event.getServletContext());
     }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    调用contextInitialized()

    创建WebApplicationContext,设置必要的参数,如配置文件位置contextConfigLocation。
    调用refresh()。在这个过程中,XmlWebApplicationContext 会解析 XML 配置文件,加载 bean 定义,并创建并初始化所有的 bean。将这个容器当作父容器存到servletContext里边。

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    	/**
    	 * 首先通过WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个String类型的静态变量获取一个父容器
    	 * 父容器作为全局变量存储在application对象中,如果存在则有且只能有一个
    	 * 如果在初始化父容器时发现已经存在则直接抛出异常
    	 * 这个值在下面的代码中会放入application中
    	 */
    	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!");
    	}
    
    	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 {
    		// context代表父容器,已经有值了
    		// xml会在这里创建
    		if (this.context == null) {
    			this.context = createWebApplicationContext(servletContext);
    		}
    		if (this.context instanceof ConfigurableWebApplicationContext) {
    			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    			if (!cwac.isActive()) {
    				if (cwac.getParent() == null) {
    					ApplicationContext parent = loadParentContext(servletContext); // null
    					cwac.setParent(parent);
    				}
    				// 创建spring容器,调用refresh()方法完成父容器的初始化
    				configureAndRefreshWebApplicationContext(cwac, servletContext);
    			}
    		}
    		// 将父容器放入到了servletContext中
    	    //在servlet域中设置根容器(在子容器就可以直接拿到了)
    	    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 (RuntimeException | Error ex) {
    		logger.error("Context initialization failed", ex);
    		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
    		throw ex;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    2、DispatcherServlet

    当创建完成父容器之后,就开始创建子容器。解析servlet标签,即DispatcherServlet。
    1、在web.xml文件中配置dispatcherServlet的servlet-name,servlet-class,init-param和load-on-startup等信息。
    2、会根据load-on-startup的值,按照从小到大的顺序初始化servlet实例,并调用它们的init()方法

    先看一下DispatcherServlet的继承关系:
    在这里插入图片描述
    org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup

    public void onStartup(ServletContext servletContext) throws ServletException {
    	super.onStartup(servletContext); // 创建ContextLoaderListener
    	registerDispatcherServlet(servletContext); // 创建DispatcherServlet
    }
    
    protected void registerDispatcherServlet(ServletContext servletContext) {
    	String servletName = getServletName();
    	Assert.hasLength(servletName, "getServletName() must not return null or empty");
    
    	WebApplicationContext servletAppContext = createServletApplicationContext(); // 子容器
    	Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
    
    	// 创建DispatcherServlet
    	FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
    	Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    	dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); // null
    
    	ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    	if (registration == null) {
    		throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
    				"Check if there is another servlet registered under the same name.");
    	}
    
    	registration.setLoadOnStartup(1);
    	registration.addMapping(getServletMappings()); // 处理哪些映射
    	registration.setAsyncSupported(isAsyncSupported()); // 默认为true,支持异步
    
    	Filter[] filters = getServletFilters(); // 过滤器
    	if (!ObjectUtils.isEmpty(filters)) {
    		for (Filter filter : filters) {
    			// 添加filter到servlet容器中
    			registerServletFilter(servletContext, filter);
    		}
    	}
    
    	customizeRegistration(registration);
    }
    
    protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
    	return new DispatcherServlet(servletAppContext);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    1. HttpServletBean#init()

    WEB容器启动后调用Servlet的init()方法进行初始化,此方法的实现实在父类HttpServletBean中:
    org.springframework.web.servlet.HttpServletBean#init

    @Override
    public final void init() throws ServletException {
       // 解析 init-param 并封装只 pvs 中(xml)
       PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
       if (!pvs.isEmpty()) {
             // 将当前的这个 Servlet 类转化为一个 BeanWrapper,从而能够以 Spring 的方法来对 init-param 的值进行注入
             BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
             ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
             bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
             initBeanWrapper(bw);
             // 修改servlet状态,并将pvs里边的值赋值给servlet
             bw.setPropertyValues(pvs, true);
       }
       // 初始化Servlet,创建Spring容器
       initServletBean();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2. FrameworkServlet#initServletBean()

    protected final void initServletBean() throws ServletException {
    	getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    	if (logger.isInfoEnabled()) {
    		logger.info("Initializing Servlet '" + getServletName() + "'");
    	}
    	long startTime = System.currentTimeMillis();
    
    	try {
    		// 对子容器进行初始化
    		this.webApplicationContext = initWebApplicationContext();
    		initFrameworkServlet(); // NOP
    	}
    	catch (ServletException | RuntimeException ex) {
    		logger.error("Context initialization failed", ex);
    		throw ex;
    	}
    
    	if (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";
    		logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
    				"': request parameters and headers will be " + value);
    	}
    
    	if (logger.isInfoEnabled()) {
    		logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3. FrameworkServlet#initWebApplicationContext()

    protected WebApplicationContext initWebApplicationContext() {
    	// 从ServletContext中获取父容器
    	WebApplicationContext rootContext =
    			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    	WebApplicationContext wac = null;
    
    	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); // 父子间建立关系
    				}
    				// 调用refresh()方法对子容器进行初始化
    				configureAndRefreshWebApplicationContext(cwac);
    			}
    		}
    	}
    	if (wac == null) { // 不会进入
    		wac = findWebApplicationContext();
    	}
    	if (wac == null) { // 不会进入
    		wac = createWebApplicationContext(rootContext);
    	}
    
    	if (!this.refreshEventReceived) { // refreshEventReceived=true不会进入
    		synchronized (this.onRefreshMonitor) {
    			onRefresh(wac);
    		}
    	}
    
    	if (this.publishContext) {
    		String attrName = getServletContextAttributeName();
    		// 将子容器也放入到ServletContext中
    		getServletContext().setAttribute(attrName, wac);
    	}
    
    	return wac;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    4. FrameworkServlet#configureAndRefreshWebApplicationContext()

    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());
    		}
    	}
    
    	wac.setServletContext(getServletContext());
    	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); // NOP
    	applyInitializers(wac);
    	// invoke refresh method
    	wac.refresh();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在子容器初始化之前添加了一个监听容器刷新的事件ContextRefreshListener,当容器刷新完成后将会调用ContextRefreshListener.onApplicationEvent()方法。发布容器刷新事件ContextRefreshedEvent,最终会再刷新事件处理器中调用FrameworkServlet.onApplicationEvent()。

    5. FrameworkServlet.onApplicationEvent()

    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
    
    	@Override
    	public void onApplicationEvent(ContextRefreshedEvent event) {
    		FrameworkServlet.this.onApplicationEvent(event);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6. FrameworkServlet#onApplicationEvent()

    public void onApplicationEvent(ContextRefreshedEvent event) {
    	this.refreshEventReceived = true;
    	synchronized (this.onRefreshMonitor) {
    		onRefresh(event.getApplicationContext());
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7. DispatcherServlet#onRefresh()

    protected void onRefresh(ApplicationContext context) {
    	initStrategies(context);
    }
    
    protected void initStrategies(ApplicationContext context) {
        // 初始化用于处理文件上传的MultipartResolver,即:从IOC中获取名称为“multipartResolver”的bean
        initMultipartResolver(context); 
        
        // 初始化用于国际化的LocaleResolver(从IOC中获取名称为“localeResolver”的bean)
        initLocaleResolver(context);
        
        // 初始化用于主题风格的ThemeResolver(从IOC中获取名称为“themeResolver”的bean)
        initThemeResolver(context);
        
        // 初始化用于处理Request请求及流转的HandlerMapping,根据变量”detectAllHandlerMappings“,有如下3种获取方式:
        //   方式1:获取IOC中所有类型为HandlerMapping的bean集合
        //   方式2:获取名称为“handlerMapping”的bean
        //   方式3:获取DispatcherServlet.properties文件中配置的HandlerMapping的bean列表
        initHandlerMappings(context);
        
        // 初始化用于进行请求处理的HandlerAdapter,根据变量”detectAllHandlerAdapters“,有如下3种获取方式:
        //   方式1:获取IOC中所有类型为HandlerAdapter的bean集合
        //   方式2:获取名称为“handlerAdapter”的bean
        //   方式3:获取DispatcherServlet.properties文件中配置的HandlerAdapter的bean列表
        initHandlerAdapters(context);
        
        // 初始化用于对异常的类型进行处理并生成ModelAndView的HandlerExceptionResolver,根据变量”detectAllHandlerExceptionResolvers“,有如下3种获取方式:
        //   方式1:获取IOC中所有类型为HandlerExceptionResolver的bean集合
        //   方式2:获取名称为“handlerExceptionResolver”的bean
        //   方式3:获取DispatcherServlet.properties文件中配置的HandlerExceptionResolver的bean列表
        initHandlerExceptionResolvers(context);
        
        // 初始化用于获取“逻辑视图名称”的RequestToViewNameTranslator(从IOC中获取名称为“viewNameTranslator”的bean)
        initRequestToViewNameTranslator(context);
        
        // 初始化用于创建View对象的ViewResolver,根据变量”viewResolver“,有如下3种获取方式:
        //   方式1:获取IOC中所有类型为ViewResolver的bean集合
        //   方式2:获取名称为“viewResolver”的bean
        //   方式3:获取DispatcherServlet.properties文件中配置的ViewResolver的bean列表
        initViewResolvers(context);
        
        // 初始化用于管理FlashMap的FlashMapManager(从IOC中获取名称为“flashMapManager”的bean)
        initFlashMapManager(context);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    8、总结

    如果在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的各个功能组件。比如异常处理器、视图处理器、请求映射处理等。

    3、DispatcherServlet.properties

    DispatcherServlet中默认的组件

    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.method.annotation.RequestMappingHandlerMapping,\
    	org.springframework.web.servlet.function.support.RouterFunctionMapping
    
    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
    	org.springframework.web.servlet.function.support.HandlerFunctionAdapter
    
    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    	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.SessionFlashMapManager
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    4、SpringMVC中的9大内置组件初始化

    4.1 初始化文件解析器initMultipartResolver()

    初始化multipart解析器的, MultipartResolver是一个接口,在Web开发中,multipart解析器主要用于处理HTTP请求中的multipart/form-data类型的数据,这种数据类型通常用于文件上传。

    SpringMVC提供了两种multipart解析器:

    1、CommonsMultipartResolver:基于Apache Commons FileUpload库实现的multipart解析器。
    2、StandardServletMultipartResolver:基于Servlet 3.0规范实现的multipart解析器。

    在Spring MVC中,如果你定义了一个名为"multipartResolver"的bean,那么Spring MVC就会使用你定义的这个bean作为multipart解析器。否则,Spring MVC就不会处理multipart/form-data类型的请求。
    LOCALE_RESOLVER_BEAN_NAME = “localeResolver”;

    1、一种是我们自定义上传文件解析器

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 设置最大上传文件为5MB -->
        <property name="maxUploadSize" value="5242880"/>
    </bean>
    
    或者--------------
    
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setMaxUploadSize(5242880);
        return multipartResolver;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2、默认文件上传解析器

    如果我们没有自定义上传文件的bean,那么就会利用spi去配置文件里边取值,然后加载到子容器中。

    // 通过PropertiesLoaderUtils工具类加载DispatcherServlet.properties
    //DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
    ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
    //spi
    defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
    
    • 1

    4.2 初始化国际化应用initLocaleResolver()

    初始化 DispatcherServlet 中的 LocaleResolver。在Spring MVC中,LocaleResolver是一个接口,它定义了如何解析客户端的地区信息。地区信息通常被用于国际化应用,可以根据不同的地区显示不同的信息。
    该方法的主要逻辑如下:
    首先,尝试从Spring上下文中获取名为localeResolver的bean,如果获取到了,就使用该bean作为地区解析器。
    如果没有获取到,那么就会使用getDefaultStrategy方法来获取一个默认的地区解析器。具体的实现类是在DispatcherServlet.properties中配置的和上边的一样也是利用SPI技术。

    在Spring MVC中,有几个预定义的LocaleResolver的实现:

    1. AcceptHeaderLocaleResolver: 根据HTTP的Accept-Language头来解析地区信息。
    2. FixedLocaleResolver: 提供固定的地区信息,无论请求是什么。
    3. CookieLocaleResolver: 将地区信息保存在浏览器的Cookie中。
    4. SessionLocaleResolver: 将地区信息保存在HTTP Session中。
    5. 如果我们没有自己配置LocaleResolver的Bean,Spring
      MVC将默认使用AcceptHeaderLocaleResolver。
    private void initLocaleResolver(ApplicationContext context) {
     try {
      this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
     catch (NoSuchBeanDefinitionException ex) {
      this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.3 初始化主题initThemeResolver()

    初始化 ThemeResolver 的方法。ThemeResolver 是一个接口,定义了如何解析应用程序的主题的规则。主题是Spring MVC中用于改变视图层外观的一种机制,例如颜色、CSS样式等。在Spring MVC中,可以为每个用户设置不同的主题,或者为所有用户设置一个统一的主题。
    具体查找和上边一样,查找的是themeResolverbean,默认实现的是FixedThemeResolver,保存到themeResolver 。

    4.4 初始化HandlerMapping()

    初始化处理器映射(Handler Mapping),这里也是和上边一样如果我们自己定义HandlerMapping,就用我们自己定义的,我们可以定义多个,如果没有自定义用默认的,默认有三个实现。

    4.4.1 自定义实现HandlerMapping

    实现HandlerMapping接口或者继承AbstractHandlerMapping。创建一个CustomHandlerMapping类,它继承自AbstractHandlerMapping。
    然后,在Spring MVC配置中注册这个自定义的HandlerMapping。当一个请求的URL路径为"/custom"时,CustomHandlerMapping就会返回CustomHandler作为处理器。
    自定义的HandlerMapping与Spring MVC默认的HandlerMapping(比如RequestMappingHandlerMapping)是可以共存的(需要手动注入了,因为有默认的了,就不加载默认的了)。当一个请求到来时,Spring MVC会按照HandlerMapping的顺序来查找处理器。你可以通过实现Ordered接口或者使用@Order注解来调整HandlerMapping的顺序。

    public class CustomHandlerMapping extends AbstractHandlerMapping {
     //保存映射关系
        Map<String, Object> pathHandlers = new HashMap<>();
    
        public CustomHandlerMapping() {
            // 添加一些自定义的映射
            // 此处仅作示例,一般我们不会在构造函数中添加映射
            // 更常见的是在配置类中或者通过其他方式动态添加映射
            this.pathHandlers.put("/custom", new CustomHandler());
        }
    
        @Override
        protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
            // 根据请求的URL路径查找对应的处理器
            String path = request.getServletPath();
            return this.pathHandlers.get(path);
        }
    }
    
    class CustomHandler {
        // 这里可以添加自定义的处理器方法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    4.4.2 默认实现BeanNameUrlHandlerMapping

    BeanNameUrlHandlerMapping:这个处理器映射使用 Spring bean 的名称作为 URL
    来映射请求。例如,一个名为 “/test” 的 bean 将会处理来自 “/test” URL
    的请求。这种方式在实际开发中用的比较少,因为它不够灵活且易于混淆。

    BeanNameUrlHandlerMapping的寻找流程:
    1、找出Spring容器中所有的beanName
    2、判断beanName是不是以“/”开头
    3、如果是,则把它当作一个Handler,并把beanName作为key,bean对象作为value存入handlerMap中,handlerMap就是一个Map

    4.4.3 默认实现SimpleUrlHandlerMapping

    SimpleUrlHandlerMapping:这个处理器映射允许我们显式地指定 URL与处理器之间的映射关系。然后将handlerMapping都保存到handlerMappings集合中。

    4.4.4 默认实现 RequestMappingHandlerMapping

    RequestMappingHandlerMapping:这个是最常用的处理器映射。它会扫描 Spring容器中的所有控制器(Controller),找出带有 @RequestMapping 注解的方法,然后根据注解的参数(例如,HTTP方法、URL 等)来建立请求与方法之间的映射。在处理 HTTP 请求时,它会找到与请求参数匹配的那个方法来处理请求。

    RequestMappingHandlerMapping查找handler流程

    默认得处理器映射(Handler Mapping)是spring创建的,是经过了bean得生命周期得,也就是通过getBean() -> createBean()那一套流程得,RequestMappingHandlerMapping类图:

    在这里插入图片描述
    RequestMappingHandlerMapping实现了InitializingBean,在初始化时候会执行afterPropertiesSet方法然后调用initHandlerMethods方法,这个方法会去解析所有的handler。
    在 Spring MVC 中,RequestMappingHandlerMapping 是负责处理标有 @RequestMapping 注解的 Controller 的。它的工作流程如下:
    1、当 Spring 容器启动的时候,RequestMappingHandlerMapping 会进行初始化,在初始化的过程中,afterPropertiesSet 方法会被调用。这个方法会进一步调用 initHandlerMethods 方法。

    2、在 initHandlerMethods 方法中,会获取Spring 容器中所有的Bean。对于获取到的每一个 Bean,RequestMappingHandlerMapping 都会检查它是否标有 @Controller 或者 @RequestMapping 注解。只有标有这些注解的 Bean 才会被认为是一个有效的 Controller。

    3、如果一个 Bean 被认为是一个有效的 Controller,那么 RequestMappingHandlerMapping 会进一步检查这个 Controller 中的所有方法。对于每一个方法,如果它上面有 @RequestMapping 注解,那么 RequestMappingHandlerMapping 会根据这个注解以及它所在的 Controller 的信息,创建一个 RequestMappingInfo 对象。

    4、RequestMappingInfo 对象包含了请求的 URL、请求的方法、请求的参数等信息。这个对象会被用来与进来的 HTTP 请求进行匹配,以决定哪一个方法应该被用来处理这个请求。

    5、最后将保存k-v,将path-List保存到pathLookup map中,为什么是list,因为相同的路径有get、post区分。将 RequestMappingInfo-headlMethod保存到registry map中。

    创建handlerMethod
    当解析到带有@RequestMapping注解的方法时,会创建一个HandlerMethod对象,该对象包含了方法的相关信息,如所属的类、方法名、参数列表等在这里插入图片描述
    构建RequestMappingInfo:
    代表了一个请求路径和请求方法的映射关系
    在这里插入图片描述

    4.5 初始化handler适配器initHandlerAdapters()

    处理器适配器(HandlerAdapter)负责调用处理器(Handler)的处理方法,最后的方法都封装成了handler, 不同的handler有不同的handlerAdapter来负责调用。
    首先从应用程序上下文中获取所有类型为HandlerAdapter的bean。如果找不到任何HandlerAdapter的bean,那么它会使用一组默认的处理器适配器。这组默认的处理器适配器包括RequestMappingHandlerAdapter、HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter、HandlerFunctionAdapter,并将其保存到handlerAdapters集合中。

    适配逻辑:

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            for (HandlerAdapter adapter : this.handlerAdapters) {
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                                   "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.5.1 自定义HandlerAdapter

    当我们自定义了handlerMapping就需要自定义HandlerAdapter来对他的handler进行匹配,当然,也可以利用默认的。

    4.5.2 HttpRequestHandlerAdapter

    HttpRequestHandlerAdapter:这个类是用于处理实现了HttpRequestHandler接口的类,这个接口只有一个方法handleRequest,用于直接处理请求和响应。它会将HttpServletRequest和HttpServletResponse对象作为参数传递给handleRequest方法,并将返回值封装成一个ModelAndView对象。

    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    			throws Exception {
    	((HttpRequestHandler) handler).handleRequest(request, response);
    	return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.5.3 SimpleControllerHandlerAdapter

    SimpleControllerHandlerAdapter:这个类是用于处理实现了Controller接口的类,这个接口也只有一个方法handleRequest,用于返回一个ModelAndView对象。它会将HttpServletRequest和HttpServletResponse对象作为参数传递给handleRequest方法,并直接返回其返回值。

    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    			throws Exception {
    	return ((Controller) handler).handleRequest(request, response);
    }
    
    • 1
    • 2
    • 3
    • 4

    4.5.4 HandlerFunctionAdapter

    HandlerFunctionAdapter:这个类是用于处理实现了HandlerFunction接口的类,这个接口是一个函数式接口,用于定义一个函数,接受一个ServerRequest对象并返回一个Mono对象。它会将HttpServletRequest和HttpServletResponse对象转换成ServerRequest和ServerResponse对象,并调用apply方法来执行函数,并将返回值转换成一个ModelAndView对象。

    HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
    serverResponse = handlerFunction.handle(serverRequest);
    
    • 1
    • 2

    上面这几个接收的直接就是Requeset对象,不用SpringMVC做额外的解析,所以比较简单,比较复杂的是RequestMappingHandlerAdapter,它执行的是加了@RequestMapping的方法,而这种方法的写法可以是多种多样,SpringMVC需要根据方法的定义去解析Request对象,从请求中获取出对应的数据然后传递给方法,并执行。

    4.5.5 RequestMappingHandlerAdapter

    RequestMappingHandlerAdapter:这个类是用于处理带有@RequestMapping注解的方法。它会根据请求的参数,头部,属性等来解析方法的参数,并调用反射来执行方法。
    此处比较复杂,后续单独说明

    4.6 初始化异常initHandlerExceptionResolvers()

    负责将在处理器处理过程中抛出的异常转换为对应的模型-视图结果。
    在initHandlerExceptionResolvers()方法中,获取所有类型为HandlerExceptionResolver的Bean。如果找不到任何HandlerExceptionResolver的Bean,那么它会使用一组默认的处理器异常解析器。
    这里的默认是会取读取DispatcherServlet.properties

    在这里插入图片描述

    private void initHandlerExceptionResolvers(ApplicationContext context) {
            this.handlerExceptionResolvers = null;
    
            if (this.detectAllHandlerExceptionResolvers) {
                // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
                Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                        .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
                if (!matchingBeans.isEmpty()) {
                    this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
                    // We keep HandlerExceptionResolvers in sorted order.
                    AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
                }
            }
            else {
                try {
                    HandlerExceptionResolver her =
                            context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
                    this.handlerExceptionResolvers = Collections.singletonList(her);
                }
                catch (NoSuchBeanDefinitionException ex) {
                    // Ignore, no HandlerExceptionResolver is fine too.
                }
            }
    
            // Ensure we have at least some HandlerExceptionResolvers, by registering
            // default HandlerExceptionResolvers if no other resolvers are found.
            if (this.handlerExceptionResolvers == null) {
                this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
                if (logger.isTraceEnabled()) {
                    logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
                            "': using default strategies from DispatcherServlet.properties");
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    4.6.0 DispatcherServlet.properties

    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
        org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
        org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
    
    • 1
    • 2
    • 3

    4.6.1 ExceptionHandlerExceptionResolver

    用于处理通过@ExceptionHandler注解处理的方法抛出的异常。

    4.6.2 ResponseStatusExceptionResolver

    用于处理通过@ResponseStatus注解处理的异常

    4.6.3 DefaultHandlerExceptionResolver

    用于处理Spring MVC自己抛出的一些特定的异常

    4.7 initRequestToViewNameTranslator

    initRequestToViewNameTranslator方法用于初始化RequestToViewNameTranslator的,RequestToViewNameTranslator是一个接口,用于根据请求解析出一个默认的视图名称,当ModelAndView对象不为null,但是没有指定视图时,就会使用这个接口来获取视图名称。
    寻找bean名字为viewNameTranslator试图解析器,没有的话用DefaultRequestToViewNameTranslator,并存放到viewNameTranslator。
    工作原理是:它会去掉请求URL的前缀和后缀,然后将剩下的部分作为视图名称。例如,如果我们设置prefix为"/app/“,suffix为”.html",stripExtension为false,那么请求的URL"/app/home.html"就会被解析为"home.html"。如果我们设置stripExtension为true,那么就会被解析为"home"。

    4.8 初始化视图解析器initViewResolvers()

    默认是寻找所有类型为ViewResolver的bean,将他们作为试图解析器,默认的试图解析器是InternalResourceViewResolver。它们是用于将控制器返回的逻辑视图名解析为具体的视图对象,这个对象可以是一个 JSP、一个 HTML 页面、一个 PDF 视图、一个 Thymeleaf 模板等。

    4.9 初始化重定向initFlashMapManager()

    默认的是SessionFlashMapManager。
    FlashMap是Spring MVC提供的一种临时存储数据的方式,它用于存储一次请求的数据,并在下一次请求中使用。FlashMap通常用于重定向请求时传递数据,比如,当你在处理POST请求后重定向到一个GET请求时,你可能希望重定向的GET请求能够访问POST请求处理的一些结果数据,这就可以使用FlashMap来实现。例如可以这样存储Flash属性:

    @RequestMapping(method = RequestMethod.POST)
    public String handlePostData(@ModelAttribute("data") Data data, RedirectAttributes redirectAttrs) {
        // 处理POST请求...
    
        // 将一些数据存储在FlashMap中,以便在重定向后的GET请求中使用
        redirectAttrs.addFlashAttribute("message", "123");
    
        // 重定向到GET请求
        return "redirect:/get-data";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后在重定向后的GET请求中提取Flash属性:

    @RequestMapping(method = RequestMethod.GET)
    public String displayData(Model model) {
        // 如果存在Flash属性,它们会自动添加到Model中
        if (model.containsAttribute("message")) {
            System.out.println("Message-FlashMap: " + model.getAttribute("message"));
        }
        
        // 处理GET请求...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5、总体流程

    在这里插入图片描述

  • 相关阅读:
    腾讯正式开源 Spring Cloud Tencent,微服务套件又多一个选择
    【C++ techniques】要求/禁止/判断—对象产生于堆中
    springboot基于协同过滤算法的书籍推荐毕业设计源码101555
    [单片机框架][drivers层][cw2015] fuelgauge 硬件电量计(二)
    关系数据理论 规范化
    乳企齐冲上市,是百家争鸣还是内卷加剧?
    R语言文本挖掘tf-idf,主题建模,情感分析,n-gram建模研究
    【智能优化算法】基于凌日算法求解单目标优化问题附matlab代码Transit Search Optimization Algorithm
    浏览器解码过程分析
    java设计模式---责任链模式详解
  • 原文地址:https://blog.csdn.net/springsdl/article/details/133686529