【稀里糊涂学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);
}

//这里说一下strategy这个词,看到他不要想得那么麻烦,他意思为策略的意思,但实际上就是初始化一些对象到容器中。所以千万不要跟这个单词的意思联系在一块
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呢?大家都见过通过在管理系统上选择样色样式,然后系统的色调就都变了的效果吧。关于如何使用主题解析器,可以参考这篇文章:springMVC源码分析--动态样式ThemeResolver(一)_井底之蛙-CSDN博客

    下面来看一下如何通过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篇:【ViewResolvers里面有详细说明。下面我们来看这个方法的源码:

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


     

  • protected LocaleContext buildLocaleContext(final HttpServletRequest request)方法
    看这个方法之前,必须说一下什么是LocalContext.看到Local就自然要想到本地化,区域等相关内容。我们知道在Local是java.util下的类,它主要是用来表示特定的地理、政治或文化区域,比如显示一个日期,不同的地理位置显示的时间格式有可能不是一样的。这些都跟Local有关。LocalContext就是他是一个接口,他就是专门用来获取这个Local的。
    下面我们来看一下源码是怎么创建这个LocalContext的

    @Override
    protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
        // LocaleContextResolver是LocalResolver子类
        if (this.localeResolver instanceof LocaleContextResolver) {
            return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request);
        }
        else {
            // 通过匿名类,直接实现接口,返回我们initLocalResolver方法中生成的localResolver
            return new LocaleContext() {
                @Override
                public Locale getLocale() {
                    return localeResolver.resolveLocale(request);
                 }
            };
        }
    }

    首先来说一下,这个方法是谁来调用的?它是由FrameworkServlet#processRequest方法来调用的,然后将这个返回的LocalContext赋值给LocaleContextHolder这个类。也就是当发起请求时,处理请求的线程会有一个值得LocalContext,就是从这里获取的。如果我们用的springmvc,并且没有手动想容器中注入localResolver,那么便会执行这里的else,然后使用我们在initViewResolver中过去的那个默认的resolver,也就是AcceptHeaderLocaleResolver这个类的实例。

  • protected HttpServletRequest checkMultipart(HttpServletRequest request)方法

    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
        // isMutipart方法判断,如果是post方法,并且请求中contentType是multipart/开头的,就为true
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
    		return this.multipartResolver.resolveMultipart(request);
        }
    		return request;
    }

    此方法用来check我们发起的请求是否属于multpart,如果容器中有multipartResolver并且请求属于multpart请求(通过post提交,并且contentType以multipart/开头,则为true),则生成一个MultipartHttpServletRequest,然后使用这个reqeust发送到controller中,如下图所示:

    通过上图可以看出reqeust已经变成了带有multpart的DefaultMultpartHttpServletRequest了。当然,如果我们想直接处理这个上传的文件,那么一般都如下写法,然后直接过去到上传的文件。

  • protected void cleanupMultipart(HttpServletRequest request) 方法

    protected void cleanupMultipart(HttpServletRequest request) {
        MultipartHttpServletRequest multipartRequest =
    				WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
        if (multipartRequest != null) {
            this.multipartResolver.cleanupMultipart(multipartRequest);
        }
    }

    这个方法在doDispatch方法最后的finally中调用,就是最后清空所有的multpart内容,不做详细分析了。

  • protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz)方法

  • protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface)方法

  • protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    上面三个方法一起来讲,因为这三个方法共同完成一件事情,就是通过到DispatcherServlet.properties文件中找到默认的key指向的那个类,然后实例化,放倒容器中。下面分别贴出源码。

    protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
        //strategyInterface就是要在DispatcherServlet.properties中使用的key,根据这个key找类
        List<T> strategies = getDefaultStrategies(context, strategyInterface);
        return strategies.get(0);
    }
    @SuppressWarnings("unchecked")
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        String key = strategyInterface.getName();
        // 从DispatcherServlet.properties中找到这个key对应的所有类,文件中的类之间通过/分割
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            // 按照/分割后,将这些类放在数组中,然后用来实例化。
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<T>(classNames.length);
            //通过反射,获得class,然后在createDefaultStrategy方法中实例化
            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.properteis文件中找对应的那些类(文件在哪,自行百度,我之前的文章讲过),因为文件中有可能一个接口,对应多个类,类之间使用/分割的,如下,一个HandlerAdapter接口,对应多个类,就需要用/分割

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

    然后通过commaDelimitedListToStringArray方法根据/分割连在一起的类,然后分别放在数组中好实例化。实例化便是调用下面的这个方法

    protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
        return context.getAutowireCapableBeanFactory().createBean(clazz);
    }

    这个方法,就是来实例化的,只需知道传进来一个Class,然后就可以实例化了。
    经过上面三个方法,就可以成功从DispatcherServlet.properties中获取到要实例化的类,然后实例化,然后作为默认的对象放到容器中了。这三个方法只在那8个init方法中用到
     

  • private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) 

  • protected String getDefaultViewName(HttpServletRequest request)
    在doDispatch方法中,访问完controller中的方法后,会调用applyDefaultViewName,然后又调用getDefaultViewName,所以这两个放在一块讲。
    先看源码,然后讲解作用

    private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
        if (mv != null && !mv.hasView()) {
            mv.setViewName(getDefaultViewName(request));
        }
    }
    protected String getDefaultViewName(HttpServletRequest request) throws Exception {
        return this.viewNameTranslator.getViewName(request);
    }

    说着两个方法之前,要先讲一下viewname,我们知道当通过controller来显示一个jsp页面时,都是在controller的方法中return "jsp名称",这个"jsp名称"就是viewname,根据这个viewname,然后从jsp中找对应的jsp文件。在springmvc执行过程中,显示jsp文件其实分为两步,第一步,通过controller中return的viewname,去jsp文件中查找,找到了便会生成一个mv,里面会有对应的view,如果mv没有view,也就是applyDefaultViewName方法中的代码,那就说明controller中return的viewname找不到对应的jsp文件。这时就进行第二步:通过getDefaultViewName再生成一个viewname,然后再去找一遍。如果再找不到,就会在浏览器显示404了。再次生成viewname的规则也就是viewNameTranslator,请参考我之前的文章【ViewNameTranslator解析】

  • protected HandlerExecutionChain getHandler(HttpServletRequest request)方法

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }

    方法很简单,就是从handlerMappings中获取处理当前请求的handler,也就是controller。handlerMappings是启动时初始化的。关于什么是handler和handlerMapping,参考我相关的文章即可。

  • protected HandlerAdapter getHandlerAdapter(Object handler)方法

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        for (HandlerAdapter ha : this.handlerAdapters) {
            if (ha.supports(handler)) {
                return ha;
            }
        }
    }

    通过handler获取对应的adatper,什么是adapter,参考我相关的文章。这里简单说一下,adapter就是指明用handler中的那个方法来处理请求。也就是说,实际是通过adapter去调用controller中的方法的。handlerAdapters也是程序启动时就初始化好的。

  • protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response)方法
    当找不到处理请求的handler时(controller),就会调用这个方法,然后就直接返回404了

    protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 如果throwExceptionIfNoHandlerFound值为true,那么就是抛出异常,否则就显示404.基本都是显示404。throwExceptionIfNoHandlerFound可以在web.xml中设置
        if (this.throwExceptionIfNoHandlerFound) {
            throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
    					new ServletServerHttpRequest(request).getHeaders());
        }else {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }

  • private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)方法

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    			HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
        boolean errorView = false;
        //如果controller中抛出了异常
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }else {
                // 使用handlerExceptionResolver去处理异常
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }
        if (mv != null && !mv.wasCleared()) {
            // render方法就是将所有的model合并起来,比如flashmap中的和controller中的
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }else {}
        if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            return;
        }
        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }
    

    这个方法就是用来处理controller中方法执行结果的。这块代码看着有点多,其实中心思想就是:如果controller在执行中出现异常,那么就处理这个异常,比如用handlerExceptionResolver来处理异常并返回结果。如果没有抛出异常,就将所有的model和一些reqeust中的attribute中的内容合并,也就是render方法的作用。说白了就是汇总所有的数据,然后放到reqeust中。最下面几行代码可以不用关心。

  • protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
                Object handler, Exception ex)方法

    上面的方法会调用这个方法。

    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    			Object handler, Exception ex) throws Exception {
    
        ModelAndView exMv = null;
        for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
            exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
        if (exMv != null) {
            if (exMv.isEmpty()) {
                request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
                return null;
            }
            // 如果没有没有view,那么就使用默认规则生成一个viewname
            if (!exMv.hasView()) {
                exMv.setViewName(getDefaultViewName(request));
            }
            WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
            return exMv;
        }
    
        throw ex;
    }
    

    这个方法主要用来处理controller中抛出的异常,使用谁来处理呢?就是用handlerExceptionResolvers中的resolver处理异常。可以看到处理完也返回一个mv,这说明可以通过页面来显示异常的处理结果。详情开参考我的文章【HandlerExceptionResolver解析】

  • protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)方法
     

    /**
     * Render the given ModelAndView.
     * <p>This is the last stage in handling a request. It may involve resolving the view by name.
     */
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    	Locale locale = this.localeResolver.resolveLocale(request);
    	response.setLocale(locale);
    
    	View view;
        // mv中是否已经有view了?
    	if (mv.isReference()) {
    		//创建View,比如如果是就jsp页面,就会创建JstlView.class。FreeMarker就会创建对应的view
            // 创建view前,先回到缓存中查找是否存在,不存在,才创建
    		view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
    		if (view == null) {
    			throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
    					"' in servlet with name '" + getServletName() + "'");
    		}
    	}
    	else {
    		view = mv.getView();
    		if (view == null) {
    			throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
    					"View object in servlet with name '" + getServletName() + "'");
    		}
    	}
    
    	try {
    		if (mv.getStatus() != null) {
    			response.setStatus(mv.getStatus().value());
    		}
            //这个地方就开始进行渲染了,在jsp中,这个方法一般需要2步:
            // 第一步:将我们的model设置到request的attribute里面去
            // 第二步:将这些属性绑定到真正的View里(比如jsp就是JstlView,还有freemark对应的view)
    		view.render(mv.getModelInternal(), request, response);
    	}
    	catch (Exception ex) {
    		throw ex;
    	}
    }
    

    我们在jsp中为什么可以通过request.getAttributes("")来获取到controller中设置的值,其实就是在这个方法里面完成的。这个方法把所有的数据汇集起来,比如model中的数据,flashmap中的数据。然后统统设置给reqeust的attribute中。这样在页面中就可以获取到了。具体View的解析,可以参考我的文章【View解析】

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值