如何在Java Hibernate Validation中使用多个Resource Bundle完成校验
实现方案
方案1:自定义聚合资源包定位器(推荐)
这是最符合Bean Validation规范的实现方式,无需重复拷贝消息,也支持应用侧覆盖类库的同名消息:
- 首先实现自定义的
AggregateResourceBundleLocator,用于聚合多个资源包定位器,按优先级查找消息:
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator; import java.util.*; public class AggregateResourceBundleLocator implements ResourceBundleLocator { private final List<ResourceBundleLocator> locatorList; public AggregateResourceBundleLocator(ResourceBundleLocator... locators) { this.locatorList = Arrays.asList(locators); } @Override public ResourceBundle getResourceBundle(Locale locale) { return new CombineResourceBundle(locale, locatorList); } private static class CombineResourceBundle extends ResourceBundle { private final List<ResourceBundleLocator> locators; private final Locale currentLocale; public CombineResourceBundle(Locale locale, List<ResourceBundleLocator> locators) { this.currentLocale = locale; this.locators = locators; } @Override protected Object handleGetObject(String key) { for (ResourceBundleLocator locator : locators) { ResourceBundle bundle = locator.getResourceBundle(currentLocale); if (bundle != null && bundle.containsKey(key)) { return bundle.getObject(key); } } return null; } @Override public Enumeration<String> getKeys() { Set<String> allKeys = new HashSet<>(); for (ResourceBundleLocator locator : locators) { ResourceBundle bundle = locator.getResourceBundle(currentLocale); if (bundle != null) { allKeys.addAll(bundle.keySet()); } } return Collections.enumeration(allKeys); } } }
- 替换原有的校验器初始化逻辑,把应用和类库的资源包都传入聚合定位器:
private static final ValidatorFactory FACTORY = Validation.byDefaultProvider() .configure() .messageInterpolator(new ResourceBundleMessageInterpolator( new AggregateResourceBundleLocator( // *注意顺序:前面的资源包优先级更高,应用侧放在前面可覆盖类库同名key的消息* new PlatformResourceBundleLocator("MyAppValidationMessages"), // 替换为你的应用侧资源包名 new PlatformResourceBundleLocator("MyLibraryValidationMessages") ) )) .buildValidatorFactory();
该方案完全兼容现有代码,不需要修改POJO上的校验注解,也没有消息冗余。如果使用的Hibernate Validator版本≥5.2,官方已经提供了现成的org.hibernate.validator.resourceloading.AggregateResourceBundleLocator,不需要自己实现上述类,直接传入多个资源包名称即可使用。
方案2:硬编码消息实现(无需维护properties文件)
如果你不需要维护独立的properties文件,可以直接自定义MessageInterpolator把所有消息写在代码里:
import javax.validation.MessageInterpolator; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; import java.util.HashMap; import java.util.Map; public class StaticMessageInterpolator implements MessageInterpolator { // 统一存放所有校验消息,key为注解中使用的消息模板key private static final Map<String, String> ALL_MESSAGES = new HashMap<>(); static { // 类库侧消息 ALL_MESSAGES.put("lib.validation.notnull", "必填字段不能为空"); ALL_MESSAGES.put("lib.validation.range", "数值必须在{min}到{max}之间"); // 应用侧消息 ALL_MESSAGES.put("app.validation.mobile", "手机号格式错误"); } private final MessageInterpolator defaultInterpolator = new ResourceBundleMessageInterpolator(); @Override public String interpolate(String messageTemplate, Context context) { return interpolate(messageTemplate, context, Locale.getDefault()); } @Override public String interpolate(String messageTemplate, Context context, Locale locale) { String key = messageTemplate.replace("{", "").replace("}", ""); if (ALL_MESSAGES.containsKey(key)) { String msg = ALL_MESSAGES.get(key); // 替换注解参数占位符 for (Map.Entry<String, Object> attr : context.getConstraintDescriptor().getAttributes().entrySet()) { msg = msg.replace("{" + attr.getKey() + "}", String.valueOf(attr.getValue())); } return msg; } // 未匹配到的消息走默认逻辑 return defaultInterpolator.interpolate(messageTemplate, context, locale); } }
初始化校验器时直接使用该自定义插值器即可:
private static final ValidatorFactory FACTORY = Validation.byDefaultProvider() .configure() .messageInterpolator(new StaticMessageInterpolator()) .buildValidatorFactory();
优化建议
如果你有权限修改类库代码,可以在类库中暴露一个获取自身ResourceBundleLocator的静态方法,应用侧聚合时直接调用该方法即可,不需要硬编码类库的资源包名称,降低两侧耦合。
内容的提问来源于stack exchange,提问作者Eddified
相关产品推荐
相关产品推荐

