天天看點

Java 的動态代理簡單實作對某個接口的 mock

作者:inkfoxer

使用 Java 的動态代理來實作對某個接口的 mock,并将傳遞給該接口的參數、類名和方法名等資訊傳遞給指定的第三方接口,模拟第三方接口傳回的結果和指定響應的時間

可以通過在動态代理類 ApiMock 中實作 InvocationHandler 接口的 invoke 方法,在該方法中模拟第三方接口傳回的結果,并指定傳回結果的時間。具體實作方法如下:

package com.myfunnel.mock;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class ApiMock implements InvocationHandler {

    private Object target;
    private String thirdPartyUrl;

    public ApiMock(Object target, String thirdPartyUrl) {
        this.target = target;
        this.thirdPartyUrl = thirdPartyUrl;
    }

    public static Object mock(Object target, String thirdPartyUrl, Class<?>... interfaces) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                interfaces,
                new ApiMock(target, thirdPartyUrl));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String className = target.getClass().getName();
        String methodName = method.getName();
        String parameterTypes = Arrays.toString(method.getParameterTypes());
        String arguments = Arrays.toString(args);

        // 構造需要傳遞給第三方系統的請求參數
        Map<String, Object> thirdPartyParams = new HashMap<>();
        thirdPartyParams.put("className", className);
        thirdPartyParams.put("methodName", methodName);
        thirdPartyParams.put("parameterTypes", parameterTypes);
        thirdPartyParams.put("arguments", arguments);

        // 同步調用第三方系統接口
        long start = System.nanoTime();
        String response = ThirdPartyApi.call(thirdPartyUrl, thirdPartyParams);
        long elapsed = System.nanoTime() - start;

        // 模拟第三方系統傳回的結果和響應時間
        int delay = getDelay(className, methodName, parameterTypes, arguments);
        TimeUnit.MILLISECONDS.sleep(delay);

        // 列印模拟結果和響應時間
        System.out.printf("%s.%s(%s) => %s (in %d ms)%n",
                className, methodName, arguments, response, elapsed / 1000000);

        // 傳回模拟結果
        return getResponse(className, methodName, parameterTypes, arguments);
    }

    /**
     * 傳回模拟結果
     */
    private Object getResponse(String className, String methodName, String parameterTypes, String arguments) {
        if (className.equals("com.example.SomeApi") && methodName.equals("someMethod")) {
            // 傳回模拟資料
            return "mock result";
        }
        // 其他方法傳回 null
        return null;
    }

    /**
     * 傳回模拟響應時間
     */
    private int getDelay(String className, String methodName, String parameterTypes, String arguments) {
        if (className.equals("com.example.SomeApi") && methodName.equals("someMethod")) {
            // 模拟 500~1000 毫秒的延遲
            return (int) (500 + Math.random() * 500);
        }
        // 其他方法不延遲
        return 0;
    }

}
           

在這段代碼中,我們實作了一個帶有模拟功能的動态代理類 ApiMock,并在 invoke 方法中對傳遞給被代理接口的參數、類名和方法名等資訊進行記錄,并将這些資訊作為請求參數傳遞給第三方接口 ThirdPartyApi.call。同時,在模拟響應結果和響應時間時,我們實作了兩個私有方法 getResponse 和 getDelay,可以根據接口名、方法名、參數類型和參數值等條件來指定模拟的結果和延遲的時間,以模拟不同情況下的傳回結果和響應時間。

在實際使用時,需要将被代理接口的實作類作為 target 參數傳入 ApiMock 構造函數中,同時将需要模拟的第三方接口的 URL 作為 thirdPartyUrl 參數傳入。

ThirdPartyApi 是一個自定義的類,其中 call 方法用于調用第三方接口。你需要自己實作這個類,并根據實際的需求調用指定的第三方接口。

下面是一個簡單的示例代碼:

package com.myfunnel.mock;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

public class ThirdPartyApi {
    public static String call(String url, Map<String, Object> params) {
        // 将 params 拼接成 GET 請求參數格式,并拼接到 url 上
        String queryString = encodeParams(params);
        String requestUrl = url + "?" + queryString;
        StringBuffer result = new StringBuffer();
        try {
            // 發送 GET 請求
            HttpURLConnection conn = (HttpURLConnection) new URL(requestUrl).openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);

            // 讀取響應結果
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;

            while ((line = in.readLine()) != null) {
                System.out.println(line);
                result.append(line);
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result.toString();
    }

    /**
     * 将請求參數拼接成 GET 參數格式
     */
    private static String encodeParams(Map<String, Object> params) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        return sb.toString();
    }
}
           

這個 ThirdPartyApi 類定義了一個 call 方法,接受一個 url 和一個參數映射,将參數映射轉換成 GET 請求的參數格式,并将其拼接到 url 上,然後發送 GET 請求,并讀取響應結果列印到控制台中。在實際使用時,需要将 call 方法中的代碼替換成實際的調用目标第三方接口的代碼。

測試代碼:

下面是一個示例代碼,展示了如何使用 ApiMock 對 SomeApi 接口進行 mock,并将傳遞給該接口的參數、類名和方法名等資訊傳遞給指定的第三方接口 http://example.com/api:

// 建立被代理接口執行個體
SomeApi someApi = new SomeApiImpl();

// 建立代理類執行個體
SomeApi api = (SomeApi) ApiMock.mock(someApi, "http://example.com/api");

// 調用 api 接口,就會被轉發到 ApiMock 中的 invoke 方法中進行處理
String result = api.someMethod(arg1, arg2, ...);

           

這段代碼中的 SomeApi 接口定義如下:

public interface SomeApi {
    String someMethod(String arg1, int arg2, boolean arg3);
}

           

其中,someMethod 方法接收三個參數,傳回一個字元串結果。在實際使用中,根據業務需求,你需要實作 getResponse 和 getDelay 方法,以模拟不同的情況,并指定不同方法的傳回結果和響應時間。

Java 的動态代理簡單實作對某個接口的 mock