简单控制器的流程定义了简单的控制器接口 (Controller),在流程的开始,它通常通过 Bean名字 URL处理器映射 (BeanNameUrlHandlerMapping)来获得支持此 HTTP请求的一个控制器实例和支持这个控制器的处理器拦截器 (HandlerInterceptor)。通过简单控制处理适配器 (SimpleControllerHandlerAdapter)传递 HTTP请求到简单的控制器( SimpleFormController)来实现 Spring Web MVC的控制流程。这个流程如下图所示,
图表 4 ‑15
首先,我们分析派遣器 Servlet是如何通过 Bean名字 URL处理器映射获得处理器执行链,也就是上图中的第一步。
Bean名字 URL处理器映射是通过一些列的父类继承最终实现处理器映射接口的。其中不同的父类抽象出一个独立的类级别,一个类级别完成一个最小化而又完善的功能。下图是它的类继承实现的树结构。
图表 4 ‑16
上图中灰色类,包括 Web应用程序对象支持类和应用程序对象支持类是 Spring环境项目的实现, Web应用程序对象支持类用来初始化时得到 Web应用程序环境 ,而应用程序对象支持类用来初始化时得到 Servlet环境。这里我们不再剖析他们的实现。
抽象处理器映射是这个体系结构中直接实现处理器映射接口的抽象类,这个类继承了 Web应用程序对象支持类,目的是监听 Web应用程序环境初始化事件。在初始化事件中,初始化拦截器,这些拦截器是应用到所有处理器的。如下程序注释,
public void setInterceptors(Object[] interceptors) { //通过注入的方式设置通用拦截器,这些拦截器是对象类型,其中真正支持的类型包括HandlerInterceptor和WebRequestInterceptor,这些通用拦截器是应用在所有的处理器上的 this.interceptors.addAll(Arrays.asList(interceptors)); } @Override protected void initApplicationContext() throws BeansException { //提供占位符方法让子类添加新的拦截器对象 extendInterceptors(this.interceptors); //初始化拦截器,因为拦截器有不同的是是实现,这里需要将不同的拦截器适配到最终的HandlerInterceptor的是实现,这是通过HandlerInterceptorAdapter来实现的 initInterceptors(); } protected void initInterceptors() { //如果配置的通用拦截器不为空 if (!this.interceptors.isEmpty()) { //对配置的通用拦截器进行适配 this.adaptedInterceptors = new HandlerInterceptor[this.interceptors.size()]; for (int i = 0; i < this.interceptors.size(); i++) { Object interceptor = this.interceptors.get(i); //对空的拦截器进行校验 if (interceptor == null) { throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null"); } //对每个拦截器进行适配 this.adaptedInterceptors[i] = adaptInterceptor(interceptor); } } } protected HandlerInterceptor adaptInterceptor(Object interceptor) { if (interceptor instanceof HandlerInterceptor) { //如果拦截器是HandlerInterceptor本身的是实现,不需要适配 return (HandlerInterceptor) interceptor; } else if (interceptor instanceof WebRequestInterceptor) { //如果拦截器是WebRequestHandlerInterceptorAdapter,则适配到通用的HandlerInterceptor的实现 return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor); } else { //不支持其他类型的拦截器 throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName()); } }
当派遣器 Servlet要求处理器映射翻译一个请求到处理器执行链的时候,抽象处理器则映射一个内部的处理器,连同初始化的拦截器一起构成处理器执行链返回。抽象处理器定义映射内部的处理器逻辑作为一个抽象的方法,子类需要实现这个方法来解析处理器。如下程序注释,
//实现处理器映射的方法,这个方法是派遣器Servlet要求翻译HTTP请求到处理器执行链的入口 public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //使用某种映射逻辑,将请求映射到一个真正的处理器,这个方法定义为抽象的,子类必须实现它,例如,实现基于URL到Beean名称的映射逻辑 Object handler = getHandlerInternal(request); //如果没有映射的处理器,则使用缺省的处理器 if (handler == null) { //子类可以设置缺省的处理器,也可以通过注射的方式设置缺省的处理器 handler = getDefaultHandler(); } //如果没有发现任何处理器,则返回空处理器, 派遣器Servlet将发送HTTP错误响应SC_NOT_FOUND(404) if (handler == null) { return null; } //如果内部映射逻辑实现返回一个字符串,则认为这个字符串是Bean的名字 if (handler instanceof String) { String handlerName = (String) handler; //在应用程序环境中通过Bean名字查找这个Bean handler = getApplicationContext().getBean(handlerName); } //连同处理器拦截器一起构造处理器执行链 return getHandlerExecutionChain(handler, request); } //抽象的处理器映射逻辑,这个逻辑的目的是映射一个HTTP请求到一个处理器对象,子类应该根据某些规则进行实现,通常是根据URL匹配Bean的名字来实现的,当然也可以匹配Bean的类或者其他的特征,我们将在第四小结剖析所有子类的实现 protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception; protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { //判断处理器对象的类型 if (handler instanceof HandlerExecutionChain) { //如果处理器已经是处理器对象 HandlerExecutionChain chain = (HandlerExecutionChain) handler; //添加初始化的拦截器 chain.addInterceptors(getAdaptedInterceptors()); //返回处理器执行链 return chain; } else { //否则使用处理器和初始化的拦截器构造处理器执行链的对象 return new HandlerExecutionChain(handler, getAdaptedInterceptors()); } }
我们看见抽象的处理器映射也实现了 Ordered接口,这个接口是用来在有多个处理器映射可供派遣器 Servlet使用时的优先级。
类体系结构中的下一个类是抽象 URL处理器映射,正如我们所愿,抽象 URL处理器映射实现了 getHandlerInternal()方法,在方法实现里通过请求的 URL匹配相应的映射处理器和映射拦截器来返回处理器执行链对象的。
那么,这些映射拦截器和映射处理器是如何进行初始化的呢?映射的拦截器是在初始化的时候,通过在 Web应用程序环境中查找得到的。这些映射拦截器必须是 MappedInterceptor的子类,而且注册在 Web应用程序环境中。它也提供了一个方法 registerHandler(),供子类调用注册响应的处理器。如下程序注释,
//改写了抽象处理器的拦截器初始化的方法,初始化更多配置在Web应用程序环境中的映射拦截器,映射拦截器是一个从URL到处理器拦截器对象映射的实现 @Override protected void initInterceptors() { //初始化父类的通用拦截器,这些拦截器应用到所有的处理器上 super.initInterceptors(); //查找所有在Web应用程序环境中注册的MappedInterceptor的实现 Map
我们可以看到这些映射拦截器是在 Web应用程序环境中查找得到的,他们必须实现 MappedInterceptor接口。得到的这些 MappedInterceptor接口保存在 MappedInterceptors集合类中,这个类同时提供了基于 URL路径过滤的功能。如下程序注释,
public Set
抽象 URL处理器映射初始化了拦截器和处理器之后,它将如何实现映射请求到处理器执行链的逻辑呢?正如我们所想,它通过 URL精确匹配或者最佳匹配查找注册的拦截器和处理器,然后构造处理器执行链对象。如下程序注释,
//实现抽象处理器映射的抽象发放,提供基于URL匹配的实现 @Override protected Object getHandlerInternal(HttpServletRequest request) throws Exception { //通过实用方法获得查找路径,这个查找路径 = URI - "http://" - hostname:port - application context - servlet mapping prefix //例如 http://www.robert.com/jpetstore/petstore/insert/ - "http://" - "www.robert.com" - "jpetstore" - "petstore" = "/insert" String lookupPath = this.urlPathHelper.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) { //翻译处理器Bean名字到Bean对象本身,如果配置了懒惰加载为false, 而且处理器是单例模式,这个转换在初始化的时候已经做完了 String handlerName = (String) rawHandler; rawHandler = getApplicationContext().getBean(handlerName); } //定义占位符方法校验处理器 validateHandler(rawHandler, request); //增加新的处理器拦截器导出最佳匹配路径和查找路径,既然我们使用了根处理器或者缺省处理器,这两个值都是查找路径 handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } if (handler != null && this.mappedInterceptors != null) { //如果存在最佳匹配处理器,过滤映射拦截器,得到所有匹配的处理器拦截器,匹配过程在前文中已经分析 Set
我们看到,抽象 URL处理器映射是在 Web应用程序环境初始化的时候初始化了拦截器,并且提供了通过 URL对拦截器过滤的功能。同时提供方法注册处理器。现在我们将分析,一个子类应该如何注册处理器。在类的体系结构中的下一个类是抽象探测 URL处理器映射,它探测 Web应用程序环境中的所有 Bean,通过某种规则对 Bean名字进行过滤来决定是否注册这个 Bean作为一个处理器。如下程序所示,
//改写应用程序初始化方法,获得注册处理器的机会 @Override public void initApplicationContext() throws ApplicationContextException { //保持原来的初始化实现 super.initApplicationContext(); //从Web应用程序环境中探测处理器 detectHandlers(); } protected void detectHandlers() throws BeansException { if (logger.isDebugEnabled()) { logger.debug("Looking for URL mappings in application context: " + getApplicationContext()); } //找到所有的对象类的实现,其实是eb应用程序环境中所有的Bean,并且返回Bean名字 String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); // 对于每一个Bean的名字 for (String beanName : beanNames) { //映射Bean的名字到一个或者多个URL String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { //如果这个Bean的名字能映射到一个或者多个URL,则注册Bean作为一个处理器 registerHandler(urls, beanName); } else { //否则打印日志 if (logger.isDebugEnabled()) { logger.debug("Rejected bean name '" + beanName + "': no URL paths identified"); } } } } //提供子类机会选择不同的策略映射名字到URL Pattern protected abstract String[] determineUrlsForHandler(String beanName);
在上面的实现中,处理器是在应用程序环境中探测得到的。所以,我们称为这个类作为抽象探测 URL处理器映射。但是,在抽象 URL处理器映射中,初始化的实现也自动的在应用程序环境中探测了处理器拦截器的实现,所以,我认为把探测处理器拦截器的实现加入当前这个类中更合理。
事实上,映射 Bean的名字到 URL Pattern的实现是非常简单的,子类 Bean名 URL处理器映射通过查看是否一个 Bean名字或者别名以字符 /开头,如果是以字符 /开头,则认为是一个处理器。如下程序所示,
protected String[] determineUrlsForHandler(String beanName) { List
流程分析到这里,派遣器 Servlet已经通过处理器映射得到了处理器执行链对象。处理器执行链对象包含着一个以 Object为类型的处理器,和一套应用在处理器上的处理器拦截器。接下来派遣器 Servlet则轮询所有注册的处理器适配器(如图 4-15 第二步),查找是否有一个处理器适配器支持此处理器。 Bean名 URL处理器映射 (BeanNameUrlHandlerMapping)通常用于映射简单的控制器对象,所以,返回的处理器执行链对象里面通常包含着控制器接口( Controller)的实现类。这个轮询结果将返回简单控制处理适配器 (SimpleControllerHandlerAdapter),并且通过它将 HTTP请求传递给控制器进行处理(如果 4-15 第四步和第五步)。简单的控制处理适配器的实现非常简单,正如它的名字所示,它仅仅是个适配器,请看如下类图,
图表 4 ‑17
如下程序注释,
public class SimpleControllerHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { 支持任何控制器接口的实现类 return (handler instanceof Controller); } public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 适配HTTP请求,HTTP响应到处理器,这个处理器一定是控制器接口的实现,并且返回模型和视图对象,派遣器Servlet讲用模型和视图对象构造HTTP响应 return ((Controller) handler).handleRequest(request, response); } public long getLastModified(HttpServletRequest request, Object handler) { if (handler instanceof LastModified) { 如果一个控制器实现了最后修改接口,则适配最后修改请求到控制器 return ((LastModified) handler).getLastModified(request); } 如果没有实现了最后修改接口,则返回不支持最后修改操作,每次都返回一个全新的HTTP GET请求,尽管这个资源自从上次请求没有改变过 return -1L; } }
现在我们理解,一个 HTTP请求经过处理器适配器传递到简单控制器的实现,简单控制器将实现任何必要的业务逻辑,最后返回模型数据和逻辑视图给作为总控制器的派遣器 Servlet。简单控制器接口有许多抽象和具体的实现,每个抽象和具体的实现都能完成一个特定的功能,结构清晰合理,易于扩展,使客户程序能够根据业务逻辑的需要选择不同的类继承处理不同的 HTTP请求。下面我们以简单 Form控制器为例说明它是如何完成对 HTTP请求的处理并且返回模型数据和逻辑视图对象给派遣器 Servlet的。下图是简单 Form控制器的实现流程,
图表 4 ‑18
从上图我们可以看到,简单 Form控制器是通过 HTTP请求方法来判断执行初始化操作还是业务逻辑操作。如果请求是 HTTP GET方法,说明这次请求是这个模块的第一次加载,那么我们应该显示一个 Form给用户,以至于用户可以填写业务逻辑的输入数据。当用户填写了业务逻辑数据后,提交 Form给此模块,那么用户提交使用的一定是 HTTP POST请求,这个请求包含着此模块业务逻辑的输入数据,所以,简单 Form控制器会绑定这些输入数据到业务逻辑模型对象中,这里称为一个命令 (Command)对象 ,以下分析中的命令对象, Form的 Backing Bean和业务逻辑模型对象指同一个事物,不再做区分。然后,对命令对象进行校验。如果在绑定或者校验过程中出现任何错误,则导出错误对象到 HTTP请求的属性里,接下来在显示 Form视图的时候,同时显示错误,于是,用户得知哪些输入是不合法的。当用户提交了完整而且有效的输入数据后,简单 Form控制器在绑定和校验后,获得了此模块需要的输入数据后,使用服务层的服务进行业务逻辑的处理,处理后会返回处理结果数据,也就是模型数据,这些模型数据连同成功视图一起返回给作为总控制器的派遣器 Servlet.
为了让程序具有可重用性和可扩展性,上面的流程并不是通过一个类实现的。而是通过多个类的继承最终由简单 Form控制器实现的。如下是这些类的实现类图,
图表 4 ‑19
我们在分析处理器映射的实现中得知上图中被标识为灰色的类和接口是用来对一个对象注入 Web应用程序环境和 Servlet环境的,这里我们不再详述其实现。继承自 Web应用程序对象支持类的第一个类就是 Web内容产生器,这个类用于校验支持的 HTTP方法,也会产生 HTTP缓存头信息等。如下程序注释,
//这个方法实现检查HTTP请求方法的合法性和产生缓存头信息,这个类是抽象类,这个方法是实体方法给子类使用的 //lastModified通常是根据控制器是不是实现了LastModified接口来决定的 protected final void checkAndPrepare( HttpServletRequest request, HttpServletResponse response, boolean lastModified) throws ServletException { // 代理另外一个方法并且传入更多的参数(配置的缓存时间) checkAndPrepare(request, response, this.cacheSeconds, lastModified); } protected final void checkAndPrepare( HttpServletRequest request, HttpServletResponse response, int cacheSeconds, boolean lastModified) throws ServletException { // 检查是不是支持当前的HTTP请求方法 String method = request.getMethod(); if (this.supportedMethods != null && !this.supportedMethods.contains(method)) { // 如果不支持,则抛出异常,终止处理 throw new HttpRequestMethodNotSupportedException( method, StringUtils.toStringArray(this.supportedMethods)); } // 如果Session不存在,则抛出特定的异常HttpSessionRequiredException,这个异常通常被捕获,捕获后创建Session,然后重试 if (this.requireSession) { if (request.getSession(false) == null) { throw new HttpSessionRequiredException("Pre-existing session required but none found"); } } // 添加缓存信息到响应头中 // 如果控制器支持最后修改操作,则设置必须重新校验信息到响应头中 applyCacheSeconds(response, cacheSeconds, lastModified); } protected final void applyCacheSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) { // 如果缓存的时间大于0 if (seconds > 0) { // 设置缓存时间到响应头中 cacheForSeconds(response, seconds, mustRevalidate); } else if (seconds == 0) { // 设置绝不缓存信息到响应头中 preventCaching(response); } // 如果缓存时间小于0, 服务器不决定是否缓存,由客户自己决定 } protected final void cacheForSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) { // 这里我们需要兼容HTTP 1.0和HTTP 1.1 if (this.useExpiresHeader) { // 如果是 HTTP 1.0 头信息,设置缓存的时间 response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis() + seconds * 1000L); } if (this.useCacheControlHeader) { // 如果是HTTP 1.1 头信息,设置缓存时间 String headerValue = "max-age=" + seconds; //如果支持最后修改操作,则设置标识重新校验 if (mustRevalidate) { headerValue += ", must-revalidate"; } response.setHeader(HEADER_CACHE_CONTROL, headerValue); } } protected final void preventCaching(HttpServletResponse response) { //在HTTP响应头中,设置不使用缓存 response.setHeader(HEADER_PRAGMA, "no-cache"); if (this.useExpiresHeader) { // 如果是HTTP 1.0 头信息,则使用1代表不使用缓存 response.setDateHeader(HEADER_EXPIRES, 1L); } if (this.useCacheControlHeader) { // 如果是HTTP 1.1 头信息: "no-cache" 是标准值, "no-store" 用在firefox浏览器里的 ,他们都是用來告诉浏览器不需要缓存网页的 response.setHeader(HEADER_CACHE_CONTROL, "no-cache"); if (this.useCacheControlNoStore) { response.addHeader(HEADER_CACHE_CONTROL, "no-store"); } } }
类层次的下一个类抽象控制器类实现了控制器接口。但是,这个类除了对方法 handleRequest()进行同步没有做任何实现,并且适配到一个抽象方法 handleRequestInternal(),这个方法是由子类来实现的。如下程序所示,
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { // 调用Web内容产生器进行检查HTTP方法和产生缓存信息的响应头 checkAndPrepare(request, response, this instanceof LastModified); // 如果Session内同步标识打开,则对handleRequestInternal()进行同步调用 if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { // 如果Session存在则取得同步对象,缺省是Session对象自己 Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { // 在Session内同步处理请求 return handleRequestInternal(request, response); } } } // 如果不需要Session内同步,直接调用了抽象方法handleRequestInternal() return handleRequestInternal(request, response); } // 这个方法需要由子类实现,来处理不同的业务流程 protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception;
类实现体系结构中的下一个类基本命令控制器中引入了领域对象模型命令对象 (Command)的概念,它是一个通用的对象类型,用来存储输入的参数信息。并且引入了对这个领域对象模型进行初始化和校验的逻辑,如下程序注释,
//继承自应用程序支持类的初始化方法,实现更多的初始化逻辑 protected void initApplicationContext() { //如果注册了校验器 if (this.validators != null) { for (int i = 0; i < this.validators.length; i++) { //如果存在着不支持配置的命令类的校验器,则抛出异常,停止处理 if (this.commandClass != null && !this.validators[i].supports(this.commandClass)) throw new IllegalArgumentException("Validator [" + this.validators[i] + "] does not support command class [" + this.commandClass.getName() + "]"); } } } //提供了取得命令的方法给子类使用 protected Object getCommand(HttpServletRequest request) throws Exception { //默认实现是通过配置的类实例化一个新的命令对象 return createCommand(); } protected final Object createCommand() throws Exception { //如果没有配置命令类,则抛出异常推出 if (this.commandClass == null) { throw new IllegalStateException("Cannot create command without commandClass being set - " + "either set commandClass or (in a form controller) override formBackingObject"); } if (logger.isDebugEnabled()) { logger.debug("Creating new command of class [" + this.commandClass.getName() + "]"); } //通过配置的类实例化一个命令对象 return BeanUtils.instantiateClass(this.commandClass); } protected final boolean checkCommand(Object command) { //查看命令是否和配置的命令类匹配 return (this.commandClass == null || this.commandClass.isInstance(command)); } //提供方法进行绑定命令和校验命令给子类使用 protected final ServletRequestDataBinder bindAndValidate(HttpServletRequest request, Object command) throws Exception { //通过当前HTTP请求和命令对象创建绑定对象 ServletRequestDataBinder binder = createBinder(request, command); //把绑定结构放入到版定异常中,erros和binder共享一个绑定结构的引用,校验中会把错误存贮在这个绑定结果上 BindException errors = new BindException(binder.getBindingResult()); //查看是否配置了跳过绑定 if (!suppressBinding(request)) { //如果没有跳过绑定 //绑定请求数据到命令数据里 binder.bind(request); //绑定后,激发绑定后事件 onBind(request, command, errors); //查看校验设置 if (this.validators != null && isValidateOnBinding() && !suppressValidation(request, command, errors)) { //如果绑定时校验开启和跳过校验关闭,则使用校验器进行校验命令 for (int i = 0; i < this.validators.length; i++) { //在校验器里,如果有错误,就会存入errors对象里面,而errors是从绑定的绑定结果构造的,传递的是引用,所以共享一个结果对象,这个对象最后会被逐层返回 ValidationUtils.invokeValidator(this.validators[i], command, errors); } } //绑定和校验后,激发绑定和校验后时间 onBindAndValidate(request, command, errors); } //如果设置了跳过绑定,则直接将绑定对象返回 return binder; } protected boolean suppressBinding(HttpServletRequest request) { //默认情况下是进行绑定操作的 return false; } protected void onBind(HttpServletRequest request, Object command, BindException errors) throws Exception { //响应绑定操作执行后的事件方法,子类可以改写实现特殊的绑定操作 onBind(request, command); } protected void onBind(HttpServletRequest request, Object command) throws Exception { //占位符方法,同上方法同能 } protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errors) throws Exception { //响应绑定和校验操作执行后的事件方法,子类可以改写实现特殊的校验操作 } protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object command) throws Exception { //实例化一个Servlet请求数据绑定 ServletRequestDataBinder binder = new ServletRequestDataBinder(command, getCommandName()); //对绑定进行简单的初始化,例如,绑定使用的消息代码解析器,绑定错误处理器,客户化编辑器等 prepareBinder(binder); //提供机会通过Web绑定初始化器初始化绑定对象 initBinder(request, binder); //返回绑定对象 return binder; } protected final void prepareBinder(ServletRequestDataBinder binder) { //设置是否使用直接字段存取 if (useDirectFieldAccess()) { binder.initDirectFieldAccess(); } //设置消息代码解析器 if (this.messageCodesResolver != null) { binder.setMessageCodesResolver(this.messageCodesResolver); } //设置绑定错误处理器 if (this.bindingErrorProcessor != null) { binder.setBindingErrorProcessor(this.bindingErrorProcessor); } //这是客户化 if (this.propertyEditorRegistrars != null) { for (int i = 0; i < this.propertyEditorRegistrars.length; i++) { this.propertyEditorRegistrars[i].registerCustomEditors(binder); } } } protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception { if (this.webBindingInitializer != null) { //使用Web绑定初始化器对绑定进行初始化 this.webBindingInitializer.initBinder(binder, new ServletWebRequest(request)); } }
由此可见,基本命令控制器的实现中提供了实用方法创建命令对象,绑定命令对象和校验命令对象。那么,在下一个类层次中,抽象 Form控制器则使用这些方法进行创建命令对象,校验命令对象,进而实现整体的显示 Form,处理 Form和显示成功试图的流程。如下程序注释,
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { // 判断是提交Form还是显示Form if (isFormSubmission(request)) { // 如果是提交Form(HTTP POST) try { // 取得命令对象,新创建的命令对象或者从Session中提取的命令对象,取决与配置 Object command = getCommand(request); // 使用父类提供的实用方法进行绑定和校验 ServletRequestDataBinder binder = bindAndValidate(request, command); // 构造绑定错误对象,这个对象是简历在绑定结构智商的 BindException errors = new BindException(binder.getBindingResult()); // 处理提交Form逻辑 return processFormSubmission(request, response, command, errors); } catch (HttpSessionRequiredException ex) { // 如果没有form-bean(命令对象)存在在Session里 if (logger.isDebugEnabled()) { logger.debug("Invalid submit detected: " + ex.getMessage()); } // 如果需要Session存在或者使用了Session From但是没有Session Form存在,则初始化Session Form,然后重试 return handleInvalidSubmit(request, response); } } else { 如果是显示Form(HTTP GET) // 则显示Form输入视图 return showNewForm(request, response); } } protected boolean isFormSubmission(HttpServletRequest request) { // 只有HTTP POST才会使用提交Form的处理逻辑 return "POST".equals(request.getMethod()); } protected final Object getCommand(HttpServletRequest request) throws Exception { // 如果不是session-form 模式, 穿件一个全新的form-backing命令对象. if (!isSessionForm()) { return formBackingObject(request); } // 如果是Session-form 模式 HttpSession session = request.getSession(false); if (session == null) { // 如果Session不存在,则抛出特殊的异常HttpSessionRequiredException throw new HttpSessionRequiredException("Must have session when trying to bind (in session-form mode)"); } String formAttrName = getFormSessionAttributeName(request); // 取得用来保存在Session里的Session Form的关键字 Object sessionFormObject = session.getAttribute(formAttrName); if (sessionFormObject == null) { //如果关键字不存在,则抛出特殊的异常HttpSessionRequiredException throw new HttpSessionRequiredException("Form object not found in session (in session-form mode)"); } // 抛出特殊的异常HttpSessionRequiredException,会被抓住,然后,创建Session和命令本身,然后重试 if (logger.isDebugEnabled()) { logger.debug("Removing form session attribute [" + formAttrName + "]"); } // 流程处理后,将命令对象从Session中移除,如果再次需要命令对象,则需要重建命令对象,然后绑定,校验等等 session.removeAttribute(formAttrName); // 调用占位符方法取得当前的命令对象 return currentFormObject(request, sessionFormObject); } protected Object formBackingObject(HttpServletRequest request) throws Exception { //使用父类的使用方法创建一个全新的form-backing命令对象 return createCommand(); } protected Object currentFormObject(HttpServletRequest request, Object sessionFormObject) throws Exception { // 返回传入的命令对象本身,也可能对命令对象做客户化的改变 return sessionFormObject; } //抽象方法,子类用来实现对HTTP请求的处理逻辑 protected abstract ModelAndView processFormSubmission( HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception; //如果使用Session Form模式,但是没有Session或者Session里不存在命令对象 protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception { // 创建一个命令对象 Object command = formBackingObject(request); // 校验和绑定 ServletRequestDataBinder binder = bindAndValidate(request, command); BindException errors = new BindException(binder.getBindingResult()); // 进行对HTTP提交请求的处理,这个方法是抽象方法,子类需要实现响应的对HTTP请求的处理逻辑 return processFormSubmission(request, response, command, errors); } protected final ModelAndView showNewForm(HttpServletRequest request, HttpServletResponse response) throws Exception { logger.debug("Displaying new form"); // 显示一个Form视图 return showForm(request, response, getErrorsForNewForm(request)); } protected final BindException getErrorsForNewForm(HttpServletRequest request) throws Exception { // 穿件命令对象(form-back对象) Object command = formBackingObject(request); // 这个命令对象不能为空 if (command == null) { throw new ServletException("Form object returned by formBackingObject() must not be null"); } // 简单命令对象和配置的命令类是否兼容 if (!checkCommand(command)) { throw new ServletException("Form object returned by formBackingObject() must match commandClass"); } //创建绑定对象,但是不需要真正的绑定和校验 ServletRequestDataBinder binder = createBinder(request, command); BindException errors = new BindException(binder.getBindingResult()); // 缺省情况下对于显示Form视图,不绑定命令对象 if (isBindOnNewForm()) { // 手工配置为显示Form视图时,进行绑定 logger.debug("Binding to new form"); binder.bind(request); // 传递绑定时间 onBindOnNewForm(request, command, errors); } // 返回绑定结果错误对象,这里面可能不包含错误 return errors; } //子类实现用来决定如何显示Form视图 protected abstract ModelAndView showForm( HttpServletRequest request, HttpServletResponse response, BindException errors) throws Exception;
如此可见,抽象 Form控制器实现了处理一个 Web Form的主要流程。也就是说,在 Form初始化的时候,则显示 Form视图,而在 Form提交的时候,则处理 Form提交,最后显示成功视图。因此,定义了两个抽象的方法, showForm()和 processFormSubmission()。也正如我们所想,子类通过实现这两个方法来处理不同的流程。在简单 Form控制器的实现中, showForm()简单的显示了配置的 Form视图。而 processFormSubmission()的实现则判断是否有绑定和校验错误,如果有错误,则转发请求到 Form视图,在 Form视图中显示错误并且提示用户重新输入。如果用户输入了正确有效的数据并且提交 Form,简单 Form控制器则使用服务层的服务处理逻辑,并且连同包含处理结果的模型数据和成功视图返回给作为主控制器的派遣器 Servlet。如下程序注释,
//实现父类的抽象方法,最终处理一个对Form初始化HTTP请求 @Override protected ModelAndView showForm( HttpServletRequest request, HttpServletResponse response, BindException errors) throws Exception { return showForm(request, response, errors, null); } protected ModelAndView showForm( HttpServletRequest request, HttpServletResponse response, BindException errors, Map controlModel) throws Exception { //显示配置的Form视图 return showForm(request, errors, getFormView(), controlModel); } @Override protected ModelAndView processFormSubmission( HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception { if (errors.hasErrors()) { if (logger.isDebugEnabled()) { logger.debug("Data binding errors: " + errors.getErrorCount()); } //如果绑定和校验返回错误,则转发到Form视图,并且显示错误,提示用户重新输入 return showForm(request, response, errors); } else if (isFormChangeRequest(request, command)) { logger.debug("Detected form change request -> routing request to onFormChange"); //如果是Form改变请求,例如一个相关对话框数据改变等 onFormChange(request, response, command, errors); //转发到Form视图,继续用户输入 return showForm(request, response, errors); } else { logger.debug("No errors -> processing submit"); //处理提交Form逻辑并且返回模型和视图对象 return onSubmit(request, response, command, errors); } } protected ModelAndView onSubmit(Object command, BindException errors) throws Exception { //执行业务逻辑处理,返回模型和视图对象 ModelAndView mv = onSubmit(command); if (mv != null) { // 如果返回了模型和视图对象,则直接返回它给派遣器Servlet,缺省情况下并不返回模型和视图对象 return mv; } else { // 缺省情况下返回配置的成功视图 if (getSuccessView() == null) { throw new ServletException("successView isn't set"); } return new ModelAndView(getSuccessView(), errors.getModel()); } } protected ModelAndView onSubmit(Object command) throws Exception { //业务逻辑处理的缺省实现,调用一个占位符方法 //子类可以改写此方法的实现,调用服务层处理逻辑,返回模型和视图对象 doSubmitAction(command); return null; } protected void doSubmitAction(Object command) throws Exception { //如果子类不需要返回特殊的视图,那么仅仅需要改写此方法 }
简单 Form控制器是这个实现体系结构中的最后一个类,在使用它之前,需要为它配置 Form视图和成功视图,它就可以开始工作了。但是,一个真正的控制器类应该改写它的 doActionSubmit()方法,从而实现需要的业务逻辑调用。
最总我们可以看见,简单 Form 控制器实现并不是单一的类实现,在实现上有很多的层次,每个层次完成一个相对独立的功能,下一层紧紧的依赖于上一层。当你选择实现一个控制器的时候,可以根据需求选择实现哪个层次的抽象类控制器,甚至控制器接口本身。