代理模式增強之路(代理+責任鏈模式)

blue星空發表於2020-11-23

 

        關於各種代理的基本資訊,請檢視代理模式詳解。本文主要講解基於JDK的動態代理的元件化封裝之路。

        首先,我們看一下傳統的使用方式:

1,介面

package effectiveJava.proxy;

public interface HelloService {
    void sayHello();
}

2,代理元

package effectiveJava.proxy;

public class HelloServiceImpl implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("Hello Proxy.");
    }
}

3,代理類(必須實現InvocationHandler介面)

package effectiveJava.proxy.v0;

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

public class ProxyInvocation implements InvocationHandler {
    /**
     * 代理元
     */
    private Object target;

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

    /**
     *
     * @param proxy 代理類例項
     * @param method 實際要呼叫的方法
     * @param args  實際要呼叫方法的引數型別
     * @return 結果值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("HelloInvocation : Before Hello....");
        Object reslut = method.invoke(target, args);
        System.out.println("HelloInvocation : After Hello....");
        return reslut;
    }

}

4,測試類

package effectiveJava.proxy.v0;

import effectiveJava.proxy.HelloService;
import effectiveJava.proxy.HelloServiceImpl;

import java.lang.reflect.Proxy;

/**
* 通過Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)建立代理物件的例項
*/
public class HelloInvocationDemo {
    public static void main(String[] args) {
        HelloServiceImpl helloService = new HelloServiceImpl();
        ProxyInvocation helloInvocation = new ProxyInvocation(helloService);
        HelloService impl = (HelloService)Proxy.newProxyInstance(
                helloService.getClass().getClassLoader(),
                helloService.getClass().getInterfaces(),
                helloInvocation);
        impl.sayHello();
    }
}

 

           我們發現,客戶端每次使用代理的時候,都必須通過Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)建立代理物件的例項,使用不太方便。我們可以將該方法封裝到代理類(ProxyInvocation)中,簡化客戶端的呼叫。程式碼修改如下:

3,代理類(必須實現InvocationHandler介面)
package effectiveJava.proxy.v1;

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

public class ProxyInvocation implements InvocationHandler {
    /**
     * 代理元
     */
    private Object target;

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

    /**
     *
     * @param proxy 代理類例項
     * @param method 實際要呼叫的方法
     * @param args  實際要呼叫方法的引數型別
     * @return 結果值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before Hello....");
        Object reslut = method.invoke(target, args);
        System.out.println("after Hello....");
        return reslut;
    }

    public static Object getProxyObject(Object target) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new ProxyInvocation(target));
    }
}

4,測試類

package effectiveJava.proxy.v1;

import effectiveJava.proxy.HelloService;
import effectiveJava.proxy.HelloServiceImpl;

public class HelloInvocationDemo {
    public static void main(String[] args) {
        HelloService impl = (HelloService)ProxyInvocation.getProxyObject(new HelloServiceImpl());
        impl.sayHello();
    }
}

 

          出於組建化考慮,我們可以把代理類封裝成一個元件,因此我們需要將可能產生變化的邏輯剝離出來(3中黃色部分)。我們可以定義一個攔截器介面,用來封裝這些動態邏輯,然後用代理類(ProxyInvocation)呼叫這個攔截器的方法。而動態邏輯在攔截器介面的具體實現類中實現。修改後程式碼程式碼如下:

3,攔截器介面

package effectiveJava.proxy.v2.jar;

public interface Interceptor {
    void intercept();
}

4,代理類

package effectiveJava.proxy.v2.jar;

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

public class ProxyInvocation implements InvocationHandler {

    private Object target;
    private List<Interceptor> beforeInterceptors;
    private List<Interceptor> afterInterceptors;

    public ProxyInvocation(Object target, List<Interceptor> beforeInterceptors, List<Interceptor> afterInterceptors) {
        this.target = target;
        this.beforeInterceptors = beforeInterceptors;
        this.afterInterceptors = afterInterceptors;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        for(Interceptor interceptor : beforeInterceptors) {
            interceptor.intercept();
        }

        Object reslut = method.invoke(target, args);

        for(Interceptor interceptor : afterInterceptors) {
            interceptor.intercept();
        }

        return reslut;
    }

    public static Object getProxyObject(Object target,List<Interceptor> beforeInterceptors,List<Interceptor> afterInterceptors) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new ProxyInvocation(target,beforeInterceptors,afterInterceptors));
    }
}

5,攔截器的實現類

package effectiveJava.proxy.v2;

import effectiveJava.proxy.v2.jar.Interceptor;

public class BeforeInterceptor implements Interceptor {
    @Override
    public void intercept() {
        System.out.println("BeforeInterceptor : Before Hello....");
    }
}
package effectiveJava.proxy.v2;

import effectiveJava.proxy.v2.jar.Interceptor;

public class AfterIntercept implements Interceptor {
    @Override
    public void intercept() {
        System.out.println("AfterIntercept : After Hello....");
    }
}

6,測試類

package effectiveJava.proxy.v2;

import effectiveJava.proxy.HelloService;
import effectiveJava.proxy.HelloServiceImpl;
import effectiveJava.proxy.v2.jar.ProxyInvocation;

import java.util.Arrays;

public class HelloInvocationDemo {
    public static void main(String[] args) {
        HelloService impl = (HelloService) ProxyInvocation.getProxyObject(
                new HelloServiceImpl(),
                Arrays.asList(new BeforeInterceptor()),
                Arrays.asList(new AfterIntercept()));
        impl.sayHello();
    }
}

 

        上述方法中,代理類中的invoke()需要分別便利前置通知和後置通知,比較繁瑣,需要再次優化。可以通過建立Invocation類把攔截物件資訊進行封裝,作為攔截器攔截方法的引數,把攔截目標物件真正的執行方法放到Interceptor中完成。修改後程式碼程式碼如下:

3,攔截物件

package effectiveJava.proxy.v3.jar;

import java.lang.reflect.Method;

/**
* 封裝攔截的目標,包含:類例項、方法、引數
* @author Winn
*/
public class Invocation {

    private Object target;
    private Method method;
    private Object[] args;

    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }

    public Object invoke() throws Throwable {
        return method.invoke(target, args);
    }
}

4, 攔截器介面【代理物件的例項在還介面的方法中建立】

package effectiveJava.proxy.v3.jar;

public interface Interceptor {

    Object intercept(Invocation invocation) throws Throwable;

/**
*使用JDK8中的介面預設方法
*/
default Object wrap(Object target) { return ProxyInvocationHandler.getProxyObject(target,this); } }

5,代理類

package effectiveJava.proxy.v3.jar;

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

public class ProxyInvocationHandler implements InvocationHandler {

    private Object target;
    private Interceptor interceptor;

    public ProxyInvocationHandler(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Invocation invocation = new Invocation(target, method, args);
        return interceptor.intercept(invocation);
    }

    public static Object getProxyObject(Object target, Interceptor interceptor) {
        ProxyInvocationHandler handler = new ProxyInvocationHandler(target, interceptor);
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
    }
}

6,攔截器的實現類(新增附加邏輯,並呼叫代理元)

package effectiveJava.proxy.v3;

import effectiveJava.proxy.v3.jar.Interceptor;
import effectiveJava.proxy.v3.jar.Invocation;

public class LogIntercept implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("LogIntercept : After Hello....");
        Object invoke = invocation.invoke();
        System.out.println("LogIntercept : After Hello....");
        return invoke;
    }
}

7,測試類

package effectiveJava.proxy.v3;

import effectiveJava.proxy.HelloService;
import effectiveJava.proxy.HelloServiceImpl;

public class HelloInvocationDemo {
    public static void main(String[] args) {
        HelloService target = new HelloServiceImpl();
        //攔截器呼叫兩次,日誌輸出兩遍
        target = (HelloService)new LogIntercept().wrap(target);
        target = (HelloService)new LogIntercept().wrap(target);
        target.sayHello();
    }
}

 

        至此,將代理進行元件化封裝已經告一段落。攔截物件(3),攔截器介面(4),代理類(5)可以封裝成一個jar包。然後根據業務,自定義需要的介面(1)、介面實現類(2),以及攔截器(6)。由於我們可能需要多個不同功能的攔截器,通過上述方式,我們需要不停地在客戶端呼叫各個攔截器例項的wrap()方法,這樣不太美觀。我們利用責任鏈模式的思想,再將攔截器封裝到一個集合裡,用這個集合容器去實現呼叫。修改後程式碼程式碼如下:

7,攔截器鏈

package effectiveJava.proxy.v4.jar;

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

public class InterceptChain {

    private List<Interceptor> interceptorList = new ArrayList<>();

    public void regist(Interceptor interceptor) {
        interceptorList.add(interceptor);
    }

    public void registAll(Interceptor ... interceptors) {
        for(Interceptor interceptor : interceptors) {
            regist(interceptor);
        }
    }

    public Object wrap(Object target) {
        for(Interceptor interceptor : interceptorList) {
            target = interceptor.wrap(target);
        }
        return target;
    }
}

8,測試類

package effectiveJava.proxy.v4;

import effectiveJava.proxy.HelloService;
import effectiveJava.proxy.HelloServiceImpl;
import effectiveJava.proxy.v4.jar.InterceptChain;

public class HelloInvocationDemo {
    public static void main(String[] args) {
        InterceptChain chain = new InterceptChain();
        chain.registAll(new LogIntercept(),new LogIntercept(),new LogIntercept());
        HelloService target = new HelloServiceImpl();
        target = (HelloService)chain.wrap(target);
        target.sayHello();
    }
}

 

根據不斷地優化封裝,產生了多個版本的程式碼,各個版本的完成程式碼請檢視https://github.com/winn-hu/summary/tree/master/comm/src/main/java/effectiveJava/proxy

 

相關文章