You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java Hibernate Validation中使用多个Resource Bundle完成校验

实现方案

方案1:自定义聚合资源包定位器(推荐)

这是最符合Bean Validation规范的实现方式,无需重复拷贝消息,也支持应用侧覆盖类库的同名消息:

  1. 首先实现自定义的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);
        }
    }
}
  1. 替换原有的校验器初始化逻辑,把应用和类库的资源包都传入聚合定位器:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 12:54:03