Spring 靜態代理和動態代理
阿新 • • 發佈:2018-12-18
靜態代理:
優點:
1、 實現鬆散耦合。
2、做到在不修改目標物件的功能前提下,對目標功能擴充套件。
缺點:
如果專案中有多個類,則需要編寫多個代理類,工作量大,不好修改,不好維護,不能應對變化。
模擬某位學生去考試作為例子:
下面建立 考試介面
public interface Examable {
void exam();
}
建立Student學生實現這個考試介面(被代理)
public class Student implements Examable {
@Override
public void exam() {
System.out.println("奮筆疾書,完成考題。。。");
}
}
創鍵Cheater(代理)物件,同時也去實現Examable介面
public class Cheater implements Examable {
//被代理物件
private final Examable student;
public Cheater(Examable student){
this.student=student;
}
@Override
public void exam() {
System.out.println("在現場唱了一首歌,差點被勸退");
student.exam();//呼叫Student類的方法
}
}
測試:
public class Main {
//組合優於繼承
public static void main(String[] args) {
//cheater 就是一個代理物件
//因為它是受student 委託,完成某個功能
//它要完成的主要功能,來自student
//除了委託做的事情,可能還會擴充套件一些行為
Examable xiaoming = new Student();//原來的行為
xiaoming.exam();
System.out.println("------下面是代理行為------");
Examable cheater = new Cheater(xiaoming);
cheater.exam();
}
}
結果:
奮筆疾書,完成考題。。。 ------下面是代理行為------ 在現場唱了一首歌,差點被勸退 奮筆疾書,完成考題。。。
動態代理:
使用JDK內建的Proxy實現
接著拿上面作為例子
下面建立 考試介面
public interface Examable {
void exam();
}
建立Student學生實現這個考試介面(被代理)
public class Student implements Examable {
@Override
public void exam() {
System.out.println("奮筆疾書,完成考題。。。");
}
}
建立一個 JdkProxy 類 實現 InvocationHandler 介面
public class JdkProxy implements InvocationHandler { private Object object;//被代理 public JdkProxy() { } public JdkProxy(Object object) { this.object = object; ////初始化的時候就賦值 } /** * 當用戶呼叫物件中的每個方法時都通過下面的方法執行,方法必須在介面 * proxy 被代理後的物件 * method 將要被執行的方法資訊(反射) * args 執行方法時需要的引數 */ @Override public Object invoke(Object proxy, Method method, Object[] args) { Object invoke = null; try { invoke = method.invoke(object, args); } catch (Exception e) { System.out.println("異常的資訊" + e.getMessage()); } return invoke;//呼叫被代理物件原來的方法(行為) } }
測試:
public class Main {
public static void main(String[] args) {
/* * 通過Proxy的newProxyInstance方法來建立我們的代理物件,我們來看看其三個引數 * 第一個引數 handler.getClass().getClassLoader() ,我們這裡使用handler這個類的ClassLoader物件來載入我們的代理物件 * 第二個引數person1.getClass().getInterfaces(),我們這裡為代理物件提供的介面是真實物件所實行的介面,表示我要代理的是該真實物件,這樣我就能呼叫這組介面中的方法了 * 第三個引數handler, 我們這裡將這個代理物件關聯到了上方的 InvocationHandler 這個物件上 */ ClassLoader cl= Thread.currentThread().getContextClassLoader(); Examable o = (Examable) Proxy.newProxyInstance( cl,//類載入器 new Class[]{Examable.class},//獲取被代理物件的所有介面 new JdkProxy(new Student())//InvocationHandler物件 ); o.exam();//代理後的行為 }
}
結果:
使用內建的Proxy實現動態代理有一個問題:被代理的類必須實現介面,未實現介面則沒辦法完成動態代理。
2、動態代理,使用cglib實現
還是拿上面的例子來說吧。
這次我們不寫介面了。不過 我們要實現MethodInterceptor介面,並實現方法
去maven 中心倉庫 找到 CGlib 的依賴
Student類
public class Student { public void exam(){ System.out.println("奮筆疾書,完成考試啦"); } }
建立一個 CglibProxy 的類 並 實現 MethodInterceptor 介面 ,並實現方法
package com.nf147.sim.proxy.p5; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxy implements MethodInterceptor { /* * 引數 * Object 為由CGLib動態生成的代理類例項 * method 為上文中實體類所呼叫的被代理的方法引用 * objects 為引數值列表 * methodProxy 為生成的代理類對方法的代理引用 * return 從代理例項的方法呼叫返回的值 * */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o, objects); } }
測試:
package com.nf147.sim.proxy.p5; import com.nf147.sim.proxy.p1.Student; import net.sf.cglib.proxy.Enhancer; public class Main { public static void main(String[] args) { //第一種方式 //增強器,動態程式碼生成器 Enhancer enhancer = new Enhancer(); //設定 生成類 的 父類 enhancer.setSuperclass(Student.class); //回撥函式 enhancer.setCallback(new CglibProxy()); //動態生成位元組碼並返回代理物件 Student o = (Student) enhancer.create(); o.exam(); System.out.println("-----------"); //第二種方式 //這裡是簡化寫法 //第一個引數 設定 生成類 的父類 ,第二引數 被代理類的所有介面 ,回撥函式 Student student = (Student) Enhancer.create(Student.class, null, new CglibProxy()); student.exam(); } }
結果: