250228-记一次Java引用SDK导致的功能异常
背景
由于公司需要,开发了一个注册中心(提供统一的注册服务功能,允许公司进行离线的产品激活管理)。为了方便其他应用使用,还开发了一个SDK包,让第三应用可以更快的接入到这个注册中心。在开发测试阶段一切正常,第一次服务器上运行也没有任何问题,均按照预期流程进行。但是当更新版本更新时,发现激活状态丢失(最后发现是由于注册文件夹生成位置会变动而导致更新后无法找到激活文件导致)。
情况说明
Java-a服务引用sdk,a服务调用sdk的generate方法,sdk会在指定文件夹下生成registry激活文件夹,文件夹下会存放相关激活文件。
复现
本地测试
使用测试方法进行复现,发现文件夹存放于target/registry下。
client-代码测试
使用client测试包进行测试,发现文件夹存放于target/classes/registry
client-jar包测试
将client进行打包,使用jar包进行测试,发现文件夹存放于app.jar!/BOOT-INF/classes!/registry(本目录以与jar包同级目录为基准)
问题定位
经过测试,并与线上情况比对,发现问题存在,当版本进行更新,由于线上的jar包的格式为app_x.y.z.jar格式,每次更新版本号都会修改,且生成的文件夹路径与jar包名称相关,故版本更新后无法找到原始的激活文件,导致系统检测激活状态异常。故问题代码应该是存在于获取基路径的相关代码中。
原代码在代码中指定注册文件夹的路径为相对路径registry,并未关心具体基路径,认为预期路径应该是与jar包同级,即父文件夹下存放.jar文件和registry文件夹,但是实际情况与预期情况不符,故产生此问题。
解决方案
目标
指定生成的注册文件夹registry的地址,保证每次生成的地址相同。
方案1 ——指定存放于用户文件夹
- 简单快捷
- 当一台服务器存在多个服务时,可能会存在B服务的激活覆盖A服务的激活,导致最终使用情况不理想。或需要额外传递参数,增加sdk使用难度
方案2——使用运维方法,每次重命名激活文件夹
- 运维脚本编写复杂
- 不同项目需要重复编写运维代码
方案3——在代码中找到jar包位置并设置为基目录(最终采用)
- 一次修改
代码实现
/**
* 默认路径
*/
protected static final String DEFAULT_DIR;
static {
String parentDir = null;
try {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
String mainClassName = null;
for (StackTraceElement element : stackTrace) {
if ("main".equals(element.getMethodName())) {
mainClassName = element.getClassName();
break;
}
}
Class<?> mainClass = Class.forName(mainClassName);
parentDir = findSource(mainClass).getParent();
} catch (Exception e) {
}
if (parentDir != null) {
DEFAULT_DIR = parentDir + File.separator + "registry";
} else {
DEFAULT_DIR = "registry";
}
}
/**
* get from org/springframework/boot/system/ApplicationHome.java
*/
private static File findSource(Class<?> sourceClass) {
try {
ProtectionDomain domain = (sourceClass != null) ? sourceClass.getProtectionDomain() : null;
CodeSource codeSource = (domain != null) ? domain.getCodeSource() : null;
URL location = (codeSource != null) ? codeSource.getLocation() : null;
File source = (location != null) ? findSource(location) : null;
if (source != null && source.exists()) {
return source.getAbsoluteFile();
}
} catch (Exception ignored) {
}
return null;
}
private static File findSource(URL location) throws IOException, URISyntaxException {
URLConnection connection = location.openConnection();
if (connection instanceof JarURLConnection) {
return getRootJarFile(((JarURLConnection) connection).getJarFile());
}
return new File(location.toURI());
}
private static File getRootJarFile(JarFile jarFile) {
String name = jarFile.getName();
int separator = name.indexOf("!/");
if (separator > 0) {
name = name.substring(0, separator);
}
return new File(name);
}

