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

Eclipse RCP 整合 IoC 容器實戰:基於 ASM 字節碼增強與 Java Agent 的無侵入依賴注入

     編輯:Java 相關知識

動機:RCP 插件間解耦痛點

  • Eclipse RCP 基於 **插件 + 擴展點** 機制,插件間物理依賴強 (需在 `MANIFEST.MF` `Require-Bundle`)。
  • 單例服務 (數據源、日志、偏好設置) 獲取方式碎片化:`Platform.getService()`、`Activator.getDefault().getService()`、JNDI 等。
  • 期望:**聲明式依賴注入** (`@Inject`/`@Autowired`),容器自動裝配,業務代碼零容器 API 污染。

技術棧選型

層面選型理由
字節碼操作ObjectWeb ASM輕量、高效、Visitor 模式、無運行時依賴
類加載攔截java.lang.instrument (Java Agent)JVM 標準 API、premain 階段介入、無需修改啟動腳本 (IDE 支援 `-javaagent`)
元數據Java Annotation (`@Serviceable`, `@Injected`)標準化、IDE 支援、編譯期檢查
容器實現自研 ServiceLocator (Eclipse 插件)適配 RCP 插件類加載器隔離、擴展點註冊服務工廠

核心註解定義


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Serviceable {} // 標記類需容器管理

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Injected {
    boolean optional() default false; // 依賴可選
}
// 用法示例
@Serviceable
public class ViewPartImpl extends ViewPart {
    private IFortuneCookie cookie;
    
    @Injected(optional = false)
    public void setCookie(IFortuneCookie cookie) { this.cookie = cookie; }
}

Java Agent 字節碼增強流程


public class IOCTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class redefined,
                            ProtectionDomain pd, byte[] bytecode) {
        // 1. 掃描類註解
        ClassReader cr = new ClassReader(bytecode);
        ClassAnnotationVisitor cav = new ClassAnnotationVisitor(new ConstructorVisitor());
        cr.accept(cav, ClassReader.SKIP_DEBUG);
        
        // 2. 若無 @Serviceable 或無構造器,直接返回
        if (cav.getConstructors().isEmpty()) return null;
        
        // 3. 增強所有構造器:插入 ServiceLocator.service(this)
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassConstructorWriter ccw = new ClassConstructorWriter(cav.getConstructors(), cw);
        cr.accept(ccw, 0);
        return cw.toByteArray();
    }
    
    // Agent 入口
    public static void premain(String args, Instrumentation inst) {
        inst.addTransformer(new IOCTransformer());
    }
}

ASM Visitor 關鍵邏輯


// 識別 @Serviceable 類註解
class ClassAnnotationVisitor extends ClassVisitor {
    private boolean serviceable = false;
    private ConstructorVisitor cv;
    
    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (visible && desc.equals("Lcom/example/Serviceable;")) serviceable = true;
        return super.visitAnnotation(desc, visible);
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] ex) {
        return serviceable ? cv.visitMethod(access, name, desc, sig, ex) : null; // 非目標類直接忽略
    }
}

// 收集構造器
class ConstructorVisitor extends MethodVisitor {
    private List constructors = new ArrayList<>();
    @Override
    public void visitMethod(int access, String name, String desc, String sig, String[] ex) {
        if (name.equals("") && Type.getReturnType(desc).equals(Type.VOID_TYPE)) {
            constructors.add(new ConstructorInfo(access, desc));
        }
    }
}

// 增強構造器:插入靜態方法調用
class ClassConstructorWriter extends ClassVisitor {
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] ex) {
        MethodVisitor mv = super.visitMethod(access, name, desc, sig, ex);
        if (isTargetConstructor(name, desc)) {
            return new MethodVisitor(Opcodes.ASM5, mv) {
                @Override
                public void visitCode() {
                    super.visitCode();
                    // 插入: ServiceLocator.service(this)
                    mv.visitVarInsn(Opcodes.ALOAD, 0); // this
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                        "com/example/ServiceLocator", "service",
                        "(Ljava/lang/Object;)V", false);
                }
            };
        }
        return mv;
    }
}

ServiceLocator 容器實現 (Eclipse 插件)


public class ServiceLocator {
    private static ServiceLocator INSTANCE = new ServiceLocator();
    private Map, IServiceFactory> factories = new ConcurrentHashMap<>();
    private Set serviced = Collections.newSetFromMap(new IdentityHashMap<>());
    
    // 由擴展點註冊工廠
    public void registerFactory(Class serviceInterface, IServiceFactory factory) {
        factories.put(serviceInterface, factory);
    }
    
    // 構造器增強調用入口
    public static void service(Object serviceable) {
        INSTANCE.doService(serviceable);
    }
    
    private void doService(Object obj) {
        if (serviced.contains(obj)) return; // 防重複 (構造器鏈)
        serviced.add(obj);
        
        for (Method m : obj.getClass().getMethods()) {
            Injected ann = m.getAnnotation(Injected.class);
            if (ann == null) continue;
            
            Object[] args = new Object[m.getParameterCount()];
            for (int i = 0; i < args.length; i++) {
                Class paramType = m.getParameterTypes()[i];
                IServiceFactory factory = factories.get(paramType);
                if (factory == null) {
                    if (!ann.optional()) throw new ServiceException("Missing factory for " + paramType);
                    continue;
                }
                args[i] = factory.getServiceInstance();
            }
            try { m.invoke(obj, args); }
            catch (Exception e) { if (!ann.optional()) throw new ServiceException(e); }
        }
    }
}


Eclipse 插件化部署

  1. ServiceLocator 插件**:導出 `ServiceLocator`、`IServiceFactory`、註解 API;定義擴展點 `com.example.servicefactory`。
  2. 業務服務插件** (如 `FortuneService`):實現 `IServiceFactory`,在 `plugin.xml` 註冊:
    
    
        
    
    
  3. 客戶端插件** (如 `FortuneClient`):View 類標註 `@Serviceable`、`@Injected`,**無任何容器 API 依賴**。
  4. 啟動配置**:在 `.product` / `config.ini` 添加 `-javaagent:ioc-agent.jar`。

優勢總結

傳統 RCP 方式本方案
手動查找服務 (`Activator.getDefault().getService()`)聲明式 `@Injected` 自動裝配
業務代碼依賴容器 API**零侵入**,純註解
插件啟動順序敏感構造器注入天然解決初始化順序
難以單元測試 (需啟動 Platform)POJO 可獨立測試,Mock 服務輕鬆

擴展方向

  • 生命週期管理:`@PostConstruct`/`@PreDestroy` 掛鉤 `ServiceLocator` 緩存清理。
  • 作用域支援:Prototype、SessionScoped (結合 IEclipseContext)。
  • 循環依賴檢測:構造器注入階段圖算法預檢。
  • 整合 Eclipse 4 DI (EclipseContext/ContextFunction) 實現原生兼容。

總結

結合 **ASM 字節碼增強 + Java Agent 類加載攔截 + Eclipse 擴展點服務註冊**,在**不修改 RCP 核心、不污染業務代碼**前提下,為 Eclipse RCP 植入了現代化 IoC 容器。此模式同樣適用於任何基於 OSGi/模塊化的 Java 模塊化應用改造。

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