12.1 国际化
March 4, 2026 · View on GitHub
Properties的定义
Java 中的多语言字符串模板一般保存在properties资源文件中,且遵照以下的命名规范:<资源名>_<语言代码>.properties。例如,用户可以定义中英文两种多语言资源文件,并将其放在 resources 目录下,其中定义的资源名为 message:
- 文件名为 message_en.properties,表示英文资源文件,内容为
HELLO=Hello, {0}!
- 文件名为 message_cn.properties,表示中文资源文件,内容为
HELLO=你好, {0}!
可以看到,两种语言的 Key 完全一致,只是 Value 是对应语言的字符串。这样应用程序就可以通过 Locale 对象和 Key 精确调用到某个具体的属性值了。
StringResource
FIT 框架提供了 StringResource 接口用于为应用程序的国际化提供字符串管理能力,定义了方法getMessage()和getMessageWithDefault()用于获取在指定语言环境中使用的指定键的字符串资源,其中,参数locale表示目标语言环境,key表示资源的键,defaultMessage表示资源的默认值,args表示资源的格式化参数:
// 获取消息,无默认值
String getMessage(Locale locale, String key, Object... args);
// 获取消息,支持指定默认值
String getMessageWithDefault(Locale locale, String key, String defaultMessage, Object... args);
FIT 内部提供了三个实现类:
EmptyStringResource:为StringResource提供空实现,返回一个null值。ResourceBundleStringResource:提供一个基于ResourceBundle的实现,可用以获取指定资源的在多语言环境下使用的消息资源,其中,ResourceBundle是java.util提供的一个用于管理和访问国际化资源的类。StringResourceComposite:为StringResource提供组合模式的实现,可将一系列的消息提供程序组合为一个实例。
这些实现类为框架内部使用,开发者通过 StringResources.forPlugin(Plugin) 工厂方法获取 StringResource 实例,示例如下:
StringResource resource = StringResources.forPlugin(plugin);
String messageEn = resource.getMessage(new Locale("en"), "HELLO", "world");
地区解析器
FIT 框架提供了 LocaleResolver 接口用于从 HTTP 请求中解析用户的地区设置,该接口定义了两个核心方法:
public interface LocaleResolver {
/**
* 解析用户的地区设置。
*
* @param request 表示待解析 HTTP 请求的 {@link HttpClassicServerRequest}。
* @return 表示解析出来地区信息的 {@link Locale}。
*/
Locale resolveLocale(HttpClassicServerRequest request);
/**
* 设置地区到返回响应中。
*
* @param response 表示待设置地区的 HTTP 响应的 {@link HttpClassicServerResponse}。
* @param locale 表示待设置地区的 {@link Locale}。
*/
void setLocale(HttpClassicServerResponse response, Locale locale);
}
其中,resolveLocale() 方法用于从请求中解析地区信息,setLocale() 方法用于在响应中设置地区信息。
FIT 提供了 DefualtLocaleResolver 实现类,该类支持从多个来源解析地区信息,按优先级顺序为:
- Cookie:从名为
locale的 Cookie 中获取地区信息。 - 请求头:从
Accept-Language请求头中解析地区信息。
注意:URL 请求参数中的
locale参数由LocaleResolveFilter负责解析,其优先级高于上述来源。详见下方"地区解析过滤器"章节。
地区解析过滤器
LocaleResolveFilter 是一个 HTTP 过滤器,用于自动处理请求的地区解析和响应的地区设置。该过滤器会在请求处理过程中自动执行以下操作:
- 使用配置的
LocaleResolver解析请求中的地区信息。 - 将解析得到的地区信息设置到
LocaleContextHolder中。 - 根据
LocaleResolver的实现逻辑在响应中设置相应的地区状态。 - 在请求处理完成后清理线程本地的地区信息。
使用示例如下:
@Component
public class WebConfig {
@Bean
public LocaleResolveFilter localeResolveFilter() {
return new LocaleResolveFilter(new DefualtLocaleResolver());
}
}
地区上下文管理
FIT 框架提供了 LocaleContextHolder 类用于在当前线程中管理地区信息,该类使用 ThreadLocal 机制确保多线程环境下的地区信息隔离。主要方法包括:
public class LocaleContextHolder {
/**
* 设置当前线程的地区上下文。
*
* @param locale 表示待存储在当前线程地区上下文的 {@link Locale}。
*/
public static void setLocale(Locale locale) {
if (locale != null) {
LOCALE_CONTEXT_HOLDER.set(locale);
}
}
/**
* 获取当前线程的地区。
*
* @return 表示当前线程上下文存储地区信息的 {@link Locale}。
*/
public static Locale getLocale() {
return LOCALE_CONTEXT_HOLDER.get();
}
/**
* 清除当前线程的地区上下文。
*/
public static void clear() {
LOCALE_CONTEXT_HOLDER.remove();
}
}
使用示例:
public class testLocaleContextHolder {
public void test() {
try {
// 设置当前线程的地区为英文。
LocaleContextHolder.setLocale(Locale.ENGLISH);
// 获取当前线程的地区。
Locale currentLocale = LocaleContextHolder.getLocale();
} finally {
// 请在使用完毕后尽快清理当前线程的地区信息。
LocaleContextHolder.clear();
}
}
}
校验消息国际化
FIT 框架的校验功能支持国际化,实现通过 LocaleContextMessageInterpolator 类实现校验消息的多语言支持。该类会根据 LocaleContextHolder 中设置的地区信息自动选择对应语言的校验消息模板。
12.2 Event事件
12.2.1 相关接口
FIT 支持不同插件之间通过发布-订阅机制进行解耦的通信,提供了三个事件相关的接口供调用方使用,分别是Event、EventPublisher、EventHandler,其中,Publisher订阅Event,并将Event分发到各个EventHandler做出响应,定义如下:
Event接口定义了事件,含一个接口方法publisher(),开发者需要通过扩展及实现该接口来自定义事件属性。
public interface Event {
Object publisher();
}
EventPublisher是事件的发布程序的接口:
public interface EventPublisher {
<E extends Event> void publishEvent(E event);
}
FIT 提供了两种EventPublisher实现类供开发者使用:
| 实现类 | 获取方式 | 说明 |
|---|---|---|
| FitRuntimeEventPublisher | FitRuntime | 该类为 FitRuntime 提供事件发布程序,适合发布应用级事件时调用,应用级事件表示事件的发布和处理有可能需要跨插件,与整个应用程序相关,如应用启动、服务连接等事件。 |
| PluginEventPublisher | Plugin | 该类为 Plugin 提供事件发布程序,适合发布插件级事件时调用,插件级事件表示事件的发布和处理都在本插件内,不与整个应用程序相关。 |
EventHandler是为事件提供处理程序的接口:
public interface EventHandler<E extends Event> {
void handleEvent(@Nonnull E event);
}
12.2.2 自定义Event事件
首先,通过实现Event接口来进行事件的自定义,示例如下:
public class MyEvent implements Event {
private final User user;
private final Object publisher;
public MyEvent(User user, Object publisher) {
this.user = user;
this.publisher = publisher;
}
public User getUser() {
return this.user;
}
@Override
public Object publisher() {
return this.publisher;
}
}
此处,通过构造函数入参的方式来提供当前事件的发布者,通常为发布事件的对象本身,为事件处理类提供事件发布者的信息。
通过实现EventHandler接口来自定义接收到事件后作出的响应,添加注解@Component使之生成Bean,可自动被EventPublisher检测到,示例如下:
@Component
public class MyEventHandler implements EventHandler<MyEvent> {
@Override
public void handleEvent(MyEvent event) {
System.out.println(event.getUser().getName());
}
}
定义Event以及EventHandler后,需要使用EventPublisher对事件进行发布,FIT 提供了FitRuntimeEventPublisher和PluginEventPublisher 两个实现类。
FitRuntimeEventPublisher适用于发布应用级事件时使用,可通过依赖注入的方式获取FitRuntime,使用方式示例如下:
@Component
public class UserController {
private static int counter = 0;
private final FitRuntime runtime ;
public UserController(FitRuntime runtime) {
this.runtime = runtime;
}
@GetMapping(path = "/user")
public User getUser(@RequestParam("name") String name, @RequestParam("age") String age) {
User user = new User(name, age, ++counter);
this.runtime.publisherOfEvents().publishEvent(new MyEvent(user, this));
return user;
}
}
PluginEventPublisher适用于发布插件级事件时使用,可通过依赖注入的方式获取Plugin,使用方式示例如下:
@Component
public class UserController {
private static int counter = 0;
private final Plugin plugin;
public UserController(Plugin plugin) {
this.plugin = plugin;
}
@GetMapping(path = "/user")
public User getUser(@RequestParam("name") String name, @RequestParam("age") String age) {
User user = new User(name, age, ++counter);
this.plugin.publisherOfEvents().publishEvent(new MyEvent(user, this));
return user;
}
}
12.2.3 FIT 生命周期事件
FIT 框架启动过程中提供了一系列事件供开发者监听和处理,允许开发者在 FIT 的启动阶段执行自定义逻辑,这些类都属于Events的内部类:
| 事件类 | 实现接口 | 说明 |
|---|---|---|
| Prepared | FitRuntimePreparedEvent | 事件在 FIT 初始化结束,准备启动时触发,指示 FIT 运行时环境已准备就绪 |
| Started | FitRuntimeStartedEvent | 事件在 FIT 启动成功后触发,指示 FIT 运行时环境已启动 |
| Failed | FitRuntimeFailedEvent | 事件在 FIT 启动时捕获到异常时触发,指示 FIT 运行时环境启动失败 |
定义一个EventHandler类处理Prepared类事件,可通过runtime()函数获取运行时环境,也可通过duration()函数获取 FIT 运行时环境准备就绪的时长,示例如下:
@Component
public class MyEventHandler implements EventHandler<FitRuntimePreparedEvent> {
@Override
public void handleEvent(FitRuntimePreparedEvent event) {
System.out.println(event.duration());
}
}
定义一个EventHandler类处理Started类事件,可通过runtime()函数获取运行时环境,也可通过duration()函数获取 FIT 运行时环境启动的时长,示例如下:
@Component
public class MyEventHandler implements EventHandler<FitRuntimeStartedEvent> {
@Override
public void handleEvent(FitRuntimeStartedEvent event) {
System.out.println(event.duration());
}
}
定义一个EventHandler类处理Failed类事件,可通过runtime()函数获取运行时环境,也可通过cause()函数获取 FIT 运行时环境启动失败的原因,示例如下:
@Component
public class MyEventHandler implements EventHandler<FitRuntimeFailedEvent> {
@Override
public void handleEvent(FitRuntimeFailedEvent event) {
System.out.println(event.cause().getMessage());
}
}
12.3 Validation 插件
FIT 框架提供了基于 Hibernate Validator 的数据校验功能,支持对方法参数进行自动校验。
fit-validation-hibernate-javax:基于 javax.validation 规范,适用于传统 Java EE 环境fit-validation-hibernate-jakarta:基于 jakarta.validation 规范,适用于 Jakarta EE 环境
约束由约束注解和约束验证实现的组合定义。约束注解可以应用于类、方法、字段或其他约束注解(在组合的情况下)。
12.3.1 依赖
当前 FIT 实现中同时存在两套 Validation 插件,分别对应 javax.validation 和 jakarta.validation 标准。 两套校验插件同时运行,通过责任链的方式校验 FIT 程序中需要被校验的参数。
用户可根据自己的开发需要,自行选择对应的标准依赖:
传统 Java EE 环境(javax):
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
Jakarta EE 环境:
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.1.1</version>
</dependency>
12.3.2 注解用法
基本用法
- 类级别校验:在类上添加
@Validated注解,框架会自动对该类的所有方法参数进行校验。
@Component
@Validated
public class UserController {
public void createUser(@NotBlank(message = "用户名不能为空") String username,
@Min(value = 18, message = "年龄必须大于等于 18") Integer age) {
// 业务逻辑
}
}
- 复杂对象校验:对于复杂对象,需要在参数前添加
@Valid注解,并在对象内部字段上添加校验注解。
public class UserDto {
@NotBlank(message = "名称不能为空")
private String name;
@Min(value = 0, message = "年龄必须大于等于 0")
@Max(value = 150, message = "年龄必须小于等于 150")
private Integer age;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^[a-zA-Z]+$", message = "代码只能包含字母")
private String code;
// getter/setter...
}
@PostMapping("/user")
public void createUser(@RequestBody @Valid UserDto userDto) {
// 业务逻辑
}
泛型类型校验
- 泛型类型参数校验:方法的某个入参是泛型类型,如
Collection、Map的参数类型,需要在校验对象前添加@Valid注解(按照 Hibernate Validator reference 推荐,但您仍然可以以 @Valid List这样的方式添加注解,但不能以 @Valid List<List > 的方式添加注解),否则添加的约束规则不生效,并在泛型内部类型对需要校验的字段添加所需的校验注解。
// List 类型参数校验
public void createUsers(List<@Valid UserDto> users) {
// 会对 List 中每个 UserDto 对象进行校验
}
// Map 类型参数校验
public void processUserData(Map<String, @Valid UserDto> userMap) {
// 会对 Map 中每个 UserDto 值进行校验
}
// Set 类型参数校验
public void updateUsers(Set<@Valid UserDto> users) {
// 会对 Set 中每个 UserDto 对象进行校验
}
嵌套对象校验
- 嵌套校验支持:FIT 支持对嵌套校验,即当泛型类型中内嵌泛型类型、非基本数据类型的字段中包含非基本数据类型时,需要在嵌套字段上添加
@Valid注解。
public class CompanyDto {
@NotBlank(message = "公司名称不能为空")
private String name;
@Min(value = 0, message = "员工数量不能为负数")
private Integer employeeCount;
// 嵌套单个对象校验
@Valid
@NotNull(message = "经理信息不能为空")
private UserDto manager;
// 嵌套集合对象校验
@Valid
@Size(min = 1, message = "至少需要一个员工")
private List<UserDto> employees;
// 嵌套 Map 对象校验
@Valid
private Map<String, DepartmentDto> departments;
// getter/setter...
}
public class DepartmentDto {
@NotBlank(message = "部门名称不能为空")
private String departmentName;
@Valid
private List<UserDto> members;
// getter/setter...
}
// 使用示例
public void createCompany(@Valid CompanyDto company) {
// 会递归校验 company 及其所有嵌套对象
}
// 复杂嵌套泛型校验
public void processComplexData(Map<String, List<@Valid CompanyDto>> companyGroups) {
// 会校验 Map 中每个 List,以及 List 中每个 CompanyDto 及其嵌套对象
}
- 校验失败处理:方法被调用时,如果传入的实际参数与约束规则不符,会直接抛出
ConstraintViolationException,表明参数校验失败。
支持的校验注解
框架支持完整的 Bean Validation 注解集合:
空值校验
@NotNull:值不能为 null@NotEmpty:值不能为 null 且不能为空(适用于字符串、集合、数组、键值对)@NotBlank:字符串不能为 null、空字符串或只包含空白字符@Null:值必须为 null
布尔值校验
@AssertTrue:值必须为 true@AssertFalse:值必须为 false
数值校验
@Min(value):数值必须大于等于指定值@Max(value):数值必须小于等于指定值@DecimalMin(value):小数值必须大于等于指定值@DecimalMax(value):小数值必须小于等于指定值@Positive:数值必须为正数(大于 0)@PositiveOrZero:数值必须为正数或零(大于等于 0)@Negative:数值必须为负数(小于 0)@NegativeOrZero:数值必须为负数或零(小于等于 0)@Digits(integer, fraction):数字格式校验,integer 指定整数位数,fraction 指定小数位数
大小校验
@Size(min, max):字符串、集合、数组、键值对的大小必须在指定范围内
时间校验
@Past:日期必须是过去的时间@PastOrPresent:日期必须是过去或现在的时间@Future:日期必须是未来的时间@FutureOrPresent:日期必须是未来或现在的时间
格式校验
@Pattern(regexp):字符串必须匹配指定的正则表达式@Email:字符串必须是有效的邮箱格式
使用示例
public class CompleteValidationDto {
@NotNull(message = "ID 不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度必须在 2-20 之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄必须大于等于 18")
@Max(value = 100, message = "年龄必须小于等于 100")
private Integer age;
@DecimalMin(value = "0.0", message = "薪资不能为负数")
@DecimalMax(value = "999999.99", message = "薪资不能超过 999999.99")
@Digits(integer = 6, fraction = 2, message = "薪资格式不正确")
private BigDecimal salary;
@Past(message = "生日必须是过去的日期")
private LocalDate birthday;
@Future(message = "合同到期日必须是未来的日期")
private LocalDate contractEndDate;
@AssertTrue(message = "必须同意用户协议")
private Boolean agreeTerms;
@Size(min = 1, max = 5, message = "技能数量必须在 1-5 之间")
@Valid
private List<SkillDto> skills;
// getter/setter...
}
分组校验
支持校验分组功能,可以在不同场景下应用不同的校验规则:
public class UserDto {
// 分组接口定义
public interface BasicGroup {}
public interface AdvancedGroup {}
public interface StudentGroup {}
public interface TeacherGroup {}
@NotBlank(message = "名称不能为空")
private String name;
@Max(value = 200, message = "高级组年龄必须小于等于 200", groups = AdvancedGroup.class)
private Integer age;
// getter/setter...
}
@Component
@Validated(UserDto.AdvancedGroup.class)
public class AdvancedUserService {
public void processAdvancedUser(@Valid UserDto user) {
// 只会应用 AdvancedGroup 分组的校验规则
}
}
12.3.3 校验原理
核心组件
- ValidationHandler:校验入口类,通过 AOP 方式拦截带有
@Validated注解的类或方法,自动触发校验逻辑。 - Hibernate Validator:底层使用 Hibernate Validator 作为校验引擎。
- ConstraintViolationException:校验失败时抛出的异常,包含详细的错误信息。
校验流程
- 当调用带有
@Validated注解的类的方法时,ValidationHandler会拦截该调用 - 检查方法参数是否包含校验注解
- 使用 Hibernate Validator 对参数进行校验
- 如果校验失败,抛出
ConstraintViolationException异常 - 可通过全局异常处理器捕获并处理校验异常
12.3.4 版本差异
两个插件的主要差异:
| 特性 | fit-validation-hibernate-javax | fit-validation-hibernate-jakarta |
|---|---|---|
| 校验规范 | javax.validation | jakarta.validation |
| Hibernate 版本 | 6.2.5.Final | 9.0.1.Final |
| 适用环境 | 传统 Java EE | Jakarta EE |
| 包名空间 | javax.validation.* | jakarta.validation.* |
选择建议:
- 新项目推荐使用 Jakarta 版本
- 现有项目根据依赖情况选择对应版本
- 两个版本功能完全一致,仅包名空间不同
12.3.5 校验的国际化
与校验扩展一样,需要手动处理报错信息中的内容,以完成国际化功能