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