1. 程式人生 > >JDK、CGLib、Javassist實現動態代理

JDK、CGLib、Javassist實現動態代理

一、類載入

1.類載入過程模擬(先明白類載入過程,方可模擬類執行期間載入-建立代理類,呼叫目標方法)

public class Programmer {
    public void code() {
        System.out.println("I'm a Programmer,Just Coding.....");
    }
}
/**
 * 自定義一個類載入器,用於將位元組碼轉換為class物件 
 */
public class MyClassLoader extends ClassLoader {
    public Class<?> defineMyClass(byte[] b, int off, int len) {
        //TODO SOURCE CODE
        return super.defineClass(null,b, off, len);
    }
}
public class MyTest {
    public static void main(String[] args) throws IOException {
        //讀取本地的class檔案內的位元組碼,轉換成位元組碼陣列  
        File file = new File(".");
        InputStream input = new FileInputStream(file.getCanonicalPath() +
                "\\target\\classes\\com\\max\\dproxy\\loadseq\\Programmer.class");
        byte[] result = new byte[1024];//位元組型

        int count = input.read(result);
        // 使用自定義的類載入器將 byte位元組碼陣列轉換為對應的class物件  
        MyClassLoader loader = new MyClassLoader();
        Class clazz = loader.defineMyClass(result, 0, count);
        //測試載入是否成功,列印class 物件的名稱  
        System.out.println(clazz.getCanonicalName());
        
        try {
            //例項化一個Programmer物件  
            Object o = clazz.newInstance();
            //呼叫Programmer的code方法  
            clazz.getMethod("code", null).invoke(o, null);
        } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

2.類載入過程


注:綠色橢圓內即JVM虛擬機器狀態,具體不做贅述。

二、動態代理

代理模式:


靜態代理:手動編寫代理類代理目標類方法。缺點:手動建立;代理類越來越多,系統規模增大,不易維護;

動態代理:由於JVM通過位元組碼的二進位制(byte-code)資訊載入類的,那麼,如果我們在執行期系統中,1.遵循Java編譯系統組織.class檔案的格式和結構2.生成相應的二進位制資料3.然後再把這個二進位制資料載入轉換成對應的類,這樣,就完成了在程式碼中,動態建立一個類的能力了。


JDK實現動態代理

public interface Vehicle {
    void drive();
}
public interface Rechargable {
    void recharge();
}
public class ElectricCar implements Rechargable, Vehicle {
    @Override
    public void drive() {
        System.out.println("Electric Car is Moving silently...");
    }
    @Override
    public void recharge() {
        System.out.println("Electric Car is Recharging...");
    }
}
public class InvocationHandlerImpl implements InvocationHandler {
    private ElectricCar car;
    public InvocationHandlerImpl(ElectricCar car) {
        this.car = car;
    }
    @Override
    public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
        System.out.println("You are going to invoke " + paramMethod.getName() + " ...");
        paramMethod.invoke(car, null);
        System.out.println(paramMethod.getName() + " invocation Has Been finished...");
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
        ElectricCar car = new ElectricCar();
        // 1.獲取對應的ClassLoader  
        ClassLoader classLoader = car.getClass().getClassLoader();
        // 2.獲取ElectricCar 所實現的所有介面
        Class[] interfaces = car.getClass().getInterfaces();
        // 3.設定一個來自代理傳過來的方法呼叫請求處理器,處理所有的代理物件上的方法呼叫  
        InvocationHandler handler = new InvocationHandlerImpl(car);
        /* 
          4.根據上面提供的資訊,建立代理物件 在這個過程中,  
          a.JDK會通過根據傳入的引數資訊動態地在記憶體中建立和.class檔案等同的位元組碼 
          b.然後根據相應的位元組碼轉換成對應的class,  
          c.然後呼叫newInstance()建立例項 
         */
        Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
        Vehicle vehicle = (Vehicle) o;
        vehicle.drive();
        Rechargable rechargeable = (Rechargable) o;
        rechargeable.recharge();
    }
}


newProxyInstance過程做了3件事:

1.通過傳入的類資訊(載入器、介面、處理器-增強方法)動態的在記憶體中建立和.class檔案同等的位元組碼(代理類位元組碼)

2.將該位元組碼轉換成對應類(生成代理類的步驟)

3.通過newInstance建立類,而後呼叫1中傳入的所有介面方法。

對比類的載入過程,123步驟。

cglib實現動態代理

public class Programmer {
    public void code() {
        System.out.println("I'm a Programmer,Just Coding.....");
    }
}
/*
 * 實現了方法攔截器介面 Spring AOP實現方式
 */
public class Hacker implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");
        proxy.invokeSuper(obj, args);
        System.out.println("****  Oh,what a poor programmer.....");
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
        Programmer progammer = new Programmer();
        Hacker hacker = new Hacker();

        //cglib 中加強器,用來建立動態代理  
        Enhancer enhancer = new Enhancer();
        //設定要建立動態代理的類  
        enhancer.setSuperclass(progammer.getClass());
        // 設定回撥,這裡相當於是對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實行intercept()方法進行攔截  
        enhancer.setCallback(hacker);
        Programmer proxy = (Programmer) enhancer.create();
        proxy.code();
    }
}


javassist實現動態代理

public class Programmer {
    public void code() {System.out.println("I'm a Programmer,Just Coding.....");
    }
}
public class MyGenerator {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        //建立Programmer類       
        CtClass cc = pool.makeClass("com.max.dproxy.javassist.Programmer");
        //定義code方法  
        CtMethod method = CtNewMethod.make("public void code(){}", cc);
        //插入方法程式碼  增強
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
        cc.addMethod(method);

        //儲存生成的位元組碼  
        cc.writeFile("d://temp");
    }
}

以上先簡單顯示一下javassist的用法。(其實insertBefore就已經是在針對code方法的增強,上述程式碼補充上cc位元組碼的load和建立過程【如下下例中的】及完整的顯示了javassist建立代理的步驟)。

//獲取動態生成的class  
        Class c = cc.toClass();
        //獲取構造器  
        Constructor constructor = c.getConstructor(TicketServiceImpl.class);
        //通過構造器例項化  
        TicketService o = (TicketService) constructor.newInstance(new TicketServiceImpl());
        o.sellTicket();

以下顯示使用javassist實現動態代理的完整步驟

public interface TicketService {
    //售票
    void sellTicket();
    //問詢
    void inquire();
    //退票
    void withdraw();
}
public class TicketServiceImpl implements TicketService {
    @Override  
    public void sellTicket() {  
        System.out.println("\n\t售票.....\n");  
    }  
    @Override  
    public void inquire() {  
        System.out.println("\n\t問詢。。。。\n");  
    }  
    @Override  
    public void withdraw() {  
        System.out.println("\n\t退票......\n");  
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        createProxy();
    }
    /* 
     * 手動建立位元組碼 
     */
    private static void createProxy() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("com.max.dproxy.staticproxy.StationProxy");
        
        //設定介面  
        CtClass interface1 = pool.get("com.max.dproxy.staticproxy.TicketService");
        cc.setInterfaces(new CtClass[] { interface1 });

        //設定Field  
        CtField field = CtField.make("private com.max.dproxy.staticproxy.TicketServiceImpl station;", cc);
        cc.addField(field);

        CtClass stationClass = pool.get("com.max.dproxy.staticproxy.TicketServiceImpl");
        CtClass[] arrays = new CtClass[] { stationClass };
        CtConstructor ctc = CtNewConstructor.make(arrays, null, CtNewConstructor.PASS_NONE, null, null, cc);
        //設定建構函式內部資訊  
        ctc.setBody("{this.station=$1;}");
        cc.addConstructor(ctc);

        //建立5個方法
        //建立收取手續方法
        CtMethod takeHandlingFee = CtMethod.make("private void takeHandlingFee() {}", cc);
        takeHandlingFee.setBody("System.out.println(\"收取手續費,打印發票。。。。。\");");
        cc.addMethod(takeHandlingFee);

        //建立showAlertInfo方法  
        CtMethod showInfo = CtMethod.make("private void showAlertInfo(String info) {}", cc);
        showInfo.setBody("System.out.println($1);");
        cc.addMethod(showInfo);

        //建立sellTicket
        CtMethod sellTicket = CtMethod.make("public void sellTicket(){}", cc);
        //"{this.showAlertInfo 都是在呼叫這些方法,括號中傳入需print的string
        //但proxy物件中,sellTicket方法並未執行,而是執行的動態代理中method的流程
        sellTicket.setBody("{this.showAlertInfo(\"showAlertInfo********\");" + "station.sellTicket();"
                + "this.takeHandlingFee();" + "this.showAlertInfo(\"××××BYBY!××××\");}");
        cc.addMethod(sellTicket);

        //建立inquire方法
        CtMethod inquire = CtMethod.make("public void inquire() {}", cc);
        inquire.setBody("{this.showAlertInfo(\"××××歡迎光臨本代售點,問詢服務不會收取任何費用,本問詢資訊僅供參考,具體資訊以車站真實資料為準!××××\");"
                + "station.inquire();" + "this.showAlertInfo(\"××××歡迎您的光臨,再見!××××\");}");
        cc.addMethod(inquire);

        //建立widthraw方法
        CtMethod withdraw = CtMethod.make("public void withdraw() {}", cc);
        withdraw.setBody("{this.showAlertInfo(\"××××歡迎光臨本代售點,退票除了扣除票額的20%外,本代理處額外加收2元手續費!××××\");"
                + "station.withdraw();" + "this.takeHandlingFee();}");
        cc.addMethod(withdraw);

        //獲取動態生成的class  
        Class c = cc.toClass();
        //獲取構造器  
        Constructor constructor = c.getConstructor(TicketServiceImpl.class);
        //通過構造器例項化  
        TicketService o = (TicketService) constructor.newInstance(new TicketServiceImpl());
        o.sellTicket();
    }
}


這裡的Proxy代理類即可刪除,動態生成代理,取代以下的代理類建立。

public class StationProxy implements TicketService {
    private TicketServiceImpl station;
    public StationProxy(TicketServiceImpl station) {
        this.station = station;
    }
    @Override
    public void sellTicket() {
        // 1.做真正業務前,提示資訊
        this.showAlertInfo("××××showAlertInfo before sellTicket××××");
        // 2.呼叫真實業務邏輯
        station.sellTicket();
        // 3.後處理
        this.takeHandlingFee();
        this.showAlertInfo("××××sellTicket over××××\n");
    }
    @Override
    public void inquire() {
        // 1做真正業務前,提示資訊
        this.showAlertInfo("××××showAlertInfo before inquire××××");
        // 2.呼叫真實邏輯  
        station.inquire();
        // 3。後處理  
        this.showAlertInfo("××××inquire over××××\n");
    }
    @Override
    public void withdraw() {
        // 1。真正業務前處理
        this.showAlertInfo("××××howAlertInfo before withdraw××××");
        // 2.呼叫真正業務邏輯  
        station.withdraw();
        // 3.後處理  
        this.takeHandlingFee();
    }
//    TODO 除了實現public介面,代理類新增private方法,在test中仍新增進method中
    /* 
     * 展示額外資訊 
     */
    private void showAlertInfo(String info) {
        System.out.println(info);
    }
    /* 
     * 收取手續費 
     */
    private void takeHandlingFee() {
        System.out.println("takeHandlingFee......\n");
    }
}

三、總結

1.類的載入:
.java編譯形成.class bytecode;而後將byte code通過類載入期load到執行期中進行解釋、編譯、執行
2.動態代理
實現原理:在執行期模擬類的載入過程,將增強類生成位元組碼,載入轉成類(代理類)
jdk:基於介面?從何說起?因為在newPorxy建立代理類時,傳入被代理類的所有介面。
    InvocationHandler作用?
1)自定義的方法處理器,在invoke方法中加入被代理類的增強邏輯。
2)通過InvocationHandler統一管理器,在呼叫介面方法(drive、recharge時)進行攔截,統一呼叫invoke方法,根據invoke中傳入的method引數,確定呼叫被代理類的具體方法。
缺點:被代理類與代理類都繼承自同一介面,無法實現隨機自由組合增強。
cglib:兩個無關類,增強類只需實現MethodInterceptor,通過enhancer的setCallback,呼叫intercept增強方法。intercept寫法與jdk類似。
javassist:採用類的包名載入類,編譯形成位元組碼,設定介面、欄位資訊,新增構造方法、介面方法,最後使用構造器例項化類,呼叫介面方法。