(轉)動態代理模式和靜態代理模式區別,動態代理底層實現原理
靜態代理
靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類,程式碼如下
程式碼示例:
介面:IPersonDao.java
public interface IPersonDao {
void update();
}
目標物件:PersonDao.java
public class PersonDao implements IPersonDao{ public void update() { // TODO Auto-generated method stub System.out.println("修改個人資訊"); } }
代理物件:PersonDaoProxy.java
public class PersonDaoProxy implements IPersonDao{ private IPersonDao target; public PersonDaoProxy(IPersonDao target){ this.target = target; } @Override public void update() { System.out.println("修改個人資訊前記錄日誌"); this.target.update(); System.out.println("修改個人資訊後記錄日誌"); } }
測試類:App.java
public class App {
public static void main(String[] args){
PersonDao p = new PersonDao();
PersonDaoProxy pProxy = new PersonDaoProxy(p);
pProxy.update();
}
}
靜態的缺點就是代理類也要實現介面,會導致代理類太多.而且介面增加方法,目標物件與代理物件都要維護
動態代理
動態代理有兩種,JDK代理跟Cglib代理
兩種的區別在於,JDK需要被代理類是有介面的
在Spring的AOP程式設計中: 如果加入容器的目標物件有實現介面,用JDK代理
如果目標物件沒有實現介面,用Cglib代理
JDK代理用到的兩個類:InvocationHandler介面跟Proxy類
代理類所在包:java.lang.reflect.Proxy JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數,完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意該方法是在Proxy類中是靜態方法,且接收的三個引數依次為:
ClassLoader loader,
:指定當前目標物件使用類載入器,獲取載入器的方法是固定的Class<?>[] interfaces,
:目標物件實現的介面的型別,使用泛型方式確認型別InvocationHandler h
:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入
程式碼示例:
介面類IPersonDao.java以及介面實現類,目標物件PersonDao是一樣的,沒有做修改.在這個基礎上,增加一個代理工廠類(ProxyFactory.java),將代理類寫在這個地方,然後在測試類(需要使用到代理的程式碼)中先建立目標物件和代理物件的聯絡,然後使用代理物件的中同名方法
InvocationHandler實現類:PersonInvocation.java
public class PersonInvocation implements InvocationHandler{
Object target;
public PersonInvocation(Object target){
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] arg2)throws Throwable {
// TODO Auto-generated method stub
System.out.println("修改個人資訊前記錄日誌");
method.invoke(target);
System.out.println("修改個人資訊後記錄日誌");
return null;
}
}
測試類:App.java
public class App {
public static void main(String[] args) {
// 目標物件
IUserDao target = new UserDao();
// 【原始的型別 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass());
// 給目標物件,建立代理物件
IUserDao proxy = (IUserDao) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new PersonInvocation());
// class $Proxy0 記憶體中動態生成的代理物件
System.out.println(proxy.getClass());
// 執行方法 【代理物件】
proxy.save();
}
}
總結:
代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理
有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理
Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件.
- JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.
- Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
- Cglib包的底層是通過使用一個小而快的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉.
Cglib子類代理實現方法:
1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接在Mava中新增pring-core.jar
即可.如果非maven工程需要引入cglib.jar 和asm.jar;
2.引入功能包後,就可以在記憶體中動態構建子類 3.代理的類不能為final,否則報錯 4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法.
cglib也用到兩個類MethodInterceptor介面和Enhancer
實現MethodInterceptor介面,在目標方法被呼叫時,就會先呼叫這個接口裡的intercept方法
Enhancer是指定要使用哪個代理類,程式碼如下
public class CglibImpl {
public void addBook() {
System.out.println("增加圖書的普通方法...");
}
}
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回撥方法
enhancer.setCallback(this);
// 建立代理物件
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {
System.out.println("事物開始");
proxy.invokeSuper(obj, args);
System.out.println("事物結束");
return null;
}
}
public class CgligTest {
public static void main(String[] args) {
CglibProxy cglib=new CglibProxy();
CglibImpl bookCglib=(CglibImpl)cglib.getInstance(new CglibImpl());
bookCglib.addBook();
}
}