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

news2024/11/20 8:35:38

a、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>

b、父子容器的概念

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

父容器(Root WebApplicationContext)

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

子容器(Servlet WebApplicationContext)

主要负责加载与Web应用程序相关的bean和配置。它通常包含以下内容:
  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。

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

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

调用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;
	}
}

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

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

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

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

在子容器初始化之前添加了一个监听容器刷新的事件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);
	}
}

6. FrameworkServlet#onApplicationEvent()

public void onApplicationEvent(ContextRefreshedEvent event) {
	this.refreshEventReceived = true;
	synchronized (this.onRefreshMonitor) {
		onRefresh(event.getApplicationContext());
	}
}

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

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

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

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

在这里插入图片描述

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

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

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 {
    // 这里可以添加自定义的处理器方法
}

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

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

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

4.5.4 HandlerFunctionAdapter

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

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

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

4.5.5 RequestMappingHandlerAdapter

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

4.6 初始化异常initHandlerExceptionResolvers()

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

4.6.1 ExceptionHandlerExceptionResolver

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

4.6.2 ResponseStatusExceptionResolver

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

4.6.3 DefaultHandlerExceptionResolver

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

4.6.4 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.7 初始化视图解析器initViewResolvers()

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

4.8 初始化重定向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";
}

然后在重定向后的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请求...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1081437.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Django实现音乐网站 ⒆

使用Python Django框架做一个音乐网站&#xff0c; 本篇主要为排行榜功能及音乐播放器部分功能实现。 目录 排行榜列表 设置路由 视图处理 模板渲染 设置跳转入口 播放器功能开发 设置路由 模板页面 脚本渲染 列表渲染和播放器实现 音乐播放器列表展示关闭 总结 排…

U3d力扣基础刷题-2

移除元素 public int RemoveElement(int[] nums, int val){// int j 0;// for (int i 0; i < nums.Length; i) // {// if (nums[i] ! val)// {// nums[j] nums[i];// }// }// return j;int j0;for(int i0;i<nums.Length;i){if(nums[i]!v…

关于导出的Excel文件的本质

上篇文章中提到关于xlsx改造冻结窗格的代码&#xff0c;我是怎么知道要加pane的呢&#xff0c;加下来就把我的心路历程记录一下。 我改造之前也是没有头绪的&#xff0c;我网上查了很多&#xff0c;只告诉我如何使用&#xff0c;但源码里没有针对!freeze的处理&#xff0c;所以…

有效回收组分含量

声明 本文是学习GB-T 42944-2023 纸、纸板和纸制品 有效回收组分的测定. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件描述了纸、纸板和纸制品中有效回收组分的测定方法。 本文件适用于各种纸、纸板和纸制品&#xff0c;也适用于铝箔…

单目标优化算法:墨西哥蝾螈优化算法(Mexican Axolotl Optimization,MAO)求解23个函数MATLAB

一、墨西哥蝾螈优化算法 墨西哥蝾螈优化算法&#xff08;Mexican Axolotl Optimization&#xff0c;MAO&#xff09;由Yenny Villuendas-Rey 1等人于2021年提出&#xff0c;该算法具有较强的平衡全局搜索与局部搜索能力。 参考文献&#xff1a; [1]Villuendas-Rey, Yenny, Jo…

Apollo源码

目录结构介绍 cyber 消息中间件&#xff0c;替换ros作为消息层 data 地图等生成好的数据放在这里&#xff08;其他数据待补充&#xff09; docker 容器相关 docs 文档相关 modules 定位&#xff0c;预测&#xff0c;感知&#xff0c;规划等自动驾驶模块 scripts 脚本 third_p…

工学云打卡签到自动实现关于异地时定位的问题解决|蘑菇钉

工学云打卡助手&#xff0c;能解决你在异地时每天不间断签到的问题&#xff0c;仔细看图哦 1.自动签到 2.自定义打卡地区 3.生成日周月报与总结自动发表 4.支持随机通用内容 5.支持打卡结果推送 你是否曾经因为缺乏自律而无法坚持学习目标&#xff1f;是否曾经因为无法衡量…

WorkPlus一站式解决方案,助力企业构建统一门户系统

在信息爆炸的时代&#xff0c;企业管理面临着海量的数据和各类业务应用的复杂性。如何实现信息的井然有序、高效管理&#xff0c;成为企业发展的关键。WorkPlus作为领先的品牌&#xff0c;致力于打造统一门户系统&#xff0c;为企业提供全方位的服务和解决方案。本文将以知乎的…

Java实现B树

1.介绍 B树是一种自平衡的搜索树数据结构&#xff0c;常用于数据库和文件系统中的索引结构。它具有以下好处和功能&#xff1a; 高效的查找操作&#xff1a;B树的特点是每个节点可以存储多个关键字&#xff0c;并且保持有序。通过在节点上进行二分查找&#xff0c;可以快速定位…

使用PyTorch处理多维特征输入的完美指南

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

WorkPlus私有部署即时通信助力企业信息安全与高效协作

在当今快速发展的商业环境中&#xff0c;高效的内部沟通对企业的成功至关重要。然而&#xff0c;在保障信息安全的同时&#xff0c;如何实现高效的协作和沟通一直是企业所面临的挑战。传统的公共即时通信平台&#xff0c;尽管提供了便利的沟通工具&#xff0c;但其数据存储和控…

python- excel 创建/写入/删sheet+花式遍历

文章目录 前言python- excel 创建/写入/删sheet花式遍历1. excel 创建2. 写入excel3. 创建写入excel demo实战4. 删除sheet5. excel 花式遍历 demo实战5.1. 获取 A1的值5.2. 获取指定列的切片数据&#xff0c;获取 B1到B5的值5.3. 循环整个excel的这个sheet5.4. 遍历指定行&…

VR全景云端看车,让你享受不一样的购车体验

这个“黄金周”可谓是热闹非凡&#xff0c;不仅房企大降价&#xff0c;部分车企也在“黄金周”发力&#xff0c;降价优惠促销量&#xff0c;那么你准备买车了吗&#xff1f;买车也是一个大件&#xff0c;需要多家店去走动比对价位&#xff0c;难免会挑花了眼&#xff0c;其实我…

中国SaaS行业等待“渡劫时刻”

期待、追捧、失望、质疑&#xff0c;中国SaaS行业激荡十几年&#xff0c;尝遍了市场浮沉&#xff0c;如今潮水褪去&#xff0c;SaaS企业们到了见真章的时刻。 一开始&#xff0c;SaaS行业被人们寄予厚望。互联网的蓬勃发展&#xff0c;数字化转型的历史进程&#xff0c;似乎都…

快讯|Tubi 有 Rabbit AI 啦

在每月一期的 Tubi 快讯中&#xff0c;你将全面及时地获取 Tubi 最新发展动态&#xff0c;欢迎星标关注【比图科技】微信公众号&#xff0c;一起成长变强&#xff01; Tubi 推出 Rabbit AI 帮助用户找到喜欢的视频内容 Tubi 于今年九月底推出了 Rabbit AI&#xff0c;这是一项…

基于 gin框架搭建入门项目

go mod init gin-ranking go: creating new go.mod: module gin-ranking go: to add module requirements and sums:go mod tidy下载gin框架 cmd窗口中执行命令&#xff1a; go get -u github.com/gin-gonic/ginpackage mainimport ("github.com/gin-gonic/gin"&qu…

圭亚那奥罗拉金矿配电工程中AM5SE系列微机保护装置

安科瑞 崔丽洁 摘要&#xff1a;目前&#xff0c;微机保护装置广泛应用于电力系统中&#xff0c;该类装置能够有效监测电力系统的运行状况&#xff0c;并实时记录电力系统出现故障的位置及性质&#xff0c;从而为故障的快速处理提供有效的参考信息。本文介绍的AM5SE系列微机保…

android:can not find libdevapi.so

一、为什么会出现这样的报错&#xff1f; 引用了一些第三方的sdk的so库之后通常都会遇到这样的错误&#xff0c;&#xff08;“nativeLibraryDirectories”[/data/app/com.lukouapp-1/lib/arm64, /vendor/lib64, /system/lib64]]] couldnt find "libxxxx.so"&#x…

zookeeper节点数据类型介绍及集群搭建

一、zookeeper介绍 zookeeper官网&#xff1a;Apache ZooKeeper zookeeper是一个分布式协调框架&#xff0c;保证的是CP&#xff0c;即一致性和分区容错性&#xff1b;zookeeper是一个分布式文件存储系统&#xff0c;文件节点可以存储数据&#xff0c;监听子文件节点等可以实…

绥化市中心广场焕发新活力:OLED透明拼接屏的奇观展示

OLED透明拼接屏技术在绥化城市的应用引起了广泛关注。 绥化市位于中国东北地区&#xff0c;是黑龙江省的一个重要城市。 该市拥有悠久的历史&#xff0c;历经多个朝代的兴衰。绥化的历史背景赋予了这座城市独特的文化底蕴和魅力。 绥化市内有许多著名景点&#xff0c;其中最…