转载文章,原文地址https://elim.iteye.com/blog/1753271javascript
SpringMVC Controller 介绍html
在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据通过业务处理层处理以后封装成一个Model ,而后再把该Model 返回给对应的View 进行展现。在SpringMVC 中提供了一个很是简便的定义Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller ,而后使用@RequestMapping 和@RequestParam 等一些注解用以定义URL 请求和Controller 方法之间的映射,这样的Controller 就能被外界访问到。此外Controller 不会直接依赖于HttpServletRequest 和HttpServletResponse 等HttpServlet 对象,它们能够经过Controller 的方法参数灵活的获取到。为了先对Controller 有一个初步的印象,如下先定义一个简单的Controller :java
在上面的示例中,@Controller 是标记在类MyController 上面的,因此类MyController 就是一个SpringMVC Controller 对象了,而后使用@RequestMapping(“/showView”) 标记在Controller 方法上,表示当请求/showView.do 的时候访问的是MyController 的showView 方法,该方法返回了一个包括Model 和View 的ModelAndView 对象。这些在后续都将会详细介绍。web
@Controller 用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping 注解。@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器,这个接下来就会讲到。spring
单单使用@Controller 标记在一个类上还不能真正意义上的说它就是SpringMVC 的一个控制器类,由于这个时候Spring 还不认识它。那么要如何作Spring 才能认识它呢?这个时候就须要咱们把这个控制器类交给Spring 来管理。拿MyController 来举一个例子express
这个时候有两种方式能够把MyController 交给Spring 管理,好让它可以识别咱们标记的@Controller。cookie
第一种方式是在SpringMVC 的配置文件中定义MyController 的bean 对象。session
<bean class="com.host.app.web.controller.MyController"/>mvc
第二种方式是在SpringMVC 的配置文件中告诉Spring 该到哪里去找标记为@Controller 的Controller控制器。app
注:
上面 context:exclude-filter 标注的是不扫描 @Service 标注的类
可使用@RequestMapping 来映射URL 到控制器类,或者是到Controller 控制器的处理方法上。当@RequestMapping 标记在Controller 类上的时候,里面使用@RequestMapping 标记的方法的请求地址都是相对于类上的@RequestMapping 而言的;当Controller 类上没有标记@RequestMapping 注解时,方法上的@RequestMapping 都是绝对路径。这种绝对路径和相对路径所组合成的最终路径都是相对于根路径“/ ”而言的。
在这个控制器中,由于MyController 没有被@RequestMapping 标记,因此当须要访问到里面使用了@RequestMapping 标记的showView 方法时,就是使用的绝对路径/showView.do 请求就能够了。
这种状况是在控制器上加了@RequestMapping 注解,因此当须要访问到里面使用了@RequestMapping 标记的方法showView() 的时候就须要使用showView 方法上@RequestMapping 相对于控制器MyController 上@RequestMapping 的地址,即/test/showView.do 。
URI 模板就是在URI 中给定一个变量,而后在映射的时候动态的给该变量赋值。如URI 模板http://localhost/app/{variable1}/index.html ,这个模板里面包含一个变量variable1 ,那么当咱们请求http://localhost/app/hello/index.html 的时候,该URL 就跟模板相匹配,只是把模板中的variable1 用hello 来取代。在SpringMVC 中,这种取代模板中定义的变量的值也能够给处理器方法使用,这样咱们就能够很是方便的实现URL 的RestFul 风格。这个变量在SpringMVC 中是使用@PathVariable 来标记的。
在SpringMVC 中,咱们可使用@PathVariable 来标记一个Controller 的处理方法参数,表示该参数的值将使用URI 模板中对应的变量的值来赋值。
在上面的代码中咱们定义了两个URI 变量,一个是控制器类上的variable1 ,一个是showView 方法上的variable2 ,而后在showView 方法的参数里面使用@PathVariable 标记使用了这两个变量。因此当咱们使用/test/hello/showView/2.do 来请求的时候就能够访问到MyController 的showView 方法,这个时候variable1 就被赋予值hello ,variable2 就被赋予值2 ,而后咱们在showView 方法参数里面标注了参数variable1 和variable2 是来自访问路径的path 变量,这样方法参数variable1 和variable2 就被分别赋予hello 和2 。方法参数variable1 是定义为String 类型,variable2 是定义为int 类型,像这种简单类型在进行赋值的时候Spring 是会帮咱们自动转换的,关于复杂类型该如何来转换在后续内容中将会讲到。
在上面的代码中咱们能够看到在标记variable1 为path 变量的时候咱们使用的是@PathVariable ,而在标记variable2 的时候使用的是@PathVariable(“variable2”) 。这二者有什么区别呢?第一种状况就默认去URI 模板中找跟参数名相同的变量,可是这种状况只有在使用debug 模式进行编译的时候才能够,而第二种状况是明确规定使用的就是URI 模板中的variable2 变量。当不是使用debug 模式进行编译,或者是所须要使用的变量名跟参数名不相同的时候,就要使用第二种方式明确指出使用的是URI 模板中的哪一个变量。
除了在请求路径中使用URI 模板,定义变量以外,@RequestMapping 中还支持通配符“* ”。以下面的代码我就可使用/myTest/whatever/wildcard.do 访问到Controller 的testWildcard 方法。
在上面代码中利用@RequestParam 从HttpServletRequest 中绑定了参数name 到控制器方法参数name ,绑定了参数age 到控制器方法参数age 。值得注意的是和@PathVariable 同样,当你没有明确指定从request 中取哪一个参数时,Spring 在代码是debug 编译的状况下会默认取更方法参数同名的参数,若是不是debug 编译的就会报错。此外,当须要从request 中绑定的参数和方法的参数名不相同的时候,也须要在@RequestParam 中明确指出是要绑定哪一个参数。在上面的代码中若是我访问/requestParam.do?name=hello&age=1 则Spring 将会把request 请求参数name 的值hello 赋给对应的处理方法参数name,把参数age 的值1 赋给对应的处理方法参数age 。
在@RequestParam 中除了指定绑定哪一个参数的属性value 以外,还有一个属性required ,它表示所指定的参数是否必须在request 属性中存在,默认是true ,表示必须存在,当不存在时就会报错。在上面代码中咱们指定了参数name 的required 的属性为false ,而没有指定age 的required 属性,这时候若是咱们访问/requestParam.do 而没有传递参数的时候,系统就会抛出异常,由于age 参数是必须存在的,而咱们没有指定。而若是咱们访问/requestParam.do?age=1 的时候就能够正常访问,由于咱们传递了必须的参数age ,而参数name 是非必须的,不传递也能够。
在上面的代码中咱们使用@CookieValue 绑定了cookie 的值到方法参数上。上面一共绑定了两个参数,一个是明确指定要绑定的是名称为hello 的cookie 的值,一个是没有指定。使用没有指定的形式的规则和@PathVariable 、@RequestParam 的规则是同样的,即在debug 编译模式下将自动获取跟方法参数名同名的cookie 值。
在上面的代码中咱们使用了 @RequestHeader 绑定了 HttpServletRequest 请求头host 到 Controller 的方法参数。上面方法的三个参数都将会赋予同一个值,由此咱们能够知道在绑定请求头参数到方法参数的时候规则和 @PathVariable 、 @RequestParam 以及@CookieValue 是同样的,即没有指定绑定哪一个参数到方法参数的时候,在 debug 编译模式下将使用方法参数名做为须要绑定的参数。可是有一点 @RequestHeader 跟另外三种绑定方式是不同的,那就是在使用 @RequestHeader 的时候是大小写不敏感的,即@RequestHeader(“Host”) 和 @RequestHeader(“host”) 绑定的都是 Host 头信息。记住在 @PathVariable 、 @RequestParam 和 @CookieValue 中都是大小写敏感的。
在RequestMapping 中除了指定请求路径value 属性外,还有其余的属性能够指定,如params 、method 和headers 。这样属性均可以用于缩小请求的映射范围。
params 属性用于指定请求参数的,先看如下代码。
在上面的代码中咱们用@RequestMapping 的params 属性指定了三个参数,这些参数都是针对请求参数而言的,它们分别表示参数param1 的值必须等于value1 ,参数param2 必须存在,值无所谓,参数param3 必须不存在,只有当请求/testParams.do 而且知足指定的三个参数条件的时候才能访问到该方法。因此当请求/testParams.do?param1=value1¶m2=value2 的时候可以正确访问到该testParams 方法,当请求/testParams.do?param1=value1¶m2=value2¶m3=value3 的时候就不可以正常的访问到该方法,由于在@RequestMapping 的params 参数里面指定了参数param3 是不能存在的。
method 属性主要是用于限制可以访问的方法类型的。
在上面的代码中就使用method 参数限制了以GET 或DELETE 方法请求/testMethod.do 的时候才能访问到该Controller 的testMethod 方法。
使用headers 属性能够经过请求头信息来缩小@RequestMapping 的映射范围。
headers 属性的用法和功能与params 属性类似。在上面的代码中当请求/testHeaders.do 的时候只有当请求头包含Accept 信息,且请求的host 为localhost 的时候才能正确的访问到testHeaders 方法。
(1 )HttpServlet 对象,主要包括HttpServletRequest 、HttpServletResponse 和HttpSession 对象。 这些参数Spring 在调用处理器方法的时候会自动给它们赋值,因此当在处理器方法中须要使用到这些对象的时候,能够直接在方法上给定一个方法参数的申明,而后在方法体里面直接用就能够了。可是有一点须要注意的是在使用HttpSession 对象的时候,若是此时HttpSession 对象尚未创建起来的话就会有问题。
(2 )Spring 本身的WebRequest 对象。 使用该对象能够访问到存放在HttpServletRequest 和HttpSession 中的属性值。
(3 )InputStream 、OutputStream 、Reader 和Writer 。 InputStream 和Reader 是针对HttpServletRequest 而言的,能够从里面取数据;OutputStream 和Writer 是针对HttpServletResponse 而言的,能够往里面写数据。
(4 )使用@PathVariable 、@RequestParam 、@CookieValue 和@RequestHeader 标记的参数。
(5 )使用@ModelAttribute 标记的参数。
(6 )java.util.Map 、Spring 封装的Model 和ModelMap 。 这些均可以用来封装模型数据,用来给视图作展现。
(7 )实体类。 能够用来接收上传的参数。
(8 )Spring 封装的MultipartFile 。 用来接收上传文件的。
(9 )Spring 封装的Errors 和BindingResult 对象。 这两个对象参数必须紧接在须要验证的实体对象参数以后,它里面包含了实体对象的验证结果。
(1 )一个包含模型和视图的ModelAndView 对象。
(2 )一个模型对象,这主要包括Spring 封装好的Model 和ModelMap ,以及java.util.Map ,当没有视图返回的时候视图名称将由RequestToViewNameTranslator 来决定。
(3 )一个View 对象。这个时候若是在渲染视图的过程当中模型的话就能够给处理器方法定义一个模型参数,而后在方法体里面往模型中添加值。
(4 )一个String 字符串。这每每表明的是一个视图名称。这个时候若是须要在渲染视图的过程当中须要模型的话就能够给处理器方法一个模型参数,而后在方法体里面往模型中添加值就能够了。
(5 )返回值是void 。这种状况通常是咱们直接把返回结果写到HttpServletResponse 中了,若是没有写的话,那么Spring 将会利用RequestToViewNameTranslator 来返回一个对应的视图名称。若是视图中须要模型的话,处理方法与返回字符串的状况相同。
(6 )若是处理器方法被注解@ResponseBody 标记的话,那么处理器方法的任何返回类型都会经过HttpMessageConverters 转换以后写到HttpServletResponse 中,而不会像上面的那些状况同样当作视图或者模型来处理。
(7 )除以上几种状况以外的其余任何返回类型都会被当作模型中的一个属性来处理,而返回的视图仍是由RequestToViewNameTranslator 来决定,添加到模型中的属性名称能够在该方法上用@ModelAttribute(“attributeName”) 来定义,不然将使用返回类型的类名称的首字母小写形式来表示。使用@ModelAttribute 标记的方法会在@RequestMapping 标记的方法执行以前执行。
SpringMVC 支持使用 @ModelAttribute 和 @SessionAttributes 在不一样的模型和控制器之间共享数据。 @ModelAttribute 主要有两种使用方式,一种是标注在方法上,一种是标注在 Controller 方法参数上。
当 @ModelAttribute 标记在方法上的时候,该方法将在处理器方法执行以前执行,而后把返回的对象存放在 session 或模型属性中,属性名称可使用@ModelAttribute(“attributeName”) 在标记方法的时候指定,若未指定,则使用返回类型的类名称(首字母小写)做为属性名称。关于 @ModelAttribute 标记在方法上时对应的属性是存放在 session 中仍是存放在模型中,咱们来作一个实验,看下面一段代码。
当咱们请求 /myTest/sayHello.do 的时候使用 @ModelAttribute 标记的方法会先执行,而后把它们返回的对象存放到模型中。最终访问到 sayHello 方法的时候,使用@ModelAttribute 标记的方法参数都能被正确的注入值。执行结果以下图所示:
由执行结果咱们能够看出来,此时 session 中没有包含任何属性,也就是说上面的那些对象都是存放在模型属性中,而不是存放在 session 属性中。那要如何才能存放在 session 属性中呢?这个时候咱们先引入一个新的概念 @SessionAttributes ,它的用法会在讲完 @ModelAttribute 以后介绍,这里咱们就先拿来用一下。咱们在 MyController 类上加上 @SessionAttributes 属性标记哪些是须要存放到 session 中的。看下面的代码:
在上面代码中咱们指定了属性为 intValue 或 stringValue 或者类型为 User 的都会放到Session 中,利用上面的代码当咱们访问 /myTest/sayHello.do 的时候,结果以下:
仍然没有打印出任何 session 属性,这是怎么回事呢?怎么定义了把模型中属性名为 intValue的对象和类型为 User 的对象存到 session 中,而实际上没有加进去呢?难道咱们错啦?咱们固然没有错,只是在第一次访问 /myTest/sayHello.do 的时候 @SessionAttributes 定义了须要存放到 session 中的属性,并且这个模型中也有对应的属性,可是这个时候尚未加到 session 中,因此session 中不会有任何属性,等处理器方法执行完成后 Spring 才会把模型中对应的属性添加到session 中。因此当请求第二次的时候就会出现以下结果:
当 @ModelAttribute 标记在处理器方法参数上的时候,表示该参数的值将从模型或者Session 中取对应名称的属性值,该名称能够经过@ModelAttribute(“attributeName”) 来指定,若未指定,则使用参数类型的类名称(首字母小写)做为属性名称。
在上面代码中,当咱们请求/myTest/sayHello.do 的时候,因为MyController 中的方法getModel 使用了注解@ModelAttribute 进行标记,因此在执行请求方法sayHello 以前会先执行getModel 方法,这个时候getModel 方法返回一个字符串world 并把它以属性名hello 保存在模型中,接下来访问请求方法sayHello 的时候,该方法的hello 参数使用@ModelAttribute(“hello”) 进行标记,这意味着将从session 或者模型中取属性名称为hello 的属性值赋给hello 参数,因此这里hello 参数将被赋予值world ,因此请求完成后将会在页面上看到Hello world 字符串。
@SessionAttributes 用于标记须要在Session 中使用到的数据,包括从Session 中取数据和存数据。@SessionAttributes 通常是标记在Controller 类上的,能够经过名称、类型或者名称加类型的形式来指定哪些属性是须要存放在session 中的。
在上面代码中咱们能够看到在MyController 上面使用了@SessionAttributes 标记了须要使用到的Session 属性。能够经过名称和类型指定须要存放到Session 中的属性,对应@SessionAttributes 注解的value 和types 属性。当使用的是types 属性的时候,那么使用的Session 属性名称将会是对应类型的名称(首字母小写)。当value 和types 两个属性都使用到了的时候,这时候取的是它们的并集,而不是交集,因此上面代码中指定要存放在Session 中的属性有名称为user1 或blog1 的对象,或类型为User 或Blog 的对象。在上面代码中咱们首先访问/myTest/setSessionAttribute.do ,该请求将会请求到MyController 的setSessionAttribute 方法,在该方法中,咱们往模型里面添加了user 、user1 、blog 和blog1 四个属性,由于它们或跟类上的@SessionAttributes 定义的须要存到session 中的属性名称相同或类型相同,因此在请求完成后这四个属性都将添加到session 属性中。接下来访问/myTest/useSessionAttribute.do ,该请求将会请求MyController 的useSessionAttribute(Writer writer, @ModelAttribute(“user1”) User user1, @ModelAttribute(“blog1”) Blog blog) 方法,该方法参数中用@ModelAttribute 指定了参数user1 和参数blog1 是须要从session 或模型中绑定的,刚好这个时候session 中已经有了这两个属性,因此这个时候在方法执行以前会先绑定这两个参数。执行结果以下图所示:
接下来访问/myTest/useSessionAttribute2.do ,这个时候请求的是上面代码中对应的第二个useSessionAttribute 方法,方法参数user 、user1 和blog1 用@ModelAttribute 声明了须要session 或模型属性注入,咱们知道在请求/myTest/setSessionAttribute.do 的时候这些属性都已经添加到了session中,因此该请求的结果会以下图所示:
接下来访问/myTest/useSessionAttribute3.do ,这个时候请求的是上面代码中对应的第三个useSessionAttribute 方法,咱们能够看到该方法的方法参数user 使用了@ModelAttribute(“user2”)进行标记,表示user 须要session 中的user2 属性来注入,可是这个时候咱们知道session 中是不存在user2 属性的,因此这个时候就会报错了。执行结果如图所示:
在经过处理器方法参数接收 request 请求参数绑定数据的时候,对于一些简单的数据类型Spring 会帮咱们自动进行类型转换,而对于一些复杂的类型因为 Spring 无法识别,因此也就不能帮助咱们进行自动转换了,这个时候若是咱们须要 Spring 来帮咱们自动转换的话就须要咱们给 Spring注册一个对特定类型的识别转换器。 Spring 容许咱们提供两种类型的识别转换器,一种是注册在Controller 中的,一种是注册在 SpringMVC 的配置文件中。聪明的读者看到这里应该能够想到它们的区别了,定义在 Controller 中的是局部的,只在当前 Controller 中有效,而放在SpringMVC 配置文件中的是全局的,全部 Controller 均可以拿来使用。
咱们可使用 @InitBinder 注解标注在 Controller 方法上,而后在方法体里面注册数据绑定的转换器,这主要是经过 WebDataBinder 进行的。咱们能够给须要注册数据绑定的转换器的方法一个 WebDataBinder 参数,而后给该方法加上 @InitBinder 注解,这样当该 Controller 中在处理请求方法时若是发现有不能解析的对象的时候,就会看该类中是否有使用 @InitBinder 标记的方法,若是有就会执行该方法,而后看里面定义的类型转换器是否与当前须要的类型匹配。
在上面的代码中当咱们请求 /myTest/dataBinder/20121212.do 的时候, Spring 就会利用 @InitBinder 标记的方法里面定义的类型转换器把字符串 20121212 转换为一个 Date 对象。这样定义的类型转换器是局部的类型转换器,一旦出了这个 Controller 就不会再起做用。类型转换器是经过 WebDataBinder 对象的 registerCustomEditor 方法来注册的,要实现本身的类型转换器就要实现本身的 PropertyEditor 对象。 Spring 已经给咱们提供了一些经常使用的属性编辑器,如 CustomDateEditor 、 CustomBooleanEditor 等。
PropertyEditor 是一个接口,要实现本身的 PropertyEditor 类咱们能够实现这个接口,而后实现里面的方法。可是 PropertyEditor 里面定义的方法太多了,这样作比较麻烦。在 java 中有一个封装类是实现了 PropertyEditor 接口的,它是 PropertyEditorSupport 类。因此若是须要实现本身的 PropertyEditor 的时候只须要继承 PropertyEditorSupport 类,而后重写其中的一些方法。通常就是重写 setAsText 和 getAsText 方法就能够了, setAsText 方法是用于把字符串类型的值转换为对应的对象的,而 getAsText 方法是用于把对象当作字符串来返回的。在setAsText 中咱们通常先把字符串类型的对象转为特定的对象,而后利用 PropertyEditor 的setValue 方法设定转换后的值。在 getAsText 方法中通常先使用 getValue 方法取代当前的对象,而后把它转换为字符串后再返回给 getAsText 方法。下面是一个示例:
若是须要定义全局的类型转换器就须要实现本身的 WebBindingInitializer 对象,而后把该对象注入到 AnnotationMethodHandlerAdapter 中,这样 Spring 在遇到本身不能解析的对象的时候就会到全局的 WebBindingInitializer 的 initBinder 方法中去找,每次遇到不认识的对象时, initBinder 方法都会被执行一遍。
定义了这么一个 WebBindingInitializer 对象以后 Spring 仍是不能识别其中指定的对象,这是由于咱们只是定义了 WebBindingInitializer 对象,尚未把它交给 Spring , Spring 不知道该去哪里找解析器。要让 Spring 可以识别还须要咱们在 SpringMVC 的配置文件中定义一个AnnotationMethodHandlerAdapter 类型的 bean 对象,而后利用本身定义的WebBindingInitializer 覆盖它的默认属性 webBindingInitializer 。
当Controller处理器方法参数使用@RequestParam、@PathVariable、@RequestHeader、@CookieValue和@ModelAttribute标记的时候都会触发initBinder方法的执行,这包括使用WebBindingInitializer定义的全局方法和在Controller中使用@InitBinder标记的局部方法。并且每一个使用了这几个注解标记的参数都会触发一次initBinder方法的执行,这也意味着有几个参数使用了上述注解就会触发几回initBinder方法的执行。
转载文章,原文地址https://elim.iteye.com/blog/1753271