问题
java.lang.NullPointerException: null
at org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation.getAllUnmodifiableSubscribedURLs(MetadataServiceDelegation.java:117)
at org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation.getSubscribedURLs(MetadataServiceDelegation.java:88)
推测Dubbo 3.1.2~3.1.4 之间均有这个问题。
原因
查看源码发现 getAllUnmodifiableSubscribedURLs
就是单纯没有进行空判断。
这个现象应该是Dubbo-Admin调用Dubbo的应用触发的这个bug。
现状
截至发文,2023年1月18日,Dubbo-3.1.5已经修复这个问题。
方法
-
直接升级
直接升级可能有未知风险,就像从2.x升级到3.1.2遇到这个问题一样。 -
重新编译
没必要重新编译发布到私服,这种办法一般是针对 BUG没有人发现、没人修复、迭代缓慢的项目。 -
重写+构建
通过SPI注册自己的实现类,修正他的行为,再结合maven打包插件来达成目的。
这种方法不算简单,有点舍近求远,但是不依赖其他环境,不依赖自建maven仓库,契合原来的构建系统。
利用的是OOP编程的继承,多态思想。
分析
Dubbo 使用了扩展点加载机制,也就是类SPI,本质上是一样的。
找到扩展点
/**
* Listen for Dubbo application deployment events
*/
@SPI(scope = ExtensionScope.APPLICATION)
public interface ApplicationDeployListener extends DeployListener<ApplicationModel> {
default void onModuleStarted(ApplicationModel applicationModel) {
}
}
找到实现类
ExporterDeployListener
编写子类
点击查看代码
public class SlankkaHackedExporterDeployListener extends ExporterDeployListener {
private static final Logger logger = LoggerFactory.getLogger(SlankkaHackedExporterDeployListener.class);
//提前注入自己的MetadataServiceDelegation实例
@Override
public synchronized void onModuleStarted(ApplicationModel applicationModel) {
logger.info("{} injected", SlankkaHackedExporterDeployListener.class.getName());
applicationModel.getBeanFactory().registerBean(null, new SlankkaMetadataServiceDelegation(applicationModel));
super.onModuleStarted(applicationModel);
}
static class URLComparator implements Comparator<URL> {
//etc
}
//重写有问题的类,重写getSubscribedURLs方法(可以直接复制原来的)
public static class SlankkaMetadataServiceDelegation extends MetadataServiceDelegation {
public SlankkaMetadataServiceDelegation(ApplicationModel applicationModel) {
super(applicationModel);
}
@Override
public SortedSet<String> getSubscribedURLs() {
SortedSet<URL> bizURLs = new TreeSet<>(URLComparator.INSTANCE);
List<MetadataInfo> metadataInfos = getMetadataInfos();
for (MetadataInfo metadataInfo: metadataInfos) {
Map<String, SortedSet<URL>> serviceURLs = metadataInfo.getSubscribedServiceURLs();
if (serviceURLs == null) {
continue;
}
for (Map.Entry<String, SortedSet<URL>> entry : serviceURLs.entrySet()) {
SortedSet<URL> urls = entry.getValue();
if (urls != null) {
for (URL url : urls) {
if (!MetadataService.class.getName().equals(url.getServiceInterface())) {
bizURLs.add(url);
}
}
}
}
}
return MetadataService.toSortedStrings(bizURLs);
}
}
}
构建
新建一个模块(假设叫做 dubbo-hack,后面会提到),pom.xml中的关键信息:
点击查看代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>unpack-dependencies</id>
<phase>validate</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
其中 artifactItems 中应该包含两类信息:
- 要解压的jar:指定 dubbo 的GAV, 要排除 ExporterDeployListener 的SPI文件
- 要解压的jar的传递依赖: fastjson,fastjson2,com.alibaba.spring:spring-context-support等。
- dubbo
<artifactItem>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/repackDubbo</outputDirectory>
<destFileName>slankka-repacked.jar</destFileName>
<includes>**/*</includes>
<excludes>META-INF/dubbo/internal/org.apache.dubbo.common.deploy.ApplicationDeployListener</excludes>
</artifactItem>
- 这部分省略,和上面的类似。
到此 如果直接编译会将dubbo.jar自身的文件解压到outputDirectory文件夹内。
重新打包
这个时候可以用maven-assembly-plugin。
maven-assembly-plugin 的关键配置
<configuration>
<!-- Name of new archive -->
<finalName>dubbo-${dubbo.version}-slankka-solution</finalName>
<!-- We don't want the id of the assembly descriptor in the filename -->
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<!-- Descriptor of the contents of the new archive -->
<descriptor>src/assembly/repack.xml</descriptor>
</descriptors>
</configuration>
repack.xml的关键配置 fileSets
<fileSets>
<fileSet>
<!-- Files that are extracted from the original archive -->
<directory>target/repackDubbo</directory>
<outputDirectory/>
</fileSet>
</fileSets>
到此 执行打包 会将解压后的文件重新生成一个jar。
最后一步
根据应用的不同,使用的最终打包工具不一样。
- maven-shade-plugin
- maven-jar-plugin
- maven-war-plugin
这里项目用到的是war,但一行pom都不用改,只要war所在的pom,依赖了dubbo-hack这个模块,打包的程序会自动将生成的target下的jar 打进 war内。
SPI
由于实现类是放在 war所在的模块,写一份SPI文件来替换Dubbo自己的:
文件所在位置 META-INF/dubbo/internal/org.apache.dubbo.common.deploy.ApplicationDeployListener
exporter=com.slankka.dubbo.hack.SlankkaHackedExporterDeployListener
这里写的是子类
进一步优化
由于新建的专门打包dubbo用的模块 dubbo-hack,里面没有任何代码,不需要生成jar,因此可以关闭编译过程。
编译默认使用的maven-jar-plugin
因此可以通过配置maven-jar-plugin,来关闭这个过程。
声明一个 maven-jar-plugin,填写配置
<configuration>
<skipIfEmpty>true</skipIfEmpty>
</configuration>
这样dubbo-hack生成的 target内只有一个dubbo的jar。
神奇的是,会被 war打包的过程中当成 dubbo-hack.jar,无论叫什么名字。
验证
服务启动以后,会出现一行日志
标签:11191,Dubbo,插件,dubbo,applicationModel,plugin,jar,maven From: https://www.cnblogs.com/slankka/p/17059525.htmlSlankkaHackedExporterDeployListener injected