源碼分析常用設計模式

1 代理模式

代理模式現實生活的案例:租房中介、火車票黃牛、媒人、經紀人
特點:
①執行者、被代理人;
②被代理人事情必須做,自己沒時間做或不想做;
③需獲取到被代理人的個人信息。

窮舉法
代理模式關心過程,而不是結果。

"動態代理至少寫了50遍。徹底瞭解,必須反覆重複,每次重複會發現一些新問題"。

  • 總結:代理模式最底層做的事情?字節碼重組。

在原始代碼加一些東西,編譯生成字節碼,加載到JVM動態運行。
spring用的比較多的是cglib代理模式。
jdk動態代理是通過接口,而cglib是通過繼承。eg.Son類繼承Father,該類是cglib給我們自動生成的(Father是用戶自定義,Son是Cglib給我們自動生成的)。
cglib同樣做了字節碼重組。這樣做的好處:少寫幾個類、幾個接口。
對於使用API的用戶來說是無感知的。

每種技術都是有應用場景的,eg.面向接口編程一般對外提供給別人調用,形成一種規範。

代理模式可以做什麼?可在每個方法調用前加一些代碼,在方法調用後加一些代碼。
AOP:事務代理(聲明式事務,哪個方法需要加事務,哪個方法不需要加事務)、日誌監聽

service方法  
開啓一個事務(open)  

事務的執行(由我們自己的代碼完成的)

監聽是否有異常,可能需要根據異常類型來決定這個事務是否要回滾,還是繼續提交(commit/rollback)。  
事務要關閉(close)  

spring爲什麼是優秀框架,它把停留在理論層次的方法論落地了,而且告訴大家怎麼用。

1.1 JDK動態代理

  • 類接口
public interface Person {
    String getAddress();
    String getPrice();
    /**
     * 租房
     */
    void findRoom();
}
  • 想找租房中介的小明-被代理者
/**
 * @author nanphonfy(南風zsr)
 * @date 2019/6/30
 */
public class XiaoMing implements Person {
    private String address = "南山區蛇口";
    private String price = "2800";

    @Override public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    @Override public void findRoom() {
        System.out.println(String.format("我想在%s租價位大概%s左右的房子", this.address, this.price));
        System.out.println("考慮單身公寓或合租主臥");
        System.out.println("帶獨衛和私人陽臺");
        System.out.println("有電梯");
    }
}
  • 房屋中介-代理者
/**
 * 租房中介
 *
 * @author nanphonfy(南風zsr)
 * @date 2019/6/30
 */
public class RentalAgent implements InvocationHandler {
    /**
     * 被代理對象的引用作爲一個成員變量保存下來
     **/
    private Person target;

    public Object getInstance(Person target) {
        this.target = target;
        Class clazz = target.getClass();
        System.out.println("被代理對象的class是:" + clazz);
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是房屋中介,幫忙挑選心儀房源,賺中介費,實現財富自由贏取白富美");
        System.out.println("開始進行篩選...");
        System.out.println("----------------");

        method.invoke(this.target, args);

        System.out.println("----------------");
        System.out.println("若合適,帶你看房");
        return null;
    }
}
  • 執行類
/**
 * @author nanphonfy(南風zsr)
 * @date 2019/6/30
 */
public class JdkFindRoomTest {
    public static void main(String[] args) {
        try {
            Person obj = (Person) new RentalAgent().getInstance(new XiaoMing());
            System.out.println("obj class:" + obj.getClass());
            obj.findRoom();
            obj.getAddress();

            /*原理:
            1.拿到被代理對象的引用,獲取它的接口
            2.JDK代理重新生成一個類,同時實現代理對象所實現的接口
            3.重新動態生成一個class字節碼
            4.編譯*/

            //獲取字節碼內容
            byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[] { Person.class });
            FileOutputStream os = new FileOutputStream("$Proxy0.class");
            os.write(data);
            os.close();

            //是什麼?
            //爲什麼?
            //怎麼做?
            // 自定義的類JDK代理
            /*Person obj = (Person) new NRentalAgent().getInstance(new XiaoMing());
            System.out.println("obj class:" + obj.getClass());
            obj.findRoom();*/
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.2 CgLib動態代理

  • 小by2想找中介(未實現接口)-被代理者
/**
 * @author nanphonfy(南風zsr)
 * @date 2019/6/30
 */
public class XiaoBy2 {
    public void findRoom(){
        System.out.println("我要找環境宜人,安靜舒適乾淨的房子");
    }
}
  • cglib代理小by2的要求-代理者
/**
 * @author nanphonfy(南風zsr)
 * @date 2019/6/30
 */
public class CgRentalAgent implements MethodInterceptor {
    /**
     * 疑問?
     * 好像沒有持有被代理對象的引用
     */
    public Object getInstance(Class clazz) throws Exception{
        // 生成class類的路徑
        // https://blog.csdn.net/u010811939/article/details/80763336
        // JDK動態代理生成class文件和cglib動態代理生成class文件
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://tmp");

        Enhancer enhancer = new Enhancer();
        //把父類設置爲誰?
        //這一步告訴cglib,生成的子類需要繼承哪個類
        enhancer.setSuperclass(clazz);
        //設置回調(回調intercept,所以設置this)
        enhancer.setCallback(this);

        //第一步、生成源代碼
        //第二步、編譯成class文件
        //第三步、加載到JVM中,並返回被代理對象
        return enhancer.create();
    }

    /***
     * 同樣做了字節碼重組
     * 對於使用API的用戶來說,是無感知的
     * @param obj
     * @param method
     * @param objects
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy)
            throws Throwable {
        System.out.println("我是房屋中介,幫忙挑選心儀房源,賺中介費,實現財富自由贏取白富美");
        System.out.println("開始進行篩選...");
        System.out.println("----------------");

        /*該obj的引用是由CGLib new出來的
        cglib new出來後的對象,是被代理對象的子類(繼承了我們自己寫的那個類)
        OOP, 在new子類前,實際上默認先調用了我們super()方法,
        new了子類的同時,必須先new出父類,相當於間接的持有了我們父類的引用
        子類重寫了父類的所有的方法
        改變子類對象的某些屬性,可以間接操作父類的屬性*/
        methodProxy.invokeSuper(obj,objects);

        System.out.println("----------------");
        System.out.println("若合適,帶你看房");
        return null;
    }
}
  • 執行類
/**
 * @author nanphonfy(南風zsr)
 * @date 2019/6/30
 */
public class CglibFindRoomTest {
    public static void main(String[] args) {
        try {
            /*JDK的動態代理是通過接口來進行強制轉換的
            生成後的代理對象,可強制轉換爲接口*/

            /*CGLib的動態代理是通過生成一個被代理對象的子類,然後重寫父類的方法
            生成後的對象,可強制轉換爲被代理對象(也就是用自己寫的類)
            子類引用賦值給父類*/
            XiaoBy2 xiaoBy2 = (XiaoBy2) new CgRentalAgent().getInstance(XiaoBy2.class);
            xiaoBy2.findRoom();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章