Spring国际化和Validation详解

SpringBoot国际化和Validation融合

场景

在应用交互时,可能需要根据客户端得语言来返回不同的语言数据。

前端通过参数、请求头等往后端传入locale相关得参数,后端获取参数,根据不同得locale来获取不同得语言得文本信息返回给前端。

实现原理

SpringBoot支持国际化和Validation,主要通过MessageSource接口和Validator实现。

国际化配置‌

  • 编写国际化配置文件,如messages_en_US.propertiesmessages_zh_CN.properties,并置于resources/i18n目录下。
  • 配置application.ymlapplication.properties以指定国际化文件的位置,例如spring.messages.basename=i18n/messages
  • 配置LocaleResolver以解析当前请求的locale,常用的实现是AcceptHeaderLocaleResolver,它通过请求头accept-language获取当前的locale。

‌Validation配置‌

引入spring-boot-starter-validation依赖以支持Validation功能

1
org.springframework.bootspring-boot-starter-validation

配置LocalValidatorFactoryBean以使用国际化的校验消息,需注入MessageSource

示例

引入依赖

1
org.springframework.bootspring-boot-starter-validationorg.springframework.bootspring-boot-starter-web

国际化配置文件

src/main/resources/i18n目录下创建两个文件:messages_en_US.propertiesmessages_zh_CN.properties

1
2
3
4
5
#messages_en_US.properties
welcome.message=Welcome to our website!
 
#messages_zh_CN.properties
welcome.message=欢迎来到我们的网站!

配置MessageSource‌

在Spring Boot的配置文件中(application.propertiesapplication.yml),配置MessageSource以指定国际化文件的位置。

如果你打算使用Validation的默认国际化文件,你实际上不需要为Validation单独指定文件,因为LocalValidatorFactoryBean会自动查找ValidationMessages.properties

但是,你可以配置自己的国际化文件,并让MessageSource同时服务于你的应用消息和Validation消息。

1
2
3
# 国际化文件被放置在src/main/resources/i18n目录下,并以messages为前缀
spring.messages.basename=i18n/messages,org.hibernate.validator.ValidationMessages
spring.messages.encoding=utf-8

注意:上面的配置假设你的自定义消息文件位于i18n/messages.properties,而Validation的默认消息文件是org.hibernate.validator.ValidationMessages.properties

实际上,ValidationMessages.properties文件位于Hibernate Validator的jar包中,所以你不需要显式地将它包含在你的资源目录中。Spring Boot会自动从classpath中加载它。

配置LocalValidatorFactoryBean‌

在你的配置类中,创建一个LocalValidatorFactoryBean的bean,并将MessageSource注入到它中。

这样,LocalValidatorFactoryBean就会使用Spring的MessageSource来解析校验消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
 
import java.util.Properties;
 
@Configuration
public class ValidateConfig {
 
    @Bean
    public LocalValidatorFactoryBean validatorFactoryBean(MessageSource messageSource) {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setValidationMessageSource(messageSource);
        //        设置使用 HibernateValidator 校验器
        factoryBean.setProviderClass(HibernateValidator.class);
//        设置 快速异常返回 只要有一个校验错误就立即返回失败,其他参数不在校验
        Properties properties = new Properties();
        properties.setProperty("hibernate.validator.fail_fast", "true");
        factoryBean.setValidationProperties(properties);
//        加载配置
        factoryBean.afterPropertiesSet();
        return factoryBean;
    }
}

使用校验

1
2
3
4
5
6
7
8
9
import javax.validation.constraints.NotNull;
 
public class MyModel {
 
    @NotNull(message = "{not.null.message}")
    private String field;
 
    // getters and setters
}

messages.properties文件中,你可以添加

1
not.null.message=This field cannot be null.

而在Hibernate Validator的ValidationMessages.properties文件中,已经包含了默认的校验消息,如{javax.validation.constraints.NotNull.message}的值。

自定义校验

  • 定义约束注解:创建一个注解,用@Constraint标记,并定义messagegroupspayload属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
 
 
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyValidateContent.class)
public @interface MyValidate {
    String message() default "";
    Class>[] groups() default {};
    Class extends Payload>[] payload() default {};
}
  • 实现约束验证器**:创建一个实现了ConstraintValidator接口的类,并重写isValid方法
  • isValid方法中使用ConstraintValidatorContext**‌:如果验证失败,使用ConstraintValidatorContextbuildConstraintViolationWithTemplate方法来构建ConstraintViolation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.example.dto.ParamVo;
 
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
 
 
public class MyValidateContent implements ConstraintValidator {
    @Override
    public void initialize(MyConstraint constraintAnnotation) {
        // 初始化代码(如果需要的话)
    }
    @Override
    public boolean isValid(ParamVo paramVo, ConstraintValidatorContext constraintValidatorContext) {
        if ("N".equals(paramVo.getSex())) {
            if (paramVo.getAge()

在这个例子中,如果sexN并且age小于18,验证器将使用ConstraintValidatorContext来构建一个带有错误消息的ConstraintViolation

消息模板"{template1}"将会在验证失败时被解析,并替换为你在MyValidate注解中定义的默认消息或你在messages.properties文件中定义的国际化消息。

确保你的MyValidate注解定义了一个message属性,并且你在messages.properties文件中有一个对应的条目例如:

1
2
template1=男性要大于18
template2=女性要大于20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import com.example.validate.MyValidate;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
 
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
 
@Getter
@Setter
@MyValidate
public class ParamVo {
    @NotBlank(message = "{javax.validation.constraints.NotNull.message}")
    private String sex;
    @NotNull(message = "age 不能为空")
    private Integer age;
    @NotBlank(message = "{name.not.null}")
    @Length(max = 3,message = "{name.length.max}")
    private String name;
}

Controller层异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
 
import java.util.HashMap;
import java.util.Map;
 
 
@RestControllerAdvice
public class GlobalException {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<object data-origwidth="" data-origheight="" style="width: 1264px;"> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map errors = new HashMap();
        BindingResult result = ex.getBindingResult();
        for (FieldError error : result.getFieldErrors()) {
            errors.put(error.getField(), error.getDefaultMessage());
        }
        // 这里可以根据实际需求定制返回的错误信息结构
        Map response = new HashMap();
        response.put("status", HttpStatus.BAD_REQUEST.value());
        response.put("errors", errors);
        return new ResponseEntity(response, HttpStatus.BAD_REQUEST);
    }
}
</object>

内部方法校验

1
2
3
4
5
6
7
8
9
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
//@Validated
@Validated
public interface ServiceIntface {
    //校验返回值,校验入参
    @NotNull Object hello(@NotNull @Min(10) Integer id, @NotNull String name);
}
1
2
3
4
5
6
7
8
9
10
11
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
 
@Slf4j
@Service
public class ServiceImpl implements ServiceIntface {
    @Override
    public Object hello(Integer id, String name) {
        return null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
 
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
 
 
@RestControllerAdvice
public class GlobalException {
 
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<object data-origwidth="" data-origheight="" style="width: 1264px;"> handleValidationExceptions(ConstraintViolationException ex) {
        Map errors = new HashMap();
        Set> constraintViolations = ex.getConstraintViolations();
        for (ConstraintViolation> constraintViolation : constraintViolations) {
            String key = constraintViolation.getPropertyPath().toString();
            String message = constraintViolation.getMessage();
            errors.put(key, message);
        }
 
        // 这里可以根据实际需求定制返回的错误信息结构
        Map response = new HashMap();
        response.put("status", HttpStatus.BAD_REQUEST.value());
        response.put("errors", errors);
        return new ResponseEntity(response, HttpStatus.BAD_REQUEST);
    }
}
</object>
  • 校验写在接口上的,抛出异常javax.validation.ConstraintViolationException
  • 校验写在具体实现,抛出异常javax.validation.ConstraintDeclarationException

注意点

代码中国际化使用

代码里响应,手动获取使用MessageSource的getMessage方法即可,也就是spring容器中的getMessage()

1
2
3
4
5
6
7
8
# messages_en_US.properties
welcome.message=Welcome to our website!
 
# messages_zh_CN.properties
welcome.message=欢迎来到我们的网站!
 
#定义消息,并使用占位符{0}、{1}等表示参数位置
#welcome.message=欢迎{0}来到{1}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//创建一个配置类来配置LocaleResolver,以便根据请求解析当前的语言环境:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
 
import java.util.Locale;
 
@Configuration
public class WebConfig implements WebMvcConfigurer {
 
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
        sessionLocaleResolver.setDefaultLocale(Locale.US); // 设置默认语言
        return sessionLocaleResolver;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//创建一个控制器来使用国际化的消息
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import javax.servlet.http.HttpServletRequest;
import java.util.Locale;
 
@RestController
@RequestMapping("/hello")
public class HelloController {
 
    @Autowired
    private MessageSource messageSource;
 
    @GetMapping
    public String hello(HttpServletRequest request) {
        Locale locale = (Locale) request.getAttribute(org.springframework.web.servlet.LocaleResolver.LOCALE_RESOLVER_ATTRIBUTE);
         //messageSource.getMessage("welcome.message", new Object[]{"张三", "中国"}, Locale.CHINA)。
        return messageSource.getMessage("welcome.message", null, locale);
    }
}

Locale获取

默认情况下spring注册的messageSource对象为ResourceBundleMessageSource,会读取spring.message配置。

请求中Locale的获取是通过LocaleResolver进行处理,默认是AcceptHeaderLocaleResolver,通过WebMvcAutoConfiguration注入,从Accept-Language请求头中获取locale信息。

此时前端可以在不同语言环境时传入不同的请求头Accept-Language即可达到切换语言的效果

1
2
Accept-Language: en-Us
Accept-Language: zh-CN

默认情况下前端请求中的不用处理,如果约定其他信息传递Local,使用自定义的I18nLocaleResolver替换默认的AcceptHeaderLocaleResolver,重写resolveLocale方法就可以自定义Locale的解析逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import cn.hutool.core.util.StrUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
 
/**
 *
 */
@Configuration
public class I18nConfig {
    @Bean
    public LocaleResolver localeResolver() {
        return new I18nLocaleResolver();
    }
 
    /**
     * 获取请求头国际化信息
     * 使用自定义的I18nLocaleResolver替换默认的AcceptHeaderLocaleResolver,重写resolveLocale方法就可以自定义Locale的解析逻辑。
     *
     * 自定义后使用content-language传Locale信息,使用_划分语言个地区。
     * content-language: en_US
     * content-language: zh_CN
     */
    static class I18nLocaleResolver implements LocaleResolver {
 
        @Override
        public Locale resolveLocale(HttpServletRequest httpServletRequest) {
            String language = httpServletRequest.getHeader("content-language");
            Locale locale = Locale.getDefault();
            if (StrUtil.isNotBlank(language)) {
                String[] split = language.split("_");
                locale = new Locale(split[0], split[1]);
            }
            return locale;
        }
 
        @Override
        public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
 
        }
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持IT俱乐部。

本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/code/java/14433.html
上一篇
下一篇
联系我们

联系我们

在线咨询: QQ交谈

邮箱: 1120393934@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部