本文参考<spring技术内幕>,spring版本为4.3.2
1.Spring MVC的应用模式
Spring MVC是一个MVC模式的实现,在配置文件中DispatcherServlet实现的是SUn的j2EE核心模式中的前端控制器模式(Front Controller),作为一个前端控制器,所有的Web请求都需要通过它来处理,进行转发、匹配、数据处理后,并转由页面进行展现,因此这个DispatcherServlet可以看成是Spring MVC实现中最核心的部分。
DispatcherServlet的工作大致可以分成两个部分,一个是初始化部分,接口在Servlet的init方法,由initServletBean()启动,最终调用DispatcherServlet的initStrategies方法; 另一个是对HTTP请求进行响应,作为Servlet,Web容器会调用Servlet的doGet()和doPost()方法,最终调用DispatcherServlet的doService()方法方法。
2.DispatcherServlet启动和初始化
2.1.启动 作为Servlet,DispatcherServlet的启动与Servlet的启动过程是相联系的。在Servlet的启动过程中,Servlet的init方法会被调用,以进行初始化。DispatcherServlet的基类HttpServletBean中的这个初始化init()过程如下。
在初始化开始时,需要读取配置在ServletContext中的Bean属性参数,这些属性参数设置在web.xml的web容器初始化参数中。使用编程式(通过代码而不是注解)的方式来设置这些Bean属性,在这里可以看到对PropertyValues和BeanWrapper的使用。对于这些和依赖注入相关的类的使用,在分析IoC容器初始化时尤其是在依赖注入实现分析时,有过接触。只是这里的依赖注入是与Web容器初始化想着,初始化过程由HttpServletBean来完成。
接着会执行DispatcherServlet持有IoC容器的初始化过程,
在这个初始化过程中,一个新的上下文被建立起来,这个DispatcherServlet持有的上下文被设置为根上下文 的子上下文。可以认为,根上下文是和Web应用相对应的一个上下文,而DispatcherServlet持有的上下文是和Servlet对应的一个上下文。在一个Web应用中,往往可以容纳多个Servlet存在;与些相对应,对于应用在Web容器中的上下文体系,一个根上下文可以作为许多Servlet上下文的双亲上下文。了解这一点,对在Web环境中IoC容器中的Bean设置和检索会有更多的了解,因为了解IoC工作原理知道,在向IoC容器getBean时,IoC容器会首先向其双亲上下文去getBean,也就是说,在根上下文中定义的Bean是可以被各个Servlet持有的上下文得到和共享的。DispatcherServlet持有的上下文被建立起来以后,也需要和其他IoC容器一样完成初始化,这个初始化也是通过refresh方法来完成的。最后,DispatcherServlet给这个自己持有的上下文命名,并把它设置到Web容器的上下文中,
这个名称和Web.xml中设置的DispatcherServlet的Servlet名称有关,从而保证了这个上下文在Web环境上下文体系中的唯一性。 在这里,
这个MVC的上下文就建立起来了,具体取得根上下文的过程在WebApplicationContextUtils中实现,如下代码。
这个根上下文是ContextLoader设置到ServletContext中去,使用的属性是ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,同时对这个IoC容器的Bean配置文件,ContextLoader也进行了设置,默认的位置是在/WEB-INF/applicationContext.xml文件中。由于这个根上下文是DispatcherServlet建立的上下文双亲上下文,所以根上下文中管理的Bean也是可以被DispatcherServlet的上下文使用的。通过getBean向IoC容器获取Bean时,容器会先到它的双亲IoC容器中获取getBean,这些在分析ioC容器里有讲过。
-
-
- public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
- return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
- }
-------------------------------
回到FrameworkServlet的实现中来看一下
DispatcherServlet的上下文是怎样建立的。这个建立过程与前面建立根上下文的过程非常类似,如下代理。建立DispatcherServlet的上下文,需要把根上下文作为参数传递给它。然后使用反射技术来实例化上下文对象,并为它设置参数。根据默认的配置,这个
上下文对象也是XmlWebApplicationContext对象,这个类型是在DEFAULT_CONTEXT_CLASS 参数中设置好并允许BeanUtils使用的。在实例化结束以后,需要为这个上下文对象设置好一些基本的配置,这些配置包括它的双亲上下文、Bean定义配置的文件位置等。完成这些配置以后,最后通过调用IoC容器的refresh方法来完成IoC容器的最终初始化,这和前面我们对IoC容器实现原理的分析 所看到的IoC容器初始化过程是一致的。
-
-
- public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
-
- private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
-
- public Class<?> getContextClass() {
- return this.contextClass;
- }
-
- protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
- return createWebApplicationContext((ApplicationContext) parent);
- }
-
- protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
-
- Class<?> contextClass = getContextClass();
-
- if (this.logger.isDebugEnabled()) {
- this.logger.debug("Servlet with name '" + getServletName() +
- "' will try to create custom WebApplicationContext context of class '" +
- contextClass.getName() + "'" + ", using parent context [" + parent + "]");
- }
- 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);
- wac.setConfigLocation(getContextConfigLocation());
-
- configureAndRefreshWebApplicationContext(wac);
-
- return wac;
- }
-
-
- protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
- 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());
- }
- }
-
- wac.setServletContext(getServletContext());
- wac.setServletConfig(getServletConfig());
- wac.setNamespace(getNamespace());
- wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
-
- ConfigurableEnvironment env = wac.getEnvironment();
- if (env instanceof ConfigurableWebEnvironment) {
- ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
- }
-
- postProcessWebApplicationContext(wac);
- applyInitializers(wac);
-
- wac.refresh();
- }
这个时候DispatcherServlet中的IoC容器已经建立起来了,
这个IoC容器是根上下文的子容器。这样的设置,使得对具体的一个Bean定义查找过程来说,如果要查找一个由DispatcherServlet所在的IoC容器来管理的Bean,系统会首先到根上下文中去查找。如果查找不到,才会到DispatcherServlet所管理的IoC容器去进行查找,这是由IoC容器getBean的实现来决定的。关于这个机制,可以参考前面的getBean实现的分析。通过一系列在Web容器中执行的动作,在这个上下文体系建立和初始化完毕的基础上,Spring MVC 就可以发挥其作用了。下面来分析一下Spring MVC的具体实现。
2.2.初始化
在前面分析DispatcherServlet的初始化过程中可以看到,DispatcherServlet持有一个以自己的Servlet名称命名的IoC容器。这个IoC容器是一个WebApplicationContext对象,这个IoC容器建立起来以后,意味着DispatcherServlet拥有自己的Bean定义空间,这为使用各个独立的XML文件来配置MVC中各个Bean创造了条件。由于在初始化结束以后,与Web容器相关的加载过程实际上已经完成了,Spring MVC的具体实现和普通的Spring应用程序的实现并没有太大的差别。在Spring MVC DispatcherServlet的初始化过程中,以对HandlerMapping的初始化调用作为触发点,了解Spring MVC模块初始化的方法调用关系。
-
-
- protected void onRefresh(ApplicationContext context) {
-
- }
-
-
-
-
- @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);
- }
对于具体的初始化过程,根据上面的方法名称,很容易理解。以HanderMapping为例来说明这个initHanderMappings过程。这里的Mapping关系的作用是,为HTTP请求找到相应 的Controller控制器,从而利用这些控制器Controller去完成设计好的数据处理工作。HandlerMappings完成对MVC中Controller的定义和配置,只不过在Web这个特定的应用环境中,这些控制器是与具体的HTTP请求相对应的。DispatcherServlet中HandlerMappings初始化过程的具体实现如下。在HandlerMapping初始化的过程中,把在Bean配置文件中配置好的handlerMapping从IoC容器中取得。
-
-
- private void initHandlerMappings(ApplicationContext context) {
- this.handlerMappings = null;
-
-
-
-
- if (this.detectAllHandlerMappings) {
- Map<String, HandlerMapping> matchingBeans =
- BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
- if (!matchingBeans.isEmpty()) {
- this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
- AnnotationAwareOrderComparator.sort(this.handlerMappings);
- }
- }
- else {
- try {
- HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
- this.handlerMappings = Collections.singletonList(hm);
- }
- catch (NoSuchBeanDefinitionException ex) {
-
- }
- }
-
-
-
- if (this.handlerMappings == null) {
- this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
- if (logger.isDebugEnabled()) {
- logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
- }
- }
- }
经过以上的读取过程,handlerMappings变量就已经获取了在BeanDefinition中配置好的映射关系。其他的初始化过程和handlerMappings比较类似,都是直接从IoC容器中读入配置,所以这里的MVC初始化过程是建立在IoC容器已经初始化完成的基础上的。至于上下文是如何获得的,可以参见前面对IoC容器在Web环境中加载的实现原理的分析。
3.MVC处理HTTP分发请求
3.1.HandlerMapping的配置和设计原理 在初始化完成时,在上下文环境中已定义的所有HandlerMapping都已经被加载了,这些加载的handlerMappings被放在一个List中并排序,存储着HTTP请求对应的映射数据。这个List中的每一个元素都对应着一个具体handlerMapping的配置,一般每一个handlerMapping可以持有一系列从URL请求到Controller的映射,而Spring MVC 提供了一系列的HandlerMapping实现。
- public interface HandlerMapping {
-
- String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
-
- String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
-
- String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
-
- String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
-
- String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
-
- String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
-
-
-
- HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
-
- }
这个HandlerExecutionChain的实现看起来比较简洁,它持有一个拦截器链和一个handler对象,这个handler对象实际上就是HTTP请求对应的Controller,在持有这个handler对象的同时,还在HandlerExecutionChain中设置了拦截器链,通过这个拦截器链中的拦截器,可以为handler对象提供功能的增强。要完成这些工作,需要对拦截器链和handler都进行配置,这些配置都是在这个类的初始化函数中完成的。
为了维护这个拦截器和handler, HandlerExecutionChain还提供了一系列与拦截器链维护相关一些操作,比如可以为拦截器链增加拦截器的addInterceptor方法等。
-
-
- public class HandlerExecutionChain {
-
- private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
-
- private final Object handler;
- private HandlerInterceptor[] interceptors;
- private List<HandlerInterceptor> interceptorList;
-
- private int interceptorIndex = -1;
-
-
- public HandlerExecutionChain(Object handler) {
- this(handler, (HandlerInterceptor[]) null);
- }
-
- public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
- if (handler instanceof HandlerExecutionChain) {
- HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
- this.handler = originalChain.getHandler();
- this.interceptorList = new ArrayList<HandlerInterceptor>();
- CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
- CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
- }
- else {
- this.handler = handler;
- this.interceptors = interceptors;
- }
- }
-
- public Object getHandler() {
- return this.handler;
- }
-
- public void addInterceptor(HandlerInterceptor interceptor) {
- initInterceptorList().add(interceptor);
- }
-
- public void addInterceptors(HandlerInterceptor... interceptors) {
- if (!ObjectUtils.isEmpty(interceptors)) {
- initInterceptorList().addAll(Arrays.asList(interceptors));
- }
- }
-
- private List<HandlerInterceptor> initInterceptorList() {
- if (this.interceptorList == null) {
- this.interceptorList = new ArrayList<HandlerInterceptor>();
- if (this.interceptors != null) {
-
- this.interceptorList.addAll(Arrays.asList(this.interceptors));
- }
- }
- this.interceptors = null;
- return this.interceptorList;
- }
-
- public HandlerInterceptor[] getInterceptors() {
- if (this.interceptors == null && this.interceptorList != null) {
- this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
- }
- return this.interceptors;
- }
-
- boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = 0; i < interceptors.length; i++) {
- HandlerInterceptor interceptor = interceptors[i];
- if (!interceptor.preHandle(request, response, this.handler)) {
- triggerAfterCompletion(request, response, null);
- return false;
- }
- this.interceptorIndex = i;
- }
- }
- return true;
- }
-
- void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = interceptors.length - 1; i >= 0; i--) {
- HandlerInterceptor interceptor = interceptors[i];
- interceptor.postHandle(request, response, this.handler, mv);
- }
- }
- }
-
- void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
- throws Exception {
-
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = this.interceptorIndex; i >= 0; i--) {
- HandlerInterceptor interceptor = interceptors[i];
- try {
- interceptor.afterCompletion(request, response, this.handler, ex);
- }
- catch (Throwable ex2) {
- logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
- }
- }
- }
- }
-
- void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = interceptors.length - 1; i >= 0; i--) {
- if (interceptors[i] instanceof AsyncHandlerInterceptor) {
- try {
- AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptors[i];
- asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
- }
- catch (Throwable ex) {
- logger.error("Interceptor [" + interceptors[i] + "] failed in afterConcurrentHandlingStarted", ex);
- }
- }
- }
- }
- }
-
- @Override
- public String toString() {
- if (this.handler == null) {
- return "HandlerExecutionChain with no handler";
- }
- StringBuilder sb = new StringBuilder();
- sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
- if (!CollectionUtils.isEmpty(this.interceptorList)) {
- sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
- if (this.interceptorList.size() > 1) {
- sb.append("s");
- }
- }
- return sb.toString();
- }
-
- }
HandlerExecutionChain中定义的Handler和Interceptor需要在定义HandlerMapping时配置好,例如对具体的SimpleURLHandlerMapping, 要做的就是根据URL映射的方式,注册Handler和Interceptor,从而维护一个反映这种映射关系的handlerMap。当需要匹配HTTP请求时,需要查询这个handlerMap中信息来得到对应的HandlerExecutionChain。这些信息是什么时候配置好的呢?
这里有一个注册过程,这个注册过程在容器对Bean进行依赖注入时发生,它实际上是通过一个Bean的postProcessor来完成的。如果想了解这个过程,可以从依赖注入那里去看看,doCreateBean->initializeBean -> postProcessBeforeInitialization -> setApplicationContext -> initApplicationContext.
以SimpleUrlHandlerMapping为例,需要注意的是,这里用到了对容器的回调,只有SimpleUrlHandlerMapping是AppicationContextAware的子类才能启动这个注册过程。这个注册过程完成的是反映URL和Controller之间的映射关系的handlerMap的建立。具体分析如下:
- public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
-
- private final Map<String, Object> urlMap = new LinkedHashMap<String, Object>();
-
- public void setMappings(Properties mappings) {
- CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
- }
-
-
- public void setUrlMap(Map<String, ?> urlMap) {
- this.urlMap.putAll(urlMap);
- }
- public Map<String, ?> getUrlMap() {
- return this.urlMap;
- }
-
-
- @Override
- public void initApplicationContext() throws BeansException {
- super.initApplicationContext();
- registerHandlers(this.urlMap);
- }
-
- protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
- if (urlMap.isEmpty()) {
- logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
- }
- else {
-
- for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
- String url = entry.getKey();
- Object handler = entry.getValue();
-
- if (!url.startsWith("/")) {
- url = "/" + url;
- }
-
- if (handler instanceof String) {
- handler = ((String) handler).trim();
- }
- registerHandler(url, handler);
- }
- }
- }
-
- }
这个SimpleUrlHandlerMapping注册过程的完成,很大一部分需要它的基类来配合,这个基类就是AbstractUrlHandlerMapping.
-
-
-
-
- private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();
-
-
-
-
- protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
- Assert.notNull(urlPath, "URL path must not be null");
- Assert.notNull(handler, "Handler object must not be null");
- Object resolvedHandler = handler;
-
-
- if (!this.lazyInitHandlers && handler instanceof String) {
- String handlerName = (String) handler;
- if (getApplicationContext().isSingleton(handlerName)) {
- resolvedHandler = getApplicationContext().getBean(handlerName);
- }
- }
-
- Object mappedHandler = this.handlerMap.get(urlPath);
- if (mappedHandler != null) {
- if (mappedHandler != resolvedHandler) {
- throw new IllegalStateException(
- "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
- "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
- }
- }
- else {
- if (urlPath.equals("/")) {
- if (logger.isInfoEnabled()) {
- logger.info("Root mapping to " + getHandlerDescription(handler));
- }
- setRootHandler(resolvedHandler);
- }
-
- else if (urlPath.equals("/*")) {
- if (logger.isInfoEnabled()) {
- logger.info("Default mapping to " + getHandlerDescription(handler));
- }
- setDefaultHandler(resolvedHandler);
- }
-
- else {
- this.handlerMap.put(urlPath, resolvedHandler);
- if (logger.isInfoEnabled()) {
- logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
- }
- }
- }
- }
这个配置好URL请求和handler映射数据的handlerMap,为Spring MVC响应HTTP请求准备好了基本的映射数据,根据这个handlerMap以及设置于其中的映射数据,可以方便地由URL请求得到它所对应的handler。有了这些准备工作,Spring MVC就可以等待HTTP请求的到来了。
3.2.使用HandlerMapping完成请求的映射处理
继续通过SimpleUrlHandlerMapping的实现来分析HandlerMapping的接口方法getHandler,该方法会根据初始化时得到的映射关系来生成DispatcherServlet需要的HandlerExecutionChain,也就是说,这个getHandler方法是实际使用HandlerMapping完成请求的映射处理的地方。
- @Override
- public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
- Object handler = getHandlerInternal(request);
-
-
- if (handler == null) {
- handler = getDefaultHandler();
- }
- if (handler == null) {
- return null;
- }
-
-
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = getApplicationContext().getBean(handlerName);
- }
-
-
- HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
-
- if (CorsUtils.isCorsRequest(request)) {
- CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
- CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
- CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
- executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
- }
- return executionChain;
- }
-
-
- protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
- HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
- (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
-
- String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
- for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
- if (interceptor instanceof MappedInterceptor) {
- MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
- if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
- chain.addInterceptor(mappedInterceptor.getInterceptor());
- }
- }
- else {
- chain.addInterceptor(interceptor);
- }
- }
- return chain;
- }
取得handler的具体过程在getHandlerInternal方法中实现,这个方法接受HTTP请求作为参数,它的实现在AbstractHandlerMapping的子类AbstractUrlHandlerMapping中,这个实现过程包括从HTTP请求中得到URL,并根据URL到urlMapping中获得handler。代码实现如下:
-
-
-
- protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
-
-
-
-
-
- @Override
- protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
-
- String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
-
-
-
- Object handler = lookupHandler(lookupPath, request);
- if (handler == null) {
-
-
- Object rawHandler = null;
- if ("/".equals(lookupPath)) {
- rawHandler = getRootHandler();
- }
- if (rawHandler == null) {
- rawHandler = getDefaultHandler();
- }
- if (rawHandler != null) {
-
- if (rawHandler instanceof String) {
- String handlerName = (String) rawHandler;
- rawHandler = getApplicationContext().getBean(handlerName);
- }
- validateHandler(rawHandler, request);
- handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
- }
- }
- if (handler != null && logger.isDebugEnabled()) {
- logger.debug("Mapping [" + lookupPath + "] to " + handler);
- }
- else if (handler == null && logger.isTraceEnabled()) {
- logger.trace("No handler mapping found for [" + lookupPath + "]");
- }
- return handler;
- }
-
-
- protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
-
- Object handler = this.handlerMap.get(urlPath);
- if (handler != null) {
-
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = getApplicationContext().getBean(handlerName);
- }
- validateHandler(handler, request);
- return buildPathExposingHandler(handler, urlPath, urlPath, null);
- }
-
- List<String> matchingPatterns = new ArrayList<String>();
- for (String registeredPattern : this.handlerMap.keySet()) {
- if (getPathMatcher().match(registeredPattern, urlPath)) {
- matchingPatterns.add(registeredPattern);
- }
- else if (useTrailingSlashMatch()) {
- if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
- matchingPatterns.add(registeredPattern +"/");
- }
- }
- }
- String bestPatternMatch = null;
- Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
- if (!matchingPatterns.isEmpty()) {
- Collections.sort(matchingPatterns, patternComparator);
- if (logger.isDebugEnabled()) {
- logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
- }
- bestPatternMatch = matchingPatterns.get(0);
- }
- if (bestPatternMatch != null) {
- handler = this.handlerMap.get(bestPatternMatch);
- if (handler == null) {
- Assert.isTrue(bestPatternMatch.endsWith("/"));
- handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
- }
-
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = getApplicationContext().getBean(handlerName);
- }
- validateHandler(handler, request);
- String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
-
-
-
- Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
- for (String matchingPattern : matchingPatterns) {
- if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
- Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
- Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
- uriTemplateVariables.putAll(decodedVars);
- }
- }
- if (logger.isDebugEnabled()) {
- logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
- }
- return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
- }
-
- return null;
- }
经过这一系列对HTTP请求进行解析和匹配handler的过程,得到了与请求对应的handler处理器。在返回的handler中,已经完成了在HandlerExecutionChain中的封装工作,为handler对HTTP请求的响应做好了准备。然后,在MVC中,还有一个重要的问题:请求是怎样实现分发,从而到达对应的handler的呢?
3.Spring MVC对HTTP请求的分发处理
重新回到DispatcherServlet,这个类不但建立了自己持有的IoC容器,还肩负着请求分发处理的重任。在MVC框架初始化完成以后,对HTTP请求的处理是在doService()方法中完成的,DispatcherServlet也是通过这个方法来响应HTTP的请求。然后,依据Spring MVC的使用,业务逻辑的调用入口是在handler的handler函数中实现的,这里是连接Spring MVC和应用业务逻辑实现的地方。DispatcherServlet的doService的实现代码如下:
- @Override
- protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
- if (logger.isDebugEnabled()) {
- String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
- logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
- " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
- }
-
-
-
- Map<String, Object> attributesSnapshot = null;
- if (WebUtils.isIncludeRequest(request)) {
- attributesSnapshot = new HashMap<String, Object>();
- Enumeration<?> attrNames = request.getAttributeNames();
- while (attrNames.hasMoreElements()) {
- String attrName = (String) attrNames.nextElement();
- if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
- attributesSnapshot.put(attrName, request.getAttribute(attrName));
- }
- }
- }
-
-
- request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
- request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
- request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
- request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
-
- FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
- if (inputFlashMap != null) {
- request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
- }
- request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
- request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
-
- try {
- doDispatch(request, response);
- }
- finally {
- if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
-
- if (attributesSnapshot != null) {
- restoreAttributesAfterInclude(request, attributesSnapshot);
- }
- }
- }
- }
- protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
- HttpServletRequest processedRequest = request;
- HandlerExecutionChain mappedHandler = null;
- boolean multipartRequestParsed = false;
-
- WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
-
- try {
- ModelAndView mv = null;
- Exception dispatchException = null;
-
- try {
- processedRequest = checkMultipart(request);
- multipartRequestParsed = (processedRequest != request);
-
-
- mappedHandler = getHandler(processedRequest);
- if (mappedHandler == null || mappedHandler.getHandler() == null) {
- noHandlerFound(processedRequest, response);
- return;
- }
-
-
- HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
-
-
- String method = request.getMethod();
- boolean isGet = "GET".equals(method);
- if (isGet || "HEAD".equals(method)) {
- long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
- if (logger.isDebugEnabled()) {
- logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
- }
- if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
- return;
- }
- }
-
- if (!mappedHandler.applyPreHandle(processedRequest, response)) {
- return;
- }
-
-
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
- if (asyncManager.isConcurrentHandlingStarted()) {
- return;
- }
-
- applyDefaultViewName(processedRequest, mv);
- mappedHandler.applyPostHandle(processedRequest, response, mv);
- }
- catch (Exception ex) {
- dispatchException = ex;
- }
- catch (Throwable err) {
- dispatchException = new NestedServletException("Handler dispatch failed", err);
- }
-
-
- processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
-
- }
- catch (Exception ex) {
- triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
- }
- catch (Throwable err) {
- triggerAfterCompletion(processedRequest, response, mappedHandler,
- new NestedServletException("Handler processing failed", err));
- }
- finally {
- if (asyncManager.isConcurrentHandlingStarted()) {
-
- if (mappedHandler != null) {
- mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
- }
- }
- else {
-
- if (multipartRequestParsed) {
- cleanupMultipart(processedRequest);
- }
- }
- }
- }
- protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
- for (HandlerMapping hm : this.handlerMappings) {
- if (logger.isTraceEnabled()) {
- logger.trace(
- "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
- }
- HandlerExecutionChain handler = hm.getHandler(request);
- if (handler != null) {
- return handler;
- }
- }
- return null;
- }
- protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
- for (HandlerAdapter ha : this.handlerAdapters) {
- if (logger.isTraceEnabled()) {
- logger.trace("Testing handler adapter [" + ha + "]");
- }
- if (ha.supports(handler)) {
- return ha;
- }
- }
- throw new ServletException("No adapter for handler [" + handler +
- "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
- }
-
-
-
- public interface HandlerAdapter {
-
- boolean supports(Object handler);
-
- ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
-
- long getLastModified(HttpServletRequest request, Object handler);
-
- }
-
- public class SimpleControllerHandlerAdapter implements HandlerAdapter {
-
- @Override
- public boolean supports(Object handler) {
- return (handler instanceof Controller);
- }
-
- @Override
- public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
-
- return ((Controller) handler).handleRequest(request, response);
- }
-
- @Override
- public long getLastModified(HttpServletRequest request, Object handler) {
- if (handler instanceof LastModified) {
- return ((LastModified) handler).getLastModified(request);
- }
- return -1L;
- }
-
- }
经过上面一系列的处理,得到了handler对象,接着就可以开始调用handler对象中的HTTP响应动作了。在handler中封装了应用业务逻辑,由这些逻辑对HTTP请求进行相应的处理,生成各种需要的数据,并把这些数据封装到ModelAndView对象中去,这个ModelAndView的数据封装是Spring MVC框架的要求。对handler来说,这些都是通过调用handler的handlerRequest方法来触发完成的。在得到ModelAndView对象以后,这个ModelAndView对象会被交给MVC模式中的视图类,由视图类对ModelAndView对象中的数据进行呈现。视图呈现的调用入口在DispatcherServlet的doDispatch方法中实现,它的调用入口是render方法。这个方法在下面介绍。
二、Spring MVC视图的呈现
1.DispatcherServlet视图呈现的设计
前面分析了Spring MVC中的M(Model)和 C(Controller)相关的实现,其中的M大致对应成ModelAndView的生成,而C大致对应到DispatcherServlet和用户业务逻辑有关的handler实现。在Spring MVC框架中,DispatcherServlet起到了非常杧的作用,是整个MVC框架的调用枢纽。对于下面关心的视图呈现功能,它的调用入口同样在DispatcherServlet中的doDispatch方法中实现。具体来说,在DispatcherServlet中,对视图呈现的处理是在render方法调用中完成的,代码如下。
为了完成视图的呈现工作,需要从ModelAndViewc对象中取得视图对象,然后调用视图对象的render方法,由这个视图对象来完成特定的视图呈现工作。同时,由于是在Web的环境中,因此视图对象的呈现往往需要完成与HTTP请求和响应相关的处理,这些对象会作为参数传到视图对象的render方法中,供render方法使用。
- protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
-
- Locale locale = this.localeResolver.resolveLocale(request);
- response.setLocale(locale);
-
- View view;
- if (mv.isReference()) {
-
- 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() + "'");
- }
- }
-
-
- if (logger.isDebugEnabled()) {
- logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
- }
- try {
- if (mv.getStatus() != null) {
- response.setStatus(mv.getStatus().value());
- }
- view.render(mv.getModelInternal(), request, response);
- }
- catch (Exception ex) {
- if (logger.isDebugEnabled()) {
- logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
- getServletName() + "'", ex);
- }
- throw ex;
- }
- }
- protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
- HttpServletRequest request) throws Exception {
-
-
- for (ViewResolver viewResolver : this.viewResolvers) {
-
- View view = viewResolver.resolveViewName(viewName, locale);
- if (view != null) {
- return view;
- }
- }
- return null;
- }
-
-
-
- public interface ViewResolver {
-
- View resolveViewName(String viewName, Locale locale) throws Exception;
-
- }
- public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
-
- private int order = Integer.MAX_VALUE;
-
-
- public void setOrder(int order) {
- this.order = order;
- }
-
- @Override
- public int getOrder() {
- return this.order;
- }
-
-
- @Override
- public View resolveViewName(String viewName, Locale locale) throws BeansException {
- ApplicationContext context = getApplicationContext();
- if (!context.containsBean(viewName)) {
- if (logger.isDebugEnabled()) {
- logger.debug("No matching bean found for view name '" + viewName + "'");
- }
- return null;
- }
-
- if (!context.isTypeMatch(viewName, View.class)) {
- if (logger.isDebugEnabled()) {
- logger.debug("Found matching bean for view name '" + viewName +
- "' - to be ignored since it does not implement View");
- }
- return null;
- }
- return context.getBean(viewName, View.class);
- }
-
- }
这样就得到了需要的View对象,下面介绍View。下面是View的继承体系
1.JSP视图的实现
使用JSP的页面作为Web UI,是使用Java设计Web应用比较常见的选择之一,如果在JSP使用Jstl(JSP Standard Tag Library)来丰富JSP的功能,在Spring MVC中就需要使用JstlView来作为View对象,从而对数据进行视图呈现。JstlView没有实现render方法,使用的render方法是在它的基类AbstractView中实现的,实现如下:
-
-
- @Override
- public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
- if (logger.isTraceEnabled()) {
- logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
- " and static attributes " + this.staticAttributes);
- }
-
-
- Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
- prepareResponse(request, response);
-
-
- renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
- }
-
-
- protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
- HttpServletResponse response) {
-
- @SuppressWarnings("unchecked")
- Map<String, Object> pathVars = (this.exposePathVariables ?
- (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
-
-
- int size = this.staticAttributes.size();
- size += (model != null ? model.size() : 0);
- size += (pathVars != null ? pathVars.size() : 0);
-
- Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
- mergedModel.putAll(this.staticAttributes);
- if (pathVars != null) {
- mergedModel.putAll(pathVars);
- }
- if (model != null) {
- mergedModel.putAll(model);
- }
-
-
- if (this.requestContextAttribute != null) {
- mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
- }
-
- return mergedModel;
- }
-
-
- protected abstract void renderMergedOutputModel(
- Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
-
-
-
- @Override
- protected void renderMergedOutputModel(
- Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
-
-
- exposeModelAsRequestAttributes(model, request);
-
-
-
- exposeHelpers(request); 2.--------------->
-
-
- String dispatcherPath = prepareForRendering(request, response);
-
-
- RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
- if (rd == null) {
- throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
- "]: Check that the corresponding file exists within your web application archive!");
- }
-
-
- if (useInclude(request, response)) {
- response.setContentType(getContentType());
- if (logger.isDebugEnabled()) {
- logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
- }
- rd.include(request, response);
- }
-
- else {
-
- if (logger.isDebugEnabled()) {
- logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
- }
- rd.forward(request, response);
- }
- }
-
- protected void exposeHelpers(HttpServletRequest request) throws Exception {
- }
-
-
-
-
-
-
- protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
- for (Map.Entry<String, Object> entry : model.entrySet()) {
- String modelName = entry.getKey();
- Object modelValue = entry.getValue();
- if (modelValue != null) {
- request.setAttribute(modelName, modelValue);
- if (logger.isDebugEnabled()) {
- logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
- "] to request in view with name '" + getBeanName() + "'");
- }
- }
- else {
- request.removeAttribute(modelName);
- if (logger.isDebugEnabled()) {
- logger.debug("Removed model object '" + modelName +
- "' from request in view with name '" + getBeanName() + "'");
- }
- }
- }
- }
-
-
-
-
- @Override
- protected void exposeHelpers(HttpServletRequest request) throws Exception {
- if (this.messageSource != null) {
- JstlUtils.exposeLocalizationContext(request, this.messageSource);
- }
- else {
- JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext()));
- }
- }
在这些处理的基础 上,实际的数据到页面的输出是由InternalResourceView来完成的,render完成资源的重定向处理。需要做的是,在得到实际视图的InternalResource路径以后,把请求转发到资源中去。如何得到资源的路径呢?在InternalResourceView中可以看到调用,如下代码。在这里可以看到,从request取得URL的路径,并对取得的路径进行了相应的处理。
- protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
- throws Exception {
-
- String path = getUrl();
- if (this.preventDispatchLoop) {
- String uri = request.getRequestURI();
- if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
- throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
- "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
- "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
- }
- }
- return path;
- }
在得到URL路径之后,使用RequestDispatcher把请求转发到这个资源上,就完成了带JSTL的JSP页面的展现。