@RequestMapping 注解及参数接收、校验详解
Spring4.3中引进了{@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping},来帮助简化常用的HTTP方法的映射,并更好地表达被注解方法的语义。
从命名约定我们可以看到每个注释都是为了处理各自的传入请求方法类型,即 @GetMapping 用于处理请求方法的 GET 类型, @ PostMapping 用于处理请求方法的 POST 类型等。
一、@RequestMapping 注解
1、@RequestMapping 注解可以在控制器类的级别和/或其中的方法的级别上使用。
在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上。之后你还可以另外添加方法级别的注解来进一步指定到处理方法的映射关系。
下面是一个同时在类和方法上应用了 @RequestMapping 注解的示例:
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping("/") String get() { //mapped to hostname:port/home/ return "Hello from get"; } @RequestMapping("/index") String index() { //mapped to hostname:port/home/index/ return "Hello from index"; } }
如上述代码所示,到 /home 的请求会由 get() 方法来处理,而到 /home/index 的请求会由 index() 来处理。
2、@RequestMapping 来处理多个 URI
可以将多个请求映射到一个方法上去,只需要添加一个带有请求路径值列表的 @RequestMapping 注解就行了。
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = { "", "/page", "page*", "view/*,**/msg" }) String indexMultipleMapping() { return "Hello from index multiple mapping."; } }
如你在这段代码中所看到的,@RequestMapping 支持统配符以及ANT风格的路径。前面这段代码中,如下的这些 URL 都会由 indexMultipleMapping() 来处理:
-
localhost:8080/home
-
localhost:8080/home/
-
localhost:8080/home/page
-
localhost:8080/home/pageabc
-
localhost:8080/home/view/
-
localhost:8080/home/view/view
3、带有 @RequestParam 的 @RequestMapping
@RequestParam 注解配合 @RequestMapping 一起使用,可以将请求的参数同处理方法的参数绑定在一起。
@RequestParam 注解使用的时候可以有一个值,也可以没有值。这个值指定了需要被映射到处理方法参数的请求参数, 代码如下所示:
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = "/id") String getIdByValue(@RequestParam("id") String personId) { System.out.println("ID is " + personId); return "Get ID from query string of URL with value element"; } @RequestMapping(value = "/personId") String getId(@RequestParam String personId) { System.out.println("ID is " + personId); return "Get ID from query string of URL without value element"; } }
代码解析:
(1)、 id 这个请求参数被映射到了getIdByValue() 这个处理方法的参数 personId 上。
(2)、如果请求参数和处理方法参数的名称一样的话,@RequestParam 注解的 value 这个参数就可省掉了。如代码所示: String getId(@RequestParam String personId)。
@RequestParam 注解的 required 这个参数定义了参数值是否是必须要传。
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = "/name") String getName(@RequestParam(value = "person", required = false) String personName) { return "Required element of request param"; } }
在这段代码中,因为 required 被指定为 false,所以 getName() 处理方法对于如下两个 URL 都会进行处理:
-
/home/name?person=xyz
-
/home/name
@RequestParam 的 defaultValue 取值就是用来给取值为空的请求参数提供一个默认值的。 代码
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = "/name") String getName(@RequestParam(value = "person", defaultValue = "John") String personName) { return "Required element of request param"; } }
在这段代码中,如果 person 这个请求参数为空,那么 getName() 处理方法就会接收 John 这个默认值作为其参数。
4、用 @RequestMapping 处理 HTTP 的各种方法
Spring MVC 的 @RequestMapping 注解能够处理 HTTP 请求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。
所有的请求默认都会是 HTTP GET 类型的。
为了能降一个请求映射到一个特定的 HTTP 方法,需要在 @RequestMapping 中使用 method 来声明 HTTP 请求所使用的方法类型,如下所示:
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(method = RequestMethod.GET) String get() { return "Hello from get"; } @RequestMapping(method = RequestMethod.DELETE) String delete() { return "Hello from delete"; } @RequestMapping(method = RequestMethod.POST) String post() { return "Hello from post"; } @RequestMapping(method = RequestMethod.PUT) String put() { return "Hello from put"; } @RequestMapping(method = RequestMethod.PATCH) String patch() { return "Hello from patch"; } }
在上述这段代码中, @RequestMapping 注解中的 method 元素声明了 HTTP 请求的 HTTP 方法的类型。
所有的处理处理方法会处理从这同一个 URL( /home)进来的请求, 但要看指定的 HTTP 方法是什么来决定用哪个方法来处理。
例如,一个 POST 类型的请求 /home 会交给 post() 方法来处理,而一个 DELETE 类型的请求 /home 则会由 delete() 方法来处理。
5、用 @RequestMapping 来处理生产和消费对象
(1)、使用 @RequestMapping 注解的 produces 和 consumes 这两个元素来缩小请求映射类型的范围。
为了能用请求的媒体类型来产生对象, 你要用到 @RequestMapping 的 produces 元素再结合着 @ResponseBody 注解。
你也可以利用 @RequestMapping 的 comsumes 元素再结合着 @RequestBody 注解用请求的媒体类型来消费对象。
下面这段代码就用到的 @RequestMapping 的生产和消费对象元素:
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = "/prod", produces = { "application/JSON" }) @ResponseBody String getProduces() { return "Produces attribute"; } @RequestMapping(value = "/cons", consumes = { "application/JSON", "application/XML" }) String getConsumes() { return "Consumes attribute"; } }
在这段代码中, getProduces() 处理方法会产生一个 JSON 响应, getConsumes() 处理方法可以同时处理请求中的 JSON 和 XML 内容。
(2)、使用 @RequestMapping 来处理消息头
@RequestMapping 注解提供了一个 header 元素来根据请求中的消息头内容缩小请求映射的范围。
在可以指定 header 元素的值,用 myHeader = myValue 这样的格式:
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = "/head", headers = { "content-type=text/plain" }) String post() { return "Mapping applied along with headers"; } }
在上面这段代码中, @RequestMapping 注解的 headers 属性将映射范围缩小到了 post() 方法。有了这个,post() 方法就只会处理到 /home/head 并且 content-typeheader 被指定为 text/plain 这个值的请求。
你也可以像下面这样指定多个消息头:
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = "/head", headers = { "content-type=text/plain", "content-type=text/html" }) String post() { return "Mapping applied along with headers"; } }
这样, post() 方法就能同时接受 text/plain 还有 text/html 的请求了.
6、使用 @RequestMapping 来处理请求参数
@RequestMapping 直接的 params 元素可以进一步帮助我们缩小请求映射的定位范围。使用 params 元素,你可以让多个处理方法处理到同一个URL 的请求, 而这些请求的参数是不一样的。
你可以用 myParams = myValue 这种格式来定义参数,也可以使用通配符来指定特定的参数值在请求中是不受支持的。
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = "/fetch", params = { "personId=10" }) String getParams(@RequestParam("personId") String id) { return "Fetched parameter using params attribute = " + id; } @RequestMapping(value = "/fetch", params = { "personId=20" }) String getParamsDifferent(@RequestParam("personId") String id) { return "Fetched parameter using params attribute = " + id; } }
在这段代码中,getParams() 和 getParamsDifferent() 两个方法都能处理相同的一个 URL (/home/fetch) ,但是会根据 params 元素的配置不同而决定具体来执行哪一个方法。
例如,当 URL 是 /home/fetch?id=10 的时候, getParams() 会执行,因为 id 的值是10,。对于 localhost:8080/home/fetch?personId=20 这个URL, getParamsDifferent() 处理方法会得到执行,因为 id 值是 20。
7、使用 @RequestMapping 处理动态 URI
@RequestMapping 注解可以同 @PathVaraible 注解一起使用,用来处理动态的 URI,URI 的值可以作为控制器中处理方法的参数。你也可以使用正则表达式来只处理可以匹配到正则表达式的动态 URI。
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = "/fetch/{id}", method = RequestMethod.GET) String getDynamicUriValue(@PathVariable String id) { System.out.println("ID is " + id); return "Dynamic URI parameter fetched"; } @RequestMapping(value = "/fetch/{id:[a-z]+}/{name}", method = RequestMethod.GET) String getDynamicUriValueRegex(@PathVariable("name") String name) { System.out.println("Name is " + name); return "Dynamic URI parameter fetched using regex"; } }
在这段代码中,方法 getDynamicUriValue() 会在发起到 localhost:8080/home/fetch/10 的请求时执行。这里 getDynamicUriValue() 方法 id 参数也会动态地被填充为 10 这个值。
方法 getDynamicUriValueRegex() 会在发起到 localhost:8080/home/fetch/category/shirt 的请求时执行。不过,如果发起的请求是 /home/fetch/10/shirt 的话,会抛出异常,因为这个URI并不能匹配正则表达式。
@PathVariable 同 @RequestParam的运行方式不同。你使用 @PathVariable 是为了从 URI 里取到查询参数值。换言之,你使用 @RequestParam 是为了从 URI 模板中获取参数值。
8、@RequestMapping 默认的处理方法
在控制器类中,你可以有一个默认的处理方法,它可以在有一个向默认 URI 发起的请求时被执行。
下面是默认处理方法的示例:
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping() String default () { return "This is a default method for the class"; } }
在这段代码中,向 /home 发起的一个请求将会由 default() 来处理,因为注解并没有指定任何值。
9、@RequestMapping 快捷方式
Spring 4.3 引入了方法级注解的变体,也被叫做 @RequestMapping 的组合注解。组合注解可以更好的表达被注解方法的语义。它们所扮演的角色就是针对 @RequestMapping 的封装,而且成了定义端点的标准方法。
例如,@GetMapping 是一个组合注解,它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一个快捷方式。 方法级别的注解变体有如下几个:
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
如下代码展示了如何使用组合注解:
@RestController @RequestMapping("/home") public class IndexController { @GetMapping("/person") public @ResponseBody ResponseEntity < String > getPerson() { return new ResponseEntity < String > ("Response from GET", HttpStatus.OK); } @GetMapping("/person/{id}") public @ResponseBody ResponseEntity < String > getPersonById(@PathVariable String id) { return new ResponseEntity < String > ("Response from GET with id " + id, HttpStatus.OK); } @PostMapping("/person") public @ResponseBody ResponseEntity < String > postPerson() { return new ResponseEntity < String > ("Response from POST method", HttpStatus.OK); } @PutMapping("/person") public @ResponseBody ResponseEntity < String > putPerson() { return new ResponseEntity < String > ("Response from PUT method", HttpStatus.OK); } @DeleteMapping("/person") public @ResponseBody ResponseEntity < String > deletePerson() { return new ResponseEntity < String > ("Response from DELETE method", HttpStatus.OK); } @PatchMapping("/person") public @ResponseBody ResponseEntity < String > patchPerson() { return new ResponseEntity < String > ("Response from PATCH method", HttpStatus.OK); } }
在这段代码中,每一个处理方法都使用 @RequestMapping 的组合变体进行了注解。尽管每个变体都可以使用带有方法属性的 @RequestMapping 注解来互换实现, 但组合变体仍然是一种最佳的实践 — 这主要是因为组合注解减少了在应用程序上要配置的元数据,并且代码也更易读。
10、@Getmapping 注解说明
(1)、@Getmapping是@RequestMapping(method = RequestMethod.GET) 快捷方式 。
源码如下:
/** * Annotation for mapping HTTP {@code GET} requests onto specific handler * methods. * * <p>Specifically, {@code @GetMapping} is a <em>composed annotation</em> that * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.GET)}. * * * @author Sam Brannen * @since 4.3 * @see PostMapping * @see PutMapping * @see DeleteMapping * @see PatchMapping * @see RequestMapping */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @RequestMapping(method = RequestMethod.GET) public @interface GetMapping { /** * Alias for {@link RequestMapping#name}. */ @AliasFor(annotation = RequestMapping.class) String name() default ""; ... }
(2)、@GetMapping不支持consumes属性
consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
consumes例子:
@Controller @RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json") public void addPet(@RequestBody Pet pet, Model model) { // implementation omitted }
(3)、@GetMapping 入参注解注意事项
GET 请求当使用 @RequestParm 注解和不加注解时,只能接收到 params 携带的参数 ,参数放在请求头 和请求体中均接受不到。
GET 请求 不可以使用 @RequestBody 注解
11、@PostMapping注解说明
(1)、@PostMapping是@RequestMapping(method = RequestMethod.POST) 快捷方式
(2)、@GetMapping 入参注解注意事项
POST请求 当使用 @RequestParm 注解 和 不加注解时,只能接收到 params 和请求体xxx格式携带的参数,加注解无法接收到对象参数。
POST请求 当使用 @RequestBody注解 ,只能接收到请求体JSON格式和表单携带的参数 其他类型参数均接受不到
POST请求接收一个参数 不可以使用 @RequestBody 注解。
二、参数接收
1、Body参数
Body参数一般是POST请求,主要有两种方式
-
以JSON格式接收可通过@RequestBody获取对应的参数
-
以form表单形式提交的,暂无注解适配,可直接对象接收
(1)、JSON参数接收
例如:添加用户的接口, 前端PostMan 请求信息如下:
后端接收代码1:
@PostMapping(value = "/user/map") public ResultVO createUser(@RequestBody Map<String,Object> user) { String name=user.get("name").toString(); return RV.success(user); }
后端接收代码2:
@PostMapping(value = "/user") public ResultVO createUser2(@RequestBody User user) { System.out.println("User Info:"+user.toString()); return RV.success(user); }
(2)、FORM 参数接收
form方式提交,在PostMan中对应参数如下
对应后端接收代码:
@PostMapping(value = "/user/form") public ResultVO createUser3(User user) { System.out.println("User Info:"+user.toString()); return RV.success(user); }
2、请求头和Cookie参数
-
@RequestHeader,是直接获取请求头HttpServletRequest对象里面Header中的参数
-
@CookieValue 可以直接获取HttpServletRequest对象里面Cookies中的参数
通过注解的方式获取,分别如下
@GetMapping("demo3") public void demo3(@RequestHeader(name = "myHeader") String myHeader, @CookieValue(name = "myCookie") String myCookie) { System.out.println("myHeader=" + myHeader); System.out.println("myCookie=" + myCookie); }
通过硬编码的获取方式为
@GetMapping("/demo3") public void demo3(HttpServletRequest request) { System.out.println(request.getHeader("myHeader")); for (Cookie cookie : request.getCookies()) { if ("myCookie".equals(cookie.getName())) { System.out.println(cookie.getValue()); } } }
由上可以看出,通过注解的方式可以极大简化编码,使我们的代码变得美观大方,如果通过request对象直接获取,也是可以的,这个主要是看什么样的需求,一般情况下,通过注解的方式基本上能满足我们的绝大部分需求,所以在项目中我们比较推荐直接通过注解的方式获取参数。
3、文件上传参数
文件上传主要是基于@RequestParam 结合MultipartFile对象;如下示例;
这里需要注意,前端的传入需要用表单方式提交,并且在Head的Content-Type里面加入,如下信息
Content-Type: application/x-www-form-urlencoded
对应后端代码如下
@Value("${file.upload.url}") private String filePath; @RequestMapping("/upload") public ResultVO httpUpload(@RequestParam("files") MultipartFile files[]){ for(int i=0;i<files.length;i++) { String fileName = files[i].getOriginalFilename(); // 文件名 File dest = new File(filePath+'/'+fileName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } try { files[i].transferTo(dest); } catch (Exception e) { log.error("{}",e); return RV.result(ErrorCode.UPLOAD_FILE_ERROR,e); } } return RV.success(null); }
三、参数校验
在Controller层的参数校验可以分为两种场景,单个参数校验和实体参数校验
(1)、单个参数校验
后端代码如下
@RestControllerpublic class ValidateController { @GetMapping("/getUser") @Validated public ResultVO getUserStr(@NotNull(message = "name 不能为空") String name, @Max(value = 99, message = "不能大于99岁") Integer age) { return RV.success("name: " + name + " ,age:" + age); } }
在Postman请求如下,可以看到请求后,返回了系统异常,应该是被全局异常拦截了.
我们再看后台打印的日志:
如果我们系统中有很多地方都用到了参数校验,那么我们最好是能够在全局异常拦截处直接拦截ConstraintViolationException异常,并进行统一处理,可以在我们前面章节讲到的,在加了@RestControllerAdvice注解的GlobalException类中加入如下拦截的新方法;由于ConstraintViolationException继承了ValidationException异常类,所以我们可以直接拦截父类异常,直接进行多个不同的校验异常进行处理.
@RestControllerAdvice public class GlobalException { @ExceptionHandler(value = ValidationException.class) public ResultVO validationException(ValidationException e){ Map map=new HashMap(); if(e instanceof ConstraintViolationException){ ConstraintViolationException exs = (ConstraintViolationException) e; Set<ConstraintViolation>> violations = exs.getConstraintViolations(); for (ConstraintViolation> item : violations) { //打印验证不通过的信息 System.out.println(item.getMessage()); map.put(item.getPropertyPath(),item.getMessage()); } } return RV.result(ErrorCode.FORM_VALIDATION_ERROR,map) ; } }
同样,再通过Postman请求后,得到如下结果
(2、)实体参数校验
一般我们在传入参数比较少的情况下,就直接用上面的方法进行了验证了,但是,如果我们传入的是一个对象,直接在方法上一个一个属性设置,就显得有点不美观了,因此我们一般都是封装一个接收参数的对象。如下面实例提供的,添加用户接口,我们对用户对象的变量设置了校验规则,如下
@Dataclass Person { @NotNull(message = "名称不能为空") @Max(value = 30, message = "名称不能超过30个字符") String name; @Max(value = 200, message = "年龄不能超过200岁吧") @NotNull(message = "年龄不能为空") Integer age; @NotNull(message = "地址信息不能为空") String address; }
在Controller类中我们增加添加用户的方法,这里我们需要注意,我们只需要在方法对应的参数Person对象前面加上@Valid注解即可
@PostMapping(value = "/person") public ResultVO<Person> addPerson(@RequestBody @Valid Person person) { //此处略过处理业务的代码... return RV.success(person); }
其实大家可能就会问,那要是参数校验不同过,这里应该是抛出什么异常呢,当我们执行如上代码,设定超出范围的参数后,在后端可以看到异常如下
可以看到,这里抛出的MethodArgumentNotValidException与上面的异常截然不同;相同道理,我依然在GlobalException类中加入对MethodArgumentNotValidException异常的全局拦截即可
@ExceptionHandler(value = MethodArgumentNotValidException.class) public ResultVO methodArgumentNotValidException(MethodArgumentNotValidException e){ List<ObjectError> errors = e.getBindingResult().getAllErrors(); String[] errMessage=new String[errors.size()]; for (int i = 0; i < errors.size(); i++) { ObjectError error=errors.get(i); errMessage[i]=error.getDefaultMessage(); System.out.println(error.getCode()+":"+error.getDefaultMessage()); } return RV.result(ErrorCode.METHOD_ARGS_VALID_ERROR,errMessage) ; }
Validated与Valid区别
-
@Validated: 用在方法的入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
-
@Valid:用在方法入的参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
如上Person对象,如果里面再有一个对象,比如还是Person,在提交的过程中,需要这种嵌套验证,就需要通过使用Valid的嵌套验证功能,即可将代码修改如下;
@Dataclass Person { @NotNull(message = "名称不能为空") @Size(min = 2,max = 30, message = "名称长度限定在2-30之间的长度") String name; @Max(value = 200, message = "年龄不能超过200岁吧") @NotNull(message = "年龄不能为空") Integer age; @NotNull(message = "地址信息不能为空") String address; @Valid Person p; }
常用参数校验的注解
这里我们主要介绍在springboot中的几种参数校验方式。常用的用于参数校验的注解如下:
-
@AssertFalse 所注解的元素必须是Boolean类型,且值为false
-
@AssertTrue 所注解的元素必须是Boolean类型,且值为true
-
@DecimalMax 所注解的元素必须是数字,且值小于等于给定的值
-
@DecimalMin 所注解的元素必须是数字,且值大于等于给定的值
-
@Digits 所注解的元素必须是数字,且值必须是指定的位数
-
@Future 所注解的元素必须是将来某个日期
-
@Max 所注解的元素必须是数字,且值小于等于给定的值
-
@Min 所注解的元素必须是数字,且值小于等于给定的值
-
@Range 所注解的元素需在指定范围区间内
-
@NotNull 所注解的元素值不能为null
-
@NotBlank 所注解的元素值有内容
-
@Null 所注解的元素值为null
-
@Past 所注解的元素必须是某个过去的日期
-
@PastOrPresent 所注解的元素必须是过去某个或现在日期
-
@Pattern 所注解的元素必须满足给定的正则表达式
-
@Size 所注解的元素必须是String、集合或数组,且长度大小需保证在给定范围之内
-
@Email 所注解的元素需满足Email格式