首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JDK 6 動態編譯構建 OSGi 運行時編譯環境:從依賴掃描到 IBM System Director 實戰部署

     編輯:Java 相關知識

痛點:OSGi Bundle 二次開發的編譯環境地獄

OSGi 應用由數百 Bundle 組成,版本依賴錯綜複雜。即使使用 Eclipse PDE,搭建特定 Bundle 的編譯環境也極其繁瑣:每次微小修改都需重新配置 Target Platform、解析依賴、導出構建。迫切需要一個**運行時編譯環境**,直接利用容器內已加載的 Bundle 作為編譯類路徑,實現「所見即所依」。

架構模型

OSGi 應用架構 運行時編譯環境模型

  • 輸入:Java 源碼(字符串/文件)。
  • 掃描容器內所有 Bundle(已加載 JAR/Class)構建編譯類路徑。
  • 調用 JDK 6 javax.tools.JavaCompiler 進行內存編譯。
  • 輸出 .class 文件,可選郵件發送附件。

前置知識:Bundle 依賴三種方式

  1. Import-Package:導入包,MANIFEST.MF 聲明。
  2. Require-Bundle:整體依賴另一 Bundle。
  3. Register/Track Service:代碼級服務註冊發現(或 Declarative Services)。

編譯類路徑需覆蓋以上所有可見類型。

JDK 6 javax.tools 動態編譯 API

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
// 設置編譯類路徑
fm.setLocation(StandardLocation.CLASS_PATH, compileClassPath); // Iterable

// 內存源碼對象
JavaFileObject fo = new InMemoryJavaFileObject(className, sourceCode);
Iterable<? extends JavaFileObject> files = Arrays.asList(fo);

// 編譯任務
StringWriter sw = new StringWriter();
Iterable<String> opts = Arrays.asList("-d", outputDir);
JavaCompiler.CompilationTask task = compiler.getTask(sw, fm, null, opts, null, files);
if (!task.call()) { /* sw.toString() 含錯誤信息 */ }

// 自定義 JavaFileObject
static class InMemoryJavaFileObject extends SimpleJavaFileObject {
    private final String content;
    InMemoryJavaFileObject(String className, String content) {
        super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
        this.content = content;
    }
    public CharSequence getCharContent(boolean ignore) { return content; }
}

JRE 環境兼容**:無 tools.jar 時,直接實例化 com.sun.tools.javac.api.JavacTool() 獲取編譯器。

核心難題:構建完備的編譯類路徑

採用三階段掃描策略,結果緩存至靜態 List allReferences,容器重啟前只執行一次。

階段 1:BundleContext 獲取已安裝 Bundle Location

Bundle[] bundles = bundleContext.getBundles();
for (Bundle b : bundles) {
    String loc = b.getLocation(); // 如 "initial@reference:file:plugins/org.eclipse.core.jobs_3.4.1.jar"
    loc = loc.split("@")[loc.split("@").length-1]; // 取 @ 後
    loc = loc.split(":")[loc.split(":").length-1]; // 取 : 後
    File f = new File("./eclipse/" + loc);
    allReferences.add(f);
}

階段 2:遞歸掃描運行目錄所有 JAR(去重保留最高版本)

List<String> allJars = searchFiles("..", ".jar"); // 遞歸收集絕對路徑
for (String jar : allJars) {
    File f = new File(jar);
    boolean hasNewer = false;
    for (File exist : allReferences) {
        if (isSameJar(f.getName(), exist.getName()) && isOlderVersion(f.getName(), exist.getName())) {
            hasNewer = true; break;
        }
    }
    if (!hasNewer) allReferences.add(f);
}

// 版本比較邏輯:解析文件名版本段,逐位比較
private boolean isOlderVersion(String a, String b) { ... }

耗時最長(大型應用 ~30s),但保證依賴完備性。

階段 3:深度解壓指定「超級 JAR」內嵌 JAR

List<String> deepJars = Arrays.asList("com.mycompany.libs"); // 配置前綴
for (File jar : allReferences) {
    for (String prefix : deepJars) {
        if (jar.getName().startsWith(prefix)) {
            JarFile jf = new JarFile(jar);
            Enumeration<JarEntry> entries = jf.entries();
            while (entries.hasMoreElements()) {
                JarEntry e = entries.nextElement();
                if (e.getName().endsWith(".jar")) {
                    // 解壓到臨時目錄
                    File out = new File(tempDir, e.getName());
                    copy(jf.getInputStream(e), new FileOutputStream(out));
                    allReferences.add(out);
                }
            }
        }
    }
}

完整編譯流程整合

// 1. 獲取編譯類路徑
Iterable<? extends File> cp = Utils.getCompileClassPath(); // 返回 allReferences

// 2. 編譯
fm.setLocation(StandardLocation.CLASS_PATH, cp);
JavaCompiler.CompilationTask task = compiler.getTask(sw, fm, null, opts, null, files);
if (!task.call()) { log.error(sw); return; }

// 3. 收集輸出 .class 並郵件發送
File outDir = new File(outputDir);
List<File> attachments = Arrays.stream(outDir.listFiles())
    .filter(f -> f.getName().endsWith(".class")).collect(toList());
Utils.sendMail(smtpHost, "DynamicCompileTool", toEmail, attachments);

實戰部署:IBM System Director(Equinox,800+ Bundle)

工具主界面 編譯演示 郵件附件

  • Bundle 安裝於 Director,啟動時掃描依賴(首次 ~30s,生成 1790 項依賴)。
  • UI 粘貼源碼(示例用工具自身 Utils.java),點擊編譯 → 2.5s 完成,郵件收到 .class 附件含內部類。
  • 性能分析:

各階段耗時統計

擴展與遷移建議

  • 項目級編譯:結合 Ant/Maven,掃描源碼目錄批量編譯並熱部署(Bundle.update())。
  • 通用化**:不限 OSGi,任何 Java 進程(Tomcat、獨立應用)均可掃描 ClassLoader.getURLs() 構建類路徑實現運行時編譯。
  • 安全性**:生產環境需限制編譯接口訪問權限,防止任意代碼執行風險。

總結

利用 JDK 6 javax.tools 與 OSGi 運行時元數據,構建了**零配置、依賴自發現、即時編譯**的開發利器:

  1. 三階段掃描覆蓋 Bundle Location、文件系統 JAR、嵌套 JAR,依賴完備性高。
  2. 緩存機制確保首次掃描後後續編譯毫秒級響應。
  3. 郵件/文件雙輸出模式適配不同開發流程。
  4. 在 800+ Bundle 的企業級產品中驗證可行,顯著降低二次開發環境搭建成本。

此模式可推廣至熱修復、動態腳本、運行時 AOP 織入等進階場景,發揮 Java 動態編譯的最大價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved