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

《編寫高質量代碼》建議 106-109:動態代理、裝飾模式、模板方法與反射效率辯證

     編輯:Java 綜合教學

建議 106:動態代理讓代理模式更靈活

靜態代理痛點

代理類需實現同一接口,委托給真實對象,每新增一個被代理類就需寫一個代理類,代碼重復度高。

JDK 動態代理核心機制

// 1. 定義調用處理器
class SubjectHandler implements InvocationHandler {
    private Subject target;
    public SubjectHandler(Subject target) { this.target = target; }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("預處理..."); // 統一前置邏輯
        Object result = method.invoke(target, args); // 反射調用目標
        System.out.println("後處理..."); // 統一後置邏輯
        return result;
    }
}

// 2. 創建代理實例
Subject proxy = (Subject) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new SubjectHandler(target)
);

proxy.request(); // 觸發 invoke()

關鍵點

  • Proxy.newProxyInstance 根據接口動態生成實現類字節碼(繼承 Proxy、實現目標接口)。
  • 所有接口方法調用均委托給 InvocationHandler.invoke(),實現統一攔截。
  • 適用於接口驅動的場景(AOP、RPC 框架、連接池包裝)。

建議 107:反射增強裝飾模式普適性

傳統裝飾模式

裝飾類繼承組件接口,持有組件引用,構造函數傳入具體裝飾類,靜態組合,擴展需新增裝飾類。

動態代理 + 反射實現通用裝飾器

// 能力接口
interface Feature { void load(); }
class FlyFeature implements Feature { public void load() { System.out.println("加翼"); } }
class DigFeature implements Feature { public void load() { System.out.println("鑽地"); } }

// 通用裝飾動作類
class DecorateAnimal implements Animal {
    private Animal animal;
    private Class<? extends Feature> featureClz;
    
    public DecorateAnimal(Animal animal, Class<? extends Feature> clz) {
        this.animal = animal; this.featureClz = clz;
    }
    
    @Override public void doStuff() {
        InvocationHandler handler = (proxy, method, args) -> {
            if (Modifier.isPublic(method.getModifiers())) {
                method.invoke(featureClz.newInstance(), args); // 動態實例化能力並調用
            }
            animal.doStuff(); // 委托核心業務
            return null;
        };
        Feature proxy = (Feature) Proxy.newProxyInstance(
            getClass().getClassLoader(), featureClz.getInterfaces(), handler);
        proxy.load(); // 觸發能力加載
    }
}

// 客戶端疊加能力
Animal jerry = new Rat();
jerry = new DecorateAnimal(jerry, FlyFeature.class);
jerry = new DecorateAnimal(jerry, DigFeature.class);
jerry.doStuff();

優勢

  • 裝飾行為由 InvocationHandler 動態控制,可讀持久化配置決定裝飾邏輯。
  • 核心類與裝飾類完全解耦,新增能力無需修改裝飾器代碼。
  • 實現了開閉原則:對擴展開放,對修改關閉。

建議 108:反射讓模板方法模式更強大

傳統模板方法

父類定義 final templateMethod() 調用 abstract primitiveOps(),子類必須實現所有抽象方法,擴展受限。

反射增強版:約定優於配置

public abstract class AbsPopulator {
    // 模板方法:掃描所有 public 方法,匹配規則自動調用
    public final void dataInitialing() throws Exception {
        for (Method m : getClass().getMethods()) {
            if (isInitDataMethod(m)) m.invoke(this);
        }
    }
    
    // 基本方法鑒別器:命名規約 + 修飾符 + 簽名約束
    private boolean isInitDataMethod(Method m) {
        return m.getName().startsWith("init")
            && Modifier.isPublic(m.getModifiers())
            && m.getReturnType() == void.class
            && m.getParameterCount() == 0
            && !Modifier.isAbstract(m.getModifiers());
    }
}

// 子類自由定義任意數量 initXxx() 方法
public class UserPopulator extends AbsPopulator {
    public void initUser() { /* 建表、導數據 */ }
    public void initPassword() { /* 密碼策略 */ }
    public void initJobs() { /* 定時任務 */ }
}

對比 JUnit

JUnit 3/4 早期要求測試方法以 test 開頭、public、無參、無返回,本質相同。JUnit 5 改用註解(@Test),但反射掃描機制未變。

建議 109:不必過度關注反射效率

效率真相

  • 反射確實比直接調用慢(方法訪問檢查、參數數組創建、泛型擦除處理)。
  • 但絕大多數業務代碼性能瓶頸不在反射(IO、鎖、算法、數據庫才是大頭)。

實戰案例:泛型 DAO 基類獲取實際類型

abstract class BaseDao<T> {
    // 運行期獲取 T 的實際 Class
    private Class<T> entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
        .getActualTypeArguments()[0];
    
    public T get(Serializable id) {
        return session.get(entityClass, id); // Hibernate 需要 Class 對象
    }
}

class UserDao extends BaseDao<User> { } // 編譯期確定 T=User

若拒絕反射

  • 每個子類必須重寫 get() 傳入 User.class,大量重復代碼。
  • 違反 DRY 原則,維護成本指數級上升。

二八原則指導

80% 的性能消耗集中在 20% 的代碼中。優化應針對熱點路徑(經 Profiling 證實),而非預防性地拒絕反射等強大工具。

工程建議

  1. 代碼結構清晰、可讀性優先,先跑通業務。
  2. 壓測發現反射為熱點時,再考慮緩存 Method 對象、使用 MethodHandle、或代碼生成(ASM/ByteBuddy)替換。
  3. 框架級代碼(Spring、MyBatis、Hibernate)大量使用反射,證明其工程可行性。

總結

反射是 Java 動態性的基石,動態代理、裝飾模式增強、模板方法泛化、泛型類型擷取等高級模式均依賴它。效率低是真命題,因效率低而拒用是假命題。善用反射實現框架級抽象,業務層專注領域邏輯,才是高質量代碼的正道。

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