詳解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://www.dbjr.com.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è)小工廠,專門(mén)用于創(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)單的寫(xiě)在一個(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-04
Java實(shí)現(xiàn)將容器 Map中的內(nèi)容保存到數(shù)組
這篇文章主要介紹了Java實(shí)現(xiàn)將容器 Map中的內(nèi)容保存到數(shù)組,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
Java求一個(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-02
SpringBoot?使用?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-05
vscode搭建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-03
Java使用默認(rèn)瀏覽器打開(kāi)指定URL的方法(二種方法)
Java使用默認(rèn)瀏覽器打開(kāi)指定URL。2013-10-10
Java并發(fā)系列之ReentrantLock源碼分析
這篇文章主要為大家詳細(xì)介紹了Java并發(fā)系列之ReentrantLock源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02

