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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:43:40