九大组件

【1. HandlerMapping

​ 是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。

【2. HandlerAdapter

​ 从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。

​ 小结:Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。

【3. HandlerExceptionResolver

​ 其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。

【4. ViewResolver

​ ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。

【5. RequestToViewNameTranslator

ViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslatorSpring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。

【6. LocaleResolver】

解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。

【7. ThemeResolver】

​ 用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolverThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。

【8. MultipartResolver】

​ 用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。

【9. FlashMapManager】

​ 用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

原文关于九大组件的介绍:https://zhuanlan.zhihu.com/p/73312778

九大组件的初始化

发布ContextRefreshedEvent事件

在spring的refresh最后一步,调用了finishRefresh方法:发布了一个ContextRefreshedEvent事件。

	protected void finishRefresh() {
		// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// Publish the final event.
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		if (!NativeDetector.inNativeImage()) {
			LiveBeansView.registerApplicationContext(this);
		}
	}

publishEvent方法:

	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

		// Decorate event as an ApplicationEvent if necessary
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}

		// Multicast right now if possible - or lazily once the multicaster is initialized
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
                        //使用事件多播器去派发事件
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		// Publish event via parent context as well...
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}

multicastEvent源码如下:这里会在ioc容器中找到所有监听ContextRefreshedEvent事件的监听器,并执行invokeListener

SpringMVC源码:九大组件初始化

最后调用监听器的onApplicationEvent方法:

	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			doInvokeListener(listener, event);
		}
	}

	@SuppressWarnings({"rawtypes", "unchecked"})
	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
                        //执行监听器的onApplicationEvent方法
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			String msg = ex.getMessage();
			if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
					(event instanceof PayloadApplicationEvent &&
							matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception.
				Log loggerToUse = this.lazyLogger;
				if (loggerToUse == null) {
					loggerToUse = LogFactory.getLog(getClass());
					this.lazyLogger = loggerToUse;
				}
				if (loggerToUse.isTraceEnabled()) {
					loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

这里有一个监听器,叫做SourceFilteringListener是在容器配置并刷新的时候就添加到容器中的:

	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		//设置id。。。
        
		//设置一些属性
		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
                //添加SourceFilteringListener
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		//web环境准备
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}
		//在刷新容器前的后置处理,默认为空方法
		postProcessWebApplicationContext(wac);
		//执行所有的ApplicationContextInitializer<ConfigurableApplicationContext>
		applyInitializers(wac);
		//调用容器刷新方法
		wac.refresh();
	}

注意SourceFilteringListener的构造方法和onApplicationEvent方法:构造方法传入了一个ContextRefreshListener作为委托类,而SourceFilteringListener内部调用onApplicationEvent方法实际上是调用ContextRefreshListener委托类的方法onApplicationEvent

	public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
		this.source = source;
		this.delegate = (delegate instanceof GenericApplicationListener ?
				(GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event.getSource() == this.source) {
			onApplicationEventInternal(event);
		}
	}

	protected void onApplicationEventInternal(ApplicationEvent event) {
		if (this.delegate == null) {
			throw new IllegalStateException(
					"Must specify a delegate object or override the onApplicationEventInternal method");
		}
		this.delegate.onApplicationEvent(event);
	}

再看ContextRefreshListener类,是FrameworkServlet的内部类,内部调用的是FrameworkServletonApplicationEvent方法,内部又调用自己的onRefresh方法,FrameworkServlet中的onRefresh方法是空实现,由其子类DispatcherServlet实现。

/**
 * ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
 * only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance.
 */
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

   @Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
      FrameworkServlet.this.onApplicationEvent(event);
   }
}

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

DispatcherServlet实现如下:

	/**
	 * This implementation calls {@link #initStrategies}.
	 */
	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	/**
	 * 初始化所有策略
	 * 九大组件的初始化在这里
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

到这里,终于开始九大组件的初始化。

初始化MultipartResolver

代码很简单,方法如下:

	private void initMultipartResolver(ApplicationContext context) {
		try {
                        //从容器中获取名字叫做multipartResolver的 class类型为MultipartResolver的bean
			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Detected " + this.multipartResolver);
			}
			else if (logger.isDebugEnabled()) {
				logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Default is no multipart resolver.
			//容器中有,就用,没有默认为空
			this.multipartResolver = null;
			if (logger.isTraceEnabled()) {
				logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
			}
		}
	}

初始化组件的套路

第一个初始化MultipartResolver后,后面还有八个init方法的调用,但是点进去,发现都是一个套路:

例如:initLocaleResolver方法和initThemeResolver,都是先从容器中查找对应的组件,如果找不到,就从默认的策略中加载组件

	private void initLocaleResolver(ApplicationContext context) {
		try {
                        //从容器中获取LocaleResolver
			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Detected " + this.localeResolver);
			}
			else if (logger.isDebugEnabled()) {
				logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// We need to use the default.
			//从默认策略中加载LocaleResolver
			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
						"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
			}
		}
	}

	private void initThemeResolver(ApplicationContext context) {
		try {
                        //从容器中获取ThemeResolver
			this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Detected " + this.themeResolver);
			}
			else if (logger.isDebugEnabled()) {
				logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			//从默认策略中加载ThemeResolver
			this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No ThemeResolver '" + THEME_RESOLVER_BEAN_NAME +
						"': using default [" + this.themeResolver.getClass().getSimpleName() + "]");
			}
		}
	}

默认的策略方法getDefaultStrategies如下:

	protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		if (defaultStrategies == null) {
			try {
				// Load default strategy implementations from properties file.
				// This is currently strictly internal and not meant to be customized
				// by application developers.
				//去DispatcherServlet所在的类路径下找DispatcherServlet.properties资源
				ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
                                //从DispatcherServlet.properties文件中加载默认策略
				defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
			}
			catch (IOException ex) {
				throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
			}
		}

		String key = strategyInterface.getName();
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
                        //按照逗号分隔出全类名
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<>(classNames.length);
			for (String className : classNames) {
				try {
                                        //加载Class
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                                        //调用createDefaultStrategy方法,创建出Class对应的组件实例
					Object strategy = createDefaultStrategy(context, clazz);
                                          //添加到strategies集合中
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
					throw new BeanInitializationException(
							"Could not find DispatcherServlet's default strategy class [" + className +
							"] for interface [" + key + "]", ex);
				}
				catch (LinkageError err) {
					throw new BeanInitializationException(
							"Unresolvable class definition for DispatcherServlet's default strategy class [" +
							className + "] for interface [" + key + "]", err);
				}
			}
                        //返回strategies集合
			return strategies;
		}
		else {
			return Collections.emptyList();
		}
	}

DispatcherServlet.properties资源在spring-webmvc模块中:在这个文件中配置了一些默认的组件。所以DispatcherServlet中默认的组件都是在该文件中配置的。

SpringMVC源码:九大组件初始化

下面看createDefaultStrategy方法,因为getDefaultStrategies方法通过它来创建组件:获取ioc容器并调用它的createBean方法,所以组件的创建、生命周期也会被IOC容器管理。

	protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
		return context.getAutowireCapableBeanFactory().createBean(clazz);//启动ioc容器的创建bean的流程
	}

HandlerMapping的创建

我们知道HandlerMapping中保存着请求url与对应handler的映射关系,那么该映射关系是如何加载的,这就需要研究对应组件的源码了。

HandlerMapping是一个接口,DispatcherServlet默认创建其中的三个实现:

  • org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
  • org.springframework.web.servlet.function.support.RouterFunctionMapping

RouterFunctionMapping与webflux有关系,不再本文讨论之内。

所以本文详细介绍BeanNameUrlHandlerMappingRequestMappingHandlerMapping的加载。

BeanNameUrlHandlerMapping加载

先看BeanNameUrlHandlerMapping类的继承关系:

SpringMVC源码:九大组件初始化

熟悉ioc容器流程的都知道,当组件实现了ApplicationContextAware接口的bean会被ApplicationContextAwareProcessor干预bean的创建,调用它的setApplicationContext方法,

BeanNameUrlHandlerMappingsetApplicationContext在它父类ApplicationObjectSupport中实现:

	@Override
	public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
		if (context == null && !isContextRequired()) {
			// Reset internal context state.
			this.applicationContext = null;
			this.messageSourceAccessor = null;
		}
		else if (this.applicationContext == null) {
			// Initialize with passed-in context.
			if (!requiredContextClass().isInstance(context)) {
				throw new ApplicationContextException(
						"Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
			}
                        //设置applicationContext和MessageSourceAccessor
			this.applicationContext = context;
			this.messageSourceAccessor = new MessageSourceAccessor(context);
                        //执行initApplicationContext方法
			initApplicationContext(context);
		}
		else {
			// Ignore reinitialization if same context passed in.
			if (this.applicationContext != context) {
				throw new ApplicationContextException(
						"Cannot reinitialize with different application context: current one is [" +
						this.applicationContext + "], passed-in one is [" + context + "]");
			}
		}
	}

这里比较复杂,用代码描述比较乱,我这里画了一个流程图,还是花了不少时间的。

流程图如下:

SpringMVC源码:九大组件初始化

最后所有beanName的url和handler的映射信息保存到AbstractUrlHandlerMapping类的handlerMap中。

RequestMappingHandlerMapping加载

还是先看该类的继承关系:

SpringMVC源码:九大组件初始化

该类实现了一系列的Aware接口,上面我们看BeanNameUrlHandlerMapping时候,它也实现了ApplicationContextAware,同时它们两有一个公共父类AbstractHandlerMapping,所以RequestMappingHandlerMapping类的初始化和BeanNameUrlHandlerMapping有类似。

同时,还注意到RequestMappingHandlerMapping类实现了InitializingBean接口,该类会在bean初始化的时候被回调,所以这也是重要的一点。

流程图如下:

SpringMVC源码:九大组件初始化

跟踪源码后发现,最后handlerMethod、url、bean等信息都保存到AbstractHandlerMethodMapping类的内部类MappingRegistry中。

相关文章: