1、代理模式
(1)概念
- 代理就是幫別人做事情,如:工廠的中介,中介負責為工廠招收工人,那么中介就是工廠的代理;客戶通過商家購買東西,商家向廠家購買貨物,商家就是工廠的代理
- 在開發中存在a類需要調用c類的方法,完成某一個功能,但是c禁止a調用。這時,可以在a和c之間創建一個b類代理,a類訪問b類,b類訪問c類。例如:登錄的時候需要進行短信驗證,這個時候代理就是中國移動的子公司來完成短信的發送功能
- 代理模式就是為其他對象提供一種代理來控制這個對象的訪問,在某些情況下一個對象不適合或不能直接引用另一個對象,而代理對象可以在客戶類和目標對象直接起到中介的作用
- 功能增強:其中目標對象實現真正的功能,但是代理對象可以對目標對象的功能做進一步的擴充
(2)設計模式
設計模式代表了最佳的實踐,通常被有經驗的面向對象的軟件開發者所采用的,它是開發中面臨的一般問題的解決方案,這些解決方案是眾多的軟件開發人員經過相當長的實踐的經驗和錯誤總結出來的
(3)舉例
如果設計一個類該類中含有加減乘除四個方法,現在需要給每一個方法添加測試方法執行時間的代碼,如果不使用代理的話,就需要對每一個方法進行修改,需要修改多次。違背了開閉原則(OCP,對擴展開放,修改關閉)和單一職責(SRP)
(4)作用
功能增強:在原有的功能上增加額外的功能
控制訪問:代理類不讓你訪問目標類
(5)實現代理的方式
靜態代理:代理類是自己手動創建的,所需要代理的目標類是確定的,實現簡單容易理解
2、靜態代理
- 案例一
是在程序運行前就已經存在代理類的字節碼文件,靜態代理通常是對原有業務邏輯的擴充,通過讓代理類持有真實對象,在代理類的源代碼中調用被代理類方法來添加我們需要的業務邏輯。例如:買車不去工廠而是去4S店。
(1)創建一個接口:
interface Animal { public abstract void show(); }
(2)代理類:
public class Fish implements Animal { Sheep sheep=new Sheep(); @Override public void show() { sheep.show(); System.out.println("我愛游泳!"); } }
創建被代理類的對象,在代理類的方法中調用被代理類的方法,在這個過程中可以實現對被代理類的功能的擴充。
(3)被代理類:
public class Sheep implements Animal{ @Override public void show() { System.out.println("我愛吃青草"); } }
(4)測試類:
public class Test { public static void main(String [] args){ Fish fish=new Fish(); fish.show(); } }
測試結果:
Sheep最愛吃青草,在他被fish類代理之后還學會了一項新的技能:游泳
(5)靜態代理的缺點
如果有多個類需要代理,那么就需要創建多個代理類分別代理目標對象,工作量較大,不利於維護。
- 案例二
(1)創建一個接口
public interface UsbSell { float sell(float price); }
這里面是廠家和商家都要完成的功能
(2)創建工廠類
//廠家,不接受用戶的單獨購買,需要商家 public class UsbFactory implements UsbSell { @Override public float sell(float price) { System.out.println("目標類"); return 70;//廠家的U盤價格 } }
在工程類中定義的是U盤的出廠價,但是改價格是不能被普通的消費者使用的,只能是商家使用
(3)創建商家類(代理類)
淘寶類:
public class TaoBao implements UsbSell { //聲明商家代理的是哪一個廠家 private UsbSell factory=new UsbFactory(); @Override public float sell(float price) {//調用目標方法,增強功能,增加價格,優惠券 float p=factory.sell(price); float p1=p+25; System.out.println("返回5元優惠券"); return p1; } }
微商類:
public class WeiShang implements UsbSell { private UsbSell factory=new UsbFactory(); @Override public float sell(float price) {//調用目標方法,增強功能 float p = factory.sell(price); float p2=p+1; return p2; } }
這兩個類都實現了與工廠類相同的接口,增強了工程類(目標類)的方法
(4)創建測試類(普通消費者)
public class ShopMain { public static void main(String[] args) { TaoBao taoBao=new TaoBao(); float price=taoBao.sell(100); System.out.println("淘寶的銷售價:"+price); } }
目標類 返回5元優惠券 淘寶的銷售價:95.0
(5)優點
- 實現簡單
- 容易理解
(6)缺點
當目標類增多了,代理類也需要增加(例如:上例中創建了一個工廠類,那么該類只能代表一個工廠,當建立了其它品牌的工廠后,還需要為該工廠創建代理類)
當接口的方法增加或修改的時候,很多類都需要修改。因為,目標類和代理類都實現了相同的接口
3、動態代理(JDK代理,接口代理)
- 案例一
(1)好處
動態代理是利用的反射機制動態地生成代理的對象,我們不需要知道誰代理誰。代理類的那部分代碼被固定下來了,不會因為業務的增加而逐漸龐大。
可以實現AOP編程
解耦
(2)創建一個接口
interface Animal { public abstract void show(); }
(3)創建代理類
實現動態代理需要將要擴展的功能寫在InvocationHandler實現類里
使用newProxyInstance方法,該方法需要接收三個參數
代理對象不需要實現接口,但是目標對象一定要實現接口
public class Fish implements Animal { @Override public void show() { Animal objectProxy= (Animal) Proxy.newProxyInstance(//創建接口實例 Animal.class.getClassLoader(),//用目標對象有相同的類加載器,動態代理類運行時創建,將類加載到內存(反射) new Class[]{Animal.class},//被代理的類所實現的接口(可以是多個) new InvocationHandler() {//綁定代理類的方法(我們自己寫的,代理類要完成的功能) @Override//提供invoke方法,代理類的每一個方法執行時,都將調用一次invoke public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我在前!!"); method.invoke(new Sheep(),args);//執行目標對象的方法 System.out.println("我在后!!"); return null; }//proxy:代理后的實例對象 //method:對象被調用的方法 //args:調用時的參數 } ); objectProxy.show(); } }
(4)被代理類
public class Sheep implements Animal{ @Override public void show() { System.out.println("我愛吃青草"); } }
(5)測試類
public class Test { public static void main(String [] args){ Fish fish=new Fish(); fish.show(); } }
測試結果:
- Method的使用
(1)創建接口
public interface HelloService { public void sayHello(String name); }
(2)接口的實現類
public class HelloServiceImpl implements HelloService { public void sayHello(String name){ System.out.println("hello"+name); } }
(3)測試類
普通方式調用sayHello方法:
public class Test { public static void main(String[] args) { HelloService helloService=new HelloServiceImpl(); helloService.sayHello("zhai"); } }
hellozhai
使用反射執行sayHello方法:
public class Test { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { //使用反射機制執行sayHello方法,核心是Method類中的方法 HelloService target=new HelloServiceImpl(); //獲取sayHello名稱對於Method的對象 Method method=HelloService.class.getMethod("sayHello",String.class); //通過Method可以執行方法的調用 //invoke是Method類中的一個方法,表示添加方法的調用,參數1:表示對象,要執行這個對象的方法 //參數2:方法執行的時候的參數值 參數3:方法要執行的時候的返回值 //表示執行target對象的sayHello方法,參數是zhai,method代表的是執行的方法 Object ret=method.invoke(target,"zhai"); } }
hellozhai
(4)為接口添加一個實現類:
public class HelloServiceImpl2 implements HelloService{ public void sayHello(String name){ System.out.println("nihao"+name); } }
測試類:
public class Test { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { //使用反射機制執行sayHello方法,核心是Method類中的方法 HelloService target=new HelloServiceImpl(); //獲取sayHello名稱對於Method的對象 Method method=HelloService.class.getMethod("sayHello",String.class); //通過Method可以執行方法的調用 //invoke是Method類中的一個方法,表示添加方法的調用,參數1:表示對象,要執行這個對象的方法 //參數2:方法執行的時候的參數值 參數3:方法要執行的時候的返回值 //表示執行target對象的sayHello方法,參數是zhai,method代表的是執行的方法 Object ret=method.invoke(target,"zhai"); HelloService target2= new HelloServiceImpl2(); Object ret2=method.invoke(target2,"zhai"); } }
hellozhai
nihaozhai
也就是說method代表的是sayHello方法,也就是目標類的方法
- JDK動態代理的實現
invoke():表示代理對象要執行的功能代碼,你的代理類要完成的功能就寫在invoke()方法中
(1)代理類要完成的功能
調用目標方法,執行目標方法的功能
增強功能
(2)invoke方法
invoke(Object proxy, Method method, Object[] args)
method:目標類中的方法,jdk負責提供method對象
Object[] args:目標類中的參數
Object proxy:jdk創建的代理對象,無需賦值
(3)使用過程
- InvocationHandler接口:表示代理要干什么(定義目標類要完成的功能)
- 創建目標類實現接口
- 創建InvocationHandler接口的實現類,在invoke方法中完成代理類的功能
invoke方法:重寫invoke方法,把原來靜態代理中代理類要完成的功能寫在方法內
method.invoke()是用來執行目標方法的
- 使用Proxy類的靜態方法,來創建代理對象,並把返回值轉換為接口類型
核心的對象,創建代理對象,之前的對象都是new類的構造方法,現在我們使用的是Proxy類的方法,代替new的使用
方法newProxyInstance(),作用是創建代理對象,需要三個參數
- 動態代理案例二
(1)創建接口
public interface UsbSell { float sell(float price); }
(2)創建U盤的工廠類
public class UsbFactory implements UsbSell { @Override public float sell(float price) { System.out.println("目標類"); return 70f;//廠家的U盤價格 } }
(3)創建InvocationHandler 接口的實現類
//必須實現InvocationHandler接口,完成代理類的功能(調用目標方法、功能增強) public class MySellHandler implements InvocationHandler { private Object target=null; //動態代理的目標對象是活動的,需要傳入進來,傳進來的是誰就給誰創建代理 public MySellHandler(Object target){ this.target=target; } //args代表傳進來的參數(100) @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object res=null; res=method.invoke(target,args); if(res!=null){ Float price=(Float)res; price=price+25; res=price; } System.out.println("淘寶商家返回5元優惠券"); return res; } }
(4)測試類
public class Test { public static void main(String[] args) { //創建目標對象 UsbSell usbFactory=new UsbFactory(); //創建invocationHandler對象 InvocationHandler invocationHandler=new MySellHandler(usbFactory); //創建代理對象 UsbSell proxy= (UsbSell) Proxy.newProxyInstance( usbFactory.getClass().getClassLoader(), usbFactory.getClass().getInterfaces(), invocationHandler ); System.out.println(proxy.sell(100)); } }
(5)添加接口的方法
public interface UsbSell { float sell(float price); void hello(); }
public class UsbFactory implements UsbSell { @Override public float sell(float price) { System.out.println("目標類"); return 70f;//廠家的U盤價格 } @Override public void hello() { System.out.println("hello"); } }
只需要修改接口的方法和目標類的方法,用Proxy對象調用即可
在不修改工廠類和接口的情況下可以增加目標類(工廠類)的方法的功能
jdk的動態代理必須有接口,目標類一定要實現接口,沒有接口的時候使用cglib動態代理
4、cglib代理(cglib字節碼增強)
(1)概念:
需要導入jar包:核心包和依賴包(spring_core.jar已經集成了這兩個包,因此,導入此包即可)
子類是在調用的時候才生成的
使用目標對象的子類的方式實現的代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展,能夠在運行時動態生成字節碼,可以解決目標對象沒有實現接口的問題
缺點:被final或static修飾的類不能用cglib代理,因為它們不會被攔截,不會執行目標對象的額外業務方法
- 總結
(1)優點
在靜態代理中的目標類很多的時候,可以使用動態代理,避免靜態代理的缺點
- 動態代理中目標類使用的即使很多,代理類的數量可以很少
- 修改接口的方法的時候不會影響到代理類
(2)概念
在程序執行的過程中,使用jdk的反射機制,創建代理類對象並動態地指定要代理的目標類。也就是說動態代理是一種創建java對象的能力,使得我們不用創建淘寶類或微商類,就能創建代理類對象
(3)作用
控制訪問:在代理中,控制是否可以調用目標對象的方法
功能增強:可以在完成目標對象的調用時,附加一些額外的功能
代理方式:
靜態代理:代理類是手工創建的,目標對象是規定的
動態代理:使用反射機制,在程序執行的時候創建代理類對象,不用創建代理類的類文件,代理類的目標類是可以設置的
(4)實現方式
jdk動態代理:
使用java反射包中的類和接口實現動態代理的功能,反射包是java.lang.reflect,里面有三個類:InvocationHandler、Method、Proxy
cglib動態代理:
cglib是第三方的工具類
原理是繼承,通過繼承目標類創建它的子類,在子類中重寫父類中的方法,實現功能的修改
要求目標類不能是final的,方法也不能是final的
對於沒有接口的類,創建動態代理就要使用cglib