【发布时间】:2021-12-30 17:29:27
【问题描述】:
我正在一个运行 Spring Batch 作业的平台上工作,这些作业负责从第三方应用程序检索一组对象,执行 bean 验证并将任何违反约束的情况返回给第三方应用程序供用户使用正确(没有违规的项目被转换并传递给另一个应用程序)。现在,我们使用 Spring Boot 配置的Validator,这一切在英文中都很好用。
我们正在扩展哪些用户可以访问第三方应用程序,现在需要以适合创建对象的用户的语言提供约束验证。我有一种方法可以查找特定对象所需的语言/区域设置,但我缺少的是如何告诉Validator 由validate(<T> object) 方法返回的Set<ConstraintViolation<T>> 中消息的区域设置。此外,可能同时运行多个作业,每个作业都验证自己的对象类型,并需要以不同的语言报告违规行为。理想情况下,最好有一个validate(<T> object, Locale locale) 方法,但Validator 接口中不存在该方法。
我的第一个想法是编写一个自定义MessageInterpolator,并在每次验证之前设置适当的Locale(请参阅下面的ValueMessageInterpolator 和DemoJobConfig)但是它不是线程安全的,所以我们可以结束用错误的语言处理消息。
我也考虑过是否有办法使用LocaleResolver 界面来代替,但我没有看到与MessageInterpolator 没有相同问题的解决方案。
根据我目前所确定的,似乎我唯一的解决方案是:
- 为每个需要一个的批处理作业/步骤实例化单独的
Validators 和MessageInterpolators,并使用已经介绍的方法。由于在这些对象之间循环,这种方法似乎效率很低。 - 创建一个包含
Validators 集合的服务bean,每个所需的区域设置一个。然后,每个批处理作业/步骤都可以引用这个新服务,并且该服务将负责委派给适当的Validator。可以像这样设置验证器,并将所需的验证器数量限制为我们支持的语言数量。
javax.validation.Validator caFRValidator = Validation.byProvider(HibernateValidator.class).configure().localeResolver(context -> {return Locale.CANADA_FRENCH;}).buildValidatorFactory().getValidator();
javax.validation.Validator usValidator = Validation.byProvider(HibernateValidator.class).configure().localeResolver(context -> {return Locale.US;}).buildValidatorFactory().getValidator();
javax.validation.Validator germanValidator = Validation.byProvider(HibernateValidator.class).configure().localeResolver(context -> {return Locale.GERMANY;}).buildValidatorFactory().getValidator();
- 不要直接调用
Validator,而是创建一个只接受对象进行验证的微服务,然后通过Accept-Language 标头传入必要的Locale。虽然我可能只拥有一个Validatorbean 就可以逃脱惩罚,但这似乎不必要地复杂。
是否有替代方法可以用来解决这个问题?
我们目前正在使用 2.5.3 spring-boot-starter-parent pom 来管理依赖项,并且在我们需要实施这些更改时可能会更新到最新的 2.6.x 版本。
ValueMessageInterpolator.java
public class ValueMessageInterpolator implements MessageInterpolator {
private final MessageInterpolator interpolator;
private Locale currentLocale;
public ValueMessageInterpolator(MessageInterpolator interp) {
this.interpolator = interp;
this.currentLocale = Locale.getDefault();
}
public void setLocale(Locale locale) {
this.currentLocale = locale;
}
@Override
public String interpolate(String messageTemplate, Context context) {
return interpolator.interpolate(messageTemplate, context, currentLocale);
}
@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
return interpolator.interpolate(messageTemplate, context, locale);
}
}
ToBeValidated.java
public class ToBeValidated {
@NotBlank
private final String value;
private final Locale locale;
// Other boilerplate code removed
}
DemoJobConfig.java
@Configuration
@EnableBatchProcessing
public class DemoJobConfig extends DefaultBatchConfigurer {
@Bean
public ValueMessageInterpolator buildInterpolator() {
return new ValueMessageInterpolator(Validation.byDefaultProvider().configure().getDefaultMessageInterpolator());
}
@Bean
public javax.validation.Validator buildValidator(ValueMessageInterpolator valueInterp) {
return Validation.byDefaultProvider().configure().messageInterpolator(valueInterp).buildValidatorFactory().getValidator();
}
@Bean
public Job configureJob(JobBuilderFactory jobFactory, Step demoStep) {
return jobFactory.get("demoJob").start(demoStep).build();
}
@Bean
public Step configureStep(StepBuilderFactory stepFactory, javax.validation.Validator constValidator, ValueMessageInterpolator interpolator) {
ItemReader<ToBeValidated> reader =
new ListItemReader<ToBeValidated>(Arrays.asList(
new ToBeValidated("values1", Locale.US), // (No errors)
new ToBeValidated("", Locale.US), // value: must not be blank
new ToBeValidated("", Locale.CANADA), // value: must not be blank
new ToBeValidated("value3", Locale.CANADA_FRENCH), // (No errors)
new ToBeValidated("", Locale.FRANCE), // value: ne doit pas être vide
new ToBeValidated("", Locale.GERMANY) // value: kann nicht leer sein
));
Validator<ToBeValidated> springValidator = new Validator<ToBeValidated>() {
@Override
public void validate(ToBeValidated value) throws ValidationException {
interpolator.setLocale(value.getLocale());
String errors = constValidator.validate(value).stream().map(v -> v.getPropertyPath().toString() +": "+v.getMessage()).collect(Collectors.joining(","));
if(errors != null && !errors.isEmpty()) {
throw new ValidationException(errors);
}
}
};
ItemProcessor<ToBeValidated, ToBeValidated> processor = new ValidatingItemProcessor<ToBeValidated>(springValidator);
ItemWriter<ToBeValidated> writer = new ItemWriter<ToBeValidated>() {
@Override
public void write(List<? extends ToBeValidated> items) throws Exception {
items.forEach(System.out::println);
}
};
SkipListener<ToBeValidated, ToBeValidated> skipListener = new SkipListener<ToBeValidated, ToBeValidated>() {
@Override
public void onSkipInRead(Throwable t) {}
@Override
public void onSkipInWrite(ToBeValidated item, Throwable t) {}
@Override
public void onSkipInProcess(ToBeValidated item, Throwable t) {
System.out.println("Skipped ["+item.toString()+"] for reason(s) ["+t.getMessage()+"]");
}
};
return stepFactory.get("demoStep")
.<ToBeValidated, ToBeValidated>chunk(2)
.reader(reader)
.processor(processor)
.writer(writer)
.faultTolerant()
.skip(ValidationException.class)
.skipLimit(10)
.listener(skipListener)
.build();
}
@Override
public PlatformTransactionManager getTransactionManager() {
return new ResourcelessTransactionManager();
}
}
【问题讨论】:
-
afaik Spring 附带的 messageinterpolator(使用 Spring
MessageSource)完全符合您的要求。 -
@m.deinum,谢谢,我又看了
LocaleContextMessageInterpolator和LocaleContextHolder.setLocale(),它解决了我的问题。
标签: spring spring-boot spring-batch hibernate-validator