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

Spring Boot 2.7.3(JVM17)集成Redisson与Bucket4j时触发InaccessibleObjectException

解决JVM 17下Redisson + Bucket4J的InaccessibleObjectException异常

我在基于JVM 17运行的Spring Boot 2.7.3应用中,用Redisson作为Bucket4j的缓存实现,先通过单元测试验证功能,但持续遇到InaccessibleObjectException异常。


相关代码

Bucket4J配置类

@Configuration
@EnableCaching
public class Bucket4JConfig {
    public static final String CACHE_NAME = "r_quota_";

    @Bean
    public Config envConfig() {
        var port = "6379";
        var host = "localhost";
        BinaryOperator<String> getUrl = (h, p) -> String.format("redis://%s:%s", h, p);
        Config config = new Config();
        config.useReplicatedServers()
                .setReadMode(ReadMode.SLAVE)
                .addNodeAddress(getUrl.apply(host, port));
        return config;
    }

    @Bean(name = "javaxCacheManager")
    public CacheManager cacheManager() {
        return Caching.getCachingProvider().getCacheManager();
    }

    @Bean
    public ProxyManager<String> proxyManager(CacheManager cacheManager, Config config) {
        Cache<String, byte[]> cache = cacheManager.getCache(CACHE_NAME);
        if (cache == null) {
            MutableConfiguration<String, byte[]> jcacheConfig = new MutableConfiguration<>();
            jcacheConfig.setExpiryPolicyFactory(null); // never expire
            javax.cache.configuration.Configuration<String, byte[]> redissonConfiguration = RedissonConfiguration.fromConfig(config, jcacheConfig);
            cache = cacheManager.createCache(CACHE_NAME, redissonConfiguration);
        }
        return new JCacheProxyManager<>(cache);
    }

    @Bean
    @Primary
    public SyncCacheResolver bucket4jCacheResolver(CacheManager cacheManager) {
        return new JCacheCacheResolver(cacheManager);
    }

    @Bean
    public JCacheManagerCustomizer jCacheManagerCustomizer(Config config) {
        return cacheManager -> cacheManager.createCache(CACHE_NAME, RedissonConfiguration.fromConfig(config));
    }
}

限流服务类

@Service
public class RateLimitService {

    private static final int QUOTA = 2;
    private final ProxyManager<String> buckets;

    public RateLimitService(ProxyManager<String> buckets) {
        this.buckets = buckets;
    }

    public void blockOnBucket(String bucketName) throws InterruptedException {
        resolveBucket(bucketName).asBlocking().consume(1);
    }

    private Bucket resolveBucket(String bucketName) {
        Supplier<BucketConfiguration> confSupplier = () -> {
            return BucketConfiguration.builder()
                    .addLimit(limit -> limit.capacity(QUOTA).refillGreedy(QUOTA, Duration.ofSeconds(1)))
                    .build();
        };
        return buckets.builder().build(bucketName, confSupplier);
    }
}

单元测试类

public class Bucket4JOverRedissonTest {

    private RateLimitService rateLimitService;

    @BeforeEach
    public void setUp() {
        Bucket4JConfig conf = new Bucket4JConfig();
        var config = conf.envConfig();
        var cacheManager = conf.cacheManager();
        System.out.printf("Caches: [%s]", Streams.stream(cacheManager.getCacheNames()).collect(Collectors.joining(",")));
        rateLimitService = new RateLimitService(conf.proxyManager(cacheManager, config));
    }

    @Test
    public void test() throws InterruptedException {
        String bucketName = "LIMITED_RESOURCE";
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();;
        rateLimitService.blockOnBucket(bucketName);
        rateLimitService.blockOnBucket(bucketName);
        rateLimitService.blockOnBucket(bucketName);
        stopWatch.stop();

        Assertions.assertTrue(stopWatch.getTotalTimeMillis() > 1000);

    }
}

Maven依赖

<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j_jdk17-redis-common</artifactId>
    <version>8.11.1</version>
</dependency>
<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j_jdk17-redisson</artifactId>
    <version>8.11.1</version>
</dependency>
<dependency>
    <groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
    <artifactId>bucket4j-spring-boot-starter</artifactId>
    <version>0.12.6</version>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>2.15.2</version>
</dependency>

异常信息

java.lang.reflect.InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @1d371b2d

    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
    at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
    at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
    at org.nustaq.serialization.FSTClazzInfo.createFieldInfo(FSTClazzInfo.java:504)
    at org.nustaq.serialization.FSTClazzInfo.createFields(FSTClazzInfo.java:367)
    at org.nustaq.serialization.FSTClazzInfo.<init>(FSTClazzInfo.java:128)
    at org.nustaq.serialization.FSTClazzInfoRegistry.getCLInfo(FSTClazzInfoRegistry.java:129)
    at org.nustaq.serialization.FSTClazzNameRegistry.addClassMapping(FSTClazzNameRegistry.java:97)
    at org.nustaq.serialization.FSTClazzNameRegistry.registerClassNoLookup(FSTClazzNameRegistry.java:84)
    at org.nustaq.serialization.FSTClazzNameRegistry.registerClass(FSTClazzNameRegistry.java:80)
    at org.nustaq.serialization.FSTConfiguration.addDefaultClazzes(FSTConfiguration.java:811)
    at org.nustaq.serialization.FSTConfiguration.initDefaultFstConfigurationInternal(FSTConfiguration.java:478)
    at org.nustaq.serialization.FSTConfiguration.createDefaultConfiguration(FSTConfiguration.java:473)
    at org.nustaq.serialization.FSTConfiguration.createDefaultConfiguration(FSTConfiguration.java:465)
    at org.redisson.codec.FstCodec.<init>(FstCodec.java:47)
    at org.redisson.config.Config.<init>(Config.java:104)
    at org.redisson.Redisson.<init>(Redisson.java:119)
    at org.redisson.Redisson.create(Redisson.java:164)
    at org.redisson.jcache.JCacheManager.createCache(JCacheManager.java:125)
    at io.pacaso.insights.api.config.Bucket4JConfig.proxyManager(Bucket4JConfig.java:50)

解决方案

原因分析

异常根源是Redisson默认使用的FST序列化器在JVM 17的模块系统下,试图访问java.lang.String的私有字段,而JVM 17严格限制了模块间的反射访问,导致权限不足。

解决步骤

  1. 更换Redisson的序列化器:改用兼容JVM 17的Jackson序列化器
    修改Bucket4JConfig中的envConfig方法,显式设置JacksonCodec:

    @Bean
    public Config envConfig() {
        var port = "6379";
        var host = "localhost";
        BinaryOperator<String> getUrl = (h, p) -> String.format("redis://%s:%s", h, p);
        Config config = new Config();
        // 设置Jackson序列化器,替换默认的FST
        config.setCodec(new JacksonCodec());
        config.useReplicatedServers()
                .setReadMode(ReadMode.SLAVE)
                .addNodeAddress(getUrl.apply(host, port));
        return config;
    }
    
  2. 升级Redisson版本(可选):当前使用的Redisson 2.15.2版本对JVM 17支持不佳,升级到3.20+版本(兼容Spring Boot 2.7.x),新版本默认使用更适配高版本JVM的序列化方案:

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.20.1</version>
    </dependency>
    
  3. 临时调试方案(不推荐):如果无法更换序列化器或升级版本,可在JVM启动参数中添加反射权限,但会破坏JVM模块封装安全性:

    --add-opens java.base/java.lang=ALL-UNNAMED
    

验证修改

重新运行单元测试,异常应消失,限流逻辑可正常执行。

内容的提问来源于stack exchange,提问作者Kramer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:40:56