第26章 SpringMVC中基于注解的Controller(二)

第26章 SpringMVC中基于注解的Controller

26.3 近看基于注解的Controller

在初步了解了Spring2.5提供的基于注解的Controller相关内容之后,让我们开始近距离地接触一下基于注解的Controller的方方面面,以便可以更好地了解它,使用它。

只介绍各种相关概念,难免会枯燥乏味,我们不妨引入一个场景。在这个场景的基础上逐步了解基于注解的Controller相关的各个方面。在我们的FX系统中,无论是面向顾客的报表还是法定报表,按照相关法律规定,都需要存储指定的年限以上,比如至少存储5年。这个期限是可选的,针对每个报表可能情况各异,所以,我们需要一个针对每个报表存储期限的设定功能。下面就将以这一功能的开发为背景,来详细介绍基于注解的Controller的各项特性。开始之前,不妨先看一下最终效果图吧(见图26-1)!

image-20220712160342321

我们需要提供一个基于注解的Controller实现,来处理这一视图相关的各个方面。现在马上开始着手…

26.3.1 声明基于注解的Controller

一个基于注解的Controller实现类,在真正成为一个Controller之前,只是一个普通的POJO。

我们可以先给出一个概念上的对象类定义,如下所示:

public class ReportSettingAnnotationController{
    
    
  ...
}

目前为止,我们只是先假定,它将用于报表设定功能相关的Web请求的处理。为了让它成为一个真正基于注解的Controller,我们还有许多事情要做!

1. 再谈@Controller

要想使一个普通的对象定义成为SpringMVC框架使用的基于注解的Controller,需要用@Controller这个注解进行标注,例如:

@Controller
public class ReportSettingAnnotationController {
    
    
  ...
}

@Controller是基于注解的Controller的身份象征,它主要有如下两个作用。

  • 使用<context:component-sacn/>之后,标注了的对象可以被纳入Spring的IoC容器进行管理,这样,就可以为其提供依赖注入等服务。

  • 在DefaultAnnotationHandlerMapping查找对应的Controller以处理当前Web请求的时候,可以将标注了@Controller的对象定义列入被考虑的范畴。

所以,对于每一个“基于注解的Controller"的实现类来说,@Controller是必须的!

2. @RequestMapping详解

只拥有@Controller这一个标志性注解,并不能让Spring MVC框架知道,针对当前Web请求应该调用哪个基于注解的Controller实现类。我们需要通过RequestMapping为DefaultAnnotationHandlerMapping提供必要的映射信息。

@RequestMapping可以被标注于类型声明或者方法声明上。如果声明在类型定义上,那么@RequestMapping通常作为请求处理映射信息的提供者。如果声明在方法定义上,那么@RequestMapping更多的是为了表明当前方法定义是一个Web请求处理方法。当然,这并不妨碍@RequestMapping同时提供请求处理相关的映射信息。鉴于@RequestMapping的双重身份,我们通常可以通过如下的组合方式来使用它。

标注于类型定义的@RequestMapping提供映射信息,标注在方法定义的@RequestMapping标志具体的请求处理方法。为了能够像SimpleFormController那样,同时提供表单的显示和处理功能,我们的ReportSettingAnnotationController就是采用这种方式来声明的,如下方代码清单所示。

@Controller
@RequestMapping("/reportSetting.anno")
public class ReportSettingAnnotationController {
    
    
	@RequestMapping(method=RequestMethod.GET)
	public String displayReportSettings(..) {
    
    
    	...
  	}
	@RequestMapping(method=RequestMethod.POST)
	public String updateReportSetting(..) {
    
    
    	...
  	}
}

类型定义上标注的@RequestMapping("/reportSetting.anno"),使得所有提交到/reportSetting.anno的Web请求都将由ReportSettingAnnotationController来处理。但是,以GET方法提交的HTTP请求将由@RequestMapping(method=RequestMethod.GET)所标注的displayReportSettings(..)方法来处理,而以POST方法提交的HTTP请求将由@RequestMapping(method=RequestMethod.POST)所标注的updateReportSetting(..)方法来处理。

在方法级别使用@RequestMapping来限定请求的处理范围的时候,可以指定两个属性,除了我们刚使用的method属性,还有一个params属性。使用params属性,可以达到与使用ParameterMethodNameResolver作为MethodNameResolver的MultiActionController类似的功能。下面是我们可以为params属性指定的两种表达式形式。

“parameterName=parametervalue”形式的表达式。表达式的前半部分,即参数名通常相同,由后面的参数值部分来区分并限定请求的处理范围,如下方代码清单所示。

@Controller
@RequestMapping("/requestUrl.anno")
public class MockMultiActionAnnotalionController {
    
    
	@RequestMapping(params="locale=en", method={
    
    RequestMethod.POST})
	public String processMethodOne(..) {
    
    
    	...
  	}
	@RequestMapping(params="1ocale=zh", method={
    
    RequestMethod.POST})
	public String processMethodTwo(..) {
    
    
    	...
  	}
}

如果通过http://.application/requestUrl.anno?parameterName=value的形式发送请求,那么不同的value值将决定当前Web请求由哪个方法定义进行处理。

“parameter”形式的表达式。只为每个@RequestMapping的params提供某个参数名,由请求中是否存在某一参数来决定将当前请求交给哪个方法处理,如下方代码清单所示。

@Controller
@RequestMapping("/requestUrl.anno")
public class MockMultiActionAnnotationController {
    
    
	@RequestMapping(params="delete", method={
    
    RequestMethod.POST})
	public void processMethodOne() {
    
    
    	...
  	}
	@RequestMapping(params="update", method={
    
    RequestMethod.POST})
	public void processMethodTwo() {
    
    
    	...
  	}
}

不管你将使用哪种方式,有一点是要注意的,启用了params的@RequestMapping只允许用于方法定义上。对启用了method属性的@RequestMapping也是同样道理。

@RequestMapping全部标注于方法定义上,类型定义不标注任何@RequestMapping。如果我们想让同一个基于注解的Controller实现类中不同的方法定义对应不同的Web请求处理,那么可以为每个方法定义都提供一个@RequestMapping,而每个@RequestMapping将提供Web请求处理所需要的所有信息。这种形式的基于注解的Controller定义类似于结合PropertiesMethodNameResolver使用的MultiActionController,如下方代码清单所示。

@Controller
public class MultiActionAnnotationController {
    
    
	@RequestMapping("/request1.anno")
	public String processMethod1() {
    
    
    	...
  	}
	@RequestMapping(value="/request2.anno", method={
    
    RequestMethod.POST})
	public ModelMap processMethoa2() {
    
    
    	...
  	}
	@RequestMapping(value="request3.anno", method={
    
    RequestMethod.GET,RequestMethod.POST})
	public void processMethod3() {
    
    
    	...
  	}
}

现在,MultiActionAnnotationController将可以服务于多个不同的Web请求的处理。基本上,这两种组合方式已经可以囊括所有可能的情况了。更多信息可以参考该注解定义的Javadoc文档。

3. 请求处理方法签名规则说明

我们使用Controller标注ReportSettingAnnotationController,使它成为了一个基于注解的Controller,又使用@RequestMapping限定ReportSettingAnnotationController要处理的Web请求,以及该类的哪些方法定义将作为Web请求的处理方法。下一步要搞清楚的,就是如何来定义那些由@RequestMapping标注了的Web请求处理方法。只是使用@RequestMapping标注这些方法,并不能,让它们成为你肚子里的蛔虫,我们还是得自己去实现相应的处理逻辑。不过,在这之前,我们得先对用于Web请求处理的方法定义做“约法三章”。

使用@RequestMapping标注的Web请求处理方法的签名比较灵活,我们几乎可以声明并使用任何类型的方法参数。不过,以下几种类型的方法参数将拥有更多语义,它们均来自框架内部(或者说,AnnotationMethodHandlerAdapter)所管理的对象引用,通过声明这些类型的方法参数,我们就可以获得请求处理过程中所需要的各种依赖。

request/reeponse/session。只要我们在方法定义中声明一个HttpServletRequest形式的方法参数。如下所示:

@RequestMapping(..)
public void processMethod(HttpServletRequest request, ..) {
    
    
  ...
}

就可以在该处理方法中直接使用对应当前Web请求的request对象。框架类将保证,该方法被调用的时候会被传入当前的request对象实例。对于response和session也是同样的道理。

org.springframework.web.context.request.WebRequest。与为处理方法声明request/response/session类型的参数效果类似,我们将在当前处理方法中获得可用的webRequest实例。

java.util.Locale。通过相应LocaleResolver所返回的对应当前Web请求的Locale。

java.io.Inputstream/java.io.Reader。用于访问对应当前Web请求的request的内容,相当于声明了HttpServletRequest类型参数后,调用request.getInputstream()或者request.getReader()所获得的对象引用。

java.io.outgutstream/java.io.Writer。它们来自response.getOutputstream()response.getWriter(),所以,我们可以通过它们向客户端写入相应的数据。

java.util.Map/org.springframework.ui.ModelMap。如果声明了Map或者ModelMap类型的方法参数,那恭喜你,现在可以对模型数据为所欲为了。要获得相应数据也好,添加相应数据也好,这些数据都将反映到视图所对应的模型数据上,例如:

@RequestMapping(..)
public void processMethod(ModelMap model) {
    
    
	//添加模型数据
	model.addAttribute(“commandObject", ...);
	//根据Key获取相应模型数据
	Object cmd = model.get("command");
}

你或许会很诧异这怎么可能?实际上,这并不难理解啊。如下所示的原型代码或许可以更容易看清楚到底发生了什么:

ModelAndView mav = ...;
ModelMap modelMap = new ModelMap();
// 调用
processMethod(modelMap);
// 将模型数据添加到ModelAndView
mav.addAllObjects(modelMap);
return mav;

框架类只需要在调用具体处理方法的时候,将事先初始化的Map或者ModelMap实例传给要调用的方法即可。调用完成后,再把其中的数据添加到要返回给视图的ModelAndView就成了。如果你能理解这一点,也就可以触类旁通了。这里所说的这几种特殊类型的方法参数声明得以被特殊对待,基本上都是类似的道理。

使用@RequestParam或者@ModelAttribute所标注的方法参数。相应方法参数将根据这两个注解的语义获得相应的对象值。我们将在介绍@RequestParam和@ModelAttribute的时候进一步说明这个问题。

org.springframework.validation.Errors/org.springframework.validation.BindingReeult。用于对Command对象进行数据验证的Errors或者BindingResult对象。我们可以向这两种类型的方法参数查询数据验证的结果,也可以在执行相应Validator进行数据验证之后,向这两种类型的方法参数中添加验证数据,如下方代码清单所示。

public String processMethod(@ModelAttribute("command") ReportSettings reportSettings, BindingResult result) {
    
    
	Validator validator = ...;
	ValidationUcils.invokeValidator(validator, reportSettings, result);
	if(result.hasErrors()) {
    
    
		return FORM_VIEW_NAME;
  	}
	return "other ViewName";
}

声明Errors或者BindingResult类型的方法参数有一个限制,它们的声明必须紧跟Command对象定义。在我们的代码示例中,BindingResult声明紧跟在Command对象ReportSettings之后,而其他类型的方法参数声明是没有任何顺序限制的。

org.springframework.web.bind.support.sessionStatus。SessionStatus主要用于管理请求处理之后Session的状态,比如清除Session中的指定数据。在我们的ReportSettingAnnotationController完成之际,SessionStatus将再次浮出水面。

除了以上列举的这几种特殊类型的方法参数声明,如果请求处理方法存在其他类型的方法参数声明的话,框架类会根据方法参数的名称、类型,或者注解标志,将Web请求的相应参数绑定到这些参数上。在详细说明基于注解的Controller中的数据绑定功能之前,我们还是先来看一下Web请求的处理方法都可以返回哪些类型的返回值吧!基于注解的Controller的请求处理方法返回值类型可以有如下4种形式。

org.springframework.web.servlet.ModelAndView。这个不用多说了吧,视图信息和模型信息都能通过它返回。

java.lang.String。该类型返回值代表逻辑视图名,模型数据需要以其他形式提供,比如为处理方法声明一个ModelMap类型的参数。

org.springframework.ui.ModelMap。ModelMap类型返回值只包含了模型数据信息而没有视图信息,框架类将根据请求的路径,按照默认规则提取相应的逻辑视图名来使用,比如:

@RequestMapping("/simple.anno")
public ModelMap processMethod() {
    
    
  ...
}

以上方法定义中,simple将被作为逻辑视图名而使用。

void。没有任何返回值,视图信息需要从请求路径中提取默认值,模型数据需要通过其他形式提供。结合只返回String类型和只返回ModelMap类型两种情况下的处理逻辑,也就不难想象框架类将如何处理void类型的返回值了。

总之,为了简化框架类的实现逻辑,处理方法的返回值只允许以上4种形式。开发中将选择哪种类型,完全因人而异。

26.3.2 请求参数到方法参数的绑定

如果基于注解的Controller的处理方法声明的参数类型属于我们规定的那几种特殊类型,那么框架类将为这些特殊类型的方法参数提供框架内管理的相应对象的引用。而如果处理方法所声明的方法参数类型在规定的几种特殊类型之外,那么Spring MVC将根据某些规则把请求参数绑定到这些方法参数上。下面我们要说的就是,如何在基于注解的Controller中实现请求参数到相应的方法参数的数据绑定。

1. 默认绑定行为

默认绑定行为是指根据名称匹配原则进行的数据绑定。当请求中的参数名与方法参数名称一致的时候,相应的参数值将被绑定到对应的方法参数上。比如,我们通过如下链接发起请求:

<a href="<c:url value='pOne.anno?age=30&author=Darren'/>">..</a>

为了能处理这一请求,我们就可以定义如下的处理方法:

@RequestMapping("/pOne.anno")
public String processMethodOne(int age, String author) {
    
    
  // assertEquals(30,age);
  // assertEquals("Darren",author);
  return "anno/pOne";
}

当在处理方法中实现相应处理逻辑的时候,框架类可以保证请求参数已经完成类型转换,并绑定到该方法的对应参数上。这实际上是一个契约关系,只要保证请求参数与方法参数名称的一致,数据绑定就能够顺利完成。但是,如果把int age方法参数改为int ageOfAuthor,绑定就会出现问题。因为框架类无法在请求中找到一个名字为ageOfAuthor的请求参数。这时,intageOfAuthor会被绑定一个null值,而int以及其他原始类型(primitive)是无法接受null值的,所以在转型期间就会抛出org.springframework.beans.TypeMismatchException。如果参数类型是String或者其他引用类型,那么绑定完成后纯粹就是一个null值了。

除了可以为基于注解的Controller的处理方法定义多个简单的参数类型,我们同样可以指定JavaBean对象引用,如下方代码清单所示。

@RequestMapping("/pOne.anno")
public String processMethodOne(int age, String author, Book book) {
    
    
  	...
	return "anno/pOne";
}

pub1ic class Book {
    
    
	private String bookName;
	private String bookPublisher;
  
	// getter和setter方法
}

只要同样保证请求参数名称能够与指定的JavaBean对应的属性名称匹配,数据绑定就能正确完成。假设我们通过如下链接发起一个Web请求:

<a href="<c:url value='pOne.anno?age=30&author=Darren&bookName=UnveilSpring&bookPublisher=unknown'/>">..</a>

当在处理方法中访问Book对象的时候,其属性值已经正确绑定了对应相同名称的请求参数值。同样的道理,如果像SimpleFormController那样需要一个Command对象,直接在基于注解的Controller的处理方法上,声明一个要使用的Command对象类型的方法参数即可。可以说是既方便又快捷。

2. 使用@RequestParam明确地指定绑定关系

默认的绑定行为需要我们严格遵守命名一致性原则。如果我们对此不满,想自定义绑定关系,可以求助于@RequestParam。

@RequestParam只可以标注于方法参数之上。在为我们的请求处理方法的某个方法参数标注相应的@RequestParam之后,框架类可以根据@RequestParam的信息,重新考虑要将哪个请求参数绑定到当前方法参数上。假设我们的方法定义不想使用age作为参数名,而要使用ageOfAuthor,为了保证名称为age的请求参数可以绑定到新的名称为ageOfAuthor的方法参数上,我们就可以使用@RequestParam对这一参数进行标注:

@RequestMapping("/pOne.anno")
public String processMethodOne(@RequestParam("age") int ageOfAuthor, String author, Book book) {
    
    
  ...
}

现在,如果我们同样以http://.../app/pOne.anno?age-30的形式发送Web请求,那么age请求参数将按照@RequestParam的指示绑定到ageOfAuthor方法参数上。

默认情况下,如果@RequestParam所指定的请求参数不存在,绑定过程中将抛出异常。这是由@RequestParam的required属性决定的,它的默认值为true,要求指定的请求参数必须存在。如果我们允许@RequestParam指定的请求参数可以时不时地外出休假(在当前请求中可以存在,也可以不存在),那么就可以将required的默认值改为false,比如:

@RequestMapping("/pOne.anno")
public String processMethodOne(@RequestParam(value="age", required=false) int ageOfAuthor, ..) {
    
    
  ...
}

如果当前请求中找不到名称为age的请求参数,那么对应的方法参数将获得相应数据类型的默认值。

3. 添加自定义数据绑定规则

Spring框架提供的数据绑定采用JavaBean规范的PropertyEditor机制进行数据转换。大多数情况下,默认注册的PropertyEditor实现类就能够满足常见数据类型的转换需求。但特殊类型的转换情况依然存在,这时我们就需要为框架提供针对特殊数据类型转换的自定义PropertyEditor实现。

在基于注解的Controller中,我们可以通过如下两种方式达到这一目的。

  • 使用@InitBinder标注的初始化方法

在SimpleFormController或者其兄弟类中,如果我们想要对它们所使用的DataBinder做进一步地定制,通常的做法是覆写父类的initBinder(..)方法。在基于注解的Controller中,做法与之相似。我们只需要在Controller实现类中声明任意一个方法,然后使用@InitBinder对该方法进行标注。这样,框架类在数据绑定之前,将保证该标注了@InitBinder的初始化方法被调用。如果我们的ReportSettingAnnotationController需要某个初始化方法来定制DataBinder(实际上我们暂时不需要),那么就可以在该类中声明一个与下方代码清单类似的初始化方法。

@Controller
@RequestMapping("/reportSetting.anno")
public class ReportSettingAnnotationController {
    
    
	@InitBinder
	public void cuetomizeDataBinder(WebDataBinder dataBinder) {
    
    
		PropertyEditor propEditor = ..;
		dataBinder.registerCustomEditor(SomeDataType, class, propEditor);
  	}
	@RequestMapping(method=RequestMethod.GET)
	public String displayReportSettings(..) {
    
    
    	...
  	}
	@RequestMapping(method=RequestMethod.POST)
	pub1ic String updateReportSetting(..) {
    
    
    	...
  	}
}

初始化方法不能有返回值,而且至少应该有一个类型为org.springframework.web.bind.WebDataBinder的方法参数,否则,是要对哪个DataBinder进行定制呢?声明WebDataBinder类型的方法参数,使得框架可以将它所使用的DataBinder引用传给我们,以做进一步的定制操作。

初始化方法的方法参数除了不能使用Command对象以及相关的Errors/BindingResult对象之外,一个典型的基于注解的Controller的处理方法可以使用的方法参数类型都可以使用。不过,大多情况下,在WebDataBinder之后提供一个WebRequest或者Locale对象就足够了。这允许我们根据情况对DataBinder做相应定制。

  • 指定自定义的WebBindingInitializer

使用@InitBinder标注的初始化方法只能对一个Controller对应的DataBinder做定制。如果某些Controller可以共享相同的一段定制逻辑,比如,几个Controller实现类都用到了某一特殊数据类型的绑定,那么它们就可能使用同一PropertyEditor实例。这时,为AnnotationMethodHandlerAdapter指定一个自定义的org.springframework.web.bind.support.WebBindingInitializer实例,可以避免在每个Controller中都重复定义几乎相同逻辑的@InitBinder初始化方法。

AnnotationMethodHandlerAdapter是Spring MVC框架为基于注解的Controller提供的HandlerAdaptor实现类。如果没有在DispatcherServlet的WebApplicationContext中明确指定的话,DispatcherServlet将默认初始化一个该类的实例来使用。现在我们要为AnnotationMethodHandlerAdapter提供自定义的WebBindingInitializer实现,所以,不得不在DispatcherServlet的WebApplicationContext中明确指定一个该类对应的bean定义了。如果我们拥有如下方代码清单所示的WebBindingInitializer实现类。

public class GenericBindingInitializer implements WebBindingInitializer {
    
    
	public void initBinder(WebDataBinder binder, WebRequest request) {
    
    
		PropertyEditor propEditorOne = ..;
		binder.registerCustomEditor(SomeDataType.class, propEditorOne);

    	//如果需要,可以注册更多propertyEditor
    	...
  	}
}

为了让它可以为多个基于注解的Controller的数据绑定提供服务,我们在DispatcherServlet的WebApplicationContext中添加如下配置内容:

<bean class="org.springframework.web.servlet,mvc.Annotation.AnnotationMethodHand1erAdapter">
	<property name="webBindingInitializer">
		<bean class="..GenericBindingInitializer"/>
	</property>
</bean>

DispatcherServlet现在可以根据类型获取这一AnnotationMethodHandlerAdapter来处理基于注解的Controller。

26.3.3 使用@ModelAttribute访问模型数据

在介绍@ModelAttribute之前,我们要先完成ReportSettingAnnotationController的一部分功能,已有的功能介绍已经可以完成80%的工作了。

在介绍@RequestMapping的时候,我们已经就ReportSettingAnnotationController所要处理的请求方法做了个骨架性的定义,现在要进一步完善它们。ReportsettingAnnotationController的displayReportSettings(..)方法负责GET方法请求的处理,也就是负责显示现有的报表设定情况。在该方法中,我们需要调用相应的服务来获取现有的报表设定信息,然后添加到模型数据中返回给视图进行显示。为了能够访问模型数据,我们可以声明一个ModelMap类型的方法参数。作用就不用多说了吧?见下方代码清单。

@Controller
@RequestMapping("1reportSetting.anno")
public class ReportSettingAnnotationController {
    
    
	public static final String FORM_VIEW_NAME = "anno/reportSetting";
	@Autowired
	private IReportSettingManager reportSettingManager;
  
	@RequestMapping(method=RequestMethod.GET)
	public String displayReportSettings(ModelMap model) {
    
    
		ReportSettings reportSettings = getReportSettingManager().getReportSettings();
		model.addAttribute("command", reportSettings);
		return FORM_VIEW_NAME;
  	}
	@RequestMapping(method=RequestMethod.POST)
	public String updateReportSetting(..) {
    
    
		...
  	}
	//对应reportSettingManager的setter和getter方法定义
}

使用Spring2.5的@Autowired为ReportSettingAnnotationController提供IReportSettingManager依赖的注入,这样可以避免通过XML配置来完成依赖注入关系的确定。

要为视图渲染提供更多模型数据,除了像我们现在这样,通过ModelMap方法参数直接添加之外,基于注解的Controller还为我们提供了另一种选择,那就是使用@ModelAttribute。@ModelAttribute可以应用在方法级别和方法参数上。如果将@ModelAttribute标注在某个方法上,该方法所返回的数据将被添加到模型数据中。这类似于SimpleFormController的referenceData()方法完成的工作。比如,我们可以将displayReportSettings()方法中添加模型数据的逻辑独立出来,新的代码如下方代码清单所示。

@Controller
@RequestMapping("/reportSetting.anno")
public class ReportSettingAnnotationController {
    
    
	public static final String FORM_VIEW_NAME = "anno/reportSetting";
	@Autowired
	privateIReportSettingManager reportSettingManager;
  
  	// 重点是下面5行
	@ModelAttribute("command")
	public ReportSettings referenceDatalikeMethod() {
    
    
		ReportSettings reportSettings = getReportSettingManager().getReportSettingB();
    	return reportSettinge;
  	}
  
	@RequestMapping(method=RequestMethod.GET)
	public String displayReportSettings(ModelMap model) {
    
    
		return FORM_VIEW_NAME;
  	}
	@RequestMapping(method=RequestMethod.POST)
	public String updateReportSetting(..) {
    
    
		...
  	}
	// 对应reportSettingManager的setter和getter方法定义
}

referenceDataLikeMethod()方法所返回的数据将以@ModelAttribute("command")所指定的"command"作为键值添加到模型数据中。

标注在方法级别上的@ModelAttribute只提供了对模型数据的“存”操作。如果将@ModelAttribute标注到处理方法的方法参数上的话,也就是应用在方法参数级别,那么"存取操作”就算圆满了。即将要实现的updateReportSetting(..)方法展示了@ModelAttribute的这一功能(见下方代码清单)。

@Controller
@RequestMapping("/reportSetting.anno")
public class ReportSettingAnnotationController {
    
    
	public static final String FORM_VIEW_NAME = "anno/reportSetting";
	@Autowired
	privateIReportSettingManager reportSettingManager;
  
  	@RequestMapping(method=RequestMethod.GET)
	public String displayReportSettings(ModelMap model) {
    
    
		ReportSettings reportSettings = getReportSettingManager().getReportSettings();
		model.addAttribute("command", reportSettings);
		return FORM_VIEW_NAME;
  	}
  
	@RequestMapping(method=RequestMethod.POST)
  	public String updateReportSetting(@ModlelAttribute("command") ReportSettings reportSettings, BindingResult result, SessionStatus status) {
    
    
		if(result.hasErrors()) {
    
    
			return FORM_VIEW_NAME;
    	}
		getReportSettingManager().updateReportSettings(reportSettings);
		status.setComplete();
		return "redirect:reportSetting.anno";
  	}
  
	// 对应reportSettingManager的setter和getter方法定义
}

框架类将保证在调用updateReportSetting(..)方法处理请求提交的时候,从模型数据中获得以@ModelAttribute("command")标注的ReportSettings reportSettings。ReportSettings reportsettings在该方法定义中同时身兼Cormand对象的重任,所以,我们在该参数之后同时添加了BindingResult result作为第二个方法参数。通过BindingResultresult,我们可以获得数据绑定结果,然后根据结果来决定后继流程该如何处理。那么,SessionStatus在这里是干什么的呢?

如果我们现在使用以上的ReportSettingAnnotationController处理表单提交,就会发现updateReportsetting(..)方法不能“正确”的工作。为什么呢?我们在GET方法请求中放到模型数据中的Command对象,与POST方法提交的请求中从模型数据中获取的Command对象是不同的,因为这是两个独立的请求。为了保证GET请求和POST请求使用模型数据中同一个Command对象,我们需要将GET请求中的Command对象存入Session中。这样,POST形式的请求就可以从Session中获取同一个Commana对象了。但是,如何在基于注解的Controller中做到这一点呢?答案就是使用@SessionAttribute。

26.3.4 通过@SessionAttribute管理Session数据

为了能够访问Session,我们当然可以为每个处理方法声明一个HttpSession类型的方法参数。不过,这看起来比较“土”,更多时候,我们可以借助@SessionAttribute来标注哪些数据需要通过session进行管理。

@SessionAttribute只应用在类型声明上,当我们将ReportSettingAnnotationController标注如下方代码清单所示样子的时候。

@Controller
@RequestMapping("/reportSetting.anno")
@SessionAttributes("command")
public class ReportSettingAnnotationController {
    
    
	public static final String FORM_VIEW_NAME = "anno/reportSetting";
	@Autowired
	private IReportSettingManager reportSettingManager;
  
	@RequestMapping(method=RequestMethod.GET)
	public String displayReportSettings(ModelMap model) {
    
    
		ReportSettings reportSettings = getReportSettingManager().getReportSettings();
		model.addAttribute("coumand", reportSettinga);
		return FORM_VIEW_NAME;
  	}
  
	@RequestMapping(method=RequestMethod.POST)
	public String updateReportSetting(@ModelAttribute("caumand") Reportsettings reportSettings, BindingResult result, SessionStatus status) {
    
    
		if(result.hasErrors()) {
    
    
			return FORM_VIEW_NAME;
    	}
		getReportSettingManager().updateReportSettings(reportSettings);
		status.setComplete();
		return "redirect:reportSetting.anno";
  	}
	// 对应reportSettingManager的setter和getter方法定义
}

在GET请求中添加到模型数据的Command对象将被存入Session中,而在POST请求处理期间从模型数据获得的,就是当初存入Session的同一个Command对象了。@SessionStatus的作用是将使用完的数据清除出Session。

@SessionAttributes,允许以属性名称或者类型两种方法,来表明将哪些数据通过Session进行管理。以上我们使用的是指定属性名称的方式,但通过类型来指定也是可行的,如下所示:

@SessionAttributes(types=ReportSettings.class)

两种指定方式可以在开发过程中根据情况选用。到此为止,我们接触了基于注解的Controller所有的特性。如果我们使用下方代码清单26-15给出的视图模板结合最终完成的ReportSettingAnnotationController,那么,最初设定的功能需求即告完成。

<form:form action="reportSetting.anno">
	<form:errors path="*"></form:errors><br/>
	<display:table name="command.reportSettings" id="row">
		<display:caption>报表设定</display:caption>
		<display:column property="reportName" title="报表名称"></display:column>
		<display:column title="保存期限">
			<form:radiobutton path="reportSettings[${row_rowNum-1}].holdingYears" value="5" label="五年"/>
			<form:radiobutton path="reportSettings[${row_rowNum-1}].holdingYears" value="10" 1abel="十年"/>
		</display:column>
	</display:table>
	<input type="submit" value="更新"/>
</form:form>

最后,希望你能在使用基于注解的Controller过程中融入更多的轻松与快乐!

26.4 小结

Spring2.5中新添加的基于注解的Controller形式,可以让我们以更加灵活高效的方式开发基于Spring MVC的Web应用。实际上,这种基于注解的Controller形式并没太多新意,只是在现有Spring MVC框架的基础上做出的自定义功能扩展。本章在分析基于注解的Controller形式的实现原理的基础上,对基于注解的Controller形式给出了详细的介绍,并进行了深入地剖析。相信你在完成本章内容之后,已经对基于注解的Controller形式爱不释手了吧!

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125873302