詳解Java是如何通過接口來創建代理並進行http請求

場景

現在想要做這麼一個事情,公司的dubbo服務都是內網的,但是提供瞭一個對外的出口,通過鏈接就能請求到對應的dubbo服務。(具體怎麼做的應該就是個網關,然後將http請求轉為dubbo請求,通過泛化調用去進行調用。代碼看不到。)現在為瞭方便測試,我需要將配置的接口,通過http請求去請求對應的鏈接。

分析

項目的思想其實跟mybatis-spring整合包的思想差不多,都是生成代理去執行接口方法。
https://www.jb51.net/article/153378.htm
項目是個簡單的spring項目就行瞭,然後項目引入項目的api,然後通過配置對應的服務名稱,通過spring生成代理,註入spring容器,然後執行方法就是根據對應的域名+接口全路徑+方法名去進行請求,參數是json。為瞭方便項目使用瞭hutool工具類,直接使用fastjson去進行序列化。

操作

首先創建工廠bean,就是用來返回代理的FactoryBean

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Proxy;

/**
 * @Title:相對於BeanFactory這個大工廠,這是一個小工廠,專門用於創建某種類型的bean(默認創建的是單例bean)
 * @Description 創建代理對象
 * @Version
 */
public class HttpProxyFactoryBean<T> implements FactoryBean<T> {

    /**
     *【註意】
     * 這裡之所以可以進行自動裝配,是因為當前的這個HttpProxyFactoryBean是會被註冊到Spring中的
     * 隻不過它的註冊方式 跟一般的不一樣(一般會在類上,加一個如@Component、@Service這樣的註解 )
     * 它是通過註冊BeanDefinition的方式註冊的,可能會註冊多個,而其中的每一個HttpProxyFactoryBean實例都會被自動裝配同一個HttpProxyInvocationHandler實例
     *
     * 也有等價的做法是:
     * 利用ApplicationContextAware接口的setApplicationContext獲取到applicationContext,
     * 然後把applicationContext 作為屬性設置到當前類中
     *  再利用applicationContext的getBean方法來獲取InvocationHandler的實例
     */
    @Autowired
    private HttpProxyInvocationHandler httpProxyInvocationHandler;

    private Class<T> rpcInterface;

    public HttpProxyFactoryBean(Class<T> rpcInterface){
        this.rpcInterface = rpcInterface;
    }


    @Override
    public T getObject() throws Exception {
        //這裡應該放ComputerService接口
        return (T)Proxy.newProxyInstance(rpcInterface.getClassLoader(),new Class[]{rpcInterface} ,httpProxyInvocationHandler);
    }

    @Override
    public Class<?> getObjectType() {
        return rpcInterface;
    }

}

每一個動態代理類都必須要實現InvocationHandler這個接口

每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯到瞭一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個接口的 invoke 方法來進行調用。

我們可以直接將實現InvocationHandler的實現類註入spring容器中,然後每一個接口走同一個innvoke方法,當然也可以每一個都new一個,然後可以在構造方法中塞入特定的一些參數。我這邊因為對應的每一個代理沒啥特殊的就走同一個瞭:
定義一些參數,請求的urlproxy.serverUrl,和請求添加的項目,proxy.project

import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description 把服務方法的調用轉換為對遠程服務的http請求
 * @Version
 */
@Component
public class HttpProxyInvocationHandler implements InvocationHandler {

    @Value("${proxy.serverUrl}")
    private String serverUrl;

    @Value("${proxy.project}")
    private String serverProject;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> declaringClass = method.getDeclaringClass();
        if (Object.class.equals(declaringClass)) {
            return method.invoke(this, args);
        }

        String methodName = method.getName();
        String name = method.getDeclaringClass().getName();

        //拼接請求地址
        String url = serverUrl + name + "/" + methodName;
//        String url = "http://test:8080/soa/com.rdd.TestService/createActivity";
        HashMap<String, String> paramMap = new HashMap<>();
    
//        String result = HttpRequest.post(url).headerMap(paramMap, true).body("[" + JSONObject.toJSONString(args) + "]").execute().body();
        String result = HttpRequest.post(url).headerMap(paramMap, true).body(JSONObject.toJSONString(args)).execute().body();

        System.out.println(">>>" + url + "的響應結果為:" + result);

        //將響應結果轉換為接口方法的返回值類型
        Class<?> returnType = method.getReturnType();
        if (returnType.isPrimitive() || String.class.isAssignableFrom(returnType)) {
            if (returnType == int.class || returnType == Integer.class) {
                return Integer.valueOf(result);
            } else if (returnType == long.class || returnType == Long.class) {
                return Long.valueOf(result);
            }
            return result;
        } else if (Collection.class.isAssignableFrom(returnType)) {
            return JSONArray.parseArray(result, Object.class);
        } else if (Map.class.isAssignableFrom(returnType)) {
            return JSON.parseObject(result, Map.class);
        } else {
            return JSONObject.parseObject(result, returnType);
        }
    }
}

最後後將對應的工廠bean封裝成bean定義,註入到spring容器中

我們的接口一般都是jar形式的,我就簡單的寫在一個proxy.txt文件中,然後去讀取對應的接口全路徑,註入到spring容器中,當然也可以通過掃描某個包,自定義註解等等方式實現。

import cn.hutool.core.io.file.FileReader;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.*;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @Title:bean工廠的後置處理器,用於動態註冊bean
 * @Date 2021/3/23 10:13
 * @Description
 * @Version
 */
@Component
@PropertySource("classpath:application.properties")
public class HttpProxyRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    /**
     * 該方法用來註冊更多的bean到spring容器中
     *
     * @param beanDefinitionRegistry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        //默認UTF-8編碼,可以在構造中傳入第二個參數做為編碼
        FileReader fileReader = new FileReader("proxy.txt");
        List<String> classStrList = fileReader.readLines();
        Set<Class<?>> proxyClazzSet = new HashSet<>();
        for (String s : classStrList) {
            if (StringUtils.isBlank(s)) {
                continue;
            }
            try {
                Class<?> aClass = Class.forName(s);
                proxyClazzSet.add(aClass);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        for (Class<?> targetClazz : proxyClazzSet) {
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(targetClazz);
            GenericBeanDefinition definition = (GenericBeanDefinition) beanDefinitionBuilder.getRawBeanDefinition();
            //設置構造方法的參數  對於Class<?>,既可以設置為Class,也可以傳Class的完全類名
            //definition.getConstructorArgumentValues().addGenericArgumentValue(targetClazz);
            definition.getConstructorArgumentValues().addGenericArgumentValue(targetClazz.getName());

            //Bean的類型,指定為某個代理接口的類型
            definition.setBeanClass(HttpProxyFactoryBean.class);
            //表示 根據代理接口的類型來自動裝配
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            beanDefinitionRegistry.registerBeanDefinition(targetClazz.getName(),definition);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

到此這篇關於詳解Java是如何通過接口來創建代理並進行http請求的文章就介紹到這瞭,更多相關java創建代理進行http請求內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: