zoukankan      html  css  js  c++  java
  • SpringMVC源码:九大组件初始化

    九大组件

    【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

    image-20211214103355642

    最后调用监听器的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中默认的组件都是在该文件中配置的。

    image-20211214103038914

    下面看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类的继承关系:

    image-20211214105554033

    熟悉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 + "]");
    			}
    		}
    	}
    

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

    流程图如下:

    BeanNameUrlHandlerMapping初始化

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

    RequestMappingHandlerMapping加载

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

    image-20211214141930131

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

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

    流程图如下:

    RequestMappingHandlerMapping初始化2

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

  • 相关阅读:
    解析三种常见分布式锁的实现
    RabbitMQ基础概念详解
    数据库事务概念
    ECIF与CRM
    MQ(消息队列)学习
    数据粒度的设计
    链表之 头节点与尾指针 区别
    牛客之错题(2016.1.15) && 带头节点与不带头的区别
    数据结构之递归回溯算法
    LeetCode--Single Number
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/15688338.html
Copyright © 2011-2022 走看看