栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

validate与国际化配合效果更加~

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

validate与国际化配合效果更加~

前言:当我们用@valid或者@validate验证controller层接收前端发来的对象数据时,在对象的实体类上的validation相关的验证注解有起效了,很多时候我们会写message=“xxx”自定义验证不通过的内容。例如:@NotNull(message = "id不能为空")很不优雅。点开源码可以看见默认的message,String message() default "{javax.validation.constraints.NotNull.message}";,这就相当优雅了,设想我们有很多的业务有很多的业务提示,直接写死的话不够优雅,后期也不方便待修改,于是就有了这篇博客,使用国际化优雅的返回提示信息。本文从对validator和国际化的用法开始介绍。

validator数据验证

数据验证给了我们很多方便,避免了不少在接收完参数之后逐一验证参数的合法性所写的大量验证代码。

相关注解

@NotNull不为空@NotBlank不为空白NotEmpty至少有一个@Range指定范围@Length指定长度范围@Min不能小于最小值@Max不能大于最大值@Email邮箱验证@URL指定URL

还有很多注解可查看javax.validation和org.hibernate.validator包。

@Validated 和 @Valid

@Valid和@Validated都可以用于验证,@Valid是JSR-303规范的注解,可以用在参数、属性、嵌套属性上,@Validated不能用在属性上。
但@Validated支持分组。注解源码如下:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface Valid {
}
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface Validated {
    Class[] value() default {};
}
自定义注解验证

可以自定义验证的注解,需要实现ConstraintValidator注解。以下写一个样例,功能是验证地址只能是自己的安全列表中的地址。

@MustIn:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MustInConstraintValidator.class)
public @interface MustIn {

    String message() default "不可输入非法地址";

    Class[] groups() default {};
    Class[] payload() default {};
}

MustInConstraintValidator:

public class MustInConstraintValidator implements ConstraintValidator {

    private final String[] local = {"安徽","淮南","寿县","北京","合肥","上海"};

    private final Log logger = LogFactory.getLog(MustInConstraintValidator.class);
    
    @Override
    public void initialize(MustIn mustIn) {
        logger.info("初始化自定义validate注解MustIn");
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 在local数组中就可
        return "".equals(value) || Arrays.stream(local).parallel().anyMatch(e -> e.equals(value));
    }
}
用法

相关依赖:

不要直接cv,自己找一下对应的版本号。

        
            org.hibernate
            hibernate-validator
            ${hibernate.version}
        
        
        
            javax.validation
            validation-api
        
        
        
            javax.el
            javax.el-api
            ${javax-el.version}
        
        
            org.glassfish
            javax.el
            ${javax-el.version}
        
 
        
            org.hibernate
            hibernate-core
        
        
        
            org.hibernate
            hibernate-entitymanager
        
        
            org.hibernate
            hibernate-envers
        
        
            org.hibernate
            hibernate-c3p0
        
        
            org.hibernate
            hibernate-proxool
        
        
            org.hibernate
            hibernate-ehcache
        

实体类Student:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    @NotNull(message = "id不能为空")
    private Long id;

    @NotNull(message = "插入时,name不能为空", groups = {InsertGroup.class})
    @NotBlank(message = "修改时,name不能为空", groups = {UpdateGroup.class})
    private String name;

    @MustIn(message = "不可输入非法地址")
    private String local;

    @Length(min = 11, max = 11, message = "11位手机号")
    private String tel;
}

controller层DemoController:

@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private MessageSource messageSource;

    private final Log logger = LogFactory.getLog(MustInConstraintValidator.class);

    @PostMapping("validate")
    public void testValidator(@Validated({InsertGroup.class, Default.class}) @RequestBody Student student){
        logger.info(student);
    }
}

异常拦截(我直接返回字符串了,一般会封装一个响应类)

@RestControllerAdvice(annotations = {RestController.class, Controller.class})
public class ExceptionHandle {

    @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String validate(MethodArgumentNotValidException e){
        return Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
    }
}

postman测试一下下:

ok 下面介绍国际化

国际化

springboot对国际化的支持很好,只需要简单的配置就可以实现。

1 引入依赖(省略)
2 配置
application.yml

spring: 
  messages: 
    basename: i18n/test

3 创建国际化文件
在basname指定的位置创建也就是在resources下创建i18n文件夹,在i18n中创建test.properties相关文件。


注意格式basename_local.properties是下划线。

4 配置类

@Configuration
public class ValidatorConfig {

    @Autowired
    private MessageSource messageSource;

    
    @Bean
    public Validator getValidator() {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(this.messageSource);
        return validator;
    }

    
    @Bean
    public LocaleResolver localeResolver() {
        AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();
        // 设置默认的Local为中文
        acceptHeaderLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return  acceptHeaderLocaleResolver;
    }
}

5 国际化文件的内容:

test.properties

test = test

test_en_US.properties

test = hihi

test_zh_CN.properties

test = 嗨嗨

6 代码调用测试

@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private MessageSource messageSource;

    private final Log logger = LogFactory.getLog(DemoController.class);

    @GetMapping("i18n")
    public String i18nTest(){
        return messageSource.getMessage("test",null, LocaleContextHolder.getLocale());
    }
}

postman 调用查看
中文(配置类中的默认)

英文(添加请求头Accept-Language值为en-US)注意en-US不是下划线

自定义业务验证提示国际化文件

严重注解中的message可以指定国际化配置。如@NotNull(message = "{student.idNotNull}")只需要在国际化配置中配置tudent.idNotNull即可。问题是所有的业务配置全部都写在一个国际化文件里岂不是很乱,很不优雅。最好是一个业务一个国际化配置。那么咋么实现呢?在配置文件中可以这样配置spring.messages.basename=i18n/test,i18n/student中间用逗号隔开。但直接在配置文件中这么写还是不够优雅。于是我动态加载了spring.messages.basename这一配置。只要在启动之初利用System.setProperty注入即可。我的思路是,启动时读取resources/i18n下的所有文件,然后将国际化配置文件拼接最后用System.setProperty注入即可。于是自定义启动插件类Launch,编写静态方法launcher()负责读取文件写入spring.messages.basename,在SpringBoot启动类的main方法开头添加 Launch.launcher();即可。代码如下:

Launch类:

public class Launch {

    private static final Log logger = LogFactory.getLog(Launch.class);
    static {
        logger.info("加载自定义启动组件");
    }
    public static void launcher() {
        // 加载国际化配置文件
        setProps();
    }

    // 读取所有配置文件 添加国际化配置文件
    @SneakyThrows
    private static void setProps() {
        StringBuffer i18nValue = new StringBuffer();
        File file = ResourceUtils.getFile("classpath:i18n");
        File[] properties = file.listFiles(f -> f.getName().endsWith("properties"));
        assert properties != null;
        Arrays.stream(properties)
                .parallel()
                .map(File::getName)
                .forEach(e -> {
                    if (!e.contains("_")){
                        e = e.substring(0,e.indexOf("."));
                        i18nValue.append("i18n/");
                        i18nValue.append(e);
                        i18nValue.append(",");
                    }
                });
        // 删除多余的逗号
        i18nValue.deleteCharAt(i18nValue.length()-1);
        System.setProperty("spring.messages.basename", String.valueOf(i18nValue));
        logger.info("spring.messages.basename:" + i18nValue);
    }
}

启动类:

@SpringBootApplication
public class FileDemoApplication {
    public static void main(String[] args) {
        //启动自定义组件
        Launch.launcher();
        SpringApplication.run(FileDemoApplication.class,args);
    }
}


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/755970.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号