前后端传值
@PathVariable
和 @RequestParam
@PathVariable
用于获取路径参数,@RequestParam
用于获取查询参数。
@GetMapping("/klasses/{klassId}/teachers")
public List<Teacher> getKlassRelatedTeachers(
@PathVariable("klassId") Long klassId,
@RequestParam(value = "type", required = false) String type ) {
...
}
如果我们请求的 url 是:/klasses/{123456}/teachers?type=web
那么我们服务获取到的数据就是:klassId=123456,type=web
。
@RequestBody
用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter
或者自定义的HttpMessageConverter
将请求的 body 中的 json 字符串转换为 java 对象。
我们有一个注册的接口:
@PostMapping("/sign-up")
public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) {
userService.save(userRegisterRequest);
return ResponseEntity.ok().build();
}
UserRegisterRequest
对象:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRegisterRequest {
@NotBlank
private String userName;
@NotBlank
private String password;
@FullName
@NotBlank
private String fullName;
}
我们发送 post 请求到这个接口,并且 body 携带 JSON 数据:
{"userName":"coder","fullName":"shuangkou","password":"123456"}
这样我们的后端就可以直接把 json 格式的数据映射到我们的 UserRegisterRequest
类上。
如果没有对应的封装类,则可以将数据绑定到 Map 对象上,如下述代码所示:
@RequestMapping("/getProductsByXx")
public List<Product> queryByXx(@RequestBody Map<String,Object> objectMap){
String productName = null;
if(objectMap.get("eqType") != null){
productName = objectMap.get("eqType").toString();
}
Integer productId = null;
if(objectMap.get("materialCode") != null){
productId = Integer.parseInt(objectMap.get("materialCode").toString());
}
return productMapper.queryByXx(productId,productName);
}
参数校验
数据的校验的重要性就不用说了,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。
JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!
校验的时候我们实际用的是 Hibernate Validator 框架。SpringBoot 项目的 spring-boot-starter-web 依赖中已经有 hibernate-validator 包,不需要引用相关依赖。
需要注意的是: 所有的注解,推荐使用 JSR 注解,即javax.validation.constraints
,而不是org.hibernate.validator.constraints
一些常用的字段验证的注解
@NotEmpty
被注释的字符串的不能为 null 也不能为空@NotBlank
被注释的字符串非 null,并且必须包含一个非空白字符@Null
被注释的元素必须为 null@NotNull
被注释的元素必须不为 null@AssertTrue
被注释的元素必须为 true@AssertFalse
被注释的元素必须为 false@Pattern(regex=,flag=)
被注释的元素必须符合指定的正则表达式@Email
被注释的元素必须是 Email 格式。@Min(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值@Max(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值@DecimalMin(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值@DecimalMax(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值@Size(max=, min=)
被注释的元素的大小必须在指定的范围内@Digits (integer, fraction)
被注释的元素必须是一个数字,其值必须在可接受的范围内@Past
被注释的元素必须是一个过去的日期@Future
被注释的元素必须是一个将来的日期- ......
验证请求体(RequestBody)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
@NotNull(message = "classId 不能为空")
private String classId;
@Size(max = 33)
@NotNull(message = "name 不能为空")
private String name;
@Pattern(regexp = "((^Man|^Woman|^UGM$))", message = "sex 值不在可选范围")
@NotNull(message = "sex 不能为空")
private String sex;
@Email(message = "email 格式不正确")
@NotNull(message = "email 不能为空")
private String email;
}
我们在需要验证的参数上加上了@Valid
注解,如果验证失败,它将抛出MethodArgumentNotValidException
。
@RestController
@RequestMapping("/api")
public class PersonController {
@PostMapping("/person")
public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
return ResponseEntity.ok().body(person);
}
}
验证请求参数(Path Variables 和 Request Parameters)
一定一定不要忘记在类上加上 Validated
注解了,这个参数可以告诉 Spring 去校验方法参数。
@RestController
@RequestMapping("/api")
@Validated
public class PersonController {
@GetMapping("/person/{id}")
public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {
return ResponseEntity.ok().body(id);
}
}
全局处理 Controller 层异常
介绍一下我们 Spring 项目必备的全局处理 Controller 层异常。
相关注解:
@ControllerAdvice
:注解定义全局异常处理类@ExceptionHandler
:注解声明异常处理方法
如何使用呢?拿我们在第 5 节参数校验这块来举例子。如果方法参数不对的话就会抛出MethodArgumentNotValidException
,我们来处理这个异常。
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
/**
* 请求参数异常处理
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
......
}
}
更多关于 Spring Boot 异常处理的内容,请看我的这两篇文章:
1. SpringBoot 处理异常的几种常见姿势
2. 使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!
Spring自定义注解
根据注解使用的位置,分为字段注解、方法、类注解。
字段注解
字段注解一般是用于校验字段是否满足要求,hibernate-validate
依赖就提供了很多校验注解 ,如@NotNull
、@Range
等,但是这些注解并不是能够满足所有业务场景的。
首先通过@interface 声明一个注解类,以及类上的 @Target
、 @Retention
和 @Constraint
注解;
创建一个验证器类,需要实现 ConstraintValidator
泛型接口。第一个泛型参数类型Check
:注解,第二个泛型参数Object
:校验字段类型。需要实现initialize
和isValid
方法,isValid
方法为校验逻辑,initialize
方法初始化工作。
方法、类注解
在开发过程中遇到过这样的需求,如只有有权限的用户的才能访问这个类中的方法或某个具体的方法、查找数据的时候先不从数据库查找,先从guava cache
中查找,在从redis
查找,最后查找mysql
(多级缓存)。
首先通过@interface 声明一个注解类,以及类上的 @Target
、 @Retention
注解;
在拦截器中获取该注解配置的参数,然后在 preHandle() 方法中对参数值进行判断,如果满足则通过。
推荐阅读:Spring自定义注解从入门到精通
参考文献
https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/framework/spring/spring-annotations.md
### @ConfigurationProperties
在编写项目代码时,我们要求更灵活的配置,更好的模块化整合。在 Spring Boot 项目中,为满足以上要求,我们将大量的参数配置在 application.properties 或 application.yml 文件中,通过 @ConfigurationProperties
注解,我们可以方便的获取这些参数值。
在 application.yml文件中创建这些参数:
data:
env: config-eureka-dev
user:
username: eureka-client-user
password: 1291029102111
如果使用 @Value 注解,则实体类这样设计:
@Data
@Component
public class GitConfig {
@Value("{data.env}")
private String env;
@Value("{data.user.username}")
private String username;
@Value("${data.user.password}")
private String password;
}
使用 @Value 注入方式比较笨重,尤其是从配置中心(Nacos或者SpringCloud-Config)动态获取最新配置信息时,@Value 无法及时获取到最新值,因此我们采用 @ConfigurationProperties
注解。
实现原理
@ConfigurationProperties注解(将配置文件中的配置,以属性的形式自动注入到实体中)可以注入在application.properties配置文件中的属性,和@Bean 或者 @Component 能生成spring bean 的注解结合起来使用;该类在加载过程中会调用AbstractAutowireCapableBeanFactory中的applyBeanPostProcessorsBeforeInitialization接口进行一些前置处理。
@ConfigurationProperties 注解其对应的bean的后置处理器为ConfigurationPropertiesBindingPostProcessor,它实现了Spring容器的postProcessBeforeInitialization方法,会在 bean 初始化之前被调用注解处理器会读取@ConfigurationProperties 注解的对象获取配置文件中的prefix,和注解对象的类成员变量然后递归将配置属性赋值给类成员变量。
@Value获取值和@ConfigurationProperties获取值比较:
@ConfigurationProperties
的基本用法非常简单:我们为每个要捕获的外部属性提供一个带有字段的类。请注意以下几点:
- 前缀定义了哪些外部属性将绑定到类的字段上
- 根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
- 我们可以简单地用一个值初始化一个字段来定义一个默认值
- 类本身可以是包私有的
- 类的字段必须有公共 setter 方法
- 特别说明的一个注属性
ignoreUnknownFields = false
这个超好用,自动检查配置文件中的属性是否存在,不存在则在启动时就报错
实体类设计如下:
@Component
@Data
@ConfigurationProperties(prefix = "data")
public class GitAutoRefreshConfig {
public static class UserInfo {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "UserInfo{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
private String env;
private UserInfo user;
}
本文作者为hresh,转载请注明。