This is the 29th day of my participation in the More Text Challenge. For more details, see more Text Challenge
Hibernate – Validator the most complete internationalization solution
In order to implement Hibernate-Validator internationalization almost to death, recently in the study of Hibernate-Validator and internationalization, in the wall and the wall to find a lot of time, may be because of the version of the update iteration, found the basic data can not be used. Oneself toss about for half a day, finally figure out, hereby record.
How to check parameters elegantly
Hibernate-validator is used in the following ways:
1. Native SpringBoot environment
Environmental information
- SpringBoot : 2.3.8.RELEASE
- Hibernate validator: 6.1.7. The Final
validation
Hibernate-validator is used to verify that the parameters requested in the previous paragraph meet the criteria
The controller layer:
@RestController
@RequestMapping("valid")
public class TestController {
@PostMapping("/test")
public Person test(@Valid @RequestBody Person person) {
returnperson; }}Copy the code
Entity class
public class Person {
@NotBlank()
private String name;
@Range(min = 2, max = 100)
private Integer age;
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge(a) {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString(a) {
return "Person{" +
"name='" + name + '\' ' +
", age=" + age +
'} '; }}Copy the code
You can see from the entity class that there are two main validation conditions
- Person. name cannot be empty
- Person. age ranges from 2 to 100
If you call the interface directly at this point with data that does not meet the criteria, you will return a 404, and you will not see the validation error message, because Spring intercepted the validation exception and returned a 404
Not returning 404 requires a custom exception interceptor that handles the validation exception itself
@RestControllerAdvice
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler({BindException.class})
public String bindExceptionHandler(final BindException e) { String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.j oining("; "));
return "{\"errors\":\"" + message + "\"}";
}
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(MethodArgumentNotValidException.class)
public String handler(final MethodArgumentNotValidException e) { String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.j oining("; "));
return "{\"errors\":\"" + message + "\"}";
}
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(ConstraintViolationException.class)
public String handler(final ConstraintViolationException e) {
String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("; "));
return "{\"errors\":\"" + message + "\"}"; }}Copy the code
Validation exceptions are these three cases:
- BindException
- MethodArgumentNotValidException
- ConstraintViolationException
After adding global exception handling, call the interface again:
There are two problems with this post-processing error message
- How to internationalize and return to English and Chinese
- The prompt message is not clear. Can you customize it
Internationalization problem
Spring provides i18N solution, with breakpoints can also find check failure is also go after this process, core classes for AcceptHeaderLocaleResolver
If you want to change it to another format or value of the header, you can override the preHandle method by inheriting the LocaleChangeInterceptor. If you want to change it to another format or value of the header, you can override the preHandle method by inheriting the LocaleChangeInterceptor. Equip custom bean usage
User-defined verification information
1. If internationalization is not considered
This is the easiest case to modify directly in the validation Bean
@notblank (message = "name must not be blank ")
private String name;
@Range(min = 2, max = 100, message = "年龄需要在2~100岁之间")
private Integer age;
Copy the code
2. Consider internationalization
In this case, you can’t simply write dead messages, you need to use hibernate- Valid internationalization
As you can see from the Hibernate source code, internationalization information is stored in a series of Properties files, which gives me the opportunity to override this by defining an internationalization file with the same name under resource
First, modify the bean’s message to bean el expression
@NotBlank(message = "{person.name}")
private String name;
@Range(min = 2, max = 100, message = "{person.age}")
private Integer age;
Copy the code
Then, define the key in your own internationalization file :(make sure to pay attention to the encoding)
After calling the interface:
If you need to customize the international file name and location, add the following configuration classes
@Configuration
public class MessageConfig {
public ResourceBundleMessageSource getMessageSource(a) throws Exception {
ResourceBundleMessageSource resourceBundle = new ResourceBundleMessageSource();
resourceBundle.setDefaultEncoding(StandardCharsets.UTF_8.name());
// Specify the internationalization file path
resourceBundle.setBasenames("i18n/valid");
return resourceBundle;
}
@Bean
public Validator getValidator(a) throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(getMessageSource());
return validator;
}
@Bean
public MethodValidationPostProcessor validationPostProcessor(a) throws Exception {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
// Specify the request validator
processor.setValidator(getValidator());
returnprocessor; }}Copy the code
The above configuration allows you to implement the validation message customization using the specified internationalization file
2. DropWizard project
DropWizard is a lightweight microservices development framework that encapsulates Hibernate-Valid itself, but does not have an internationalization solution
You need to use the hibernate-valid primitive internationalization method by setting the default language.
If you do not want the default, skip this section
Entity class:
public class Person {
@NotBlank()
private String name;
@Range(min = 2, max = 100)
private Integer age;
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge(a) {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString(a) {
return "Person{" +
"name='" + name + '\' ' +
", age=" + age +
'} '; }}Copy the code
The resource class
@Service
@Path("/test")
public class TestResource {
@POST
@Path("/test")
@Consumes({APPLICATION_JSON})
@Produces({APPLICATION_JSON})
public String test(Person person, @Context final HttpServletRequest request) {
Set<ConstraintViolation<Person>> validate = getValidator().validate(person);
if(! validate.isEmpty()) { StringJoiner message =new StringJoiner(";");
for (Object aValidate : validate) {
message.add(((ConstraintViolation) aValidate).getMessage());
}
return "{\"result\":\"" + message.toString() + "\"}";
}
return "{\"result\":\"ok\"}"; }}Copy the code
1. You only need to customize the message body
This scenario simply requires modifying Message in the entity class
public class Person {
@notblank (message = "name must not be empty ")
private String name;
@range (min = 2, Max = 100, message = "age = 2~100 ")
private Integer age;
}
Copy the code
The calling interface will return a fixed message:
2. Only internationalize
This scenario uses hibernate-Valid’s built-in internationalization message, which controls the Language type with accept-Language in the header. For example, en-us, zh-cn
And then after you get the header on the back end, you set the language
Modifying an entity class
public class Person {
@NotBlank()
private String name;
@Range(min = 2, max = 100)
private Integer age;
}
Copy the code
Modifying a Resource Class
@POST
@Path("/test")
@Consumes({APPLICATION_JSON})
@Produces({APPLICATION_JSON})
public String test(Person person, @Context final HttpServletRequest request) {
// Get the language in the header
String language = request.getHeader("Accept-Language");
Set<ConstraintViolation<Person>> validate = getValidator(language).validate(person);
if(! validate.isEmpty()) { StringJoiner message =new StringJoiner(";");
for (Object aValidate : validate) {
message.add(((ConstraintViolation) aValidate).getMessage());
}
return "{\"result\":\"" + message.toString() + "\"}";
}
return "{\"result\":\"ok\"}";
}
private static Validator getValidator(String language) {
// Set the language
Locale.setDefault(language.contains("zh")? Locale.CHINA : Locale.ENGLISH);return Validation.byDefaultProvider().configure()
.buildValidatorFactory()
.getValidator();
}
Copy the code
Call interface effect:
Need to internationalize and customize the message body
This scenario requires adding configuration classes to specify internationalization files
private static Validator getValidator(String language) {
Locale.setDefault(new Locale(language.contains("zh")?"zh" : "en"));
return Validation.byDefaultProvider().configure()
// Internationalize the file configuration
.messageInterpolator(new MyResourceBundleMessageInterpolator(new PlatformResourceBundleLocator("i18n/valid")))
.buildValidatorFactory()
.getValidator();
}
Copy the code
In Java, the default encoding of the properties configuration file is ISO-8859-1, which does not support Chinese characters. Therefore, garbled characters are required to be transcoded.
public class MyResourceBundleMessageInterpolator extends ResourceBundleMessageInterpolator {
public MyResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator) {
super(userResourceBundleLocator);
}
@Override
public String interpolate(String message, Context context) {
String result = super.interpolate(message, context);
try {
return new String(result.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
} catch (Exception e) {
returnresult; }}}Copy the code
Change the entity class message to the EL expression type:
public class Person {
@NotBlank(message = "{person.name}")
private String name;
@Range(min = 2, max = 100, message = "{person.age}")
private Integer age;
}
Copy the code
Add internationalization file under resouce
Use accept-language to control the Language type and invoke the interface effect:
3. SpringBoot project
Since Jersey is not the same as normal SpringMVC, it cannot be internationalized in the traditional way, so if you do not know or do not use Jersey in your project, you can skip this section
Entity class:
public class Person {
@NotBlank()
private String name;
@Range(min = 2, max = 100)
private Integer age;
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge(a) {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString(a) {
return "Person{" +
"name='" + name + '\' ' +
", age=" + age +
'} '; }}Copy the code
The resource class
@Component
@Path("/test")
public class TestResource {
@Autowired
private Validator validate;
@POST
@Path("/test")
@Consumes({APPLICATION_JSON})
@Produces({APPLICATION_JSON})
public String test(Person person, @Context final HttpServletRequest request) {
Set<ConstraintViolation<Person>> errorResults = validate.validate(person);
if(! errorResults.isEmpty()) { StringJoiner message =new StringJoiner(";");
for (Object result : errorResults) {
message.add(((ConstraintViolation) result).getMessage());
}
return "{\"result\":\"" + message.toString() + "\"}";
}
return "{\"result\":\"ok\"}";
}
Copy the code
1. You only need to customize the message body
This scenario simply requires modifying Message in the entity class
public class Person {
@notblank (message = "name must not be empty ")
private String name;
@range (min = 2, Max = 100, message = "age = 2~100 ")
private Integer age;
}
Copy the code
The calling interface will return a fixed message:
2. Only internationalize
This scenario uses hibernate-Valid’s own internationalization message and Spring’s I18N internationalization rules
The Language type is controlled by accept-language in the header. For example, en-us, zh-cn
Modifying an entity class
public class Person {
@NotBlank()
private String name;
@Range(min = 2, max = 100)
private Integer age;
}
Copy the code
Call interface effect:
Need to internationalize and customize the message body
This scenario requires adding configuration classes to specify internationalization files
@Configuration
public class MessageConfig {
public MessageSource getMessageSource(a) throws Exception {
ResourceBundleMessageSource rbms = new ResourceBundleMessageSource();
rbms.setDefaultEncoding(StandardCharsets.UTF_8.toString());
// Specify the internationalization file path
rbms.setBasenames("i18n/valid");
return rbms;
}
@Bean
public LocalValidatorFactoryBean getValidator(a) throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(getMessageSource());
returnvalidator; }}Copy the code
Change the entity class message to the EL expression type:
public class Person {
@NotBlank(message = "{person.name}")
private String name;
@Range(min = 2, max = 100, message = "{person.age}")
private Integer age;
}
Copy the code
Add internationalization files under resouce, making sure that the file encoding format is the same as in the configuration
The accept-language is used to control the Language type and invoke the interface effect:
summary
Hibernate-valid internationalization is relatively difficult. You need to go deep into the source code to understand how to configure it. There is no documentation even on the official website. Ha ha
Learn more skills now, in the future, less to say a word, you and I are refueling