Spring Boot JMX返回自定义对象时遇类未找到异常的解决咨询
我正在用Spring Boot JMX实现数据库变更时远程执行方法的功能。当方法返回基本数据类型时一切正常,还能通过Windows的jmc.exe监控应用。但现在需要返回同包下的自定义KeyValue类作为返回值,远程调用时却抛出了这个异常:
java.rmi.UnmarshalException: error unmarshalling return; nested exception is: java.lang.ClassNotFoundException: com.ff.KeyValue (no security manager: RMI class loader disabled).
我的ChargeCodeService代码如下:
@Service @ManagedResource(description = "JMX managed resource for updating map when database is updated", objectName = "chargeCode:name=ChargeCodeService") public class ChargeCodeService { private static final Logger logger = LoggerFactory.getLogger(ChargeCodeService.class); private final ChargeCodeRepository chargeCodeRepository; private Map<String, KeyValue<String,String>> chargeCodeMap = new HashMap<>(); @Autowired public ChargeCodeService(ChargeCodeRepository chargeCodeRepository) { this.chargeCodeRepository = chargeCodeRepository; } @PostConstruct @ManagedOperation public Map<String, KeyValue<String,String>> chargeCodMapInitial() { logger.info("ready to read chargeCode data from database. this operation will do when an update occure in database"); List<ChargeCode> chargeCodes = chargeCodeRepository.findAll(); for (ChargeCode chargeCode : chargeCodes) { chargeCodeMap.put(chargeCode.getIacChargeCode(),new KeyValue<>( chargeCode.getBankChargeCode(),chargeCode.getTopupProvider())); } return chargeCodeMap; } @ManagedAttribute public Map<String, KeyValue<String, String>> getChargeCodeMap() { return chargeCodeMap; } }
KeyValue类代码如下:
public class KeyValue<K, V> implements Map.Entry<K, V>, Serializable { private static final long serialVersionUID = -2610138893852455839L; private K key; private V value; public KeyValue() { } public KeyValue(K key, V value) { this.key = key; this.value = value; } // getter,setter }
请问该如何解决这个问题?
我之前也碰到过一模一样的坑!这本质上是RMI反序列化时的类加载问题——JMX底层靠RMI做远程通信,当你返回自定义的KeyValue对象时,远程客户端(比如你用的jmc.exe)根本找不到这个类的字节码,所以才会抛出ClassNotFoundException。下面给你几个实用的解决办法,按从简单到复杂排序:
1. 直接把自定义类塞到JMC的类路径里
这是最直接的办法,既然jmc.exe找不到com.ff.KeyValue,那我们就把这个类的字节码给它:
- 先找到你本地JDK安装目录下的
jmc文件夹(一般在你的JDK路径/lib/jmc) - 把包含
KeyValue.class的jar包(或者把你项目编译后的target/classes目录里的com/ff/KeyValue.class打包成jar)复制到jmc/lib目录下 - 重启
jmc.exe,再调用方法就不会报错了
2. 启用RMI类加载器(需配置安全策略)
默认情况下RMI的类加载器是禁用的,你可以通过配置Spring Boot启动参数来启用它,让客户端能从指定地址加载自定义类:
- 首先创建一个安全策略文件
jmx.policy,内容如下(生产环境请根据实际需求收紧权限,不要用这么宽松的配置):
grant { permission java.security.AllPermission; };
- 然后在Spring Boot的启动参数中添加:
-Djava.security.policy=path/to/your/jmx.policy -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.rmi.port=1099 -Djava.rmi.server.codebase=file:///path/to/your/project/target/classes/
这里的codebase要指向你的项目编译后的class文件目录,或者包含KeyValue类的jar包URL,这样RMI客户端就能从这个地址加载需要的类。
3. 改用JDK内置类型替代自定义类
如果不想折腾类加载的问题,可以直接用JDK自带的AbstractMap.SimpleEntry替代你的KeyValue类——它同样实现了Map.Entry接口,而且客户端肯定能识别:
修改chargeCodMapInitial方法里的代码:
chargeCodeMap.put(chargeCode.getIacChargeCode(), new AbstractMap.SimpleEntry<>(chargeCode.getBankChargeCode(), chargeCode.getTopupProvider()));
然后把方法的返回类型改成Map<String, Map.Entry<String,String>>,这样返回的是JDK内置类,客户端不需要额外加载任何类就能正常解析。
4. 用JSON序列化绕开RMI类加载限制
另一种思路是把自定义对象序列化成JSON字符串返回,客户端拿到字符串后再自行解析。比如用Jackson实现:
- 在方法里把
chargeCodeMap转换成JSON字符串:
ObjectMapper mapper = new ObjectMapper(); return mapper.writeValueAsString(chargeCodeMap);
- 把方法返回类型改成
String,这样客户端拿到的是标准JSON格式的字符串,完全绕开了RMI的类加载问题,适合复杂自定义类的场景。
内容的提问来源于stack exchange,提问作者Fatemeh Karbalaie

