• springMVC源码(2) DispatcherServlet初始化


    对DispatcherServlet的认识

    在web项目中,ContextLoaderListener起到的作用就是实例化一个父容器,管理跟逻辑业务相关的bean对象,Dispatcher实例化一个子容器,给管理跟前面比较近的一些bean对象。把拦截下来的请求,依据相应的规则分发到目标Handler来处理。

    DispatcherServlet的继承关系

    public class DispatcherServlet extends FrameworkServlet
    public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
    public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware
    

    可以看到DispatcherServlet 有这样一些继承关系。

    HttpServlet类初学web项目的时候经常用到的,它的作用就是可以处理不同的请求方法(比如:get方法,post方法等等),可以获得servletContext对象,可以获得为这个servlet配置的初始信息(initParam)。特别重要的是每一个servlet都是有生命周期的 init,service,destroy方法代表着这个servlet不同的生命周期。

    httpServletBean抽象类除了继承了httpServlet类还是实现了EnvironmentCapable, EnvironmentAware接口。这两个接口的作用分别是返回一个Environment类型的对象和设置一个Environment类型的对象,相当于是为Environment的get/set方法专门设置了两个接口。httpServletBean抽象类主要的作用就是将web.xml中为DispatcherServlet配置的相关信息(init-param)赋值给对应的专门的变量中供后面使用。

    FrameworkServlet抽象类除了实现HttpServletBean类还实现了ApplicationContextAware接口,这个接口的作用就是ApplicationContext的set方法。FrameworkServlet抽象类主要的作用集中在其initServletBean方法中,实例化了一个webApplicationContext并完成相关初始化。

    DispatcherServlet继承了FrameworkServlet抽象类,他主要功能就是完成FrameworkServlet剩下的初始化工作以及对请求的处理。

    DispacherServlet初始化过程

    从上面的继承可以看出DispacherServlet也是一个Servlet,在tomcat中使用一个servlet必须按照他的标准生命周期一步一步完成执行才行。
    首先会先执行这个servlet的init方法对servlet进行初始化,初始化只执行一次,之后就可以多次使用service方法来处理你的请求了,最后在关闭容器的时候会使用destroy方法。既然DispacherServlet也是一个Servlet(继承了HttpServlet类,在web.xml配置的时候也是把它当做一个Servlet进行配置的),那么DispatcherServlet也需要经过这样的步骤才能被使用。

    首先看看init()方法

    public final void init() throws ServletException {
    		//   将web.xml  中 dispatcherServlet的配置信息以key-value形式包装到PropertyValue中
    		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    		if (!pvs.isEmpty()) {
    			try {
    				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
    				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
    				initBeanWrapper(bw);
                                    //将为dispatchServlet配置的initParam赋值给相关变量
    				bw.setPropertyValues(pvs, true);
    			}
    			catch (BeansException ex) {
    				throw ex;
    			}
    		}
    		initServletBean();
    	}
    
    

    上面函数将web.xml中dispatchServlet配置的信息(init-param)封装一个PropertyValue然后放在PropertyValues中。
    然后把dispatchServlet包装成一个BeanWrapper类实例,这个类是Sping提供的一个来操作javaBean属性的工具,使用它可以直接修改一个对象的属性。bw.setPropertyValues(pvs, true)这个方法就是将配置的参数设置到对应的属性中去。
    initServletBean()是一个模板方法,在FrameworkServlet具体实现。

    至于它是如何实现这个功能的,就不去探究了。通过debug可以看到确实是将我们配置的信息设置到对象的属性中了。
    hr-w10

    在没有执行init()方法的时候可以看到contextId,contextConfigLocation是null值

    在执行了PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties)语句后,就将配置的ContextConfigLocation信息包装到PropertyValue并放入PropertyValues的list中了。

    在执行了bw.setPropertyValues(pvs, true)语句后可以看到ContextConfigLocation属性被设置了值(因为只设置了ContextConfigLocation参数,所以contextId还是null)。

    接着看看initServletBean()方法做了什么事情

    protected final void initServletBean() throws ServletException {
    		....
    		try {
    			this.webApplicationContext = initWebApplicationContext();
    			initFrameworkServlet();
    		}
    		catch (ServletException | RuntimeException ex) {
    			logger.error("Context initialization failed", ex);
    			throw ex;
    		}
    	}
    
    

    可以看到这个函数调用了另外两个函数,其中initFrameworkServlet()是一个模板方法但是在其子类DispatcherServlet中并没有具体实现。

    所以主要看看initWebApplicationContext()方法做了什么。

    	protected WebApplicationContext initWebApplicationContext() {
    		//从servletContext中查找是否设置了rootContext,也就是ContextLoaderListener实例化的webApplicationContext
    		WebApplicationContext rootContext =
    				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    		WebApplicationContext wac = null;
    
    		if (this.webApplicationContext != null) {
    			//如果在构建时注入了一个webApplicationContext->使用它
    			wac = this.webApplicationContext;
    			if (wac instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                                    //在当前容器不是活动状态时
    				if (!cwac.isActive()) {
                                            //设置父容器
    					if (cwac.getParent() == null) {
    						cwac.setParent(rootContext);
    					}
                                            //进行相关配置并实例化单例bean
    					configureAndRefreshWebApplicationContext(cwac);
    				}
    			}
    		}
                    //如果没有通过构造方法注入,就查看servletContext中是否有DispacherServlet实例化的WebApplicationContext(通过查找Atrribute的方式)
    		if (wac == null) {
    			wac = findWebApplicationContext();
    		}
                    //如果之前没有实例化过,那就实例化一个
    		if (wac == null) {
    			wac = createWebApplicationContext(rootContext);
    		}
                    //初始化一些处理请求的工具
    		if (!this.refreshEventReceived) {
    			synchronized (this.onRefreshMonitor) {
    				onRefresh(wac);
    			}
    		}
                    //将WebApplicationContext放入servletContext中
    		if (this.publishContext) {
    			// Publish the context as a servlet context attribute.
    			String attrName = getServletContextAttributeName();
    			getServletContext().setAttribute(attrName, wac);
    			
    		}
    
    		return wac;
    	}
    

    可以看到大概做了这么几件事情:

    步骤1.查找父容器(rootContext)
    步骤2.获得一个WebApplicationContext
    步骤3.onrefresh方法中初始化处理请求使用到的工具(比如:handlerMapping,handlerAdapter等等)
    步骤4.将webApplicationContext设置到servletContext中
    步骤1 和 步骤4 都很简单,容易理解。主要深入了解步骤2和步骤3。

    步骤2---获得一个WebApplicationContext有三个步骤:

    1.通过构造方法注入一个
    2.通过servletContext获得一个(查找servletContext的atrribute属性,属性名和查找父容器不同)
    3.自己实例化一个(主要手段)

    看看是如何实例化一个webApplicationContext。

    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    		//获得要实例化的webApplicationContext的class对象,这里获得的是XmlWebApplicationContext   class对象
    		Class<?> contextClass = getContextClass();
                    //判断class对象是否是ConfigurableWebApplicationContext的子类
    		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    			throw new ApplicationContextException(
    					"Fatal initialization error in servlet with name '" + getServletName() +
    					"': custom WebApplicationContext class [" + contextClass.getName() +
    					"] is not of type ConfigurableWebApplicationContext");
    		}
                    //实例化一个
    		ConfigurableWebApplicationContext wac =
    				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
                    //设置相关属性
    		wac.setEnvironment(getEnvironment());
    		wac.setParent(parent);
    		String configLocation = getContextConfigLocation();
    		if (configLocation != null) {
    			wac.setConfigLocation(configLocation);
    		}
                    //设置相关属性并实例化单例bean主要是(refresh方法)
    		configureAndRefreshWebApplicationContext(wac);
    
    		return wac;
    	}
    

    在看看为什么获得的是一个xmlWebApplicationContext class对象。

    	public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
            private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
    
            public void setContextClass(Class<?> contextClass) {
    		this.contextClass = contextClass;
    	}
    
    	public Class<?> getContextClass() {
    		return this.contextClass;
    	}
    

    在FrameworkServlet中直接指定了默认值,当然你也可以自己通过init-param配置webApplicationContext的class对象,前提是配置的对象必须是ConfigurableWebApplicationContext的子类。配置后会像contextConfigLocation属性一样注入到contextClass对象中。

    在看看configureAndRefreshWebApplicationContext(wac)方法具体做了什么。

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
                    //设置Contextid(这个属性可以自己配置)
    		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
    			if (this.contextId != null) {
    				wac.setId(this.contextId);
    			}
    			else {
    				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
    						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
    			}
    		}
                    
                    //设置ServletContext属性
    		wac.setServletContext(getServletContext());
                    //设置servletConfig属性
    		wac.setServletConfig(getServletConfig());
                    //设置namespace属性(可以自己配置)
    		wac.setNamespace(getNamespace());
    		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    		ConfigurableEnvironment env = wac.getEnvironment();
                    //将servletContext,servletConfig作为属性保存在environment中
    		if (env instanceof ConfigurableWebEnvironment) {
    			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    		}
                    //在刷新给定的WebApplicationContext之前对其进行后处理(后处理相信了解springFramework的一定知道)
                    // 这是个空方法
    		postProcessWebApplicationContext(wac);
                    //执行配置的实现了  ApplicationContextInitializer接口的类的initialize(C applicationContext)方法
    		applyInitializers(wac);
                    //ioc容器启动方法,会设置一些属性然后实例化没有设置懒加载的单例的bean
    		wac.refresh();
    	}
    

    看看applyInitializers(wac)方法

    	protected void applyInitializers(ConfigurableApplicationContext wac) {
                    //从servletContext中设置init-param中获得配置属性名为globalInitializerClasses的属性值
    		String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
    		if (globalClassNames != null) {
    			for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
                                    //分解globalInitializerClasses属性值(可能一次配了多个,用,隔开的)
                                    //然后loadInitializer方法实例化并放入contextInitializers中
    				this.contextInitializers.add(loadInitializer(className, wac));
    			}
    		}
                    // contextInitializerClass  配置在servlet的init-param属性中,然后通过跟ContextConfigLocation同样的方法注入
    		if (this.contextInitializerClasses != null) {
    			for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
    				this.contextInitializers.add(loadInitializer(className, wac));
    			}
    		}
                    // 进行排序
    		AnnotationAwareOrderComparator.sort(this.contextInitializers);
                    //一个一个的执行其initialize方法
    		for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
    			initializer.initialize(wac);
    		}
    	}
    

    步骤3----在看看onRefresh方法,这是个模板方法,在DispatcherServlet中实现。

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

    在看看initStrategies(context)方法

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

    可以看到它初始化了各种处理请求时用到的工具。包括MultipartResolver,LocaleResolver,ThemeResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver,RequestToViewNameTranslator,ViewResolvers,FlashMapManager。

    HandlerMapping:是根据request找到响应的处理器handler和Intercepter。
    HandlerAdapter:可以理解为使用处理器的。
    HandlerExceptionResolver:是处理异常的。
    ViewResolver:是将String类型的属兔名和Locale解析为View类型的视图。
    RequestToViewNameTranslator: ViewResolver是根据ViewName查找View,但有的Handler处理完并没有设置view也没有设置viewName,这时就需要从
    request获取viewName了,RequestToViewNameTranslator 就是做这件事的。
    LocaleResolver: 解析视图需要两个参数:一个是视图名,另一个是locale。视图名是处理器返回的或者使用RequestToViewNameTranslator 解析的默认视
    图名,locale则是由LocaleResolver解析出来的。
    ThemeResolver: 是解析主题的。
    MultipartResolver: 用于处理上传请求,处理方法是将普通的request包装成MltipartHttpServletRequest,然后直接调动其getFile方法获得file。
    FlashMapManager:用于管理FlashMap的,FlashMap主要用在redirect中传递参数。

    看看在DispatcherServlet中这些工具的声明

    	@Nullable
    	private MultipartResolver multipartResolver;
    
    	/** LocaleResolver used by this servlet */
    	@Nullable
    	private LocaleResolver localeResolver;
    
    	/** ThemeResolver used by this servlet */
    	@Nullable
    	private ThemeResolver themeResolver;
    
    	/** List of HandlerMappings used by this servlet */
    	@Nullable
    	private List<HandlerMapping> handlerMappings;
    
    	/** List of HandlerAdapters used by this servlet */
    	@Nullable
    	private List<HandlerAdapter> handlerAdapters;
    
    	/** List of HandlerExceptionResolvers used by this servlet */
    	@Nullable
    	private List<HandlerExceptionResolver> handlerExceptionResolvers;
    
    	/** RequestToViewNameTranslator used by this servlet */
    	@Nullable
    	private RequestToViewNameTranslator viewNameTranslator;
    
    	/** FlashMapManager used by this servlet */
    	@Nullable
    	private FlashMapManager flashMapManager;
    
    	/** List of ViewResolvers used by this servlet */
    	@Nullable
    	private List<ViewResolver> viewResolvers;
    

    所以可以看到有的工具只能有一个,而有的工具可以有多个。

    看看是如何初始化这些工具的。

    private void initMultipartResolver(ApplicationContext context) {
    		try {
    			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    		}
    		catch (NoSuchBeanDefinitionException ex) {
    			// Default is no multipart resolver.
    			this.multipartResolver = null;
    		}
    	}
    

    MultipartResolver工具的初始化过程是:首先从IOC容器中查找name=multipartResolver的bean对象,如果IOC容器中没有则会抛出异常,在catch中将其赋值为null。

    private void initLocaleResolver(ApplicationContext context) {
    		try {
    			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
    		}
    		catch (NoSuchBeanDefinitionException ex) {
    			// We need to use the default.
    			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
    		}
    	}
    

    LocaleResolver工具的初始化过程是:从IOC容器获取name=localeResolver的bean对象,如果IOC容器没有则抛出异常,在catch中可以看到这个语句this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);获得了一个默认的localeResolver给了this.localeResolver。

    默认的LocaleResolver从哪里来??

    static {
    		try {
    			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
    			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    		}
    		catch (IOException ex) {
    			throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
    		}
    	}
    

    在DispatcherServlet中有这么一段静态代码段,加载了DispatchServlet.properties文件
    文件位于org/springframework/web/servlet/DispatcherServlet.properties
    文件内容:
    org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
    ....

    这个文件中设置一些必须工具的默认class(String类型),getDefaultStrategy方法会通过反射实例化这些默认工具(在自己没有配置的时候)

    private void initHandlerMappings(ApplicationContext context) {
    		this.handlerMappings = null;
    
    		if (this.detectAllHandlerMappings) {
    			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
    			// 找出 所有的handlerMappings  包括父Context中的
    			Map<String, HandlerMapping> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
    			if (!matchingBeans.isEmpty()) {
    				this.handlerMappings = new ArrayList<>(matchingBeans.values());
    				// We keep HandlerMappings in sorted order.
    				AnnotationAwareOrderComparator.sort(this.handlerMappings);
    			}
    		}
    		else {
    			try {
    				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
    				this.handlerMappings = Collections.singletonList(hm);
    			}
    			catch (NoSuchBeanDefinitionException ex) {
    				// Ignore, we'll add a default HandlerMapping later.
    			}
    		}
    
    		// Ensure we have at least one HandlerMapping, by registering
    		// a default HandlerMapping if no other mappings are found.
    		if (this.handlerMappings == null) {
    			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    		}
    	}
    

    HandlerMapping 还可以通过设置detectAllHandlerMappings来判断是否需要找出父类的HandlerMapping。

  • 相关阅读:
    Windows 7 X64位平台下,VC6调试运行程序,中断调试无法退出
    在Windows中安装MinGW-w64(有图,一步一步)
    自我修养
    Core 事件总
    异步消息
    微信小程序
    安装Zookeeper集群
    Linux删除非空目录
    adoop集群动态添加和删除节点
    安装和配置hadoop集群步骤
  • 原文地址:https://www.cnblogs.com/zhangchenwei/p/12555126.html
Copyright © 2020-2023  润新知