代理模式
代理(Proxy)是一種設計模式,提供了間接對目標對象進行訪問的方式;即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的功能上,增加額外的功能補充,即擴展目標對象的功能.
這就符合了設計模式的開閉原則,即在對既有代碼不改動的情況下進行功能的擴展。
舉個例子來說明代理的作用:明星與經紀人之間就是被代理和代理的關系,明星出演活動的時候,明星就是一個目標對象,他只要負責活動中的節目,而其他瑣碎的事情就交給他的代理人(經紀人)
來解決.這就是代理思想在現實中的一個例子。
靜態代理
在使用靜態代理時,被代理對象與代理對象需要一起實現相同的接口或者是繼承相同父類,因此要定義一個接口或抽象類.
代碼案例:
// 接口 interface IStar { void sing(); } // 真實對象 class LDHStar implements IStar { @Override public void sing() { System.out.println("劉德華唱歌"); } } // 代理類需要有真實對象的控制權 (引用) class ProxyManger implements IStar { // 真實對象的引用 private IStar star; public ProxyManger() { super(); } public ProxyManger(IStar star) { super(); this.star = star; } @Override public void sing() {
System.out.println("唱歌前准備");
star.sing();
System.out.println("善后工作"); } } class Test{ public static void main(String[] args) { // 創建明星對象 IStar ldh = new LDHStar(); ProxyManger proxy = new ProxyManger(ldh); proxy.sing(); } }
靜態代理總結:
優點:可以做到在不修改目標對象的功能前提下,對目標功能擴展.
缺點:
因為代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,類太多.同時,一旦接口增加方法,目標對象與代理對象都要維護.
而動態代理方式可以解決上面的問題
動態代理
動態代理的主要特點就是能夠在程序運行時JVM才為被代理對象生成代理對象。
常說的動態代理也叫做JDK代理也是一種接口代理,JDK中生成代理對象的代理類就是Proxy,所在包是java.lang.reflect
//目標類接口
interface IDog{ void run(); } //目標類 class GunDog implements IDog{ @Override public void run() { System.out.println("獵狗在跑"); } } class DogUtils{ public static void method1() { System.out.println("增強方式一"); } public static void method2() { System.out.println("增強方式二"); } } class MyInvocationHandle implements InvocationHandler{ private Object target; public void setTarget(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { DogUtils.method1(); method.invoke(target, args); DogUtils.method2(); return null; } } //生產代理對象的工廠 class MyProxyFactory{ public static Object getProxy(Object target) { MyInvocationHandle handle = new MyInvocationHandle(); handle.setTarget(target); Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handle); return proxy; } } public class ProxyDemo { public static void main(String[] args) { IDog dog = new GunDog(); IDog proxy =(IDog) MyProxyFactory.getProxy(dog); proxy.run(); } }
總結:
代理對象不需要實現接口,但是目標對象一定要實現接口,否則不能使用動態代理,因此這也算是這種方式的缺陷。
Cglib代理
上面的靜態代理和動態代理模式有個相同點就是都要求目標對象是實現一個接口的對象,然而並不是任何對象都會實現一個接口,也存在沒有實現任何的接口的對象,
這時就可以使用繼承目標類以目標對象子類的方式實現代理,這種方法就叫做:Cglib代理,也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展.
使用JDK動態代理有一個限制,就是被代理的對象必須實現一個或多個接口,若想代理沒有實現接口的類,就需要使用Cglib實現.
由於Cglib是第三方提供的所以使用的時候需要導入相關的jar包,有兩個包如圖:
代碼案例:
public class CglibProxy { public static void main(String[] args) { int[] arr = new int[100000]; for (int i = 0; i < arr.length; i++) { arr[i] = (int) (Math.random() * 1000); } //實例化一個增強器,也就是cglib中的一個class generator Enhancer enhancer = new Enhancer(); //設置目標類 enhancer.setSuperclass(ArraySort2.class); //設置攔截對象,這里直接使用匿名內部類寫法 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object object , Method method, Object[] args, MethodProxy proxy) throws Throwable { String sortName = method.getName(); switch (sortName) { case "bubbleSort": sortName = "冒泡排序"; break; case "selectSort": sortName = "選擇排序"; break; case "quickSort": sortName = "快速排序"; break; default: break; } long start = System.currentTimeMillis(); //此處一定要使用proxy的invokeSuper方法來調用目標類的方法 proxy.invokeSuper(object, args); long end = System.currentTimeMillis(); System.out.println("本次" + sortName + "的執行時間為: " + (end -start) + "ms"); return null; } }); //生成代理類並返回一個實例 ArraySort2 arraySort = (ArraySort2) enhancer.create(); arraySort.bubbleSort(arr); arraySort.selectSort(arr); arraySort.quickSort(arr); } } class ArraySort2{ public void quickSort(int[] arr) { Arrays.sort(arr); } public void selectSort(int[] arr) { for (int i = 0; i < arr.length; i++) { for (int j = i+1; j < arr.length; j++) { if (arr[i] > arr[j]) { int temp = 0; temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } } public void bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = 0; temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } }
總結:
在Spring的AOP編程中:
如果加入容器的目標對象有實現接口,用JDK代理
如果目標對象沒有實現接口,用Cglib代理。