动态代理+注解实现HTTP接口RPC调用

项目里面经常会调用一些三方系统的接口,相信大多数的解决方法就是百度一个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方法一样,但是实际却是请求远程的接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值