日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

如何通过HTTP优雅调用第三方-Feign

發布時間:2023/11/28 生活经验 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何通过HTTP优雅调用第三方-Feign 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Java常用HTTP客戶端

  • Java原生HttpURLConnection
  • Apache HttpClient
  • OkHttp
  • Spring RestTemplate

示例

public interface Client {/*** * @param body* @return*/Response post(Object body) throws Exception;
}
public class ApacheHttpClient implements Client {private static final String DEFAULT_CONTENT_TYPE_VALUE = "application/json";private static final String DEFAULT_CHARSET_NAME = "UTF-8";private String url;private final HttpClient client;private JsonSerializer jsonSerializer;/*** * @param url*/public ApacheHttpClient(String url) {this(url, HttpClientBuilder.create().build());}/*** * @param url* @param client*/public ApacheHttpClient(String url, HttpClient client) {this(url, client, new JacksonJsonSerializer());}/*** * @param url* @param client* @param jsonSerializer*/public ApacheHttpClient(String url, HttpClient client, JsonSerializer jsonSerializer) {if (StringUtils.isBlank(url)) {throw new IllegalArgumentException("Url can't be null!");}if (Objects.isNull(client)) {throw new IllegalArgumentException("client can't be null!");}if (Objects.isNull(jsonSerializer)) {throw new IllegalArgumentException("jsonSerializer can't be null!");}this.url = url;this.client = client;this.jsonSerializer = jsonSerializer;}@Overridepublic Response post(Object body) throws Exception {if (Objects.isNull(body)) {return Response.NULL_BODY_RESPONSE;}if (body instanceof Collection && ((Collection<?>) body).size() <= 0) {return Response.EMPTY_BODY_RESPONSE;}HttpPost post = new HttpPost(url);String jsonString = jsonSerializer.serialize(body);StringEntity entity = new StringEntity(jsonString, DEFAULT_CHARSET_NAME);entity.setContentType(DEFAULT_CONTENT_TYPE_VALUE);post.setEntity(entity);HttpResponse httpResponse = client.execute(post);HttpEntity httpEntity = httpResponse.getEntity();String result = EntityUtils.toString(httpEntity, "UTF-8");Response response = new Response(httpResponse.getStatusLine().getStatusCode(),httpResponse.getStatusLine().getReasonPhrase(), result);httpResponse.getEntity().getContent().close();return response;}}

存在問題

  • 面向方法
  • 耦合HTTP客戶端
  • 手動處理請求響應
  • 無法通用,重復編碼
  • 面向接口實現HTTP調用

HTTP請求和響應的組成

  • 請求:請求行、請求頭、請求體
  • 響應:響應行、響應頭、響應體

從訪問百度主頁開始

聲明接口

package com.zby.service;public interface BaiduClient {@RequestLine(url = "http://www.baidu.com", method = "GET")String index();}

聲明注解

package com.zby.annotation;@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestLine {String method();String url();}
  • 如果我能通過注入BaiduClient接口的方式調用第三方,并且新增第三方時,只需要新增類似接口,與面向過程的方式相比,是否更優雅?
  • 要實現注入BaiduClient接口,那么必須解析RequestLine注解,并且生成BaiduClient對象

HOW?

  • 代理模式
  • 模版方法模式
  • 工廠模式
  • 聲明工廠類
package com.zby.proxy;/*** @author zby* @title HTTP代理工廠* @date 2020年6月16日* @description*/
public interface HttpProxyFactory {/*** 獲取HTTP代理對象* * @param clazz* @return*/<T> T getProxy(Class<T> clazz);}

抽象工廠,處理注解

package com.zby.proxy;import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;import com.zby.annotation.RequestLine;/*** @author zby* @title 抽象代理工廠* @date 2020年6月16日* @description*/
public abstract class AbstractHttpProxyFactory implements HttpProxyFactory {private Map<Class<?>, Map<Method, RequestLine>> classMethodCacheMap = new ConcurrentHashMap<>();@Overridepublic <T> T getProxy(Class<T> clazz) {if (!clazz.isInterface()) {throw new IllegalArgumentException();}Map<Method, RequestLine> methodCacheMap = classMethodCacheMap.get(clazz);if (methodCacheMap == null) {methodCacheMap = new HashMap<>();Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {RequestLine requestLine = method.getDeclaredAnnotation(RequestLine.class);if (requestLine == null) {throw new IllegalArgumentException(method.getName());}methodCacheMap.put(method, requestLine);}classMethodCacheMap.putIfAbsent(clazz, methodCacheMap);}return createProxy(clazz, methodCacheMap);}/*** 創建代理對象* * @param clazz* @param methodCacheMap* @return*/protected abstract <T> T createProxy(Class<T> clazz, Map<Method, RequestLine> methodCacheMap);}

基于代理和HttpURLConnection實現

package com.zby.proxy;import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;import com.zby.annotation.RequestLine;/*** @author zby* @title HttpURLConnectionProxyFactory* @date 2020年6月16日* @description*/
public class HttpURLConnectionProxyFactory extends AbstractHttpProxyFactory {@Override@SuppressWarnings("unchecked")protected <T> T createProxy(Class<T> clazz, Map<Method, RequestLine> methodCacheMap) {return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] {clazz}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {RequestLine requestLine = methodCacheMap.get(method);if (requestLine == null) {throw new IllegalArgumentException();}URL url = new URL(requestLine.url());URLConnection rulConnection = url.openConnection();HttpURLConnection httpUrlConnection = (HttpURLConnection) rulConnection;httpUrlConnection.setRequestMethod(requestLine.method());httpUrlConnection.connect();InputStream inputStream = httpUrlConnection.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));StringBuilder stringBuilder = new StringBuilder();String newLine = null;while ((newLine = bufferedReader.readLine()) != null) {stringBuilder.append(newLine);}return stringBuilder.toString();}});}
}

拉出來溜溜

package com.zby;import com.zby.proxy.HttpProxyFactory;
import com.zby.proxy.HttpURLConnectionProxyFactory;
import com.zby.service.BaiduClient;/*** @author zby* @title HttpProxyDemo* @date 2020年6月16日* @description*/
public class HttpProxyDemo {public static void main(String[] args) {HttpProxyFactory httpProxyFactory = new HttpURLConnectionProxyFactory();BaiduClient baiduClient = httpProxyFactory.getProxy(BaiduClient.class);System.out.println(baiduClient.index());}}

面向接口的好處

  • 新增第三方不再是增加方法,而是增加接口
  • 面向接口編程而不是面向方法編程
  • 無需每次自己處理請求響應
  • 隨時更換具體實現HttpURLConnectionProxyFactory而不影響原有代碼

不要重復造輪子?

  • NO!是不要重復造低級輪子。
  • 如果你的輪子比別人好,那么還是可以嘗試一下干掉別人的輪子。
  • 比如SpringMVC干掉了Strust,Mybatis干掉了Hibernate

優化
架構設計
插件式設計
客戶端抽象
編解碼抽象
注解支持
重試支持
第三方適配
Spring整合
SpringMVC注解支持
負載均衡支持
等等等等
輪子篇-Feign
Feign github地址

Feign makes writing java http clients easier

簡單來說,就是一個基于接口的HTTP代理生成器

先睹為快

io.github.openfeign
feign-core
10.2.0

package com.zby.feign;import feign.Feign;
import feign.Logger;
import feign.Logger.ErrorLogger;
import feign.RequestLine;public class FeignMain {public static void main(String[] args) {Baidu baidu = Feign.builder().logger(new ErrorLogger()).logLevel(Logger.Level.FULL).target(Baidu.class, "http://www.baidu.com");System.out.println(baidu.index());}}
interface Baidu {@RequestLine(value = "GET /")String index();}

插件式設計
默認使用HttpURLConnection作為HTTP客戶端,如果需要修改為HttpClient需要干啥?

<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId><version>10.2.0</version>
</dependency>
package com.zby.feign;import feign.Feign;
import feign.Logger;
import feign.Logger.ErrorLogger;
import feign.RequestLine;
import feign.httpclient.ApacheHttpClient;public class FeignMain {public static void main(String[] args) {Baidu baidu = Feign.builder().logger(new ErrorLogger()).logLevel(Logger.Level.FULL).client(new ApacheHttpClient()).target(Baidu.class, "http://www.baidu.com");System.out.println(baidu.index());}}
interface Baidu {@RequestLine(value = "GET /")String index();}

So Easy
其他組件

feign.Feign.Builderprivate final List<RequestInterceptor> requestInterceptors =new ArrayList<RequestInterceptor>();private Logger.Level logLevel = Logger.Level.NONE;private Contract contract = new Contract.Default();private Client client = new Client.Default(null, null);private Retryer retryer = new Retryer.Default();private Logger logger = new NoOpLogger();private Encoder encoder = new Encoder.Default();private Decoder decoder = new Decoder.Default();private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();private ErrorDecoder errorDecoder = new ErrorDecoder.Default();private Options options = new Options();private InvocationHandlerFactory invocationHandlerFactory =new InvocationHandlerFactory.Default();private boolean decode404;private boolean closeAfterDecode = true;private ExceptionPropagationPolicy propagationPolicy = NONE;

注解解析:

  • feign.Contract支持的注解:Body、HeaderMap、Headers、Param、QueryMap、RequestLine 實現feign.Contract定制屬于你自己的契約

實戰篇-對接GIO

一、定義接口

/*** 獲取事件分析數據,文檔地址:* https://docs.growingio.com/docs/developer-manual/api-reference/statistics-api/definition/getevent* * @author zby* @Date 2020年4月20日**/
public interface EventAnalysisClient {/*** 獲取事件分析數據* * @param projectUid 項目ID* @param chartId 圖表ID* @param eventAnalysisRequest 參數* @return*/@RequestLine("GET /v2/projects/{project_uid}/charts/{chart_id}.json")EventAnalysisResponse getEvent(@Param("project_uid") String projectUid, @Param("chart_id") String chartId,@QueryMap EventAnalysisRequest eventAnalysisRequest);}

二、Feign抽象配置

/*** GIOFeign抽象配置* * @author zby* @Date 2020年4月21日**/
public class AbstractGIOFeignConfiguration {/*** GIO官網地址*/@Value("${gio.url}")private String gioUrl;/*** 認證信息*/@Value("${gio.authorization}")private String authorization;/*** 創建Feign代理對象* * @param clazz* @return*/protected <T> T newInstance(Class<T> clazz) {return feign().newInstance(new Target.HardCodedTarget<T>(clazz, gioUrl));}/*** 公用的Feign對象* * @return*/@SuppressWarnings("deprecation")private Feign feign() {return Feign.builder().logger(new Slf4jLogger()).logLevel(Level.BASIC).encoder(new JacksonEncoder()).decoder(new JacksonDecoder()).requestInterceptor(new AuthorizationInterceptor())// 處理null.queryMapEncoder(new QueryMapEncoder.Default() {@Overridepublic Map<String, Object> encode(Object object) throws EncodeException {if (object == null) {return new HashMap<String, Object>();}return super.encode(object);}}).build();}/*** GIO認證信息攔截器* * @author zby* @Date 2020年4月20日**/class AuthorizationInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {template.header("Authorization", authorization);}}}

三、注入IOC容器

/*** GIO的feig客戶端配置類* * @author zby* @Date 2020年4月21日**/
@Configuration
public class GIOFeignConfiguration extends AbstractGIOFeignConfiguration {/*** 創建事件分析Feign客戶端,代優化:定義注解FeignClient直接掃描創建實例放到spring容器* * @return*/@Beanpublic EventAnalysisClient eventAnalysisClient() {return newInstance(EventAnalysisClient.class);}}

四、使用

/*** 事件分析GIO客戶端實現* * @author zby* @Date 2020年4月21日**/
@Service
public class EventAnalysisGIOClientImpl implements EventAnalysisGIOClient {private static final Logger LOGGER = LoggerFactory.getLogger(EventAnalysisGIOClientImpl.class);@Autowiredprivate EventAnalysisClient eventAnalysisClient;@Overridepublic List<NewsListEventAnalysisDto> getNewsListData(EventAnalysisRequest eventAnalysisRequest) {List<NewsListEventAnalysisDto> list;try {LOGGER.info("獲取GIO新聞列表數據參數:eventAnalysisRequest={}", eventAnalysisRequest);EventAnalysisResponse eventAnalysisResponse = eventAnalysisClient.getEvent(GIOConstant.PROJECT_UID,EventAnalysisConstant.CHART_ID_NEWS_LIST, eventAnalysisRequest);LOGGER.info("獲取GIO新聞列表數據結果:{}", eventAnalysisResponse);list = EventAnalysisUtil.parse(eventAnalysisResponse,new AbstractGIOFieldObjectFactory<NewsListEventAnalysisDto>() {});} catch (Exception e) {LOGGER.error(String.format("獲取GIO新聞列表數據出錯,請求參數:%s", eventAnalysisRequest), e);return null;}return list;}
}

贈送篇-結果封裝
GIO數據結果通用格式
基于注解、反射、工廠模式、反省、BeanWrapper等封裝通用數據格式為業務對象

/*** 事件分析響應實體* * @author zby* @Date 2020年4月20日**/
@Data
public class EventAnalysisResponse {/*** Chart Uid*/private String id;/*** Chart Name*/private String name;/*** 同參數*/private Long startTime;/*** 同參數*/private Long endTime;/*** 同參數*/private Long interval;/*** 表格頭元數據*/private Meta[] meta;/*** 表格數據*/private String[][] data;/*** 元數據,標題列* * @author zby* @Date 2020年4月21日**/@Data@NoArgsConstructor@AllArgsConstructorpublic static class Meta {/*** 名稱*/private String name;/*** 方面*/private boolean dimension;/*** 統計數據*/private boolean metric;}
}

總結

以上是生活随笔為你收集整理的如何通过HTTP优雅调用第三方-Feign的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。