Java 9模块化项目集成Spring Security时ResourceBundle.Control异常问题
ResourceBundle.Control异常 这个坑我之前帮好几个开发者踩过,本质就是Java 9的模块化系统对ResourceBundle.Control的限制,撞上了Spring Security默认的资源加载逻辑。Java 9的命名模块里明确禁止使用自定义的ResourceBundle.Control实现,而Spring Security处理国际化资源时刚好默认用到了它,这就直接抛出UnsupportedOperationException了——不管你是自定义MessageSource还是用默认配置,只要触发了Spring Security的资源加载,就会踩这个坑。
下面给几个亲测有效的解决方案,按推荐优先级排序:
1. 调整模块描述符(module-info.java)
这是最根本的适配方案,让Spring模块能合法访问你的资源:
- 在
module-info.java里,把你存放国际化资源(比如messages.properties)的包开放给Spring Security和Spring Context模块,同时声明依赖:
这样Spring就能用模块化兼容的方式加载资源,不会再触发// 替换成你实际的资源包路径 opens com.yourproject.i18n to org.springframework.security.core, org.springframework.context; requires org.springframework.security.core; requires org.springframework.boot.autoconfigure; requires org.springframework.context;ResourceBundle.Control的限制。
2. 换用ReloadableResourceBundleMessageSource绕过限制
Spring的ResourceBundleMessageSource底层依赖ResourceBundle.Control,而ReloadableResourceBundleMessageSource是用自己的资源加载逻辑,完全避开了这个问题。直接自定义这个Bean就行:
@Configuration public class MessageSourceConfig { @Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); // 指向你的资源文件,比如classpath下的messages.properties messageSource.setBasename("classpath:messages"); messageSource.setDefaultEncoding("UTF-8"); // 可选:设置缓存时间,方便开发时热更新 messageSource.setCacheSeconds(3600); return messageSource; } @Bean public LocaleResolver localeResolver() { SessionLocaleResolver localeResolver = new SessionLocaleResolver(); localeResolver.setDefaultLocale(Locale.ENGLISH); return localeResolver; } }
然后在Spring Security的配置里,确保异常处理器等组件使用这个自定义的MessageSource:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final MessageSource messageSource; public SecurityConfig(MessageSource messageSource) { this.messageSource = messageSource; } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()) .authenticationEntryPoint(authenticationEntryPoint()); } @Bean public AccessDeniedHandler accessDeniedHandler() { SimpleAccessDeniedHandler handler = new SimpleAccessDeniedHandler(); handler.setMessageSource(messageSource); return handler; } @Bean public AuthenticationEntryPoint authenticationEntryPoint() { LoginUrlAuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint("/login"); entryPoint.setMessageSource(messageSource); return entryPoint; } }
3. 临时降级Spring Security版本(不推荐长期用)
如果上面的方法暂时没法落地,可以试试把Spring Security降到5.0.x系列(对应Spring Boot 2.0的兼容版本)。早期版本的Spring Security对Java 9模块化的兼容性处理没那么严格,可能会绕过这个异常。不过这只是权宜之计,长远来看还是要适配模块化规范。
最后提醒一句:Java 9模块化下,资源文件默认只能从模块路径加载,所以要确保你的messages.properties放在src/main/resources对应的模块包下,或者通过module-info.java的opens/exports声明资源所在的包,不然Spring还是找不到资源。
内容的提问来源于stack exchange,提问作者Grzegorz B.

