项目里面经常会调用一些三方系统的接口,相信大多数的解决方法就是百度一个httpclient的工具类,借助httpclientutil工具类来实现。例如这样子:
/**
* Https请求发送方法
* application/x-www-form-urlencoded 编码
* @param requestUrl
* 请求的url
* @param requestMethod
* 请求的方法
* @param paramStr
* post方法请求参数转换成的字符串
* get方法传空
* @return
*/
public static String httpsRequest(String requestUrl, String requestMethod, String paramStr) {
logger.info("发送https请求{}{}{}",requestUrl, requestMethod, paramStr);
StringBuffer buffer = null;
try {
// 创建SSLContext
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManager[] tm = { new MyX509TrustManager() };
// 初始化
sslContext.init(null, tm, new java.security.SecureRandom());
// 获取SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod(requestMethod);
// 设置当前实例使用的SSLSoctetFactory
conn.setSSLSocketFactory(ssf);
conn.connect();
// 往服务器端写内容
if (null != paramStr) {
OutputStream os = conn.getOutputStream();
os.write(paramStr.getBytes("utf-8"));
os.close();
}
// 读取服务器端返回的内容
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
buffer = new StringBuffer();
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line);
}
} catch (Exception e) {
e.printStackTrace();
logger.info("net请求失败", e);
return null;
}
String respStr = buffer.toString();
logger.info("请求返回值{}", respStr);
return respStr;
}
如果系统中只有很小一部分是需要调用三方接口的,这样也没啥问题。但是当系统有很多地方要调用三方,并且调用不止一个三方的时候,这种实现方式就显得不是那么好了,管理起来和拓展起来都很麻烦。下面我们可以尝试借助注解和动态代理来实现这种业务需求。
先来看看结果,然后再看如何实现,比如我们现在有一个需求通过http查询别的系统的门店列表,请求时候需要带上自己的账号和密码(仅仅是个demo)。
第一步:定义请求
import org.springframework.http.HttpMethod;
import com.arvato.common.annotation.PathParam;
import com.arvato.common.annotation.Request;
public interface IMemberInfoService {
@Request(url = "https://xxxxxxx/wxwork/open/store/list", method = HttpMethod.POST)
default String test(@PathParam(key="username") String username, @PathParam(key="password") String pathword){
return null;
};
}
import com.arvato.wxwork.service.member.IMemberInfoService;
public class MemberInfoServiceImpl implements IMemberInfoService {
}
第二步:调用请求
public static void main(String[] args) {
IMemberInfoService create = RPCProxy.create(MemberInfoServiceImpl.class);
System.out.println("返回值:" + create.test("arvato_user", "arvato@123"));
}
这样子就实现了调用,很简洁。
1.分析流程
流程其实是很简单,总结下来就是一句话:发送请求,接收响应,处理返回值。发送请求可以分为:请求url,请求参数,请求Header,参数token等。接收响应:每个接口处理返回值的做法都不一样,比如有的接口的返回值0是代表OK,有的200是代表OK。有的接口返回一个json对象,有的却只有一个String字符串。所以我们把发送请求和处理返回值都抽离出来。
2.新建注解
我们需要新建注解,通过动态代理来解析,得到请求参数和处理方法。
@Request注解,这个注解中的url和httpMethod很好理解,就是请求url和请求方法。剩下的两个属性requestHandler和responseHandler是用来发送请求和处理请求的。一句话,通过@Request,代理类可以明确的知道,需要通过谁用什么方法向哪一个url发送请求,请求的返回值如何处理。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Request {
String url() default "";
HttpMethod method() default HttpMethod.GET;
Class<? extends IRequestHandler> requestHandler() default RestTemplateHandler.class;
Class<? extends IResponseHandler> responseHandler() default DefaultResponseHandler.class;
}
IRequestHandler: 抽象发送请求,默认是RestTemplateHandler
import java.util.List;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.http.HttpMethod;
/**
* 发送请求接口
* @Description
* @author smallking
* @Date 2020年3月12日
*/
public interface IRequestHandler {
default Object send(String url, HttpMethod method) {
return send(url, method, null, null);
};
default Object send(String url, HttpMethod method, List<BasicNameValuePair> param) {
return send(url, method, param, null);
};
Object send(String url, HttpMethod method, List<BasicNameValuePair> param, List<BasicNameValuePair> headers);
}
IResponseHandler: 抽象处理请求 默认是DefaultResponseHandler
/**
* 返回值处理类
* @Description
* @author smallking
* @Date 2020年3月13日
*/
public interface IResponseHandler{
<T> T handler(Object obj, Class<T> clazz);
}
@PathParam注解 用来标识url上的请求参数
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathParam {
String key() default "";
}
@BodyParam注解 用来标识请求体里面的参数
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BodyParam {
String key();
}
3.动态代理
这里我们是借助InvocationHandler来实现的动态代理。这里我们解析注解,得到url和请求方法,发送请求,处理返回值。
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.http.HttpMethod;
import com.arvato.common.annotation.BodyParam;
import com.arvato.common.annotation.PathParam;
import com.arvato.common.annotation.Request;
import com.arvato.rpc.config.RpcServiceConfig;
import com.arvato.rpc.handler.request.IRequestHandler;
import com.arvato.rpc.handler.response.IResponseHandler;
import com.arvato.wxwork.controller.front.member.WxworkMemberController;
import com.arvato.wxwork.service.member.IMemberInfoService;
import com.arvato.wxwork.service.member.impl.MemberInfoServiceImpl;
/**
* 动态代理统一实现远程调用 可直接使用,参照
*
* @see RPCProxy main方法 可结合spring ioc使用,参照
* @see RpcServiceConfig
* @see WxworkMemberController test方法
*
* @Description
* @author smallking
* @Date 2020年3月13日
*/
public class RPCProxy {
@SuppressWarnings("unchecked")
public static <T> T create(Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 第一步 解析注解,得到请求url 请求方法 等参数
boolean flag = method.isAnnotationPresent(Request.class);
if(flag) {
Request reqAnn = method.getAnnotation(Request.class);
String url = reqAnn.url() + "?";
HttpMethod reqMethod = reqAnn.method();
// 发送请求
Class<? extends IRequestHandler> requestClass = reqAnn.requestHandler();
IRequestHandler reqHandler = (IRequestHandler) requestClass.newInstance();
List<BasicNameValuePair> param = new ArrayList<BasicNameValuePair>();
// 一个方法可以包含多个参数,一个参数可以包含多个注解 所以是[][]二维数组
Annotation[][] paramAnns = method.getParameterAnnotations();
// 遍历参数
for(int i = 0; i < args.length; i ++) {
for(Annotation ann : paramAnns[i]) {
if(ann.annotationType().equals(BodyParam.class)) {
BodyParam bodyParam = (BodyParam) ann;
param.add(new BasicNameValuePair(bodyParam.key(), args[i].toString()));
}
if(ann.annotationType().equals(PathParam.class)) {
PathParam pathParam = (PathParam) ann;
url += pathParam.key() + "=" + args[i] + "&";
}
}
}
if(url.endsWith("&")) {
url = url.substring(0, url.length() - 1);
}
Object result = reqHandler.send(url, reqMethod, param);
// 处理返回值
Class<? extends IResponseHandler> responseClass = reqAnn.responseHandler();
Class<?> retuenType = method.getReturnType();
IResponseHandler respHandler = (IResponseHandler) responseClass.newInstance();
return respHandler.handler(result, retuenType);
}else {
throw new RuntimeException("未检测到 @Request注解");
}
}
});
}
public static void main(String[] args) {
IMemberInfoService create = RPCProxy.create(MemberInfoServiceImpl.class);
System.out.println("返回值:" + create.test("arvato_user", "arvato@123"));
}
}
5.RestTemplateHandler和DefaultResponseHandler
如下是我的发送请求和处理返回值的实现,你们也可以加入自己的实现,只要实现IRequestHandler和IResponseHandler就可以了。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import com.arvato.common.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
/**
* 通过spring的RestTemplate 发送请求
* @Description
* @author smallking
* @Date 2020年3月13日
*/
@Slf4j
public class RestTemplateHandler implements IRequestHandler {
@Override
public Object send(String url, HttpMethod method, List<BasicNameValuePair> param, List<BasicNameValuePair> headers) {
// 构建header
HttpHeaders header = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
header.setContentType(type);
Optional.ofNullable(headers)
.orElseGet(() -> new ArrayList<>())
.stream()
.forEach(o -> {
header.add(o.getName(), o.getValue());
});
// 构建请求体
Map<String, Object> paramMap = new HashMap<String, Object>();
Optional.ofNullable(param)
.orElseGet(() -> new ArrayList<>())
.stream()
.forEach(o -> {
paramMap.put(o.getName(), o.getValue());
});
HttpEntity<String> requestEntity = new HttpEntity<String>(JsonUtil.obj2String(paramMap), header);
RestTemplate template = new RestTemplate();
log.info("请求url:{} 请求method:{} 请求entity:{}", url, method, requestEntity);
ResponseEntity<String> response = template.exchange(url, method, requestEntity, String.class);
return response.getBody();
}
}
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DefaultResponseHandler implements IResponseHandler{
@Override
public <T> T handler(Object obj, Class<T> clazz) {
log.info("开始处理返回值:{}", obj);
// TODO 借助json进行类型转换 这里面的逻辑 完全根据接口返回值 自定义
return clazz.cast(obj);
}
}
6.总结
其实这个可以看作是一个简单的RPC远程调用框架。搭建完成之后,调用者就好像调用自己本地的java方法一样,但是实际却是请求远程的接口。