Spring Boot2--Web开发(二)
注解@PathVariable用于从请求URL中获取参数并映射到方法参数中,如下代码:
@Controller
@RequestMapping("/user/{id}")
public class HelloworldController {
@Autowired
UserService userService;
@GetMapping(path="/{type}/get.json")
public @ResponseBody User getUser(@PathVariable Long id,@PathVariable Integer type){
return userService.getUserById(id);
}
}
符号{}中的变量名与方法中的参数名一一对应,如果不想对应,如路径中的名字是id,方法签名是userId,则可以使用@PathVariable(“id”)long userId来对应。
通常情况下,Java的的编译代码的时候,会将参数名称也编译到类字节码里,因此春季会根据名字匹配自动映射如果上述例子中出现如下错误:
出现意外错误(type = Internal Server Error,status = 500)。
参数类型[java.lang.Long]的名称不可用,参数名称信息也未在类文件中找到。
则表明你的编译环境未将调试信息加入类中,建议将编译器改成默认设置。以Eclipse中为例,选择工程,右键选择属性,找到的Java编译器,在类文件生成选项中勾选除内联终于块外的所有选项,如图所示:
Spring也支持URL中的矩阵变量,所谓矩阵变量,就是出现在URL片段中,通过“;”分割的多个变量,比如/user/id=123;status=1/update.json。
JavaBean中的接受HTTP 参数
HTTP提交的参数可以映射到方法参数上,按照名称来映射,比如一个请求/javaBean/update.json?name=abc&id=1,将会映射到如下方法:
@GetMapping(path="/update.json")
public @ResponseBody User getUser(Integer id,String type){
return userService.getUserById(id);
}
可以通过@RequestParam来进一步限定HTTP参数到控制器方法的映射关系,RequestParam支持如下属性:
value:指明HTTP参数的名称。
需要:布尔类型,声明此参数是否必须有,如果HTTP参数里没有,则会抛出400错误。
defaultValue:字符类型,如果HTTP参数没有提供,可以指定一个默认字符串,Spring类型转换为目标类型,如上一个例子,我们可以提供默认参数:
@GetMapping(path="/update.json")
public @ResponseBody User getUser(@RequestParam(value = "id", required = true, defaultValue = "12") Integer id,String type) {
return userService.getUserById(id);
}
可以将HTTP参数转为JavaBean对象,HTTP参数的名字对应到POJO的属性名。通常,HTTP提交了多个参数,Spring支持按照前缀自动映射到不同的对象上。简单来说,Spring有如下表所示的HTTP参数到JavaBean中的映射规则:
示例 |
解释 |
名称 |
的对象名称属性 |
order.name |
的对象顺序属性的名称属性 |
细节[0]。名称 |
对象的详细信息属性,要求详情是个数组或者List (不能是Set ,因为Set 不具备根据索引取值的功能),details [0] 表示详细信息属性的第一个元素 |
@RequestBody 接受JSON
控制器方法带有@RequestBody注解的参数,意味着请求的HTTP消息体的内容是一个JSON,需要转化为注解指定的参数类型.Spring Boot默认使用Jackson来处理反序列化工作。
MultipartFile
通过MultipartFile来处理文件上传,MultipartFile提供了以下方法来获取上传的文件信息:
getOriginalFilename:获取上传的文件名字。
getBytes:获取上传文件内容,转为字节数组。
getInputStream:获取一个InputStream。
isEmpty:文件上传为空,或者就没有文件上传。
getSize:文件上传的大小。
transferTo(File dest):保存上传文件到目标文件系统。
如果同时上传多个文件,则使用MultipartFile数组类来接受多个文件上传。
Spring Boot中可以通过配置文件application.properties对上传文件进行限定,默认为如下配置:
spring.servlet.multipart.enabled=true
spring.servlet.multipart.file-size-threshold=0
spring.servlet.multipart.location=
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.resolve-lazily=false
参数enabled默认为true,即允许上传,file-size-threshold限定了当上传的文件超过一定长度时,就写到临时文件里。者有助于上传文件不占用过多的内存,单位时MB或者KB,默认是0,即不限定阀值。位置指的是临时文件的存放目录,如果不设定,则网络服务器提供一个临时目录。
最大文件大小属性指定了单个文件的最大长度,默认是1MB,最大请求大小的属性说明单次HTTP请求上传的最大长度,默认是10MB。解决,懒洋洋地表示当文件和参数被访问的时候再解析成文件。
如果上传大文件失败,则需要检查是不是因为Spring Boot对文件的限定过小造成的。另一方面,有些Spring Boot应用设置了代理服务器,比如设置了Apache,也需要检查代理服务器是否支持大文件上传,是否对超时做了设定。
@ModelAttribute
注解的ModelAttribute通常作用做控制器的某个方法上,此方法会首先被调用,并将方法结果作为模型的属性,然后再调用对应的控制器处理方法。
@ModelAttribute
public void findUserById(@PathVariable Long id,Model model){
model.addAttribute("user", userService.getUserById(id));
}
@GetMapping(path="/{id}/get.json")
public @ResponseBody String getUser(Model model) {
System.out.println(model.containsAttribute("user"));
return "success";
}
对于HTTP请求,modelattribute / 1 / get.json,会先调用findUserById方法取得user,并添加到模型里。使用ModelAttribute通常可以用来向一个Controller中需要的公共模型添加数据。
如果findUserById仅仅添加一个对象到模型中,则可以改写成如下形式:
@ModelAttribute
public void findUserById(@PathVariable Long id){
userService.getUserById(id);
}
这样,返回到对象自动添加到Model中,相当于调用model.addAttribute(user)。
@InitBinder
将HTTP参数绑定到JavaBean的对象中,其实春天框架是通过WebDataBinder类实现这种绑定到,所以,可以在控制器中用注解@InitBinder声明一个方法,来自己扩展绑定到特性,比如:
@InitBinder
public void findUserById(WebDataBinder binder){
binder.addCustomFormatter(new DateFormatter("yyyy-mm-dd"));
}
@RequestMapping(path="/date")
public @ResponseBody void printDate(Date d) {
System.out.println(d);
return;
}
当需要绑定到一个Date类型的时候,如上述代码所示,则采用“yyyy-MM-dd”格式,比如用户访问databind / date?d = 2001-1-1。
验证框架
Spring Boot支持JSR-303,Bean验证框架,默认实现用的是Hibernate验证器。在Spring MVC中,只需要使用@Valid注解标注中方法参数上,Spring Boot即可对参数对象进行校验,校验结果放在BindingResult对象中。
JSR-303定义了一系列注解用来验证的Bean的属性,常用的有如下几种:
@空值 |
验证对象是否为空 |
@NotNull |
验证对象不为空 |
@NotBlank |
验证字符串不为空或者不为空字符串,比如“” 和“” 都会验证失败 |
@不是空的 |
验证对象不为null ,或者集合不为空 |
@Size(MIN =,最大=) |
验证对象长度,可支持字符串,集合 |
@长度 |
字符串大小 |
@Min |
验证数字是否大于等于指定的值 |
@Max |
验证数字是否小于等于指定的值 |
@digits |
验证数字是否符合指定格式,如@digits(整数= 9,分数= 2) |
@范围 |
验证数字是否在指定的范围内,如@range(分钟= 1,最大值为100) |
@电子邮件 |
验证是否为邮件格式,为空则不做校验 |
@图案 |
验证字符串对象是否符合正则表达式的规则 |
通常,不同的业务逻辑会有不同的验证逻辑,比如对于WorkInfoForm来说,当更新的时候,id必须不为null,但增加的时候,id必须是null。
JSR-303定义了group概念,每个校验注解都必须支持。校验注解作用在字段上的时候,可以指定一个或多个组,当Spring Boot校验对象的时候,也可以指定校验的上下文属于哪个组。这样,只有组匹配的时候,校验注解才能生效。上面的WorkInfoForm定义id字段校验可以更改为如下内容:
public class WorkInfoForm{
//定义一个类,更新时校验组
public interface Update{}
//定义一个类,添加时校验组
public interface Add{}
@NotNull(groups={Update.class})
@Null(groups={Add.class})
Long id;
}
这段代码表示,当校验上下文为Add.class的时候,@ null生效,ID需要为空才能校验通过;当校验上下文为Update.class的时候,@ NotNull生效,ID不能为空。
MVC 中使用@Validated
在控制器中,只需要给方法参数加上@Validated即可触发一次校验。
@RequestMapping("/addworkinfo.html")
public void addWorkInfo(@Validated({WorkInfoForm.Add.class}) WorkInfoForm woekInfo,BindingResult result) {
if(result.hasErrors()){
List<ObjectError> list = result.getAllErrors();
FieldError error = (FieldError) list.get(0);
System.out.println(error.getObjectName()+","+error.getField()+","+error.getDefaultMessage());
return;
}
return;
}
此方法可以接受HTTP参数并映射到WorkInfoForm对象,此参数使用了@Validated注解,将触发Spring到校验,并将验证结果存放到BindingResult对象中。这里,验证注释使用了校验的上下文WorkInfoForm.Add .class,因此,整个校验将按照Add.class来校验。
BindingResult包含了验证结果,提供了如下方法:
hasErrors,判断验证是否通过。
getAllErrors,得到所有的错误信息,通常返回的是FieldError列表。
如果控制器参数未提供BindingResult对象,则Spring MVC将抛出异常。
自定义校验
JSR-303提供的大部分校验注解已经够用,也允许定制校验注解,比如在WorkInfoForm类中,我们新增一个加班时间:
@WorkOverTime
int workTime;
属性workTime使用了注解@WorkOverTime,当属性值超过max值的时候,将会验证失败.WorkOverTime跟其他注解差不多,但提供了@Constraint来说明用什么类作为验证注解实现类,代码如下:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Constraint(validatedBy = {WorkOverTimeValidator.class})
@Documented
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WorkOverTime {
String message() default "加班时间过长,不能超过{max}小时";
int max() default 5;
Class<?> [] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Constraint注解说明用什么类来实现验证,我们将创建一个WorkOverTimeValidator。来进行验证注解必须提供如下信息:
message,用于创建错误信息,支持表达式,如“错误,不能超过(max)小时”。
groups,验证规则分组,比如新增和修改的验证规则不一样,分为两个组,验证注解必须提供。
有效载荷,定义了验证的有效负荷。
WorkOverTimeValidator必须实现ConstraintValidator接口初始化方法及验证方法isValid:
package com.scg.springboot;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class WorkOverTimeValidator implements ConstraintValidator<WorkOverTime, Integer>{
WorkOverTime work;
int max;
public void initialize(WorkOverTime work){
//获取注解的定义
this.work = work;
max = work.max();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if(value==null){
return true;
}
return value<max;
}
}
WebMvcConfigurer
WebMvcConfigurer是用来全局定制化Spring Boot的MVC特性。开发者可以通过实现WebMvcConfigurer接口来配置应用的MVC全局特性。
package com.scg.springboot;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfigurer implements WebMvcConfigurer{
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
WebMvcConfigurer.super.addInterceptors(registry);
}
//跨域访问配置
@Override
public void addCorsMappings(CorsRegistry registry) {
//允许所有跨域访问
registry.addMapping("/**");
//允许来自domain2.com的跨域访问,并且限定访问路径为/api、方法是POST或者GET。
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("POST","GET");
}
//格式化
@Override
public void addFormatters(FormatterRegistry registry) {
/**
* 将HTTP请求映射到Controller方法的参数上后,Spring会自动进行类型转化。对于日期类型的参数,
* Spring默认并没有配置如何将字符串转为日期类型。为了支持可按照指定格式转为日期类型,需要添加
* 一个DateFormatter类:
*/
registry.addFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
}
//URI到视图到映射
@Override
public void addViewControllers(ViewControllerRegistry registry) {
WebMvcConfigurer.super.addViewControllers(registry);
}
}
使用杰克逊
在MVC框架中,Spring Boot内置了Jackson来完成JSON的序列化和反序列化。在Controller中,方法注解为@ResponseBody,自动将方法返回的对象序列化成JSON。如果想自己全局自定义一个ObjectMapper来代替默认的,则可以使用Java Config,联合使用@Bean,代码如下:
package com.scg.springboot.controller;
import java.text.SimpleDateFormat;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
public class JackSonConf {
@Bean
public ObjectMapper getObjectMapper(){
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
return objectMapper;
}
}
上述Java Config会使用Spring Boot使用自定义的Jackson来序列化而非默认配置的。以下是一个用来获取当前时间的请求:
package com.scg.springboot.controller;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/json")
public class JackSonController {
@GetMapping("/user/{id}.json")
public @ResponseBody Map<String,Date> now(){
Map<String,Date> mp = new HashMap<String,Date>();
mp.put("time", new Date());
return mp;
}
}
当用户访问now.json的时候,会得到如下输出:
{“time”:“2018-10-23 20:50:05”}
重定向和转发
有些情况下,Controller会返回客户端一个HTTP Redirect重定向请求,希望客户端按照指定地址重新发起一次请求,比如客户端登录成功后,重定向到后台系统首页。再比如客户端通过POST提交了一个名单,可以返回一个重定向请求到此订单明显的请求地址。这样做的好处是,如果用户再次刷新页面,则访问的是订单详情地址,而不会再次提交订单。
Controller中重定向可以返回以“redirect:”为前缀的URI:
@RequestMapping("/order/saveorder.html")
public String saveOrder(Order order){
Long orderId = service.addOrder(order);
return "redirect:/order/detail.html?orderId="+orderId;
}
还可以在ModelAndView对象中设置带有“重定向:”前缀的URI:
ModelAndView view = new ModelAndView("redirect:/order/detail.html?orderId="+orderId);
或者直接使用RedirectView的的类:
RedirectView view = new RedirectView("/order/detail.html?orderId="+orderId);
Spring MVC也支持forward前缀,用来在Controller执行完毕后,再执行另外一个Controller的方法。
@RequestMapping("/bbs")
public String index(){
//forward 到 module方法
return "forward:/bbs/module/1-1.html";
}
@RequestMapping("/bbs/moudle/{type}-{page}")
public ModelAndView module(@PathVariable int type,@PathVariable int page){
……
}
对所有访问/ bbs的请求,都会转到模块方法,因为转发的URL是/bbs/module/1-1.html,正好匹配模块方法的@RequestMapping的定义。