springmvc篇:【DispatcherServlet源码详解】

注意:为了突出源码主要代码逻辑,本章中贴出的源码,将删除例如log,try等非主要的代码。而知贴出重要的代码块
先看入口

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;

static {
    // 将spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet 目录下的
    // DispatcherServlet.properties文件中的内容放到properties对象中,下面各个init方法中的
    // getDefaultStrategy方法里,将会通过这个对象,来生成默认放到容器中的类
    ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
    defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		
}

@Override
protected void onRefresh(ApplicationContext 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);
}

当我们启动tomcat时,会通过onRefresh方法开始进入,然后出初始化一些内容。下面我们分别来看一下

  • initMultipartResolver(context)方法
    public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
    private void initMultipartResolver(ApplicationContext context) {
        // 从容器中获取实现了MultipartResolver接口并且名字为multipartResolver的bean
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    }

    这个方法主要是从容器中获取名字为multipartResolver的bean。这个bean主要是用来处理文件上传用的,并且这个bean必须实现MultipartResolver接口。现在再来回想一下,我们spring-mvc.xml中是怎么配置文件上传的?是不是如下样子:

    注意看id,它与源码中的常量字符串是一样的。这也就是为什么这个id必须要这么写的原因了。不这么写,在这个init方法里面就无法获取到我们的这个bean了。
    MultipartResolver的默认实现类有两个,上图便是使用其中一个:

  • initLocaleResolver(context)方法

    public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
    private void initLocaleResolver(ApplicationContext context) {
       try {
            this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
            }
        }
        catch (NoSuchBeanDefinitionException ex) {
            // 将DispatcherServlet.properties中名为LocaleResolver对应的类放到容器中
            this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        }
    }
    
    DispatcherServlet.properties位于
    spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet目录下,内容如下:
    org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
    
    ...其他的省略....

    这个方法是用来从容器中获取实现本地化的bean,回想一下,我们如果要做本地化的话,在spring-mvc.xml中是怎么写的?
    是不是一般会如下的写法:

    注意看id,它与源码中的常量字符串是一样的。这也就是为什么这个id必须要这么写的原因了。不这么写,在这个init方法里面就无法获取到我们的这个bean了。
    这个bean你既可以自己通过实现接口自定义一个,也可以使用springmvc自己的一些实现类,如下:


     

  • initThemeResolver(context)方法

    public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
    private void initThemeResolver(ApplicationContext context) {
        try {
            this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
           
        }catch (NoSuchBeanDefinitionException ex) {
             // 将DispatcherServlet.properties中名为LocaleResolver对应的类放到容器中
            this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
        }		
    }
    
    
    DispatcherServlet.properties内容如下:
    org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
    
    ...其他的省略....

    这个方法主要是从容器中获取主题解析器(ThemeResolver)的,什么是ThemeResolver呢?大家都见过通过在管理系统上选择样色样式,然后系统的色调就都变了的效果吧。关于如何使用主题解析器,可以参考这篇文章:https://blog.csdn.net/qq924862077/article/details/53438710

    下面来看一下如何通过spring-mvc.xml来声明一个主题解析器相关的bean,如下:

    注意看id,它与源码中的常量字符串是一样的。这也就是为什么这个id必须要写themeResolver的原因了。不这么写,在这个init方法里面就无法获取到我们的这个bean了。

    下面来看一下springmvc默认给我们带了那些ThemeResolver的实现类,如下:

     

  • initHandlerMappings(context)方法

    public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
        // 通过在xml中设置detectAllHandlerMappings这个属性,来决定是加载所有的handlermapping还是只加载我们自定义的那个handlermapping
        // 如果未true,从容器中获取所有的handlermapping
        if (this.detectAllHandlerMappings) {
        // 从容器中获取所有的handlerMapping,比如springmvc启动时自动加载的和我们自定义的
        Map<String, HandlerMapping> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // 对这些handlermapping的顺序进行排序
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
    	}else { // 为false,那么就只从容器中获取名字为handlerMapping的那个handlermapping
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
    		}
            //如果从容器找不到,就生成一个默认的
    		if (this.handlerMappings == null) {
                // 从DispatcherServlet.properties文件中找HandlerMapping对应的那些就是默认的handlermapping
                this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    		}
    	}

    这个方法主要就是从容器中获取handlermapping的(什么是handlermapping,请参考前面的文章)。springmvc默认给我们带的handlermapping有哪些,如下图:

    到底哪些handlermapping在程序启动时,会自动放到容器中,这就需要根绝一些触发条件了,比如,如果我们少用了<mvc:annotation-driven/>标签,那么RequestMappingHandlerMapping就会自动放到容器中(原因不在此解释)。
    基本上来看,当detectAllHandlerMappings为true时,下面三个handlermapping会在容器出现。
    1) org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
    2) org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
    3) org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
    那么如果detectAllHandlerMappings为false时,会从容器中获取名字为handlerMapping的那个bean,也就是说如果我们想让springmvc只用我们指定的handlermapping时,通过两步即可,如下:
    第一步:在web.xml中设置detectAllHandlerMappings为false(默认为true)如下:

    第二步:在spring-mvc.xml中声明要使用的handlermapping,如下:

    <bean id="/myHanler" class="com.lhb.controller.BeanNameURLHandlerMappingController"/> 
    <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

    注意id必须为handlerMapping,不这么写,在这个init方法里面就无法获取到我们的这个bean了。
    如果此时此时在容器中找不到对应的名为handlerMapping的bean则会调用
    this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    去获取默认的handlerMapping,我们看看getDefaultStrategies是如何获取的,源码如下:

    private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
    static {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
            // key就是接口的名字HandlerMapping
    		String key = strategyInterface.getName();
            // 这里就是从springmvc的jar包中的\org\springframework\web\servlet 文件夹下的DispatcherServlet.properties文件中找到一个名为HandlerMapping的key,它对应了springmvc默认放到容器中的handlerMapping类。下面会截图
    		String value = defaultStrategies.getProperty(key);
    		if (value != null) {
                // 默认的类有好多个,用逗号分隔的,所以这里使用逗号进行分隔
    			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
    			List<T> strategies = new ArrayList<T>(classNames.length);
                // 通过逗号分隔后,然后通过全类名来实例化
    			for (String className : classNames) {
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    Object strategy = createDefaultStrategy(context, clazz);
    					strategies.add((T) strategy);
    			}
    			return strategies;
    		}
    		else {
    			return new LinkedList<T>();
    		}
    	}

    DispatcherServlet.properties如下:

    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
    ...其他内容省略...

    从上面源码可以看出,获取默认的handlermapping就是从DispatcherServlet.properties文件中找到HandlerMapping对应的这两个类的全限定名,然后在程序中通过反射实例化放到容器的而已。这也就是为什么有时候我们没声明bean,程序未报错的原因了。
     

  • initHandlerAdapters(context)方法

    public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
    private void initHandlerAdapters(ApplicationContext context) {
        this.handlerAdapters = null;
        // detectAllHandlerAdapters为true,表示从容器获取所有的handlerAdpater
        if (this.detectAllHandlerAdapters) {
            Map<String, HandlerAdapter> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
                AnnotationAwareOrderComparator.sort(this.handlerAdapters);
            }
        }else {//detectAllHandlerAdapters为false,则只从容器中找名字为handlerAdapter的bean
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        // 如果找不到一个handlerAdpater都没有
        if (this.handlerAdapters == null) {
            // 那么就加载DispatcherServlet.properties中名为HandlerAdapter的类作为默认的handlerAdapter放到容器中
            this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        }
    }
    
    

    从源码可以看出initHandlerAdapters和initHandlerMapping如出一致,逻辑都是相同的,都是根据detectAllHandlerAdapters来决定是从容器中获取所有adapter还是只从容器中获取名为handlerAdapter的adapter。
    同理,他的getDefaultStrategies方法,也是将DispatcherServlet.properties中名为HandlerAdapter对应的类进行实例化,然后放到容器中来作为默认的adatper的。这里就不多做解释了。
    DispatcherServlet.properties如下:

    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

     

  • initHandlerExceptionResolvers(context)方法

    
    public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
    private void initHandlerExceptionResolvers(ApplicationContext context) {
    		this.handlerExceptionResolvers = null;
            
    		if (this.detectAllHandlerExceptionResolvers) {
                // 从容器中获取所有的HandlerExceptionResolver的实现类
    			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
    					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
    			if (!matchingBeans.isEmpty()) {
    				this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
    				// 对这些HandlerExceptionResolver的实现类进行排序
    				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
    			}
    		}
    		else {
                // 从容器中找的名为handlerExceptionResolver的那个类,来处理controller抛出的异常
                HandlerExceptionResolver her =
    						context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
                this.handlerExceptionResolvers = Collections.singletonList(her);
    		}
    
    		// 如果从容器中一个对应的resolver都找不到,就生成DispatcherServlet.properties中指定的默认resolver。
    		if (this.handlerExceptionResolvers == null) {
    			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
    			
    			}
    		}
    	}

    从源码可以看出,同样是通过detectAllHandlerExceptionResolvers这个属性来决定是从容器中获取素有的exceptionResolver还是只从容器值获取指定名字的exceptionResolver。如果从容器中一个exceptionResolver都获取不到,那么就通过getDefaultStrategies方法,将DispatcherServlet.properties中写的exceptionResolver进行实例化,来使用。
    DispatcherServlet.properties如下:

    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver


     

  • initRequestToViewNameTranslator(context)方法
    什么是RequestToViewNameTranslator?作用是什么?可以看我的文章spring篇:【RequestToViewNameTranslator】里面有详细说明。下面我们来看源码:

    public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
    private void initRequestToViewNameTranslator(ApplicationContext context) {
        try {
            this.viewNameTranslator =context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
        }catch (NoSuchBeanDefinitionException ex) {
            // We need to use the default.
            this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
        }
    }
    

    源码很简单,就是从容器中去获取名为viewNameTranslator的translator,如果容器中没有,那么就把DispatcherServlet.properties中写的那个translator进行实例化来使用。DispatcherServlet.properties如下:

    org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator


     

  • initViewResolvers(context)方法
    什么是RequestToViewNameTranslator?作用是什么?可以看我的文章spring篇:【RequestToViewNameTranslator】里面有详细说明。下面我们来看这个方法的源码:
     

    public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
    private void initViewResolvers(ApplicationContext context) {
    		this.viewResolvers = null;
            //是否从容器中检测所有的ViewResolver,detectAllViewResolvers默认为true,可通过web.xml来修改这个值
    		if (this.detectAllViewResolvers) {
    			// 从容器中检测所有的ViewResolver的实现类
    			Map<String, ViewResolver> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
    			if (!matchingBeans.isEmpty()) {
    				this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
    				// 将从容器中检索到的所有ViewResolver进行排序
    				AnnotationAwareOrderComparator.sort(this.viewResolvers);
    			}
    		}
    		else {
                //否则只从容器中检测名字为viewResolver的ViewResolver 
                ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
                this.viewResolvers = Collections.singletonList(vr);
    		}
    
    		// 如果一个ViewResolver也没得到,就根据DispatcherServlet.properties中指定的来创建一个默认的ViewResolver
    		if (this.viewResolvers == null) {
    			this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
    			}
    		}
    	}


     

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章