帶你用例項學習代理模式:靜態代理、動態代理(JDK、CGlib)以及區別和優缺點
Spring AOP的核心技術就是動態代理,所以小編學習並整理了代理模式的材料,供大家一起學習。
1、代理模式滿足的三個必要條件:
- 兩個角色:執行者、被代理物件
- 這個過程必須要做,但是自己不能做或者不想做,交給專業的人(媒婆)
- 執行者必須拿到被代理物件的引用(需要知道你要什麼資訊)
2、代理模式分:靜態代理、動態代理(JDK動態代理和cglib動態代理)
3、靜態代理:
a.定義一個介面
public interface Count(){
public void queryCount();
}
b.業務邏輯類實現介面
public class CountImpl implements Count(){
@Override
public void queryCount(){
System.out.println("實現業務邏輯類");
}
}
c.定義業務代理類
public class CountProxy implements Count(){
private CountImpl countImpl;
public CountProxy(CountImpl countImpl){
this.countImpl = countImpl;
}
@Override
public void queryCount(){
System.out.println("在呼叫代理業務類之前執行");
countImpl.queryCount();
System.out.println("在呼叫代理業務類之後執行");
}
}
d.呼叫
public static void main(String []args){
CountImpl countImpl = new CountImpl();
CountProxy countProxy = new CountProxy(countImpl);
countProxy.queryCount();
}
缺點:一個代理類只能代理一個業務介面,如果要代理多個業務介面需要定義多個實現類和代理類,如果在呼叫代理業務類前後的程式碼是一樣的,則多個代理類會有很多冗餘程式碼。
4、動態代理:根據傳進來的業務實現類和方法名進行具體呼叫
JDK動態代理
a.定義業務邏輯介面
public interface BookFacade(){
public void addBook();
}
b.定義業務實現類
public class BookFacadeImpl implements BookFacade(){
@Override
public void addBook(){
System.out.printin("增加圖書的方法");
}
}
c.建立動態代理類:InvocationHandler是呼叫管理介面
public class BookFacadeProxy implements InvocationHandler {
// 業務實現類物件,用來呼叫具體的業務方法
private Object target;
// 繫結業務物件並返回一個代理類
public Object bind(Object targer) {
this.targer = targer;
// 通過反射機制,建立一個代理類物件並返回例項,使用者進行方法呼叫時使用
// 建立代理物件時,需要傳遞該業務類的類載入器(用來獲取業務實現類的元資料,呼叫真的的業務方法)、介面、handler實現類
return Proxy.newProxyInstance(this.targer.getClass().getClassLoader(), this.targer.getClass().getInterfaces(), this);
}
// 包裝呼叫方法:進行預處理、呼叫後處理
public Object invoke(Object proxy, Method method, Object []args) throws Throwable {
Object result = null;
System.out.println("進行預處理");
// 呼叫真正的業務方法
method.invoke(targer, args)
System.out.println("進行呼叫後處理");
}
}
d.在使用時,先建立一個業務實現類物件和一個代理類物件,然後定義介面引用(向上轉型)並用代理物件.bind(業務實現類物件)的返回值進行賦值,最後通過介面引用呼叫真實業務方法。(介面引用指向一個綁定了業務類的代理類物件,所以通過介面名呼叫的是被代理的方法們)
public static void main(String []args) {
BookFacadeImpl bookFacadeImpl = new BookFacadeImpl();
BookFacadeProxy proxy = new BookFacadeProxy();
BookFacade bookFacade = (BookFacade)proxy.bind(bookFacadeImpl);
bookFacade.addBook();
}
缺點:JDK動態代理的代理物件在建立時,需要有業務實現類所實現的介面作為引數(因為後面代理方法需要根據介面內的方法名進行呼叫)。如果業務實現類沒有實現介面而是直接定義介面的話,就無法使用JDK動態代理。並且如果業務實現類中新增了介面中沒有的方法,這些方法也是無法被代理的(因為無法呼叫)。
CGlib動態代理:是根據針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理,(因為採用的是繼承,所以不能對final修飾的類進行代理)。
a.定義業務類,無需實現介面
public calss BookFacadeImpl1 {
public void addBook() {
System.out.println("增加一本書");
}
}
b.實現MethodInterceptor方法代理介面,建立代理類
public class BookFacadeCglib implements MethodInterceptor {
// 業務物件類,代理方法中進行真正的業務方法呼叫
private Object targer;
// 相當於JDK動態代理的bind 繫結
public Object getInstance(Object targer) {
this.targer = targer;
// 建立加強器,用來建立動態代理
Enhancer enhancer = new Enhancer();
// 加強器要指定代理的業務類(即:為下面生成的代理類指定父類)
enhancer.setSupperclass(this.targer.getClass());
// 設定回撥:對於代理類上所有方法的呼叫,都會呼叫CallBack,
enhancer.setCallBack(this);
// 建立動態代理物件並返回
return enhancer.create();
}
// 實現回撥方法
public Object intercept(Object obj, Method method, Object []args, MethodProxy proxy) {
System.out.println("預處理方法");
// 呼叫父類的方法
proxy.invokeSuper(obj, args);
System.out.println("呼叫後方法");
}
}
c.建立業務類物件和代理類物件,代理類物件.getInstance(業務類物件)返回一個動態代理物件(它是業務類的子類,可以用業務類引用指向它),最後通過動態代理類物件進行方法呼叫。
public static void main(String []args) {
BookFacadeImpl1 bookFacadeImpl1 = new BookFacadeImpl1();
BookFacadeCglib bookFacadeCglib = new BookFacadeCglib();
BookFacadeImpl1 bookFacade = (BookFacadeImpl1)bookFacadeCglib.getInstance(bookFacadeImpl1);
bookFacade.addBook();
}
5.靜態代理、JDK動態搭理、CGlib動態代理比較:
靜態代理:通過在程式碼中顯式定義一個業務類一個代理,在代理類中對業務方法進行包裝,使用者通過代理類呼叫被包裝過的業務方法。
缺點:每個代理類只能代理一個業務類,如果有多個業務類需要代理則需要些多個代理類
JDK動態代理:通過傳進來的業務實現類和方法進行呼叫業務實現類的同名方法
缺點:如果該業務實現類沒有實現介面而是直接定義介面,或者是該業務實現類中增加了介面沒有的方法,則就無法被代理類呼叫
CGlib動態代理:通過繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行呼叫
據說原來的CGlib比JDK的動態代理效能要高出很多,但是由於JDK6 7 8逐漸完善,差距越來越小,而且有被反超的可能