詳解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!
推薦閱讀:
- spring boot 動態生成接口實現類的場景分析
- Java AOP動態代理詳細介紹
- Spring Bean作用域與生命周期深入講解
- Spring概述和快速構建的方式
- 一篇文章教帶你瞭解Java Spring之自動裝配