詳解Java是如何通過接口來創(chuàng)建代理并進行http請求
場景
現(xiàn)在想要做這么一個事情,公司的dubbo服務(wù)都是內(nèi)網(wǎng)的,但是提供了一個對外的出口,通過鏈接就能請求到對應(yīng)的dubbo服務(wù)。(具體怎么做的應(yīng)該就是個網(wǎng)關(guān),然后將http請求轉(zhuǎn)為dubbo請求,通過泛化調(diào)用去進行調(diào)用。代碼看不到。)現(xiàn)在為了方便測試,我需要將配置的接口,通過http請求去請求對應(yīng)的鏈接。
分析
項目的思想其實跟mybatis-spring整合包的思想差不多,都是生成代理去執(zhí)行接口方法。
http://www.dbjr.com.cn/article/153378.htm
項目是個簡單的spring項目就行了,然后項目引入項目的api,然后通過配置對應(yīng)的服務(wù)名稱,通過spring生成代理,注入spring容器,然后執(zhí)行方法就是根據(jù)對應(yīng)的域名+接口全路徑+方法名去進行請求,參數(shù)是json。為了方便項目使用了hutool工具類,直接使用fastjson去進行序列化。
操作
首先創(chuàng)建工廠bean,就是用來返回代理的FactoryBean
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Proxy; /** * @Title:相對于BeanFactory這個大工廠,這是一個小工廠,專門用于創(chuàng)建某種類型的bean(默認創(chuàng)建的是單例bean) * @Description 創(chuàng)建代理對象 * @Version */ public class HttpProxyFactoryBean<T> implements FactoryBean<T> { /** *【注意】 * 這里之所以可以進行自動裝配,是因為當(dāng)前的這個HttpProxyFactoryBean是會被注冊到Spring中的 * 只不過它的注冊方式 跟一般的不一樣(一般會在類上,加一個如@Component、@Service這樣的注解 ) * 它是通過注冊BeanDefinition的方式注冊的,可能會注冊多個,而其中的每一個HttpProxyFactoryBean實例都會被自動裝配同一個HttpProxyInvocationHandler實例 * * 也有等價的做法是: * 利用ApplicationContextAware接口的setApplicationContext獲取到applicationContext, * 然后把applicationContext 作為屬性設(shè)置到當(dāng)前類中 * 再利用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 { //這里應(yīng)該放ComputerService接口 return (T)Proxy.newProxyInstance(rpcInterface.getClassLoader(),new Class[]{rpcInterface} ,httpProxyInvocationHandler); } @Override public Class<?> getObjectType() { return rpcInterface; } }
每一個動態(tài)代理類都必須要實現(xiàn)InvocationHandler這個接口
每一個動態(tài)代理類都必須要實現(xiàn)InvocationHandler這個接口,并且每個代理類的實例都關(guān)聯(lián)到了一個handler,當(dāng)我們通過代理對象調(diào)用一個方法的時候,這個方法的調(diào)用就會被轉(zhuǎn)發(fā)為由InvocationHandler這個接口的 invoke 方法來進行調(diào)用。
我們可以直接將實現(xiàn)InvocationHandler的實現(xiàn)類注入spring容器中,然后每一個接口走同一個innvoke方法,當(dāng)然也可以每一個都new一個,然后可以在構(gòu)造方法中塞入特定的一些參數(shù)。我這邊因為對應(yīng)的每一個代理沒啥特殊的就走同一個了:
定義一些參數(shù),請求的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 把服務(wù)方法的調(diào)用轉(zhuǎn)換為對遠程服務(wù)的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 + "的響應(yīng)結(jié)果為:" + result); //將響應(yīng)結(jié)果轉(zhuǎn)換為接口方法的返回值類型 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); } } }
最后后將對應(yīng)的工廠bean封裝成bean定義,注入到spring容器中
我們的接口一般都是jar形式的,我就簡單的寫在一個proxy.txt文件中,然后去讀取對應(yīng)的接口全路徑,注入到spring容器中,當(dāng)然也可以通過掃描某個包,自定義注解等等方式實現(xiàn)。
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工廠的后置處理器,用于動態(tài)注冊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編碼,可以在構(gòu)造中傳入第二個參數(shù)做為編碼 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(); //設(shè)置構(gòu)造方法的參數(shù) 對于Class<?>,既可以設(shè)置為Class,也可以傳Class的完全類名 //definition.getConstructorArgumentValues().addGenericArgumentValue(targetClazz); definition.getConstructorArgumentValues().addGenericArgumentValue(targetClazz.getName()); //Bean的類型,指定為某個代理接口的類型 definition.setBeanClass(HttpProxyFactoryBean.class); //表示 根據(jù)代理接口的類型來自動裝配 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); beanDefinitionRegistry.registerBeanDefinition(targetClazz.getName(),definition); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { } }
到此這篇關(guān)于詳解Java是如何通過接口來創(chuàng)建代理并進行http請求的文章就介紹到這了,更多相關(guān)java創(chuàng)建代理進行http請求內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot 多任務(wù)并行+線程池處理的實現(xiàn)
這篇文章主要介紹了SpringBoot 多任務(wù)并行+線程池處理的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04Java實現(xiàn)將容器 Map中的內(nèi)容保存到數(shù)組
這篇文章主要介紹了Java實現(xiàn)將容器 Map中的內(nèi)容保存到數(shù)組,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09Java求一個分數(shù)數(shù)列的前20項之和的實現(xiàn)代碼
這篇文章主要介紹了Java求一個分數(shù)數(shù)列的前20項之和的實現(xiàn)代碼,需要的朋友可以參考下2017-02-02SpringBoot?使用?Sa-Token?完成注解鑒權(quán)功能(權(quán)限校驗)
Sa-Token?是一個輕量級?java?權(quán)限認證框架,主要解決登錄認證、權(quán)限認證、單點登錄、OAuth2、微服務(wù)網(wǎng)關(guān)鑒權(quán)?等一系列權(quán)限相關(guān)問題,這篇文章主要介紹了SpringBoot使用Sa-Token完成注解鑒權(quán)功能,需要的朋友可以參考下2023-05-05vscode搭建java開發(fā)環(huán)境的實現(xiàn)步驟
本文主要介紹了vscode搭建java開發(fā)環(huán)境,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03Java并發(fā)系列之ReentrantLock源碼分析
這篇文章主要為大家詳細介紹了Java并發(fā)系列之ReentrantLock源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02