hncdyj的gravatar头像
hncdyj 2016-12-22 09:15:12

apache hessian原理学习和自己实现rpc远程调用实例代码

因为最近有点时间,所以就想读一些技术的源码,最近选择hessian。废话不多说,进入正题:

apache hessian原理学习和自己实现rpc远程调用实例代码

apache hessian原理学习和自己实现rpc远程调用实例代码

hessian客户端时序图:

apache hessian原理学习和自己实现rpc远程调用实例代码hessian服务端时序图:

apache hessian原理学习和自己实现rpc远程调用实例代码

因为hessian源码相对于spring算很简单了。所以不浪费大家宝贵的时间来分析源码。想说说怎么实现远程调用(rpc)。

远程调用,顾名思义就是通过远程调用别的机器(JVM or ...)中的方法。要想实现远程调用,思路是怎样呢?

常用的模式代理,工厂。为啥怎么说呢?

远程调用通过网络,肯定需要工厂产出不同的类,不然一个类写,会死人的。

代理模式,因为调用前你是不知道实际的调用,肯定是通过代理调用到远程方法。

下面给大家说说远程调用的原理(PS:不懂代理的同学,先上百度搜索:java代理)。

实现自定义的远程调用,就是序列化成字节流后,发送到服务端,服务端根据调用的api、方法、参数去具体调用一个方法,调用方法后把结果输入到输出流里面,最后结果展示在代理的return中。

ok,开始贴代码:

客户端(HttpRpcClient):

String url = "http://localhost:8080/hessian-study-server/helloworld1";
		HttpProxyFactory factory = new HttpProxyFactory();
		Class<?> clazz = Class.forName("com.jzx.hessian.server.SayHelloService");
		SayHelloService sayHelloService = (SayHelloService) factory.create(clazz, url);
		System.out.println(sayHelloService.sayHello("小明", "男"));
		UserVo vo = sayHelloService.sayHello1("小明", "男");
		System.out.println("姓名:" + vo.getName() + " 性别:" + vo.getSex());

从代码中分析,首先创建了一个工厂类HttpProxyFactory,其次创建了一个类的描述。

HttpProxyFactory代码(主要目的是得到一个代理):

if (api == null) {
			throw new NullPointerException("api must not be null for HttpProxyFactory.create()");
		}
		InvocationHandler handler = null;

		handler = new HttpProxy(url, this, api);

		return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { api }, handler);

HttpProxy代理干了啥 (得到调用接口类、方法名、参数值):

HttpTransportation transportation = new HttpTransportation();
		transportation.setClazz(api);
		transportation.setMethod(method.getName());
		transportation.setParams(args);
		byte[] bytes = ObjectUtils.objectToByte(transportation);
		Object result = sendPost(url, bytes);
		return result;

序列化对象发送POST请求:

PrintWriter out = null;
		BufferedReader in = null;
		Object result = null;
		try {
			URL realUrl = new URL(url);
			// 打开和URL之间的连接
			URLConnection conn = realUrl.openConnection();
			// 设置通用的请求属性
			conn.setRequestProperty("accept", "*/*");
			conn.setRequestProperty("connection", "Keep-Alive");
			conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
			// 发送POST请求必须设置如下两行
			conn.setDoOutput(true);
			conn.setDoInput(true);
			// 获取URLConnection对象对应的输出流
			OutputStream outputStream = conn.getOutputStream();
			outputStream.write(param);
			outputStream.close();
			// 定义BufferedReader输入流来读取URL的响应
			// in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			byte[] bytes = toByteArray(conn.getInputStream());
			Object object = ObjectUtils.byteToObject(bytes);
			if (object instanceof String) {
				byte[] dest = new byte[bytes.length - 7];
				System.arraycopy(bytes, 7, dest, 0, dest.length);
				result = new String(dest, "UTF-8");
//				result = new String(bytes, "UTF-8");
			}
			if (object instanceof UserVo) {
				result = object;
			}
		} catch (Exception e) {
			System.out.println("发送 POST 请求出现异常!" + e);
			e.printStackTrace();
		}
		// 使用finally块来关闭输出流、输入流
		finally {
			try {
				if (out != null) {
					out.close();
				}
				if (in != null) {
					in.close();
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		return result;

以上是客户端的分割线------------------------服务端:

配置一个sevlet,初始化配置文件:

@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		if (getInitParameter("home-api") != null) {
			String className = getInitParameter("home-api");
			try {
				_homeAPI = Class.forName(className);
			} catch (Exception e) {

			}
		}
		if (getInitParameter("service-class") != null) {
			String className = getInitParameter("service-class");
			Class<?> clazz;
			try {
				clazz = Class.forName(className);
				_homeImpl = clazz.newInstance();
			} catch (Exception e) {

			}
		}
		httpRpcSkeleton = new HttpRpcSkeleton(_homeAPI, _homeImpl);
	}
@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;

		if (!req.getMethod().equals("POST")) {
			res.setStatus(500); // , "Hessian Requires POST");
			PrintWriter out = res.getWriter();

			res.setContentType("text/html");
			out.println("<h1>httrpc Requires POST</h1>");
			return;
		}
		OutputStream out = res.getOutputStream();
		try {
			httpRpcSkeleton.invoke(req, out);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			out.flush();
			out.close();
		}
	}

看到请求最终被HttpRpcSkeleton的invoke调用,相关代码:

public void invoke(HttpServletRequest request, OutputStream out) throws Exception {
		int len = request.getContentLength();
		ServletInputStream input = request.getInputStream();
		byte[] buffer = new byte[len];
		input.read(buffer, 0, len);
		HttpTransportation transportation = (HttpTransportation) ObjectUtils.byteToObject(buffer);
		Method method = methodMap.get(transportation.getMethod());
		Object result = method.invoke(_homeImpl, transportation.getParams());
		out.write(ObjectUtils.objectToByte(result));
		// out.write(result.getBytes());
	}

以上就是实现远程,调用的原理。

另外对项目的说明,有兴趣的童鞋,可以试下不用java本身的序列化,试试protobuf。

另外有一个疑问困扰了一个晚上,当返回为字符串的时候,ObjectInputStream会多7个字节,不解,本人的java io很low,求解。

源码为maven结构:

apache hessian原理学习和自己实现rpc远程调用实例代码

运行时截图:

apache hessian原理学习和自己实现rpc远程调用实例代码


打赏

文件名:hessian-study.zip,文件大小:607.604K 下载
最代码最近下载分享源代码列表最近下载
chengqiang  LV13 2019年10月11日
nima74  LV11 2019年9月23日
loverzhao  LV14 2019年2月13日
@MR_K  LV11 2018年3月26日
向死而生  LV2 2017年3月3日
vsalw5320  LV12 2016年12月23日
最代码官方  LV168 2016年12月22日
最代码最近浏览分享源代码列表最近浏览
17842711  LV1 2022年3月27日
newhaijun  LV15 2021年6月5日
a3870764722a  LV22 2020年4月13日
kevinkg  LV12 2020年4月3日
chengqiang  LV13 2019年10月11日
nima74  LV11 2019年9月20日
Riku2018  LV2 2019年7月29日
free sadn  LV11 2019年3月24日
时空12580  LV13 2019年2月27日
loverzhao  LV14 2019年2月13日
顶部 客服 微信二维码 底部
>扫描二维码关注最代码为好友扫描二维码关注最代码为好友