使用nacos+springboot2.4, 2.5+时启动提示ClassNotFount:ConfigurationBeanFactoryMetadata修复
最早项目是在2.1.7.release开发的, 最近有时间, 计划把框架内所有依赖都升级到较高版本, 包括springboot也拉到了2.5.6. nacos自然是0.2.10
刚完成依赖升级后, 启动提示grpc错误, 看到是nacos提示的, 查了查资料发现nacos新版本引入了grpc通讯保证稳定性, 但实际上我这里不需要, 于是降级到0.2.8, 这时候问题就来了, 启动到末尾提示ClassNotFount:ConfigurationBeanFactoryMetadata
这个原因在stackoverflow找到的解决方案是添加springboot的bootstrap启动模式依赖, 尝试过, 失败.
某度上大多就说不兼容, 于是把springboot降级到2.1.x. 我心里一阵MMP, 这算什么解决方案? 不过既然知道原因是不兼容, 那应该有人提issue, 于是就跑到github的nacos去看issue, 果不其然有发现
很多大神也给出了解决方案, 大体上就是springboot2.4.x删除了ConfigurationBeanFactoryMetadata
, 但是由于nacos是阿里的kpi项目, 人走茶凉没人维护, 所以虽然解决方案非常简单但依然没人管, 这里总结一下解决方案
方案1: 既然spring删了, 那我们再加回来
将springboot2.3.x版本中的源码类 ConfigurationBeanFactoryMetadata
拷出来, 到自己项目里新建同名包并粘进去, 加@Component
注解, 这里我先说一下, 我试过这种方法, 但还是不行, 提示ConfigurationBeanFactoryMetadata
bean找不到, 这里大概是beanfactory的加载机制问题, 而且感觉这种解决方案也很粗糙, 所以就没深入研究, 下面引入github中的解决方案
- 将低版本 spring-boot-2.3.11.RELEASE.jar 中的 ConfigurationBeanFactoryMetadata 类源码建立在项目中,路径 org.springframework.boot.context.properties 与源码相同
- 在项目的spring.factories文件中配置加载项 ,通过 EnableAutoConfiguration 加载 ConfigurationBeanFactoryMetadata ,配置如下org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.context.properties.ConfigurationBeanFactory>Metadata
- 关闭Spring Cloud与Spring Boot版本适配检查,spring.cloud.compatibility-verifier.enabled=false
方案2: 既然nacos自己不修复, 那就自己动手丰衣足食
这个方法看起来复杂, 但实际上更简单, 前提要求是最好有自己的maven私服, 或者需要手动变更本地仓库的jar
先到git上去下载源码: git clone https://github.com/nacos-group/nacos-spring-boot-project.git
网络环境不好, 多次尝试失败, 所以去gitee搞吧: git clone https://gitee.com/wu726/nacos-spring-boot-project.git
下载下来后用idea打开, 由于下载的master, 所以版本也就是0.2.10+了, 需要先回到0.2.8. 如果打算用grpc, 就需要同步更新nacos服务端, 可以用0.2.10继续. 我不想再麻烦搞服务端了, 所以通过git log, 找到0.2.8 #198版本回退回去(其实就是pom的依赖有变更, 没发现其他的什么有更新的.)
找到
nacos-spring-boot-parent
下的pom.xml
文件, 修改38行所有springboot依赖到自己项目差不多的版本, 我这里修改为2.5.6(有maven私服执行此步骤)修改根目录下的pom文件11行左右, 将revision修改为0.2.8.1(或0.2.10.1). 总之就是变动一下版本号
修改
NacosBootConfigurationPropertiesBinder
文件为
package com.alibaba.boot.nacos.config.binder;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.annotation.NacosConfigurationProperties;
import com.alibaba.nacos.spring.context.properties.config.NacosConfigurationPropertiesBinder;
import com.alibaba.nacos.spring.core.env.NacosPropertySource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.StandardEnvironment;
import java.lang.reflect.Method;
/**
* @author <a href="mailto:liaochunyhm@live.com">liaochuntao</a>
* @since 0.2.2
*/
public class NacosBootConfigurationPropertiesBinder
extends NacosConfigurationPropertiesBinder {
private final Logger logger = LoggerFactory
.getLogger(NacosBootConfigurationPropertiesBinder.class);
private final ConfigurableListableBeanFactory beanFactory;
private final StandardEnvironment environment = new StandardEnvironment();
public NacosBootConfigurationPropertiesBinder(
ConfigurableApplicationContext applicationContext) {
super(applicationContext);
this.beanFactory = applicationContext.getBeanFactory();
}
@Override
protected void doBind(Object bean, String beanName, String dataId, String groupId,
String configType, NacosConfigurationProperties properties, String content,
ConfigService configService) {
synchronized (this) {
String name = "nacos-bootstrap-" + beanName;
NacosPropertySource propertySource = new NacosPropertySource(name, dataId, groupId, content, configType);
environment.getPropertySources().addLast(propertySource);
Binder binder = Binder.get(environment);
ResolvableType type = getBeanType(bean, beanName);
Bindable<?> target = Bindable.of(type).withExistingValue(bean);
binder.bind(properties.prefix(), target);
publishBoundEvent(bean, beanName, dataId, groupId, properties, content, configService);
publishMetadataEvent(bean, beanName, dataId, groupId, properties);
environment.getPropertySources().remove(name);
}
}
private ResolvableType getBeanType(Object bean, String beanName) {
Method factoryMethod = this.findFactoryMethod(beanName);
if (factoryMethod != null) {
return ResolvableType.forMethodReturnType(factoryMethod);
}
return ResolvableType.forClass(bean.getClass());
}
public Method findFactoryMethod(String beanName) {
if (beanFactory.containsBeanDefinition(beanName)) {
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
if (beanDefinition instanceof RootBeanDefinition) {
return ((RootBeanDefinition) beanDefinition).getResolvedFactoryMethod();
}
}
return null;
}
}
- 修改
NacosConfigEndpointAutoConfiguration
文件将方法上的注解@ConditionalOnEnabledEndpoint
替换为@ConditionalOnAvailableEndpoint
- 修改
NacosDiscoveryEndpointsAutoConfiguration
的文件同上
- 执行Nacos Spring Boot Project的compile检查下有无报错之类的.
到这里基本就算完成了.
如果有maven私服, 在根pom下配置好
<distributionManagement>
<repository>
<id></id>
<url></url>
</repository>
<snapshotRepository>
<id></id>
<url></url>
</snapshotRepository>
</distributionManagement>
并在自己的maven/conf/setting.xml
中配置好server, 执行deploy发布就可以, 然后将自己项目中的依赖修改为0.2.8.1就可以
如果没有maven私服, 就执行package, 将打包好的jar(nacos-config-spring-boot-actuator.jar
, nacos-config-spring-boot-autoconfigure.jar
, nacos-discovery-spring-boot-actuator.jar
)复制到自己仓库中com/alibaba/boot
并覆盖原有的jar, maven refresh一下即可, 部分高版本idea可能需要关闭重新打开index一下
到此问题就解决了.
参考git hub issue#194按照nacos的开发者所说, 近期可能会发布新版修复此问题, 但离我发帖过去半个月了也没见到消息, 不知道这个"近期"到底是啥时候, 而且还是要面临nacos升级带来的grpc问题, 所以还是自己搞0.2.8的修复版本吧