背景
前段时间在开发Feature Flag工具的时候,希望在运行过程中可以动态编译配置类,借助Spring的配置解析来实现配置的解析和设置。
实际在开发过程中遇到了一些问题,在本地IDE上运行没问题,但是部署到服务器上总会报错,信息如下:
Source: /enable_open_search.java
Line 92: cannot find symbol
symbol: variable StringUtils
location: class cn.zzq0324.feature.flag.client.enable_open_search
大概意思就是找不到依赖的类了。很好奇为什么找不到类,明明pom.xml
中已经有引入对应的包了。
分析排查
项目一开始是依赖了第三方jar来实现动态编译的,依赖的jar信息如下:
<dependency>
<groupId>com.itranswarp</groupId>
<artifactId>compiler</artifactId>
<version>1.0</version>
</dependency>
本质是通过JDK实现动态编译的。
另外,项目使用了SpringBoot
框架,打包后的jar包是比较特殊的,所以通过JDK常规的动态编译方式会加载不到对应的类,导致编译出错。
网上也有不少这类的反馈:
这和SpringBoot
打的包有关系,结构如下:
解决方案
为了减少绕弯路,最终直接使用arthas-memorycompiler
来解决问题。
pom.xml增加依赖
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-memorycompiler</artifactId>
<version>3.5.2</version>
</dependency>
执行动态编译
package cn.zzq0324.feature.flag.support;
import com.taobao.arthas.compiler.DynamicCompiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
/**
* description: JdkCompiler <br>
* date: 2021/6/11 10:31 下午 <br>
* author: zzq0324 <br>
* version: 1.0 <br>
*/
public class JdkCompiler {
private static final Logger logger = LoggerFactory.getLogger(JdkCompiler.class);
public static final String JAVA_FILE_SUFFIX = ".java";
/**
* 动态编译并返回Class
*
* @param classPackage 类的包路径
* @param classSimpleName 类名
* @param source 代码
* @return 返回编译后的Class
* @throws IOException
* @throws ClassNotFoundException
*/
public static <T> Class<? extends T> compile(String classPackage, String classSimpleName, String source) {
DynamicCompiler dynamicCompiler = new DynamicCompiler(Thread.currentThread().getContextClassLoader());
String className = classPackage + "." + classSimpleName;
dynamicCompiler.addSource(className, source);
Map<String, Class<?>> classMap = dynamicCompiler.build();
// 成功编译,未报错
if (CollectionUtils.isEmpty(dynamicCompiler.getErrors())) {
return (Class<? extends T>)classMap.get(className);
}
logger.error("Compile class: {} error, error info: {}", className,
Arrays.toString(dynamicCompiler.getErrors().toArray()));
throw new IllegalStateException("Compile error");
}
}
通过以上方式简单解决问题。