Arthas实现动态编译


背景

前段时间在开发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打的包有关系,结构如下:
image.png

解决方案

为了减少绕弯路,最终直接使用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");
    }
}

通过以上方式简单解决问题。


文章作者: zzq0324
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zzq0324 !
  目录