Springboot 注解
@validated
服务器和浏览器互不信任,不能因为前端加入参判断了后台就不处理了,这样是不对的。
比如前台传过来一个对象作为入参参数,这个对象中有些属性允许为空,有些属性不允许为空。那么你还在使用if()else{}进行非空判断吗?不妨尝试下使用注解,可以使用@Validated
基础使用
因为spring-boot已经引入了基础包,所以直接使用就可以了
1 首先在controller上声明需要对数据进行校验
@RestController
@RequestMapping("/api/v1")
public class Demo1Controller {
@PostMapping("/insert")
public String validatedDemo1(@Validated @RequestBody Use1Dto use1Dto){
System.out.println(use1Dto);
return "success";
}
}
2 然后在bean上声明需要被校验的字段
@Data
public class User1Dto {
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空!")
private String username;
/**
* 性别
*/
@NotBlank(message = "性别不能为空!")
private String gender;
/**
* 年龄
*/
@Min(value = 1, message = "年龄有误!")
@Max(value = 120, message = "年龄有误!")
private int age;
/**
* 地址
*/
@NotBlank(message = "地址不能为空!")
private String address;
/**
* 邮箱
*/
@Email(message = "邮箱有误!")
private String email;
/**
* 手机号码
*/
@Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$",message = "手机号码有误!")
private String mobile;
}
而后,当输入不能满足条件是,就会抛出异常,而后统一由异常中心处理
也可以用BindingResult
,但是用了这个后就必须手动处理异常,侵入了正常的逻辑过程,并不推荐.
说明:若不做异常处理,@Validated注解的默认异常消息如下(示例):
2020-09-05 21:48:38.106 WARN 9796 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.validateddemo.controller.DemoController.validatedDemo1(com.example.validateddemo.entity.dto.UseDto): [Field error in object 'useDto' on field 'username': rejected value [null]; codes [NotBlank.useDto.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [useDto.username,username]; arguments []; default message [username]]; default message [用户名不能为空!]] ]
常用注解类型
注意,不要错用了异常类型,比如在int上不可用@size
数值检查,建议使用在Stirng
,Integer
类型,不建议使用在int
类型上,因为表单值为""
时无法转换为int
,但可以转换为Stirng
为""
,Integer
为null
嵌套校验
@Validated或者@Valid区别
@Validated
:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上@Valid
:可以用在方法、构造函数、方法参数和成员属性(字段)上- 两者是否能用于成员属性(字段)上直接影响能否提供
嵌套验证
的功能。
未嵌套情况
比如我们现在有个实体叫做Item
:
public class Item {
@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;
@NotNull(message = "props不能为空")
@Size(min = 1, message = "至少要有一个属性")
private List<Prop> props;
}
Item
带有很多属性,属性里面有:pid
、vid
、pidName
和vidName
,如下所示:
public class Prop {
@NotNull(message = "pid不能为空")
@Min(value = 1, message = "pid必须为正整数")
private Long pid;
@NotNull(message = "vid不能为空")
@Min(value = 1, message = "vid必须为正整数")
private Long vid;
@NotBlank(message = "pidName不能为空")
private String pidName;
@NotBlank(message = "vidName不能为空")
private String vidName;
}
属性这个实体也有自己的验证机制,比如pid
和vid
不能为空,pidName
和vidName
不能为空等。
现在我们有个ItemController
接受一个Item
的入参,想要对Item进行验证,如下所示:
@RestController
public class ItemController {
@RequestMapping("/item/add")
public void addItem(@Validated Item item) {
doSomething();
}
}
在上图中,如果Item
实体的props
属性不额外加注释,只有@NotNull
和@Size
,无论入参采用@Validated
还是@Valid
验证,Spring Validation
只会对Item
的id
和props
做非空和数量验证,不会对props
字段里的Prop
实体进行字段验证,也就是@Validated
和@Valid
加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List
中有Prop
的pid
为空或者是负数,入参验证不会检测出来。
嵌套情况
为了能够进行嵌套验证,必须手动在Item
实体的props
字段上明确指出这个字段里面的实体也要进行验证。由于@Validated
不能用在成员属性(字段)上,但是@Valid
能加在成员属性(字段)上,而且@Valid
类注解上也说明了它支持嵌套验证功能,那么我们能够推断出:@Valid
加在方法参数时并不能够自动进行嵌套验证,而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated
或@Valid
来进行嵌套验证。
修改Item类如下所示:
public class Item {
@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;
@Valid // 嵌套验证必须用@Valid
@NotNull(message = "props不能为空")
@Size(min = 1, message = "props至少要有一个自定义属性")
private List<Prop> props;
}
然后我们在ItemController
的addItem
函数上再使用@Validated
或者@Valid
,就能对Item
的入参进行嵌套验证。此时Item
里面的props
如果含有Prop
的相应字段为空的情况,Spring Validation
框架就会检测出来,记录相应的错误。
@Validated和@Valid在嵌套验证功能上的区别
@Validated
:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid
进行嵌套验证。
@Valid
:用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid
进行嵌套验证。
BindingResult
@PostMapping("/a")
@ApiOperation(value = "测试", notes = "")
public void test(@RequestBody @Valid TestEntity test,BindingResult bindingResult) {
System.out.println(test.toString());
if (bindingResult.hasErrors()) {
throw new 自定义Exception("错误提示码",bindingResult.getFieldError().getDefaultMessage());
}
}
bindingResult.hasErrors()
判断是否校验通过,未通过bindingResult.getFieldError().getDefaultMessage()
获取在TestEntity
的属性设置的自定义message
,如果没有设置,则返回默认值javax.validation.constraints.XXX.message
。
@RequestMapping("/b")
@ResponseBody
public Response runBydomain(@RequestBody @Validated TaskDomainVO taskDomainVO, BindingResult bindingResult) {
log.info("Execution code analyze,", taskDomainVO.toString());
if (bindingResult.hasErrors()) {
String errorCode = bindingResult.getAllErrors().get(0).getDefaultMessage();
log.info("Execution failed, errorCode:{}", errorCode);
return new Response(ResultCode.GLOBAL_PARAM_ERROR, errorCode);
}
@Json***
jackson
的 mavern
依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.3</version>
</dependency>
使用的背景
springboot
项目中定义了很多类,我们在rest返回中直接返回或者在返回对象中使用这些类,spring已经使用jackson
自动帮我们完成这些的to json
。但是有时候自动转的json
内容太多,或者格式不符合我们的期望,因此需要调整类的to json过程
,或者说希望自定义类的json过程
。
@JsonProperty
概念
- 此注解用于属性上,作用是把该属性的名称序列化为另外一个名称,如把
trueName
属性序列化为name
,@JsonProperty(“name”)
。 - 对属性名称重命名,比如在很多场景下Java对象的属性是按照规范的驼峰书写,但在数据库设计时使用的是下划线连接方式,此处在进行映射的时候就可以使用该注解。
用法
例如:使用该注解将以下表结构转化为 Javabean
public class CustomerInfo{
private int id;
//使用 @JsonProperty注解将表结构中的字段映射到实体类中
@JsonProperty("customer_name")
private String customerName;
@JsonProperty("customer_id")
private String customerId;
@JsonProperty("product_id")
private String productId;
@JsonProperty("source_address")
private String sourceAddress;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getSourceAddress() {
return sourceAddress;
}
public void setSourceAddress(String sourceAddress) {
this.sourceAddress = sourceAddress;
}
}
@JsonIgnore
概念
- 用于属性或者方法上(最好是属性上),用来完全忽略被注解的字段和方法对应的属性,即便这个字段或方法可以被自动检测到或者还有其他的注解,一般标记在属性或者方法上,返回的
json
数据即不包含该属性。
用法
使用情景:需要把一个List
转换成json
格式的数据传递给前台。但实体类中基本属性字段的值都存储在快照属性字段中。此时我可以在业务层中做处理,把快照属性字段的值赋给实体类中对应的基本属性字段。最后,我希望返回的json
数据中不包含这两个快照字段,那么在实体类中快照属性上加注解@JsonIgnore
,那么最后返回的json
数据,将不会包含customerId
和productId
两个属性值。
public class CustomerInfo {
private int id;
//使用 @JsonIgnore注解在生成json数据时,忽略该字段
private String customerName;
@JsonIgnore
private String customerId;
@JsonIgnore
private String productId;
private String sourceAddress;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getSourceAddress() {
return sourceAddress;
}
public void setSourceAddress(String sourceAddress) {
this.sourceAddress = sourceAddress;
}
}
@JsonIgnoreProperties
概念
是类注解,作用是json
序列化时将java bean
中的一些属性忽略掉,序列化和反序列化都受影响。
用法:
@JsonIgnoreProperties(ignoreUnknown = true)
,将这个注解写在类上之后,就会忽略类中不存在的字段。这个注解还可以指定要忽略的字段,例如 @JsonIgnoreProperties(value = {"fullName", "comment"})
@Data
@JsonIgnoreProperties(value = {"fullName", "comment"})
public class User {
private String id;
private String name;
private String fullName;
private String comment;
private String mail;
@JsonIgnore
private String address;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date regDate;
private Date reg2Date;
}
说明:User类的fullName
和comment
字段会被@JsonIgnoreProperties
注解忽略。address
字段会被@JsonIgnore
注解忽略。regDate
会按照@JsonFormat(timezone = “GMT+8”, pattern = “yyyy-MM-dd HH:mm:ss”)
进行格式转。
@ApiOperation(value = "按用户id删除", notes="private")
@ApiImplicitParams({
@ApiImplicitParam(name = "userId", defaultValue = "2", value = "userID", required = true, dataType = "string", paramType = "path"),
})
@DeleteMapping(value = "/users/{userId}", produces = "application/json;charset=UTF-8")
public User delUser(@PathVariable String userId) {
User user = (User)userSvc.deleteById(userId);
log.info("rest del user={} by id={}", user, userId);
return user;
}
可以看到返回的对象是User,然后comment
、fullName
、address
属性被忽略了,regDate
的格式进行转换。
@JsonFormat
概念
用于属性或者方法上(最好是属性上),可以方便的把Date类型直接转化为我们想要的模式。
用法
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
private Date updateTime;
读取配置文件
@ConfigurationProperties
: 是springboot
的注解,用于把主配置文件(application.properties、application.yml
)中配置属性设置到对于的Bean属性上@PropertySource
:是spring的注解,用于加载指定的属性配置文件到Spring的Environment中,可以和@Value
、@ConfigurationProperties
配合使用@EnableConfigurationProperties
: 用来开启ConfigurationProperties
注解配置;如果不使用的话,@ConfigurationProperties
加入注解的类上加@Component
也是可以交于springboot
管理。
读取默认配置文件(application.properties、application.yml)
application.yml
配置:
person:
name: wumingshi
实现方式一 @ConfigurationProperties + @Component作用于类上
@ConfigurationProperties(prefix = "person")
@Componment
@Data // lombok,用于自动生成getter、setter
public class Person {
private String name;
}
@RestController
@RequestMapping("/db")
public class TestController {
@Autowired
private Person person;
@GetMapping("/person")
public String parsePerson() {
return person.getName();
}
}
实现方式二 @ConfigurationProperties + @Bean作用在配置类的bean方法上
@Data
public class Person {
private String name;
}
@Configuration
public class PersonConf{
@Bean
@ConfigurationProperties(prefix="person")
public Person person(){
return new Person();
}
}
@RestController
@RequestMapping("/db")
public class TestController {
@Autowired
private Person person;
@GetMapping("/person")
public String parsePerson() {
return person.getName();
}
}
实现方式三 @ConfigurationProperties注解到普通类、 @EnableConfigurationProperties注解定义为bean
@ConfigurationProperties(prefix="person")
@Data
public class Person {
private String name;
}
// 说明: @EnableConfigurationProperties可以直接注到启动类上,也可以放在自定义配置类,自定义配置类使用@Configuration标注
@SpringBootApplication
@EnableConfigurationProperties(Person.class)
public class DbApplication {
public static void main(String[] args) {
SpringApplication.run(DbApplication.class, args);
}
}
@RestController
@RequestMapping("/db")
public class TestController {
@Autowired
private Person person;
@GetMapping("/person")
public String parsePerson() {
return person.getName();
}
}
实现方式四 @Value作用属性上
@RestController
@RequestMapping("/db")
public class TestController {
@Value("${person.name}")
private String name;
@GetMapping("/person")
public String parsePerson() {
return name;
}
}
实现方式五 使用自带的Environment对象
@RestController
@RequestMapping("/db")
public class TestController {
@Autowired
private Environment environment;
@GetMapping("/person")
public String parsePerson() {
return environment.getProperty("person.name");
}
}
读取自定义配置文件(比如:config.properties)
说明: PropertySource默认不支持yml、yaml
config.properties
配置
person.name=wumingshi
实现方式一 @Configuration + @PropertySource + Environment
@Data
public class Person {
private String name;
}
@Configuration
@PropertySource(value = "classpath:config.properties")
public class PersonConf {
@Autowired
private Environment environment;
@Bean
public Person person(){
Person person = new Person();
person.setName(environment.getProperty("person.name"));
return person;
}
}
@RestController
@RequestMapping("/db")
public class TestController {
@Autowired
private Person person;
@GetMapping("/person")
public String parsePerson() {
return person.getName();
}
}
实现方式二 @Component+ @PropertySource + @Value
@Component
@PropertySource(value = "classpath:config.properties")
@Data
public class Person {
@Value("${person.name}")
private String name;
}
@RestController
@RequestMapping("/db")
public class TestController {
@Autowired
private Person person;
@GetMapping("/person")
public String parsePerson() {
return person.getName();
}
}
实现方式三 @Component+ @PropertySource + @ConfigurationProperties
@Component
@PropertySource("classpath:dangxiaodang.properties")
@ConfigurationProperties(prefix = "person")
public class Person{
private String name;
}
@RestController
@RequestMapping("/db")
public class TestController {
@Autowired
private Person person;
@GetMapping("/person")
public String parsePerson() {
return person.getName();
}
}
实际使用
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Data
@PropertySource(value = "classpath:config.properties")
@Component
public class ProjectConfig {
@Autowired
private Environment env;
public String getProperty(String str){
return env.getProperty(str);
}
public String getProjectPathForCover(String str){
return env.getProperty("cover_root_path")+"/"+str;
}
public String getProjectRootPath(){return env.getProperty("root_path");}
}