重点:java
1. Spring MVC中的dispatcherServletweb
2. 8个组件(handleMapping & handleAdaper & viewResolver)spring
3. MVC的关系,理解URL与handler的对应关系存放,handlerExecutionChain对象,modelAndMap对象数组
---------------------------- 原文 -----------------------------spring-mvc
地址:https://my.oschina.net/bosscheng/blog/129282session
在一个工程中若是想要使用 spring MVC的话,只须要两个步骤mvc
须要配置一个org.springframework.web.servlet.DispatcherServlet的servlet。app
在这个配置文件里面咱们只须要扩展一个路径映射关系,定义一个视图解析器,再定义一个业务逻辑的处理流程规则。框架
这样就能够搞定一个最基本的Spring MVC的应用了。jsp
对于DispatcherServlet初始化的时候初始了哪些东西,这些能够在initStrategies中看到。
/** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ protected void initStrategies(ApplicationContext context) { //初始化MultipartResolver,主要是处理文件上传服务。 initMultipartResolver(context); //用于处理应用的国际化问题 initLocaleResolver(context); //用于定义一个主题 initThemeResolver(context); //用于定义用户设置的请求映射关系 initHandlerMappings(context); //用于根据Handler的类型定义不一样的处理规则 initHandlerAdapters(context); //当Handler处理错误的时候,经过这个handler来作统一的处理 initHandlerExceptionResolvers(context); //将指定的ViewName按照定义的RequestToViewNameTranslator替换成想要的格式。 initRequestToViewNameTranslator(context); //用于将view解析成页面 initViewResolvers(context); //用于映射flash管理的。 initFlashMapManager(context); }
小结: 对于spring MVC框架中,有三个组件是用户必须定义和扩展的:
看看DispatcherServlet启用的时候作了哪些工做?
/** * Map config parameters onto bean properties of this servlet, and * invoke subclass initialization. * @throws ServletException if bean properties are invalid (or required * properties are missing), or if subclass initialization fails. */ @Override public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. try { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment)); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } // Let subclasses do whatever initialization they like. initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }
这样:spring MVC的初始化工做就完成了。这样就能够接受你的http请求了。
Spring MVC 的Control主要是由HandlerMapping(interface)和HandlerAdapter(interface)两个组件提供。
对于HandlerMapping:主要负责映射用户的URL和对应的处理类,HandlerMapping并无规定这个URL与应用的处理类如何映射,HandlerMapping接口中只定义了根据一个URL必须返回一个由HandlerExceptionChain表明的处理链,咱们能够在这个处理链中添加任意的HandlerAdapter实例来处理这个URL对应的请求,这个设计思路和Servlet规范中的Filter处理是相似的。
/** * Return a handler and any interceptors for this request. The choice may be made * on request URL, session state, or any factor the implementing class chooses. * <p>The returned HandlerExecutionChain contains a handler Object, rather than * even a tag interface, so that handlers are not constrained in any way. * For example, a HandlerAdapter could be written to allow another framework's * handler objects to be used. * <p>Returns <code>null</code> if no match was found. This is not an error. * The DispatcherServlet will query all registered HandlerMapping beans to find * a match, and only decide there is an error if none can find a handler. * @param request current HTTP request * @return a HandlerExecutionChain instance containing handler object and * any interceptors, or <code>null</code> if no mapping found * @throws Exception if there is an internal error */ HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
------------------------------------邪恶的分割线-------------------------------------------
Spring MVC自己也提供了不少HandlerMapping 的实现,默认使用的是BeanNameUrlHandlerMapping,也能够根据Bean的name属性映射到URL中。
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping { /** * Checks name and aliases of the given bean for URLs, starting with "/". */ @Override protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<String>(); if (beanName.startsWith("/")) { urls.add(beanName); } String[] aliases = getApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); } }
对于HandlerMapping,能够帮助咱们管理URL和处理类的映射关系,简单的说就是能够帮助咱们将一个或者多个URL映射到一个或者多个spring Bean中。
下面总结下spring MVC是如何将请求的URL映射到咱们定义的bean中的。
对于HandlerMapping是如何初始化的。spring MVC提供了一个HandlerMapping的抽象类 AbstractHandlerMapping。
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered { }
能够经过让HandlerMapping设置setOrder方法提升优先级和经过覆盖initApplicationContext方法实现初始化的工做。
@Override protected void initApplicationContext() throws BeansException { extendInterceptors(this.interceptors); detectMappedInterceptors(this.mappedInterceptors); initInterceptors(); }
对于SimpleUrlHandlerMapping类是如何初始化的:
public final void setApplicationContext(ApplicationContext context) throws BeansException { if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; this.messageSourceAccessor = new MessageSourceAccessor(context); initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } }
/** * Calls the {@link #registerHandlers} method in addition to the * superclass's initialization. */ @Override public void initApplicationContext() throws BeansException { super.initApplicationContext(); registerHandlers(this.urlMap); }
/** * Initializes the interceptors. * @see #extendInterceptors(java.util.List) * @see #initInterceptors() */ @Override protected void initApplicationContext() throws BeansException { extendInterceptors(this.interceptors); detectMappedInterceptors(this.mappedInterceptors); initInterceptors(); }
初始化initInterceptors()方法:将SimpleUrlHandlerMapping中定义的interceptors包装成handlerInterceptor对象保存在adaptedInterceptors数组中。
在方法registerHandlers中,调用了AbstractUrlHandlerMapping的registerHandler(url, handler)方法;
在方法registerHandler(url, handler)中,将在SimpleUrlHandlerMapping中定义的mappings注册到handlerMap集合中。
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(); // Prepend with slash if not already present. if (!url.startsWith("/")) { url = "/" + url; } // Remove whitespace from handler bean name. if (handler instanceof String) { handler = ((String) handler).trim(); } registerHandler(url, handler); } } }
在registerHandler方法中;
/** * Register the specified handler for the given URL path. * @param urlPath the URL the bean should be mapped to * @param handler the handler instance or handler bean name String * (a bean name will automatically be resolved into the corresponding handler bean) * @throws BeansException if the handler couldn't be registered * @throws IllegalStateException if there is a conflicting handler registered */
----------------------------------邪恶的分割线-----------------------------------
小结: HandlerMapping初始化工做完成的两个最重要的工做就是:
HandlerMapping 能够完成URL与Handler的映射关系,那么HandlerAdapter就能够帮助自定义各类handler了。由于SpringMVC首先帮助咱们把特别的URL对应到一个Handler,那么这个Handler一定要符合某种规则,最多见的方法就是咱们的全部handler都继承某个接口,而后SpringMVC 天然就调用这个接口中定义的特性方法。
对于spring MVC提供了三种典型的handlerAdapter实现类。
对于handlerAdapter的初始化没有什么特别之处,只是简单的建立一个handlerAdapter对象,将这个对象保存在DispatcherServlet的HandlerAdapters集合中。当Spring MVC将某个URL对应到某个Handler时候,在handlerAdapters集合中查询那个handlerAdapter对象supports这个Handler,handlerAdapter对象将会被返回,用了调用这个handlerAdapter接口对应的方法。
------------------------------------邪恶的分割线-------------------------------------------
整个Spring MVC的调用是从DispatcherServlet的doService方法开始的,在doService方法中会将ApplicationContext、localeResolver、themeResolver等对象添加到request中便于在后面使用,接着就调用doDispatch方法,这个方法是主要的处理用户请求的地方。
对于control的处理关键就是:DispatcherServlet的handlerMappings集合中根据请求的URL匹配每个handlerMapping对象中的某个handler,匹配成功以后将会返回这个handler的处理链接handlerExecutionChain对象。而这个handlerExecutionChain对象中将会包含用户自定义的多个handlerInterceptor对象。
/** * Return the HandlerExecutionChain for this request. * <p>Tries all handler mappings in order. * @param request current HTTP request * @return the HandlerExecutionChain, or <code>null</code> if no handler could be found */ 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; }
而对于handlerInterceptor接口中定义的三个方法中,preHandler和postHandler分别在handler的执行前和执行后执行,afterCompletion在view渲染完成、在DispatcherServlet返回以前执行。
PS:这么咱们须要注意的是:当preHandler返回false时,当前的请求将在执行完afterCompletion后直接返回,handler也将不会执行。
在类HandlerExecutionChain中的getHandler()方法是返回object对象的;
/** * Return the handler object to execute. * @return the handler object */ public Object getHandler() { return this.handler; }
这里的handler是没有类型的,handler的类型是由handlerAdapter决定的。dispatcherServlet会根据handler对象在其handlerAdapters集合中匹配哪一个HandlerAdapter实例支持该对象。接下来去执行handler对象的相应方法了,若是该handler对象的相应方法返回一个ModelAndView对象接下来就是去执行View渲染了。
/** * Return the handler object to execute. * @return the handler object */ public Object getHandler() { return this.handler; }
若是handler兑现返回了ModelAndView对象,那么说明Handler须要传一个Model实例给view去渲染模版。除了渲染页面须要model实例,在业务逻辑层一般也有Model实例。
ModelAndView对象是链接业务逻辑层与view展现层的桥梁,对spring MVC来讲它也是链接Handler与view的桥梁。ModelAndView对象顾名思义会持有一个ModelMap对象和一个View对象或者View的名称。ModelMap对象就是执行模版渲染时候所须要的变量对应的实例,如jsp的经过request.getAttribute(String)获取的JSTL标签名对应的对象。velocity中context.get(String)获取$foo对应的变量实例。
public class ModelAndView { /** View instance or view name String */ private Object view; /** Model Map */ private ModelMap model; /** Indicates whether or not this instance has been cleared with a call to {@link #clear()} */ private boolean cleared = false; ..... }
ModelMap其实也是一个Map,Handler中将模版中须要的对象存在这个Map中,而后传递到view对应的ViewResolver中。
public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; }
不一样的ViewResolver会对这个Map中的对象有不一样的处理方式;
-----------------------邪恶的分割线-----------------------------------------------
在spring MVC中,view模块须要两个组件来支持:RequestToViewNameTranslator和ViewResolver
public interface RequestToViewNameTranslator { /** * Translate the given {@link HttpServletRequest} into a view name. * @param request the incoming {@link HttpServletRequest} providing * the context from which a view name is to be resolved * @return the view name (or <code>null</code> if no default found) * @throws Exception if view name translation fails */ String getViewName(HttpServletRequest request) throws Exception; }
对于 ViewResolver,前面有写到了,就不写了;
RequestToViewNameTranslator:主要支持用户自定义对viewName的解析,如将请求的ViewName加上前缀或者后缀,或者替换成特定的字符串等。
ViewResolver:主要是根据用户请求的viewName建立适合的模版引擎来渲染最终的页面,ViewResolver会根据viewName建立一个view对象,调用view对象的Void render方法渲染出页面;
public interface View { void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
下面来总结下 Spring MVC解析View的逻辑:
/** * Translate the supplied request into a default view name. * @param request current HTTP servlet request * @return the view name (or <code>null</code> if no default found) * @throws Exception if view name translation failed */ protected String getDefaultViewName(HttpServletRequest request) throws Exception { return this.viewNameTranslator.getViewName(request); }
public interface RequestToViewNameTranslator { /** * Translate the given {@link HttpServletRequest} into a view name. * @param request the incoming {@link HttpServletRequest} providing * the context from which a view name is to be resolved * @return the view name (or <code>null</code> if no default found) * @throws Exception if view name translation fails */ String getViewName(HttpServletRequest request) throws Exception; }
Locale resolveLocale(HttpServletRequest request);
View resolveViewName(String viewName, Locale locale) throws Exception;