hncdyj的gravatar头像
hncdyj2016-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下载
最代码最近下载分享源代码列表最近下载
vsalw53202016年12月23日
最代码贡献等级说明
最代码官方 LV42016年12月22日
最代码贡献等级说明
最代码最近浏览分享源代码列表最近浏览
970440642月15日
最代码贡献等级说明
lv冻结2月6日
最代码贡献等级说明
lcming05012月6日
暂无贡献等级
4425294742月4日
最代码贡献等级说明
唐柏华1月25日
暂无贡献等级
zj47751月23日
最代码贡献等级说明
ponygao1月20日
暂无贡献等级
zl09191月11日
最代码贡献等级说明
7914401421月10日
最代码贡献等级说明
china04021月8日
最代码贡献等级说明
顶部客服微信二维码底部
>扫描二维码关注最代码为好友扫描二维码关注最代码为好友