解决升级到JDK17后cglib报错Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass()

场景

在JDK17中引用seata作为分布式事务控制,项目启动失败,整体报文如下:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'globalTransactionScanner' defined in class path resource [io/seata/spring/boot/autoconfigure/SeataAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.seata.spring.annotation.GlobalTransactionScanner]: Factory method 'globalTransactionScanner' threw exception; nested exception is java.lang.ExceptionInInitializerError
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:258) ~[spring-context-5.3.23.jar:5.3.23]
	at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:762) ~[spring-context-5.3.23.jar:5.3.23]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:567) ~[spring-context-5.3.23.jar:5.3.23]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.4.jar:2.7.4]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-2.7.4.jar:2.7.4]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.4.jar:2.7.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-2.7.4.jar:2.7.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-2.7.4.jar:2.7.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-2.7.4.jar:2.7.4]
	at com.fbi.fvite.FViteApplication.main(FViteApplication.java:10) ~[classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.seata.spring.annotation.GlobalTransactionScanner]: Factory method 'globalTransactionScanner' threw exception; nested exception is java.lang.ExceptionInInitializerError
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.23.jar:5.3.23]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.23.jar:5.3.23]
	... 19 common frames omitted
Caused by: java.lang.ExceptionInInitializerError: null
	at net.sf.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:166) ~[cglib-3.1.jar:na]
	at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) ~[cglib-3.1.jar:na]
	at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216) ~[cglib-3.1.jar:na]
	at net.sf.cglib.core.KeyFactory$Generator.create(KeyFactory.java:144) ~[cglib-3.1.jar:na]
	at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:116) ~[cglib-3.1.jar:na]
	at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:108) ~[cglib-3.1.jar:na]
	at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:104) ~[cglib-3.1.jar:na]
	at net.sf.cglib.proxy.Enhancer.(Enhancer.java:69) ~[cglib-3.1.jar:na]
	at io.seata.config.ConfigurationCache.proxy(ConfigurationCache.java:104) ~[seata-all-1.5.2.jar:1.5.2]
	at io.seata.config.ConfigurationFactory.buildConfiguration(ConfigurationFactory.java:136) ~[seata-all-1.5.2.jar:1.5.2]
	at io.seata.config.ConfigurationFactory.getInstance(ConfigurationFactory.java:94) ~[seata-all-1.5.2.jar:1.5.2]
	at io.seata.spring.annotation.GlobalTransactionScanner.(GlobalTransactionScanner.java:105) ~[seata-all-1.5.2.jar:1.5.2]
	at io.seata.spring.annotation.GlobalTransactionScanner.(GlobalTransactionScanner.java:162) ~[seata-all-1.5.2.jar:1.5.2]
	at io.seata.spring.boot.autoconfigure.SeataAutoConfiguration.globalTransactionScanner(SeataAutoConfiguration.java:83) ~[seata-spring-boot-starter-1.5.2.jar:1.5.2]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.23.jar:5.3.23]
	... 20 common frames omitted
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @bcec361
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) ~[na:na]
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[na:na]
	at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199) ~[na:na]
	at java.base/java.lang.reflect.Method.setAccessible(Method.java:193) ~[na:na]
	at net.sf.cglib.core.ReflectUtils$2.run(ReflectUtils.java:56) ~[cglib-3.1.jar:na]
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:318) ~[na:na]
	at net.sf.cglib.core.ReflectUtils.(ReflectUtils.java:46) ~[cglib-3.1.jar:na]
	... 39 common frames omitted

分析

  1. 这个错误是seata中引用的cglib库使用了不安全的API导致。
  2. 去cglib仓库发现最后一次更新是2019年,基本已不再维护,因此无法通过升级cglib解决;
  3. seata尚未发布修复此问题的版本,因此暂时需要拉取seata源码手动修复并编译。
    (该文章发布日seata尚未发布修复版本,但或许当你看到这篇文章时已经发布修正此问题的正式版本了,具体请留意https://github.com/seata/seata/pull/4434, 若仍未修复请看下面的临时解决方案
    考虑到使用cglib的开源库并不少,因此这篇文章以seata为例提供一个思路,可以解决所有因cglib导致的此类问题。

临时解决方案

  1. 拉取seata官方仓库https://github.com/seata/seata
  2. 修改all/pom.xmlconfig/seata-config-core/pom.xml, 修改cglib的引用,使用bytebuddy代替cglib,seata项目中已经有了bytebuddy的关联依赖,因此不需要重复指定版本号。
        <dependency>
            
            <groupId>net.bytebuddygroupId>
            <artifactId>byte-buddyartifactId>
        dependency>
  1. 修改config/seata-config-core/src/main/java/io/seata/config/ConfigurationCache.javaproxy方法,改为bytebuddy实现方式,代码如下
    public Configuration proxy(Configuration originalConfiguration) {
        DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition<Configuration> intercept = new ByteBuddy()
                .subclass(Configuration.class)
                .method(ElementMatchers.any())
                .intercept(InvocationHandlerAdapter.of((proxy, method, args) -> {
                    if (method.getName().startsWith(METHOD_PREFIX)
                            && !method.getName().equalsIgnoreCase(METHOD_LATEST_CONFIG)) {
                        String rawDataId = (String) args[0];
                        ObjectWrapper wrapper = CONFIG_CACHE.get(rawDataId);
                        ObjectWrapper.ConfigType type = ObjectWrapper.getTypeByName(method.getName().substring(METHOD_PREFIX.length()));
                        Object defaultValue = null;
                        if (args.length > 1 && method.getParameterTypes()[1].getSimpleName().equalsIgnoreCase(type.name())) {
                            defaultValue = args[1];
                        }
                        if (null == wrapper || (null != defaultValue && !Objects.equals(defaultValue, wrapper.lastDefaultValue))) {
                            Object result = method.invoke(originalConfiguration, args);
                            // The wrapper.data only exists in the cache when it is not null.
                            if (result != null) {
                                wrapper = new ObjectWrapper(result, type, defaultValue);
                                CONFIG_CACHE.put(rawDataId, wrapper);
                            }
                        }
                        return wrapper == null ? null : wrapper.convertData(type);
                    }
                    return method.invoke(originalConfiguration, args);
                }));
        try {
            return intercept.make().load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION)
                    .getLoaded().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
  1. 为避免本地包和release版本发生冲突,最好手动修改一下seata的版本号(build/pom.xml)如下:
        
        <revision>1.6.0-SNAPSHOT.fixj17revision>
  1. 编译、并安装到本地maven仓库mvn clean install
  2. 回到你的项目,排除有问题的seata依赖,并引入刚刚编译的修复版
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-seataartifactId>
            <exclusions>
                
                <exclusion>
                    <groupId>io.seatagroupId>
                    <artifactId>seata-allartifactId>
                exclusion>
            exclusions>
        dependency>
        
        <dependency>
            <groupId>io.seatagroupId>
            <artifactId>seata-allartifactId>
            <version>1.6.0-SNAPSHOT.fixj17version>
        dependency>

刷新maven依赖后,至此问题解决。这是一个解决思路,可以适用于cglib在高版本jdk中导致的此类问题。

另一种思路

除了以上方式外,spring官方也给出了修复方案,如果你使用了spring框架,可以使用spring的修正版cglib,也就是说你无需重新实现代码也可以解决此类问题。
https://github.com/spring-projects/spring-framework/tree/main/spring-core/src/main/java/org/springframework/cglib
使用这里的来代替cglib的api,它和cglib的api完全一致,无需重新实现。

  • 但由于我这里是seata-config的一个核心包导致的问题,并没有引入spring,所以我选择用前者修复.
  • 事实上seata的其他模块已经有使用spring的cglib来取代原版cglib的骚操作了,可以看一下这个类:seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/provider/SpringBootConfigurationProvider.java

你可能感兴趣的:(java,spring,spring,boot,seata,cglib)