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严格限制了模块间的反射访问,导致权限不足。
解决步骤
更换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; }升级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>临时调试方案(不推荐):如果无法更换序列化器或升级版本,可在JVM启动参数中添加反射权限,但会破坏JVM模块封装安全性:
--add-opens java.base/java.lang=ALL-UNNAMED
验证修改
重新运行单元测试,异常应消失,限流逻辑可正常执行。
内容的提问来源于stack exchange,提问作者Kramer
相关产品推荐
相关产品推荐

