代理模式
代理模式用於爲其他對象提供一種代理以控制對這個對象的訪問,代理模式中分爲代理對象和委託對象(被代理對象),就如同經紀人和藝人的關係,廣告商想要某位藝人代言,不直接與藝人聯繫,而是通過與經紀人取得聯繫來獲得藝人的代言。代理模式中的一個重要特徵就是代理對象和委託對象必須實現同一接口,萬物互聯,這就好比現實中經紀人和藝人往往屬於同一家公司。代理對象不提供直接服務,而是調用委託對象的服務,除了調用對象的服務,代理對象會進行一些預處理消息、消息過濾、服務事後處理等擴展服務,就好比經紀人肯定不是遇到廣告商的業務請求,就直接喊藝人去進行商演或者代言,肯定進行相關的調研以及與廣告商的協商、酒局等預處理,再讓藝人提供商演代言等服務,服務提供完畢可能提供相應的粉絲見面會、路演等服務事後處理等。
代理模式有兩種實現方式,一種是靜態代理,另外一種是動態代理,兩種方式的區別在於代理類是否在編譯前就已存在。在靜態代理中,代理類在進行編譯前就已經存在,而在動態代理中,並沒有代理類這個java文件,而是在運行時通過Java的反射機制生成代理類字節碼文件,再通過類加載將代理類字節碼加載進內存形成代理類Class對象,以進行使用。靜態代理的一個缺陷就是代理類與接口綁定,當需要代理的種類過多時,需要編寫的代理類就多,而動態生成代理類的方式解決了代理類和接口綁定的缺陷。(對類加載過程不熟悉的可以翻閱我的這篇博文《類加載過程》)
靜態代理
在靜態代理實現中,代理類在編譯前就存在,代理類與委託類實現相同的接口,並在內部具有一個業務類的對象,當用戶需要訪問業務時,去訪問代理類,代理類進行相應的處理然後調用業務類對象進行服務調用。靜態代理將客戶端與目標業務隔離,不僅對業務功能進行了拓展、保護了目標業務而且降低了系統的耦合。
package DesignPatterns.Proxy;
/*
@function 代理模式-靜態代理
@details
優點:代理模式在客戶端與目標對象之間起到一箇中介作用和保護目標對象的作用
代理對象可以擴展目標對象的功能
代理模式能將客戶端與目標對象分離,在一定程度上降低了系統的耦合度。
缺點:代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,類太多.同時,一旦接口增加方法,目標對象與代理對象都要維護。
@author xuxiang
@time 20-6-2
*/
//接口
interface demoAPI{
public void show();
}
//業務類
class realWork implements demoAPI{
@Override
public void show(){
System.out.println("調用業務");
}
}
//代理類
class Proxy implements demoAPI{
private demoAPI realwork;
public Proxy(demoAPI work) {
realwork=work;
}
@Override
public void show(){
before();
realwork.show();
after();
}
public void before(){
System.out.println("業務調用前處理");
}
public void after(){
System.out.println("業務調用後處理");
}
}
public class StaticProxy{
public static void main(String[] args){
demoAPI work=new realWork();
Proxy proxy=new Proxy(work);
proxy.show();
}
}
動態代理
動態代理的實現是利用Java的反射機制,在運行時根據參數生成代理類的字節碼,進而進行類加載,將代理類加載進內存使用。它的優點在於靈活的生成代理類,而不會需要編寫很多代理類,減少了代理類的數量,並且使得系統耦合度更低,代理邏輯幾乎完全和業務邏輯無關。
Java中動態代理的實現需要使用java.lang.reflect 包中的Proxy類和InvocationHandler 接口實現,通過InvocationHandler 接口實現自己的調度處理器,調度處理器的核心是 invoke()方法,通過該方法將代理類對象執行的方法轉發給業務類對象執行以此實現服務調用。例如下面代碼中的dynamicProxy代理類對象執行show()方法的過程是執行了invoke()方法,並在invoke方法中調用了 demoapi業務對象的show()方法,實現了任務的轉發。Proxy類用於代理類的生成,它提供了 newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法生成代理類對象,過程主要是代理類Class對象的生成,以及代理類構造函數的獲取,並通過構造函數來創建代理類實例。創建實例時調用處理器對象h作爲參數傳入,以進行任務的轉發。業務類的類加載器loader和接口信息interfaces用於代理類Class對象的生成。詳細過程可以自行去學習該方法的源碼。
package DesignPatterns.Proxy;
/*
@function 代理模式-動態代理 使用java.lang.reflect 包中的Proxy類和InvocationHandler 接口實現
@details 與靜態代理的區別是,動態代理類是運行時利用反射機制動態產生的,生產了動態代理類字節碼文件,再進行類加載,生成相應的.Class對象
步驟:(1)通過實現 InvocationHandler 接口創建自己的調用處理器;調用處理器的invoke()方法存放代理類的執行方法,也是通過該方法將任務轉發給業務類執行。
(2)通過爲 Proxy 類指定 ClassLoader 對象和一組 interface 來創建動態代理類;
(3)通過反射機制獲得動態代理類的構造函數,其唯一參數類型是調用處理器接口類型;
(4)通過構造函數創建動態代理類實例,構造時調用處理器對象作爲參數被傳入。
@author xuxiang
@time 20-6-2
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//接口1
interface DemoAPI{
public void show();
}
//接口2
interface DemoAPI1{
public void display();
}
//業務類1
class RealWork implements DemoAPI{
@Override
public void show(){
System.out.println("動態代理-調用業務show");
}
}
class RealWork1 implements DemoAPI1{
@Override
public void display(){
System.out.println("動態代理-調用業務display");
}}
//處理調度器 作用是將需要執行的業務方法都集中在一起執行
class MyInvocationHandler implements InvocationHandler{
private Object object;
public MyInvocationHandler(Object work){
object=work;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
before();
//obejct爲業務類對象,objects爲代理類對象執行的方法,這裏是將任務轉發給object,
//讓object對象執行objects方法,將代理類對象執行的方法轉發給業務類對象執行以此實現服務調用。
Object invoke=method.invoke(object,objects);
after();
return invoke;
}
public void setObject(Object object) {
this.object = object;
}
public void before(){
System.out.println("動態代理-業務調用前處理");
}
public void after(){
System.out.println("動態代理-業務調用後處理");
}
}
public class DynamicProxy {
public static void main(String[] args){
//在ProxyGenerator.generateProxyClass函數中 saveGeneratedFiles定義如下,其指代是否保存生成的代理類class文件,默認false不保存。
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
DemoAPI demoapi=new RealWork();
InvocationHandler myInvocation=new MyInvocationHandler(demoapi);
//該代理類是運行時通過反射機制生成代理類字節碼,再通過類加載過程將字節碼轉換成代理類Class對象
//InvocationHandler類的對象爲代理類指定代理類執行的方法,這個方法除了業務方法,還包括擴展的方法
DemoAPI dynamicProxy=(DemoAPI) Proxy.newProxyInstance(RealWork.class.getClassLoader(),RealWork.class.getInterfaces(),myInvocation);
//代理類調用方法時調用的是 InvocationHandler中的invoke方法
dynamicProxy.show();
DemoAPI1 demoapi1=new RealWork1();
((MyInvocationHandler) myInvocation).setObject(demoapi1);
DemoAPI1 dynamicProxy1=(DemoAPI1) Proxy.newProxyInstance(RealWork1.class.getClassLoader(),RealWork1.class.getInterfaces(),myInvocation);
dynamicProxy1.display();
System.out.println(""+dynamicProxy.getClass());
}
}
cglib動態代理
我們常用的基於反射機制的動態代理業務類必須實現接口,那麼如果不實現接口的類要動態代理怎麼辦呢?cglib動態代理就可解決關於類的動態代理。cglib是一個強大的、高性能、高質量的Code生成類庫。它可以在運行期擴展Java類與實現Java接口,在運行時在內存中動態生成一個子類對象從而實現對目標對象功能的擴展。他與基於JDK反射機制的動態代理的主要區別在於他可以實現對類的代理(該類沒有實現接口),他生成的代理類是業務類的子類,他重寫了業務類的方法以實現代理,與JDK動態代理的區別就相當於它是覆蓋業務類,而JDK動態代理類是包含了業務類。
1、引入cglib pom依賴
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2、業務類
ackage com.workit.demo.proxy;
public class CaptainAmerica2MovieImpl {
public void play(){
System.out.println("正在播放的電影是《美國隊長2》");
}
}
3、自定義攔截器方法
package com.workit.demo.proxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
playStart();
Object object = methodProxy.invokeSuper(o, objects);
playEnd();
return object;
}
public void playStart() {
System.out.println("電影開始前正在播放廣告");
}
public void playEnd() {
System.out.println("電影結束了,接續播放廣告");
}
}
4、測試
ckage com.workit.demo.proxy;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyTest {
public static void main(String[] args) {
// //在指定目錄下生成動態代理類
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");
//創建Enhancer對象,類似於JDK動態代理的Proxy類,下一步就是設置幾個參數
Enhancer enhancer = new Enhancer();
//設置目標類的字節碼文件
enhancer.setSuperclass(CaptainAmerica2MovieImpl.class);
//設置回調函數
enhancer.setCallback(new CglibProxyInterceptor());
//這裏的creat方法就是正式創建代理類
CaptainAmerica2MovieImpl captainAmerica2Movie = (CaptainAmerica2MovieImpl)enhancer.create();
//調用代理類的play方法
captainAmerica2Movie.play();
System.out.println("cglib動態代理《美國隊長2》:"+captainAmerica2Movie.getClass());
}
}
//此cglib部分代碼來自https://mp.weixin.qq.com/s/oBAAMQ1unTecSUkiIntfuw
自定義攔截器相當於JDK動態代理中的調度處理器(InvocationHandler),區別在於它不是將任務轉交給業務類,而是調用父類(業務類)的該方法。
總結
代理模式的作用是對業務對象的訪問進行控制,常見的應用場景如日誌處理、遠程代理、虛擬代理等,實現方式分爲靜態代理和動態代理兩種,靜態代理,代理類在編譯前就已經存在。動態代理有jdk和cglib兩種,代理類通過 Proxy.newInstance()或者ASM 生成。靜態代理和動態代理的區別是在於要不要開發者自己編寫代理類。動態代理可以在運行期間通過 動態生成代理類Class對象,但是它必須實現一個 InvocationHandler 或者 MethodInterceptor的實現類。