Cyh的博客

Email:kissyan4916@163.com
posts - 26, comments - 19, trackbacks - 0, articles - 220

笔记之Spring-MVC

Posted on 2009-02-21 00:34 啥都写点 阅读(1934) 评论(0)  编辑  收藏 所属分类: J2EE

 

  • 请求生命中的第一天:请求从离开浏览器开始知道获得一个响应,期间会有几次停留,每一次都留下一些信息并得到更多的信息。(见PPT1)

    • 配置DispatcherServlet:Spring MVC的核心是DispatcherServlet,这个servlet的功能是作为Spring MVC的前端控制器。和任何Servlet一样,必须在Web应用系统的web.xml文件中配置。

      <servlet>

          <servlet-name>roadrantz</servlet-name>

          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

          <load-on-startup>1</load-on-startup>

      </servlet>

      当DispatcherServlet载入后,它将从XML中载入Spring的应用上下文,这个XML的名字取决于Servlet的名字。在本例中,因为Servlet的名字叫做roadrantz,所以DispatcherServlet将试图从一个叫做roadrantz-servlet.xml的文件中载入应用上下文。接下来指定哪些URL需要由DispatcherServlet来处理。

      <servlet-mapping>

            <servlet-name>roadrantz</servlet-name>

            <url-pattern>*.htm</url-pattern>

      </servlet-mapping>  我们可以为DispatcherServlet选择任意URL样式。我们选择"*.htm"样式的主要原因是因为这种样式是大多数生成HTML内容的Spring MVC应用系统的惯例用法。另一个原因是生成的内容是HTML,因此URL应该反映这一点。

      • 分解应用上下文:正如前面提到的,DispatcherServlet从以<servlet-name>命名的XML文件中载入应用上下文。但这不是说你不能将你的应用上下文切到多个XML文件中。事实上,我们建议你将应用上下文分散到应用系统的各个层中。(见PPT2)由于DispatcherServlet的配置文件是roadrantz-servlet.xml,所以在这个文件中应该包含用于控制器和其他Spring MVC组件的<bean>定义信息。对于业务层和数据层的Bean,我们倾向于将他们分别放到roadrantz-service.xml和roadrantz-data.xml中。

        配置上下文载入器:为了保证所有的配置文件都被载入,需要在web.xml中配置一个上下文载入器。上下文载入器载入除DispatcherServlet载入的配置文件之外的其他上下文配置文件。最常用的上下文载入器是一个Servlet监听器,其名称为ContextLoaderListener,你需要在web.xml文件中像下面这样配置它:

        <listener>

            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

        </listener>    使用ContextLoaderListener配置时,你需要告诉它Spring配置文件的位置。如果没有指定,上下文载入器会在/WEB-INF/applicationContext.xml中找Spring配置文件。但是这样无法将应用上下文分散到应用系统的各个层中,所以你需要取代这种默认方式。 你可以通过Servlet上下文中设置contextConfigLocation参数来为上下文载入器指定一个或多个Spring配置文件:

        <context-param>

             <param-name>contextConfigLocation</param-name>

             <param-value>

                  /WEB-INF/roadrantz-service.xml,

                  /WEB-INF/roadrantz-data.xml,

                  /WEB-INF/roadrantz-security.xml

             </param-value>

        </context-param>

        Spring MVC概述:每一个Web应用程序都有一个主页。这是应用程序的一个起始点。用户可以从主页中启动应用程序,也可以在失去方向时回到主页。否则,用户会不停的点击链接,感到很困扰,甚至可能离开,进入到其他网站中。第一步是建立一个处理主页请求的控制器对象。因此让我们来编写第一个Spring MVC控制器。

        • 创建控制器:在Spring MVC中,控制器是一个与应用程序功能的接口类。(见PPT3)控制器接收请求,将请求发送给服务类进行处理,最后又收集需要返回给用户Web浏览器的结果。

          public class HomePageController extends AbstractController {

              protect ModelAndView handleRequestInternal(HttpServletRequest request,HttpServletResponse response)throws Exception {

               List recentRants = rantService.getRecentRants( );

              return new ModelAndView("home","rants",recentRants);

              }

             private RantService rantService;

             public void setRantService(RantService rantService) {

                this.rantService = rantService;

              }

            }

          } Spring控制器和Servlet或Struts Action的不同之处在于它被配置成Spring应用上下文的一个普通JavaBean。这意味着,使用控制器和使用其他Bean一样,可以充分利用依赖注入和Spring AOP。在HomePageController中,依赖注入用在了注入一个RantService.HomePageController

          引入ModelAndView:控制器执行方法都必须返回一个ModelAndView。因此需要理解这个重要的类是如何工作的。正如它的名字表述的,ModelAndView保存了视图以及视图显示的模型数据.在HomePageController里,ModelAndView对象应按照下面的方式构建: new ModeAndView("home","rants",recentRants); 构造器的第一个参数是视图组件(用于显示空气器的输出)的逻辑名称。这里,视图的逻辑名称是home。视图解析器会使用这个名称查找实际的View对象  。 后两个参数分别表示传递给视图的模型对象。这个两个参数是一个名字值对。第二个参数就是第三个参数所表示的模型对象名称。

          配置控制器Bean:现在HomePageController已经写好了,你必须将其配置到DispatcherServlet的上下文配置文件中(对于RaodRantz应用程序就是roadrantz-servlet.xml文件)。下面这段XML代码定义了HomePageController:

          <bean name="/home.htm" class="com.roadrantz.mvc.HomePageController">

             <property name="rantService" ref="rantService" />

          </bean>  有件事让你感到奇怪,不是为HomePageController Bean设置id属性,而是设置name属性。并且更奇怪的是设定的不是一个真实名字,而是给它设置一个URL"/home.htm"。这里name属性承担了两个责任,即定义Bean的名字也定义需要使用这个控制器处理的URL样式。由于URL样式含有XMLid属性中的非法字符--特别是斜杠(/),所以使用name属性,而不使用id。只要进入DispatcherServlet的请求是以"/home.htm"结尾的,DispatcherServlet就会分给HomePageController来处理,注意这个Bean的name属性使用的是URL样式的唯一原因是我们还没有配置处理映射Bean。DispatcherServlet使用的默认处理器映射是BeanNameUrlHandlerMapping,它使用URL样式的名字。

          声明一个视图解析器:返回给ModelAndView对象的其中一个值是逻辑视图名称。然而这个逻辑视图名称并不是直接引用特定的JSP的,而是用于间接的表达使用哪一个JSP。为了帮助Spring MVC了解使用哪一个JSP,你需要在roadrantz-servlet.xml中声明另一个Bean:一个视图解析器。简单来看,视图解析器的工作就是将视图的名称返回到ModelAndView中并将其映射到一个视图上。在HomePageController中,我们需要一个视图解析器将"home"解析成一个JSP文件来呈现主页。Spring MVC带来了很多可供选择的视图解析器。对于使用JSP渲染视图来说,再也没有比InteralResourceViewResolver更简单的了:

            <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">

                 <property name="prefix">

                    <value>/WEB-INF/jsp/</value>

                 </property>

                 <property name="suffix">

                    <value>.jsp</value>

                 </property>

             </bean>由于HomePageController返回的ModelAndView中的视图名是home,InternalResourceViewResolver将在/WEB-INF/jsp/home.jsp处查找视图。

    将请求映射到控制器:当请求到达DispatcherServlet时,需要一些目录来指明请求应该如何分配。处理器映射可以帮助DispatcherServlet了解请求应该被发送给哪个控制器。我们依赖DispatcherServlet默认使用的BeanNameUrlHandMapping。BeanNameUrlHandMapping很容易上手,但是 它无法满足所有情况。所幸,Spring MVC提供了几种可以选择的处理器映射实现。Spring MVC中所有的处理器映射都实现了接口org.springframework.web.servlet.HandlerMapping  (见PPT4)

    • 使用SimpleUrlHandlerMapping:它可能是最直接的Spring处理器映射。它允许你将URL样式直接映射到控制器,而且不需要以待定的方式命名你的Bean。例如:

         <bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

           <property name="mappings">

              <props>

                 <prop key="/home.htm">homePageController</prop>

                 <prop key="/rantsForVehicle.htm">rantsForVehicleControllerRss</prop>

                 <prop key="/rantsForVehicle.rss">rantsForVehicleControllerRss</prop>

              </props>

           </property>

         </bean>

      <props>元素的key属性是URL样式。和BeanNameUrlHandlerMapping一样,所有的URL样式和DispatcherServlet的<servlet-mapping>一一对应。<prop>的值是处理这个URL的控制器Bean的名字。

      使用ControllerClassNameHandlerMapping:很多时候,你会发现控制器映射的URL样式都与控制器类名很相似。例如,在RoadRantz应用程序中,我们将rantForVehicle.htm映射到rantForVehicleController,将rantsForDay.htm映射到RantsForDayController。在这些例子中,URL样式与控制器的类名相同,只不过去掉了Controller部分,加上了.htm部分。这种样式就好像对映射设置了特定的默认值,而不需要明确地进行映射。这大概就是ControllerClassNameHandlerMapping所做的工作:

      <bean id="urlMapping" class="org.springframework.web.servlet.mvc.ControllerClassNameHandlerMapping" />

      这样就可以通知Spring的DispatcherServlet将URL样式按照简单的约定映射到控制器。Spring不必明确的为每个控制器映射URL样式,而是可以根据控制器的类名自动的将控制器映射到URL样式上。 为了简单起见,要想生成URL样式,控制器类名中的Controller部分会被去掉(如果这部分存在),剩下的文本会变成小写字母,并且在最前面加上一个反斜杠/,在最后加上".htm"。

      使用CommonsPathMapHandlerMapping元数据映射控制器:它是根据控制器源代码中的元数据决定如何进行URL映射。这个元数据特定是一个org.springframework.web.servlet.handler.commonsattributes.PathMap属性,是用Jakarta Commons Attributes编译器编译到控制器中。 要使用它,只要像下面这样简单地在上下文控制文件中定义一个Bean就可以了:

      <bean id="urlMapping" class="org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlerMapping" />

      控制器源代码注释标签中的PathMap属性声明了这个控制处理的URL样式。例如,为了将HomePageController映射到"/home.htm",HomePageController的标签是这样的:

      /**

       * @@org.springframework.web.servlet.handler.commonsattributes.PathMap("/home.htm")

      */

      public class HomePageController extends AbstractController {

       ......

      }  最后,编译的时候需要将Commons Attributes编译器放进来,这样标签PathMap 才能被编译到应用代码中。要详细了解如何在Ant 或Maven中设置Commons Attributes编译器,请参考Commons Attributes 主页(http://jakarta.apache.org/commons/attributes)。

      使用多映射处理器:所有的处理器映射类都实现了Spring的Ordered接口。这意味着你可以在应用系统中声明多个处理器映射,并且设置哪个相对另一个有优先权。例如,假设想在一个应用系统中并排使用BeanNameUrlHandlerMapping和SimpleUrlHandlerMapping。你需要像下面这样声明处理器映射Bean:

      <bean id="beanNameUrlMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">

        <property name="order">

          <value>1</value>

        </property>

      </bean>

      <bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

        <property name="order"><value>0</value> </property> 

        <property name="mappings">

         ...

        </property>

      </bean>

      注意,order属性的值越小,优先级越高。

    用控制器处理请求:如果DispatcherServlet是Spring MVC的心脏,那么控制器就是它的大脑。Spring控制器与Struts/web work action相比有一个重要的区别。这就是,相比Struts或Web Work比较平坦的Action层次,Spring提供了丰富的控制器层(见PPT5)在控制器层次的最上面是接口Controller,任何实现这个接口的类都可以用来处理Spring MVC框架传递过来的请求。要创建自己的控制器,你必须要做的就是实现这个接口。Spring让你选择最适合自己需要的控制器(见PPT6)Spring控制器可以归为6种类型,沿着表格向下,功能越来越多,除了ThrowawayController,顺着控制器类层次向下,每个控制器都是建立在它上面的控制器基础之上的。

    • 处理命令:当控制器需要根据参数执行工作时,应该继承命令控制器,如AbstractCommandController(见PPT7)这个控制器会自动将参数绑定到命令对象中,并且提供了插入验证器的钩子,确保参数合法性。下面显示了RantsForVehicleController,一个用于显示特定车辆已有投诉列表的命令控制器。

      public class RantsForVehicleController  extends AbstractCommandController{

      public RantsForVehicleController() {

      setcommandClass(Vehicle.class);

      setCommandName("vehicle");

      }

      protected ModelAndView handle(HttpServletRequest request,HttpServletResponse,Object command,

      BindException errors) throws Exception {

             Vehicle vehicle = (Vehicle) command;

             List vehicleRants = rantService.getRantsForVehicle(vehicle);

             Map model =errors.getModel();

             model.put("rants", rantService.getRantsForVehicle(vehicle));

             model.put("vehicle", vehicle);

              return new ModelAndView("vehicleRants",model);

       }

      private RantService rantService;

      public void setRantService(RantService rantService) {

      this.rantService = rantService;

       }

      }

      一个命令对象是一个为了简化对请求参数访问而设计的Bean。一个Spring命令对象是一个POJO,它不需要实现任何Spring的特定类。你需要在roadrantz-servlet.xml文件中注册RantsForVehicleController

      <bean id="rantsForVehicleController" class="com.roadrantz.mvc.RantsForVehicleController">

         <property name="rantService" ref="rantService"/>

      </bean>

      处理表单提交:表单控制器比命令控制器前进了一步,(见PPT8)它在接收到HTTP GET请求的时候显示一个表单,接收到一个HTTP POST请求的时候处理这个表单。另外,在处理过程中如果发生错误的话,这个控制器会知道重新显示这个表单,这样用户就可以修改错误,重新提交。为了显示表单控制器是如何工作的,考虑以下程序中的AddRantFormController。

      public class AddRantFormController extends SimpleFormController {

      private static final String[] ALL_STATES = {"AL","AK","AZ"};

         public AddRantFormController() {

         setCommandClass(Rant.class);

         setCommandName("rant");

         }

         protected Object formBackingObject(HttpServletRequest request) throws Exception {

         Rant rantForm = (Rant)super.formBackingObject(request);

         rantForm.setVehicle(new Vehicle());

         }

         protected Map referenceDate(HttpServletRequest request) throws Exception{

         Map referenceData = new HashMap();

         referenceDate.put("states",ALL_STATES);

         return referenceData;

         }

         protected ModelAndView onSumbit(Object command,BindException bindException)throws Exception{

         Rant rant = (Rant)command;

         rantService.addRant(rant);

         return new ModelAndView(getSuccessView());

         }

         private RantService rantService;

      public void setRantService(RantService rantService) {

      this.rantService = rantService;

      }

      }虽然referenceData()方法是可选的,但是如果需要为 显示表单提供其他附加的信息,则可以使用这个方法。在正常的情况下,返回表单的命令对象一般是一个简单的命令类实例。不过对于AddRantFormController,Rant实例不会完成这项工作。表单会使用内嵌的Vehicle属性作为表单返回对象的一部分。因此,需要覆盖fromBackingObject()方法,以便设置vehicle属性。否则,在控制器视图绑定state和plateNumber属性时,会抛出NullPointerException异常。

           那么控制器是如何知道显示投诉输入表单的。如果投诉输入成功,用户将到什么页面也不是很清楚。唯一的线索是对getSuccessView()的调用结果会提交给ModelAndView。但是,提交成功的视图又从哪里来呢?SimpleFormController被设计成尽量将视图详细信息放在控制器代码之外。不是将ModelAndView对象硬编码进来,而是像下面这样在上下文配置文件中配置控制器:

      <bean id="addRantController" class="com.roadrantz.mvc.AddRantFormController">

           <property name="formView"  value="addRant" />

           <property name="successView" value="rantAdded" />

           <property name="rantService" ref="rantService" />

      </bean> formView属性是控制器接收到HTTP GET请求或有任何错误发生时需要显示的视图的逻辑名。同样,successView是提交的表单成功处理后要显示的视图的逻辑名。

      验证表单输入:org.springframework.validation.Validator接口为Spring MVC提供了验证功能,定义如下:

      public interface Validator {

          void validate(Object obj  , Errors errors);

          boolean supports(Class clazz);

      }这个接口的实现必须验证传递给validate()方法的对象的字段,用Errors对象驳回任何非法数据。supports()方法用于帮助Spring判断该验证器是否可以用于指定类。 以下是一个用于验证Rant对象的Validator实现。

      public class RantValidator implements Validator {

        public boolean supports(Class clazz){

        return clazz.equals(Rant.class);

        }

        public void validate(Object command, Errors errors) {

        Rant rant = (Rant) command;

        ValidationUtils.rejectIfEmpty(errors,"vehicle.state","required.state","State is required");

        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "rantText", "required.rantText","You must enter some rant text.");

            validatePlateNumber(rant.getVehicle().getPlateNumber(),errors);

        }

        private static final String PLATE_REGEXP = "/[a-z0-9]{2,6}/i";

        private void validatePlateNumber(String plateNumber,Errors errors) {

        Per15Util per15Util = new Per15Util();

        if(!per15Util.match(PLATE_REGEXP,plateNumber)){

        errors.reject("invalid.plateNumber","Invalid license plate number.");

        }

        }

      }还有一件事情要做,就是让AddRantFormController使用RantValidator。你可以将一个RantValidator Bean装配到AddRantFormController Bean中:

      <bean id="addRantController" class="com.roadrantz.mvc.AddRantFormController">

        <property name="formView" value="addRant"/>

        <property name="successView" value="rantAdded"/>

        <property name="rantService"  ref="rantService"/>

        <property name="validator">

           <bean class="com.roadrantz.mvc.RantValidator"></bean>

        </property>

      </bean>通过实现Validation接口,可以通过程序完全控制应用程序命令对象的验证。如果需要复杂的验证和特殊的逻辑,利用这项功能将会十分方便。对于简单的情况,例如保证填入需要的字段并按照基本格式,编写自己的Validator接口就有点麻烦了。声明性验证是一个不错的选择。

      利用命令验证器进行验证:在我们深入研究用于实现声明性Validator的Spring JavaDoc之前,需要知道Spring并不提供这样的验证器。Spring没有提供任何Validator接口的实现,而是将这个任务留给了程序员。 不过Spring Modules项目(http://springmodules.dev.java.net)是Spring的一个姊妹项目,提供了几个对Spring的扩展。其中一个是使用Jakarta Commons Validator(http://jakarta.apache.org/commons/validator)提供的声明性验证的验证模块。

           要使用验证模块,首先需要添加springmodules-validator,jar  如果使用Ant建立应用程序,需要下载Spring Modules发行包,找到spring-modules-0.6.jar文件,将这个JAR添加到<war>任务的<lib>中。如果使用Maven2建立应用程序,需要在pom.xml中添加下面的<dependency>:

      <dependency>

         <groupId>org.springmodules</groupId>

         <artifactId>springmodules-validation</artifactId>

         <version>0.6</version>

         <scope>compile</scope>

      </dependency>

       另外,还需要将Jakarta Commons Validator JAR添加到应用程序的classpath中,在Maven中,按如下所示:

      <dependency>

         <groupId>commons-validator</groupId>

         <artifactId>commons-validator</artifactId>

         <version>1.1.4</version>

         <scope>compile</scope>

      </dependency>

      Spring Module提供的Validator实现的名称为DefaultBeanValidator。DefaultBeanValidator需要按下面的方式配置在roadrantz-servlet.xml中:

      <bean id="beanValidator" class="org.springmodules.commons.validator.DefaultBeanValidator">

          <property name="validatorFactory" ref="validatorFactory"></property>

      </bean>

      DefaultBeanValidator并不做任何实际的验证工作,而是委派Commons Validator来验证字段的值。validatorFactory Bean需要使用下面的XML进行声明:

        <bean id="validatorFactory" class="org.springmodules.commons.validator.DefaultValidatorFactory">

          <property name="validationConfigLocations">

             <list>

                <value>WEB-INF/validator-rules.xml</value>

                <value>WEB-INF/validator.xml</value>

             </list>

          </property>

      </bean> validator-rules.xml文件包含了一组预定义的验证规则,可以应用于一般的验证需求。这个文件被包含在Commons Validator发行包中,因此,你不需要自己编写--值需要简单的将其添加到应用程序的WEB-INF目录中。(PPT9)中列出了validator-rules.xml中的所有验证规则。另一个文件validation.xml定义了应用程序制定的验证规则,可有直接应用于RoadRantz应用程序。下面展示了应用到RoadRantz的validation.xml

      <?xml version="1.0" encoding="UTF-8"?>

      <!DOCTYPE form-validation PUBLIC"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1

         //EN" "http://jakarta.apache.org/commons/dtds/validator_1_1.dtd" >

      <form-validation>

          <formet>

              <form>

                 <field property="rantText" depends="required">

                    <arg0 key="required.rantText"/>

                 </field>

                 <field property="vehicle.plateNumber" depends="required,mask">

                   <arg0 key="invalid.plateNumber"/>

                   <var>

                      <var-name>mask</var-name>

                      <var-value>^[0-9A-Za-z]{2,6}$</var-value>

                   </var>

                 </field>

              </form>

          </formet>

      </form-validation>

      最后一件要做的事情是改变控制器的声明,注入新的声明性Validate实现:

      <bean id="addRantController" class="com.roadrantz.mvc.AddRantFormController">

         <property name="formView" value="addRant" />

         <property name="successView" value="rantAdded" />

         <property name="rantService" value="rantService" />

         <property name="validator" value="beanValidator" />

      </bean>

        使用SimpleFormController的一个基本的原因是表单只有一页。

      用向导处理复杂表单:AbstractWizardFormController是Spring提供的功能最强大的控制器(见PPT10)它是一种特殊类型的表单控制器,它将多个页面中的表单数据聚集到一个用于处理的命令对象中。

      • 创建一个基本的向导控制器:构建一个向导控制器,它必须继承AbstractWizardFormController类,如下:

        public class MotoristRegistrationController extends AbstractWizardFormController {

          public MotoristRegistrationController() {

           setCommandClass(Motorist.class);

           setCommandName("motorist");

          }

          protected Object formBackingObject(HttpServletRequest request) throws Exception{

        Motorist formMotorist = new Motorist();

        List<Vehicle> vehicles = new ArrayList<Vehicle>();

        vehicles.add(new Vehicle());

        formMotorist.setVehicles(vehicles);

        return formMotorist;

          }

          protected Map referenceData(HttpServletRequest request,Object command,Errors errors,int page)

           throws Exception{

        Motorist motorist = (motorist) command;

        Map refData = new HashMap();

            if(page==1 && request.getParameter("_target1")!= null){

             refData.put("nextVehicle", motorist.getVehicles().size()-1);

            }

            return refData;

           }

          protected void postProcessPage(HttpServletRequest request,Object command,Errors errors,int page)throws Exception {

        Motorist motorist = (Motorist)command;

        if(page==1 && request.getParameter("_target1")!= null){

            motorist.getVehicles().add(new Vehicle());

           }

          }

          protected ModelAndView  processFinish(HttpServletRequest request,HttpServletResponse response,

          Object command,BindException errors)throws Exception {

        Motorist motorist = (motorist) command;

        // the last Vehicle is always blank...remove it

        motorist.getVehicles().remove(motorist.getVehicles().size()-1);

        rantService.addMotorist(motorist);

        return new ModelAndView(getSuccessView(),"motorist",motorist);

          }

          //inject

          private RantService rantService;

          public void setRantService(RantService rantService) {

        this.rantService = rantService;

          }

          //returns the last page as the success view

          private String getSuccessView() {

          return getPages()[getPages().length-1];

          }

        }

        AbstractWizardFormController的唯一一个必须实现的方法是processFinish()。在用户完成表单后(一般是点击完成按钮),这个方法被调用,完成整个表单。 但是AbstractWizardFormController是如何知道哪些页面构成了整个表单呢?

        <bean id="registerMotoristController" class="com.roadrantz.mvc.MotoristRegistrationController">

           <property name="rantService" ref="rantService" />

           <property name="pages">

              <list>

                <value>motoristDetailForm</value>

                <value>motoristVehicleForm</value>

                <value>motoristConfirmation</value>

                <value>redirect:home.htm</value>

              </list>

           </property>

        </bean>这样向导就知道了哪些页面构成了表单,一个视图逻辑名列表设置给了pages属性。这些名字最终被视图解析器解析成了一个个视图对象。但现在,只要假设这些名字会被解析成JSP文件名就可以了。

        分步显示表单页面:任何向导控制器显示的第一个页面都是pages属性中列表的第一个页面。AbstractWizardFormController询问它的getTargetPage()方法。这个方法返回一个整数,它是pages属性中设置的页面列表的索引值(以0为基数)。getTargetPage()方法的默认实现是根据请求中的一个参数来决定下一步是哪个页面,这个参数以"_target"开头,以数字结尾。知道了getTargetPage()的工作原理有助于你知道如何在向导HTML页面中构造下一步和上一步按钮。例如,假设用户在"motoristVehicleForm"页面上(索引=1)。要在这个页面上创建下一步和上一步按钮,你要做就是创建提交按钮,它的名字是以"_target"开头:

        <form>

        ...

          <input type="submit" value="Back" name="_target0">

          <input type="submit" value="Next" name="_target2">

        </form>getTargetPage()方法的默认实现对于大多数项目已经够用了。然后,如果你喜欢为你的向导定义自己的工作流程的话,你可以覆写这个方法。

        完成向导:还有一个特殊请求的参数"_finish",和"_targetX"参数一样,它可以被用于在页面上创建一个结束按钮:

        <form method="POST" action="feedback.htm">

        ...

          <input type="submit" value="Finish" name="_finish">

        </form>

        当AbstractWizardFormController看到请求中的"_finish"参数时,它会将控制权交给processFinish()方法,让它对表单做最后的处理。与其他表单控制器不同,AbstractWizardFormController不提供用于设置成功视图页面的内容。因此,我们在MotoristRegisttrationController中添加了一个getSuccessView()方法返回页面列表中的最后一个页面。所以,当表单已完成的方式提交时,processFinish()方法会返回一个带有视图的ModelAndView,这个视图就是页面列表中的最后一个视图。

        取消向导:如何用户完成了部分注册,决定不再继续完成,除了选择直接关闭浏览器外,还有另一种选择,你可以在表单上添加一个取消按钮。

        <form method="POST" action="feedback.htm">

        ...

           <input type="submit" value="Cancel" name="_cancel">

        </form>

        取消按钮以"_cancel"作为它的名字,当用户按下取消按钮,浏览器将一个叫做"_cancel"的参数放到请求中。AbstractWizardFormController接收到这个参数,将控制权交给processCancel()方法。 默认情况下此方法会抛出异常,表示取消操作是不被支持的,我们要覆写这个方法,将用户带到你想让他们点击取消时看到的页面。下面processCancel()方法的实现将用户带领到成功视图。

        protected ModelAndView processCancel(HttpServletRequest   request,   HttpServletResponse   response, Object command,  BindException  bindException)  throws Exception {

             return new ModelAndView(getSucessView()) ;

        }

        每次验证一个向导表单:使用其他类型的命令控制器,命令对象只装载一个次。但使用向导控制器,用户每完成向导页面中的一步,都会有一个命令对象设置进来。使用向导,只做一次验证是不可行的,太早的话,找到的验证问题可能是由于用户没有完成向导而导致的。太晚的话,在完成按钮被按下后再做检查就太迟了,因为发现的问题可能越过了多个页面(用户该回到哪个页面呢?)   向导控制器在每个页面验证一次命令对象,不是只验证一次。这是通过每次页面跳转时调用validatePage()方法实现的。validatePage()方法的默认实现是空的(也就是没有验证),但是你可以覆写这个方法,做出自己的判断。 假设在motoristDetailForm页面,询问用户的邮件地址,这个字段是可选的,但是如果输入了值,必须输入一个合法的E-mail地址。下面的validatePage()方法展示了用户从motoristDetailForm页面跳转出来的时候如何验证E-mail地址。

        protected void validatePage(Object  command,  Errors  errors, int page ) {

            Motorist  motorist =  (Motorist)  command;

            Motorist Validator  validator  = (MotoristValidator) getValidator();

            if(page == 0){

             validator.validateEmail(motorist.getEmail() , errors );

          }

        }  这里可以直接在validatePage()方法中检查E-mail。然后,向导一般有好几项字段需要验证。如果这样的话,validatePage()方法会变得很笨拙。我们建议你将验证任务委托给控制器的验证器对象的字段级验证方法,就像我们在这里调用MotoristValidator的validateEmail()方法一样。  这意味着当你配置控制器的时候,你要设置validator属性:

        <bean id="registerMotoristController" class="com.roadrantz.mvc.MotoristRegistrationController">

           <property name="rantService" ref="rantService" />

           <property name="pages">

              <list>

                <value>motoristDetailForm</value>

                <value>motoristVehicleForm</value>

                <value>motoristConfirmation</value>

                <value>redirect:home.htm</value>

              </list>

           </property>

           <property name="validator">

              <bean class="com.roadrantz.mvc.MotoristValidator" />

           </property>

        </bean>

        一个需要注意的重要事项是,不像其他命令控制器,向导控制器从不调用它们的验证器对象的标准validate()方法。这是因为validate()方法验证整个命令对象,然而在向导中命令对象将在每个页面验证一次。

      使用一次性控制器:一次性控制器比其他控制器简单很多,ThrowawayController接口可以证明:

      public interface  ThrowawayController {

            ModelAndView execute() throws Exception ;

      }

      ThrowawayController接口和Controller接口不在同一个体系里。一次性控制器自己作为自己作为自己的命令对象,而不是通过一个HttpServletRequest或一个命令对象获得参数。与WebWork Action相似,都是以同样的方式工作。我们将RantsForController实现为ThrowawayController:

      public class RantsForDayController implements ThrowawayController{

         private Day day;

         public ModelAndView execute() throws Exception {

            List<Rant>  dayRants = rantService.getRantsForDay(day);

            return new ModelAndView("dayRants","rants",dayRants);

         }

         public void setDay(Date day){

         this.day = day;

         }

         private RantService rantSrvice;

      public void setRantSrvice(RantService rantSrvice) {

      this.rantSrvice = rantSrvice;

        }

      } 你必须在DispatcherServlet的上下文配置文件中声明一次性控制器。只有一点很小的不同:

      <bean id="rantsForDayController" class="com.roadrantz.mvc.RantsForDayController" scope="prototype">

            <property name="rantService" ref="rantService">

      </bean> scope属性已经被设置为prototype。这就是一次性控制器获取它们名字的地方。默认情况下,所有的Bean都是Singleton。 但是因为ThrowawayContoller和Controller不处于同一继承层次,DispatcherServlet不知道如何通知ThrowawayController。所以你必须告诉DispathcerServlet使用一种不同的 处理适配器。确切的说,你必须像下面这样配置ThrowawayControllerHandlerAdapter:

        <bean id="throwawayHandler" class="org.springframework.web.servlet.mvc.throwaway.ThrowawayControllerHandlerAdapter" />

      如果应用程序既使用了常规控制器又使用了一次性控制器,你还应该按如下方式声明SimpleControllerHandleAdapter:

       <bean id="simpleHandler"  class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />

      声明两处理器适配器使你可以在用一个应用程序中混合使用两种类型的控制器。

    处理异常:当异常从控制器中跑出来时,SimpleMappingExceptionResolver负责营救。

    <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">

       <property name="exceptionMappings">

           <props>

               <prop key="java.lang.Exception">friendlyError</prop>

           </props>

       </property> 

    </bean>

       exceptionMappings属性取得一个java.util.Properties,它映射了异常类名和逻辑视图名。在本例中,基础异常类被映射到逻辑名为friendlyError的视图上,这样如果有任何异常抛出的话,用户不会在浏览器中看到一串晦涩的堆栈跟踪信息。



                                                                                                       --    学海无涯