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();
}
}
}