動機: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 插件化部署
ServiceLocator 插件** :導出 `ServiceLocator`、`IServiceFactory`、註解 API;定義擴展點 `com.example.servicefactory`。
業務服務插件** (如 `FortuneService`):實現 `IServiceFactory`,在 `plugin.xml` 註冊:
客戶端插件** (如 `FortuneClient`):View 類標註 `@Serviceable`、`@Injected`,**無任何容器 API 依賴**。
啟動配置** :在 `.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 模塊化應用改造。