設計模式-代理模式JAVA實現

代理模式簡單說就是對當前已有類中的方法進行前/後置干預的的一種設計模式,目的是在不對現有業務類進行修改的情況下對指定的現有業務在類級別或方法級別進行修改干預。

實現方式目前有兩種,一種是靜態代理,純粹基於設計模式通過代碼實現。另一種是動態代理,需要通過JDK默認提供的功能和導入CGLIG功能包來增強實現。

首先進行靜態代理的實現。

package proxy.staticproxy;

import java.util.List;

import bean.PickDoc;
import bean.PickList;
import bean.PickTask;
import builder.IPickTask;

靜態代理的具體實現
public class StaticPickTaskProxy implements IPickTask {

//被代理對象
private IPickTask pickTask;

public StaticPickTaskProxy(IPickTask pickTask) {
    this.pickTask = pickTask;
}

@Override
public List<PickList> getPickList(List<PickDoc> list) {

    System.out.println("前置處理"+pickTask.getClass().getName()+"對象調用前的操作");
    pickTask.getPickList(list);
    System.out.println("後置處理"+pickTask.getClass().getName()+"對象調用前的操作");
    return null;
}

@Override
public List<PickTask> getPickTask(List<PickList> list) {
    // TODO Auto-generated method stub
    return null;
}

}

//靜態代理的實際調用
package proxy.staticproxy;

import java.util.ArrayList;
import java.util.List;

import bean.PickDoc;
import builder.IPickTask;
import builder.SinglePickTask;
import builder.UnionPickTask;

public class StaticPickTaskProxyMain {

public static void main (String[] args) {

    //使用代理類代替具體的業務類來進行操作
    IPickTask pickTaskProxy = new StaticPickTaskProxy(new SinglePickTask());

    //使用代理類代替具體的業務類來進行操作
    IPickTask pickTaskProxy1 = new StaticPickTaskProxy(new UnionPickTask());

    List<PickDoc> pickDocList = new ArrayList<PickDoc>();

    //非合併揀貨
    pickTaskProxy.getPickList(pickDocList);

    //合併揀貨
    pickTaskProxy1.getPickList(pickDocList);
}

}

靜態代理比較簡單,比較容易明白。這裏要另外說的是如果前期對要開發的業務設計的好,那麼可以一定程度上降低開碼的開發量,同時提高可維護性。

比如通常描述靜態代理是隻能針對某個具體的類中的一個或多個方法來手工實現代理。但因爲示例的兩個業務實現類SinglePickTask和UnionPickTask都面向IPickTask接口進行實現(採用建造者模式)。這樣使得我一個靜態代理類可以對這一組業務實現類進行代理。 這就是現實中的好處

動態代理中的JDK默認實現
package proxy.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicJDKProxy implements InvocationHandler {

//被代理對象
private Object obj ;

public DynamicJDKProxy(Object obj) {
    this.obj = obj;
}

//當通過代理類的對象發起對被重寫的方法調用時,都會轉化爲對如下invoke方法的調用
@Override
/**
 * proxy   代理類對象,要實現被代理方法的那個對象,而不是被代類對象,這點不要搞錯
 * proxy - the proxy instance that the method was invoked on
 * 
 * 參數method是一個實例,它就是調用在代理實例上的接口方法。聲明的
 * 方法對象類是該方法聲明的接口,這個接口是所有繼承當前method的代理接口的父接口
 * method - the Method instance corresponding to the interface method 
 * invoked on the proxy instance.The declaringclass of the Method 
 * object will be the interface that the method was declared in, which 
 * may be a superinterface of theproxy interface that the proxy 
 * class inherits the method through.
 * 
 * 參數args是包含了代理方法調用中傳輸的對象數組參數。或者這個接口沒有參數。
 * 原始類型的參數被打包在合適的包裝類中,如Integer或者Boolean.
 * args - an array of objects containing the values of thearguments 
 * passed in the method invocation on the proxy instance,or null if 
 * interface method takes no arguments.Arguments of primitive types 
 * are wrapped in instances of the appropriate primitive wrapper class,
 * such as java.lang.Integer or java.lang.Boolean.
 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("當前代理對象名稱:"+obj.getClass().getSimpleName());
    System.out.println("當前代理方法名稱:"+method.getName());
    if(args!=null) {
        for(Object obj: args) {

            System.out.println("參數對象:"+obj.getClass().getSimpleName());
        }
    }
    Object returnObj =null;
    if(method.getName().equals("getPickList")) {
        System.out.println("前置處理");
        returnObj = method.invoke(obj, args);
        System.out.println("後置處理");
    }

    return returnObj;
}

}

//實現調用測試代碼
package proxy.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

import bean.PickDoc;
import builder.IPickTask;
import builder.SinglePickTask;
import builder.UnionPickTask;

public class DynamicJDKProxyMain {

public static void main(String[] arg) {

    IPickTask  singlePickTask = new SinglePickTask();

    IPickTask  unionPickTask = new UnionPickTask();

    //定義揀貨單據列表
    List<PickDoc> pickDocList = new ArrayList<PickDoc>();

     //要代理的對象
    InvocationHandler singlePickTaskHandler = new DynamicJDKProxy(singlePickTask);

    InvocationHandler unionPickTaskHandler = new DynamicJDKProxy(unionPickTask);

    //獲取類加載器
    ClassLoader loader = singlePickTask.getClass().getClassLoader();
    //獲取類接口對象列表
    Class[] interfaces = singlePickTask.getClass().getInterfaces();
     /*
     * classloader,要代理哪個類就用哪個類的加載器來加載要新創建的代理類
     * interfaces,代理類要實現哪些接口,與被代理類對象的一致
     * handler, 方法分發調用的處理器
     * loader - the class loader to define the proxy class
     * interfaces - the list of interfaces for the proxy classto implement
     * h - the invocation handler to dispatch method invocations to
     * 這裏是對要被代理的對象動態生成一個新的代理類,
     * 所以做爲一個新類,它需要有相應的類加載器,同時這個類是通過反射來構造的,
     * 所以它構造時的方法列表就來自於被被代理對象
     */
    IPickTask proxyPickTask = (IPickTask) Proxy.newProxyInstance(loader, interfaces, singlePickTaskHandler);

    proxyPickTask.getPickList(pickDocList);
}

}
//運行結果
設計模式-代理模式JAVA實現

這裏的註釋已經比較多,應該比較容易理解。總之就是JDK自動構建了一個與靜態代理實現方式一樣的代理類,來代理當前的業務類。與靜態代理相比的好處是不用一定要手工實現業務類對應接口的所有方法,尤其是在對基於多個接口實現的不同業務類的代理的時候比較好。

CGLIB動態代理實現
首先,這個不是JDK默認帶有的功能,需要單獨下載JAR包或者運行在springboot的工程下,引入springboot工程中自帶的相應實現。本例引用的是cglib-nodep-2.2.2.jar

代理類代碼如下
package proxy.dynamicproxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class DynamicCGLibProxy implements MethodInterceptor {

private Object target;

public DynamicCGLibProxy(Object target) {
    this.target = target;
}

public Object newInstance() {

    //在這裏對被代理對象增強生成一個代理對象
    Enhancer enhancer = new Enhancer();                
    enhancer.setSuperclass(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("當前代理對象名稱:"+obj.getClass().getSimpleName());
    System.out.println("當前代理方法名稱:"+method.getName());
    if(args!=null) {
        for(Object arg: args) {

            System.out.println("參數對象:"+arg.getClass().getSimpleName());
        }
    }
    Object returnObj =null;
    if(method.getName().equals("getPickList")) {
        System.out.println("前置處理");
        returnObj = proxy.invokeSuper(obj, args);
        System.out.println("後置處理");
    }

    return returnObj;
}

}

調用演示代碼如下
package proxy.dynamicproxy;

import java.util.ArrayList;
import java.util.List;

import bean.PickDoc;
import builder.IPickTask;
import builder.SinglePickTask;

public class DynamicCGLibProxyMain {

public static void main(String[] args) {

    List<PickDoc> pickDocList = new ArrayList<PickDoc>();

    //SinglePickTask 實際業務類
    //pickTaskProxy 生成的代理類象
    IPickTask pickTaskProxy=(SinglePickTask)new DynamicCGLibProxy(new SinglePickTask()).newInstance();

    pickTaskProxy.getPickList(pickDocList);

}

}
//調用效果
設計模式-代理模式JAVA實現
CGLIB代理實現本質上和jdk是一樣的,都是新生成一個代理類。區別是兩者生成代理類中的方法來源不同。JDK基於取到的對象接口列表在反射時生成相應的方法。而CGLIB通過對當前要被代理的對象生成一個子類對象來解決這個問題

spring中優先使用JDK自帶的代理實現方式,當業務方法不基於接口實現時才使用CGLIB方式

另有同學補充指正 spring boot2.x默認使用cglib做動態代理了

另外,在3種實現中均採用通過構造方法傳入需要被代理的對象,個人認爲這是個比較好的處理方式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章