换一种口味实现 HttpClient
程序员文章站
2022-05-28 19:10:34
...
基于注解 + 反射 + 动态代理
先上代码:
程序说明
1. InvokerMethod
该类比较简单,一个注解,它将作用于方法上,保留到运行期(这样才能通过反射获取其内容)。
2. HttpProxyFactoryBean
这个类比较奇特,也是这个解决方案的精华。
它实现了 FactoryBean 。 FactoryBean 是 Spring 类库的一个接口,它提供了三个方法需要实现:
和普通 Bean 不同,该类被配置为 Spring Bean 后,返回的不是 FactoryBean 本身,而是它的 getObject() 所返回的对象。 getObjectType() 将返回实例的类型,isSingleton() 可选择是否使用单例模式。
具体到本类,在 init 方法中,初始化了动态代理类 proxy ,这个 proxy 将作为 getObject() 的返回。 interfaceName 和 handler 将作为属性在 Spring 配置文件中注入:
ReceiptQueryService 大概长这个样子:
现在,当我们调用 http 服务的时候,只需要写一个接口,在方法上加一个注解就可以了,加密等操作对程序员完全透明!
3. HttpInvocationHandler
我们在第二步中用到了一个 InvocationHandler 。我知道,它是 java.lang.reflect.Proxy 构造动态代理类的第三个参数:
它只有一个必须实现的接口:
在我们 InvocationHandler 的实现类里,将通过反射获取方法的注解( path | get/post | timeout )和参数:
先上代码:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface InvokerMethod { enum HttpMethod { Get, Post } HttpMethod method() default HttpMethod.Get; String path() default ""; int timeout() default 5000; }
public class HttpProxyFactoryBean implements FactoryBean { private String interfaceName; private InvocationHandler handler; private Object proxy; private Class<?> proxyType; public void init() throws Exception { Preconditions.checkNotNull(interfaceName); Preconditions.checkNotNull(handler); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); proxyType = ClassUtils.getClass(classLoader, interfaceName.trim()); proxy = Proxy.newProxyInstance(classLoader, new Class[] { proxyType }, handler); } @Override public Object getObject() throws Exception { return proxy; } @Override public Class getObjectType() { return proxyType; } @Override public boolean isSingleton() { return true; } public void setInterfaceName(String interfaceName) { this.interfaceName = interfaceName; } public void setHandler(InvocationHandler handler) { this.handler = handler; } }
public class HttpInvocationHandler implements InvocationHandler { // 目标地址,如: http://www.example.com private String host = "******"; // 申请的 key private String key = "******"; // HttpClient private CloseableHttpClient httpClient; /** * 初始化 HttpClient 。 HttpClient 的构造其实很有讲究的。 */ public HttpInvocationHandler() { RequestConfig requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(1000) .setConnectTimeout(1000) .setSocketTimeout(1000) .build(); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); // 设置总的最大连接数 connectionManager.setMaxTotal(500); // 设置单机最大连接数 connectionManager.setDefaultMaxPerRoute(100); // 设置出口到目标地址的单机最大连接数 HttpHost httpHost = new HttpHost(parseHost()[1], 80); connectionManager.setMaxPerRoute(new HttpRoute(httpHost), 100); httpClient = HttpClients.custom() .setDefaultRequestConfig(requestConfig) .setConnectionManager(connectionManager) .build(); } /** * 代理方法,执行 http 请求。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Preconditions.checkNotNull(httpClient); Preconditions.checkNotNull(host); Preconditions.checkNotNull(key); HttpUriRequest httpRequest = buildHttpRequest(method, args); if (httpRequest == null) { throw new IllegalRequestException(); } CloseableHttpResponse httpResponse = null; try { httpResponse = httpClient.execute(httpRequest); int statusCode = httpResponse.getStatusLine().getStatusCode(); if (statusCode != 200) { throw new RemoteServiceException("Http status code: " + statusCode); } HttpEntity entity = httpResponse.getEntity(); Object response = null; if (entity != null) { InputStream inputStream = entity.getContent(); try { response = JsonUtil.fromJson(new InputStreamReader(inputStream), method.getReturnType()); } finally { inputStream.close(); } } return response; } catch (Exception e) { throw new RemoteServiceException(e); } finally { if (httpResponse != null) { httpResponse.close(); httpRequest.abort(); } } } /** * 构造 Http 请求。 */ private HttpUriRequest buildHttpRequest(Method method, Object[] args) { InvokerMethod invokerMethod = method.getAnnotation(InvokerMethod.class); if (invokerMethod == null) { return null; } if (args == null || args.length == 0) { return null; } Object request = args[0]; String jsonRequest = JsonUtil.toJson(request); HttpUriRequest httpUriRequest; switch (invokerMethod.method()) { case Get: httpUriRequest = createGetRequest(invokerMethod, jsonRequest); break; case Post: httpUriRequest = createPostRequest(invokerMethod, jsonRequest); break; default: httpUriRequest = null; break; } return httpUriRequest; } /** * 创建加密 Get 请求。 */ private HttpUriRequest createGetRequest(InvokerMethod method, String jsonRequest) { URI uri; try { String[] hostPair = parseHost(); uri = new URIBuilder() .setScheme(hostPair[0]) .setHost(hostPair[1]) .setPath(method.path()) .addParameter("json", jsonRequest) .addParameter("sign", encrypt(jsonRequest)) .addParameter("sign_type", "md5") .build(); } catch (URISyntaxException e) { return null; } RequestConfig config = RequestConfig.custom().setSocketTimeout(method.timeout()).build(); HttpGet httpGet = new HttpGet(uri); httpGet.setConfig(config); return httpGet; } /** * 创建加密 Post 请求。 */ private HttpUriRequest createPostRequest(InvokerMethod method, String jsonRequest) { URI uri; try { String[] hostPair = parseHost(); uri = new URIBuilder() .setScheme(hostPair[0]) .setHost(hostPair[1]) .setPath(method.path()) .build(); } catch (URISyntaxException e) { return null; } RequestConfig config = RequestConfig.custom().setSocketTimeout(method.timeout()).build(); HttpPost httpPost = new HttpPost(uri); httpPost.setConfig(config); List<NameValuePair> pairs = Lists.newArrayListWithCapacity(3); pairs.add(new BasicNameValuePair("json", jsonRequest)); pairs.add(new BasicNameValuePair("sign", encrypt(jsonRequest))); pairs.add(new BasicNameValuePair("sign_type", "md5")); httpPost.setEntity(new UrlEncodedFormEntity(pairs, Consts.UTF_8)); return httpPost; } /** * 使用 MD5 加密请求数据。 */ private String encrypt(String jsonRequest) { return DigestUtils.md5Hex(jsonRequest + key); } /** * http://www.example.com ==> [http, www.example.com] 。 */ private String[] parseHost() { if (host == null) { return new String[] { "", "" }; } String[] parts = StringUtils.split(host, "://"); if (parts.length != 2) { return new String[] { "", "" }; } return parts; } }
程序说明
1. InvokerMethod
该类比较简单,一个注解,它将作用于方法上,保留到运行期(这样才能通过反射获取其内容)。
2. HttpProxyFactoryBean
这个类比较奇特,也是这个解决方案的精华。
它实现了 FactoryBean 。 FactoryBean 是 Spring 类库的一个接口,它提供了三个方法需要实现:
T getObject() throws Exception; Class<?> getObjectType(); boolean isSingleton();
和普通 Bean 不同,该类被配置为 Spring Bean 后,返回的不是 FactoryBean 本身,而是它的 getObject() 所返回的对象。 getObjectType() 将返回实例的类型,isSingleton() 可选择是否使用单例模式。
具体到本类,在 init 方法中,初始化了动态代理类 proxy ,这个 proxy 将作为 getObject() 的返回。 interfaceName 和 handler 将作为属性在 Spring 配置文件中注入:
<bean id="receiptQueryService" class="com.******.HttpProxyFactoryBean" init-method="init"> <property name="interfaceName" value="com.******.ReceiptQueryService"/> <property name="handler" ref="httpInvocationHandler"/> </bean>
ReceiptQueryService 大概长这个样子:
public interface ReceiptQueryService { @InvokerMethod(method = InvokerMethod.HttpMethod.Get, path = "/xx/yy/zz") ReceiptQueryResponse queryReceipts(ReceiptQueryRequest request); }
现在,当我们调用 http 服务的时候,只需要写一个接口,在方法上加一个注解就可以了,加密等操作对程序员完全透明!
3. HttpInvocationHandler
我们在第二步中用到了一个 InvocationHandler 。我知道,它是 java.lang.reflect.Proxy 构造动态代理类的第三个参数:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
它只有一个必须实现的接口:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
在我们 InvocationHandler 的实现类里,将通过反射获取方法的注解( path | get/post | timeout )和参数:
InvokerMethod invokerMethod = method.getAnnotation(InvokerMethod.class); ... invokerMethod.method(); invokerMethod.path(); invokerMethod.timeout(); ... Object request = args[0];
推荐阅读
-
HTTPClient实现免登陆请求(带cookie请求)
-
php实现httpclient类示例
-
HttpClient实现Http+xml接口调用
-
实现在 .net 中使用 HttpClient 下载文件时显示进度
-
Java开发笔记(一百一十三)HttpClient实现下载与上传
-
使用spring 中的RestTemplate 发送请求 实现httpclient
-
Android通过HttpURLConnection和HttpClient接口实现网络编程
-
Java基于HttpClient实现RPC的示例
-
php实现httpclient类示例
-
angular httpclient实现 HTTP 客户端功能