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相关推荐

  1. feign调用第三方接口服务

    前言 做个笔记,下次直接抄 这里需要拿到response的header做验签之类的操作 所以用feign.Response来接收响应 正文 第三方接口调用的feign,自测OK import com. ...

  2. SpringBoot(32) 整合Forest实现调用第三方接口

    一.前言 Forest是什么? Forest是一个高层的.极简的轻量级 HTTP调用API框架,让Java发送HTTP/HTTPS请求不再难.它比OkHttp和HttpClient更高层,比Feign ...

  3. 【Spring Cloud Alibaba】(二)微服务调用组件Feign原理+实战

    系列目录 [Spring Cloud Alibaba](一)微服务介绍 及 Nacos注册中心实战 本文目录 系列目录 前言 什么是RPC? Feign和OpenFeign都是什么? HTTP调用 v ...

  4. 22.OpenFeign调用第三方服务

    1.在pom.xml文件中添加OpenFeign的依赖.OpenFeign中集成了ribbion实现负载均衡 <!-- OpenFeign --> <dependency>&l ...

  5. android qq第三方登录,Android调用第三方QQ登录代码分享

    本文为大家分享了调用QQ登录的相关代码,希望对大家有帮助,减少项目开发的时间,具体内容如下 1.去QQ开放平台注册帐号(http://open.qq.com/),为应用申请QQ的APP_ID , 并下 ...

  6. Swift基础--调用第三方OC项目

    第一步:创建和配置Bridging-Header.h Swift与OC进行混编,首先要有一个.h文件,这里使用Bridging-Header.h然后设置项目的Build Settings--Swift ...

  7. http方式调用第三方接口

    java如何调用对方http接口(II) - 流年煮雪 - 博客园 纯Java api HttpURLConnection Java调用外部接口_CJD的博客-CSDN博客_调用外部接口 纯Java  ...

  8. java调用webservice_WebService学习总结(四)——调用第三方提供的webService服务

    只为成功找方法,不为失败找借口! 互联网上面有很多的免费webService服务,我们可以调用这些免费的WebService服务,将一些其他网站的内容信息集成到我们的Web应用中显示,下面就以获取天气 ...

  9. python调用第三方软件发信代码_【IT专家】python调用第三方邮件接口

    本文由我司收集整编,推荐下载,如有疑问,请与我司联系 python 调用第三方邮件接口 2017/08/10 1 单线程发送 #!/usr/bin/env python# -*- coding: UT ...

最新文章

  1. 两大图像处理库Halcon和Opencv 的对比
  2. 应用层级时空记忆模型(HTM)实现对时序数据的异常检测
  3. ASP.NET MVC实践系列5-结合jQuery
  4. 互联网秒杀设计--转载
  5. 从零开始学python电子书-从零开始学Python程序设计 PDF 完整影印版
  6. python安装numba_python – 在OS X上安装Numba时出错
  7. 带你揭秘Web前端发展的前景以及技术
  8. Spring Boot中带有CKEditor的AJAX
  9. 文末赠书100本 | 当下最火爆的机器学习算法
  10. PyTorch 1.0 中文官方教程:什么是 PyTorch
  11. 补全aaz288 可能有问题的过程 P_COMPL_AAZ288
  12. Stick ------ 剪枝神题
  13. Android模仿新浪微博(写微博界面)
  14. matlab光伏最大功率,光伏系统最大功率点跟踪技术的比较
  15. 如何在云服务器上安装kali系统
  16. 数字货币合约的短线交易策略有哪些?
  17. SQL案例学习-保护个人信息
  18. 适合一个人干的小生意,最挣钱没人干的行业
  19. 【Python_046】网页爬虫(绕过SSH认证)
  20. Android UI系列之侧滑粘稠效果的实现

热门文章

  1. 2022-2028年中国AKD施胶剂行业市场研究及前瞻分析报告
  2. pandas dataframe 字符映射为数字
  3. [转载]Tensorflow 的reduce_sum()函数的axis,keep_dim这些参数到底是什么意思?
  4. 如何使用TensorCores优化卷积
  5. EventBus的粘性事件原理
  6. 2021年大数据Flink(二十七):Flink 容错机制 Checkpoint
  7. 安装PHP7.3.2make编译出现报错,内存不足导致,临时解决方法
  8. 火狐浏览器允许ajax,解决火狐浏览器发送jquery的ajax请求无效的问题
  9. windows java 小程序_JAVA第一个窗体小程序
  10. hql调用mysql存储过程_hibernate调用mysql存储过程