如何在Java模块中使用Spring @Validated且避免InaccessibleObjectException
该异常属于Java模块化体系的访问权限限制:@Validated注解触发的参数校验逻辑默认由无模块化声明的Hibernate Validator实现,该依赖会被加载到未命名模块中,运行时需要反射访问com.example.demo.ConfigProperties的包私有字段,但当前模块未开放对应包的访问权限给未命名模块,因此抛出异常。
问题1:调试定位未命名模块对应的依赖库
有两个可落地的定位方法:
- 新增JVM启动参数打印模块加载详情:启动时添加参数
-Djdk.module.showModuleResolution=true,控制台会输出所有模块的加载明细,包括未命名模块加载的Jar包来源,匹配报错信息里的@163370c2标识符对应的类加载器加载的Jar即可定位到目标依赖。 - 断点调试定位:在
java.lang.reflect.InaccessibleObjectException的构造方法上打断点,触发异常后查看调用栈,找到发起反射调用的类所属的Jar包,就是对应的依赖库。
该场景下90%以上的情况对应的依赖是Hibernate Validator,它是JSR-380校验规范的默认实现,绝大多数版本都没有提供模块化声明,会被加载到未命名模块中。
问题2:不开放整个模块的前提下解决异常
以下三种方案都不需要开放整个模块,可按需选择:
方案1:规范配置类写法(最推荐)
给ConfigProperties类的所有字段添加public的getter和setter方法,修改后代码示例:
package com.example.demo; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.validation.annotation.Validated; import javax.validation.constraints.NotNull; @Configuration @ConfigurationProperties("abc") @Validated public class ConfigProperties { @NotNull private String hello; @NotNull private String byeBye; public String getHello() { return hello; } public void setHello(String hello) { this.hello = hello; } public String getByeBye() { return byeBye; } public void setByeBye(String byeBye) { this.byeBye = byeBye; } @Override public String toString() { return "ConfigProperties{hello='" + hello + '\'' +", byeBye='" + byeBye + '\'' + '}'; } }
这种方案下Spring参数绑定通过setter方法完成,校验通过getter方法完成,不需要反射访问类字段,不会触发模块访问限制,不需要修改任何模块权限配置。
方案2:精准开放指定包给未命名模块
如果不想修改配置类代码,可以在module-info.java中只开放com.example.demo这一个包给所有未命名模块,模块下其他包的权限不受影响:
module com.example.demo { // 只开放指定包给Spring相关命名模块和所有未命名模块,其他包保持封闭 opens com.example.demo to spring.beans, spring.boot, spring.core, ALL-UNNAMED; exports com.example.demo; requires java.validation; requires org.slf4j; requires spring.beans; requires spring.boot; requires spring.boot.autoconfigure; requires spring.core; requires spring.context; }
ALL-UNNAMED是JDK模块化的官方关键字,代表所有未命名模块,不会开放给其他无关命名模块,权限控制依然精准。
方案3:用JVM参数临时配置权限
如果不想修改代码和模块配置,可以添加JVM启动参数实现临时的权限开放:--add-opens com.example.demo/com.example.demo=ALL-UNNAMED
该参数只在运行时生效,不会影响代码的模块化声明。
内容的提问来源于stack exchange,提问作者vab2048

