`
newleague
  • 浏览: 1501429 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类

Spring技术内幕——深入解析Spring架构与设计原理(五)Spring与远端调用

 
阅读更多
在应用开发中,常常涉及服务器系统中各种不同进程之间的通信与计算交互,远端调用(RMI)是实现这种计算场景的一种有效方式。此外,还存在着另一种情况,在这种应用场景中,与那些典型的基于HTML的B/S应用不同,客户端程序需要完成对服务器端应用的直接调用,这也是需要远端调用大显身手的场合。

Spring中提供了轻量级的远端调用模块,从而为我们在上面提到的应用场景开发,提供平台支持。根据Spring的既定策略,它依然只是起到一个集成平台的作用,而并不期望在实现方案上,与已有的远端调用方案形成竞争。也就是说,在Spring远端调用架构中,具体的通信协议设计、通信实现,以及在服务器和客户端对远端调用的处理封装,Spring没有将其作为实现重点,在这个技术点上,并不需要重新发明轮子。对Spring来说,它所要完成的工作,是在已有远端调用技术实现的基础上,通过IoC与AOP的封装,让应用更方便地使用这些远端调用服务,并能够更方便灵活地与现有应用系统实现集成。通过Spring封装以后,应用使用远端过程调用非常方便,既不需要改变原来系统的相关实现接口,也不需要为远端调用功能增加新的封装负担。因此,这种使用方式,在某种程度上,可以称为轻量级的远端调用方案。

在实现远端调用的过程中,往往需要涉及客户端和服务器端的相关设置,这些设置通过Spring的IoC容器就可以很好的完成,这是我们已经很熟悉的IoC容器的强项了。同时,Spring为远端调用的实现,提供了许多不同的方案,玲琅满目,任君选择。如RMI、HTTP调用器、第三方远端调用库Hessian/Burlap、基于Java RMI的解决方案,等等。

Spring对不同的远端调用的实现封装,基本上,都采用了类似的模式来完成,比如在客户端,都是通过相关的ProxyFactoryBean和ClientInterceptor来完成的,在服务器端是通过ServiceExporter来导出远端的服务对象的。有了这些统一的命名规则,应用配置和使用远端调用会非常方便,同时,通过对这些Spring远端调用基础设施实现原理的分析,还可以看到一些常用处理方法的技术实现,比如对代理对象的使用、拦截器的使用、通过afterPropertiesSet来启动远端调用基础设施的建立,等等,这些都是在Spring中常用的技术。

HTTP调用器客户端的实现
在HtttpInvokerProxyFactory中,设置了serviceProxy对象作为远端服务的本地代理对象;同时,在依赖注入完成以后,通过afterPropertiesSet来对远端调用完成设置。
Java代码 复制代码 收藏代码
  1. public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor   
  2.         implements FactoryBean<Object> {   
  3.     //这是远端对象的代理   
  4.     private Object serviceProxy;   
  5.   
  6.     @Override  
  7.     //在注入完成之后,设置远端对象代理   
  8.     public void afterPropertiesSet() {   
  9.         super.afterPropertiesSet();   
  10.         //需要配置远端调用的接口   
  11.         if (getServiceInterface() == null) {   
  12.             throw new IllegalArgumentException("Property 'serviceInterface' is required");   
  13.         }//这里使用ProxyFactory来生成远端代理对象,注意这个this,因为HttpInvokerProxyFactoryBean的基类是HttpInvokerClientInterceptor,所以代理类的拦截器被设置为HttpInvokerClientInterceptor   
  14.         this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());   
  15.     }   
  16.   
  17.     //FactoryBean生产对象的入口。返回的是serviceProxy对象,这是一个代理对象   
  18.     public Object getObject() {   
  19.         return this.serviceProxy;   
  20.     }   
  21.   
  22.     public Class<?> getObjectType() {   
  23.         return getServiceInterface();   
  24.     }   
  25.   
  26.     public boolean isSingleton() {   
  27.         return true;   
  28.     }  
public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor
		implements FactoryBean<Object> {
	//这是远端对象的代理
	private Object serviceProxy;

	@Override
	//在注入完成之后,设置远端对象代理
	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		//需要配置远端调用的接口
		if (getServiceInterface() == null) {
			throw new IllegalArgumentException("Property 'serviceInterface' is required");
		}//这里使用ProxyFactory来生成远端代理对象,注意这个this,因为HttpInvokerProxyFactoryBean的基类是HttpInvokerClientInterceptor,所以代理类的拦截器被设置为HttpInvokerClientInterceptor
		this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
	}

	//FactoryBean生产对象的入口。返回的是serviceProxy对象,这是一个代理对象
	public Object getObject() {
		return this.serviceProxy;
	}

	public Class<?> getObjectType() {
		return getServiceInterface();
	}

	public boolean isSingleton() {
		return true;
	}

可以看到,为这个代理对象配置了一个拦截器HttpInvokerClientInterceptor,在这个拦截器中,拦截了对代理对象的方法调用。如以下代码所示:
Java代码 复制代码 收藏代码
  1. //对代理对象的方法调用入口   
  2. public Object invoke(MethodInvocation methodInvocation) throws Throwable {   
  3.     if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {   
  4.         return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";   
  5.     }   
  6.     //创建RemoteInvocation对象,这个对象封装了对远端的调用,这些远端调用通过序列化的机制完成   
  7.     RemoteInvocation invocation = createRemoteInvocation(methodInvocation);   
  8.     RemoteInvocationResult result = null;   
  9.     try {   
  10.         //这里是对远端调用的入口   
  11.         result = executeRequest(invocation, methodInvocation);   
  12.     }   
  13.     catch (Throwable ex) {   
  14.         throw convertHttpInvokerAccessException(ex);   
  15.     }   
  16.     try {//返回远端调用的结果   
  17.         return recreateRemoteInvocationResult(result);   
  18.     }   
  19.     catch (Throwable ex) {   
  20.         if (result.hasInvocationTargetException()) {   
  21.             throw ex;   
  22.         }   
  23.         else {   
  24.             throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() +   
  25.                     "] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex);   
  26.         }   
  27.     }   
  28. }  
	//对代理对象的方法调用入口
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
			return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";
		}
		//创建RemoteInvocation对象,这个对象封装了对远端的调用,这些远端调用通过序列化的机制完成
		RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
		RemoteInvocationResult result = null;
		try {
			//这里是对远端调用的入口
			result = executeRequest(invocation, methodInvocation);
		}
		catch (Throwable ex) {
			throw convertHttpInvokerAccessException(ex);
		}
		try {//返回远端调用的结果
			return recreateRemoteInvocationResult(result);
		}
		catch (Throwable ex) {
			if (result.hasInvocationTargetException()) {
				throw ex;
			}
			else {
				throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() +
						"] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
			}
		}
	}

远端调用的具体实现过程,是由executeRequest来完成的,也就是在SimpleHttpInvokerRequestExecutor的实现中,封装了整个HTTP调用器客户端实现的基本过程,如下所示:
Java代码 复制代码 收藏代码
  1. //这是HTTP调用器实现的基本过程,通过HTTP的request和reponse来完成通信,在通信的过程中传输的数据是序列化的对象   
  2. protected RemoteInvocationResult doExecuteRequest(   
  3.         HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)   
  4.         throws IOException, ClassNotFoundException {   
  5.     //打开一个标准J2SE HttpURLConnection   
  6.     HttpURLConnection con = openConnection(config);   
  7.     prepareConnection(con, baos.size());   
  8.     //远端调用封装成RemoteInvocation对象,这个对象通过序列化被写到对应的HttpURLConnection中去   
  9.     writeRequestBody(config, con, baos);   
  10.     //这里取得远端服务返回的结果,然后把结果转换成RemoteInvocationResult返回   
  11.     validateResponse(config, con);   
  12.     InputStream responseBody = readResponseBody(config, con);   
  13.   
  14.     return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());   
  15. }   
  16.   
  17. //把序列化对象输出到HttpURLConnection去   
  18. protected void writeRequestBody(   
  19.         HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos)   
  20.         throws IOException {   
  21.   
  22.     baos.writeTo(con.getOutputStream());   
  23. }   
  24.   
  25. //为使用HttpURLConnection完成对象序列化,需要进行一系列的配置   
  26. //比如配置请求方式为post,请求属性等等   
  27. protected void prepareConnection(HttpURLConnection con, int contentLength) throws IOException {   
  28.     con.setDoOutput(true);   
  29.     con.setRequestMethod(HTTP_METHOD_POST);   
  30.     con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType());   
  31.     con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength));   
  32.     LocaleContext locale = LocaleContextHolder.getLocaleContext();   
  33.     if (locale != null) {   
  34.         con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));   
  35.     }   
  36.     if (isAcceptGzipEncoding()) {   
  37.         con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);   
  38.     }   
  39. }   
  40. //获得HTTP响应的IO流   
  41. protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con)   
  42.         throws IOException {   
  43.     //如果是通过gzip压缩,那么需要先解压   
  44.     if (isGzipResponse(con)) {   
  45.         // GZIP response found - need to unzip.   
  46.         return new GZIPInputStream(con.getInputStream());   
  47.     }   
  48.     else {   
  49.         // Plain response found.   
  50.         // 正常的HTTP响应输出   
  51.         return con.getInputStream();   
  52.     }   
  53. }  
	//这是HTTP调用器实现的基本过程,通过HTTP的request和reponse来完成通信,在通信的过程中传输的数据是序列化的对象
	protected RemoteInvocationResult doExecuteRequest(
			HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
			throws IOException, ClassNotFoundException {
		//打开一个标准J2SE HttpURLConnection
		HttpURLConnection con = openConnection(config);
		prepareConnection(con, baos.size());
		//远端调用封装成RemoteInvocation对象,这个对象通过序列化被写到对应的HttpURLConnection中去
		writeRequestBody(config, con, baos);
		//这里取得远端服务返回的结果,然后把结果转换成RemoteInvocationResult返回
		validateResponse(config, con);
		InputStream responseBody = readResponseBody(config, con);

		return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
	}

	//把序列化对象输出到HttpURLConnection去
	protected void writeRequestBody(
			HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos)
			throws IOException {

		baos.writeTo(con.getOutputStream());
	}
	
	//为使用HttpURLConnection完成对象序列化,需要进行一系列的配置
	//比如配置请求方式为post,请求属性等等
	protected void prepareConnection(HttpURLConnection con, int contentLength) throws IOException {
		con.setDoOutput(true);
		con.setRequestMethod(HTTP_METHOD_POST);
		con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType());
		con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength));
		LocaleContext locale = LocaleContextHolder.getLocaleContext();
		if (locale != null) {
			con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));
		}
		if (isAcceptGzipEncoding()) {
			con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
		}
	}
	//获得HTTP响应的IO流
	protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con)
			throws IOException {
		//如果是通过gzip压缩,那么需要先解压
		if (isGzipResponse(con)) {
			// GZIP response found - need to unzip.
			return new GZIPInputStream(con.getInputStream());
		}
		else {
			// Plain response found.
			// 正常的HTTP响应输出
			return con.getInputStream();
		}
	}


HTTP调用器服务器端的实现

在服务器端使用Spring HTTP远端调用,需要配置HttpInvokerServiceExporter,作为远端服务的服务导出器,来接收HTTP服务请求。在通过HTTP请求,得到客户端传过来的RemoteInvocation对象以后,就可以进行服务方法的调用了。服务调用需要的基本信息,都封装在RemoteInvocation对象中。这个服务调用过程,是由invokeAndCreateResult方法来实现的,如RemoteInvocationSerializingExporter的invoke实现所示:
Java代码 复制代码 收藏代码
  1. protected Object invoke(RemoteInvocation invocation, Object targetObject)   
  2.         throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {   
  3.   
  4.     if (logger.isTraceEnabled()) {   
  5.         logger.trace("Executing " + invocation);   
  6.     }   
  7.     try {//调用RemoteInvocationExecutor,这个执行器是DefaultRemoteInvocationExecutor   
  8.         return getRemoteInvocationExecutor().invoke(invocation, targetObject);   
  9.     }   
  10.     catch (NoSuchMethodException ex) {   
  11.         if (logger.isDebugEnabled()) {   
  12.             logger.warn("Could not find target method for " + invocation, ex);   
  13.         }   
  14.         throw ex;   
  15.     }   
  16.     catch (IllegalAccessException ex) {   
  17.         if (logger.isDebugEnabled()) {   
  18.             logger.warn("Could not access target method for " + invocation, ex);   
  19.         }   
  20.         throw ex;   
  21.     }   
  22.     catch (InvocationTargetException ex) {   
  23.         if (logger.isDebugEnabled()) {   
  24.             logger.debug("Target method failed for " + invocation, ex.getTargetException());   
  25.         }   
  26.         throw ex;   
  27.     }   
  28. }  
	protected Object invoke(RemoteInvocation invocation, Object targetObject)
			throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

		if (logger.isTraceEnabled()) {
			logger.trace("Executing " + invocation);
		}
		try {//调用RemoteInvocationExecutor,这个执行器是DefaultRemoteInvocationExecutor
			return getRemoteInvocationExecutor().invoke(invocation, targetObject);
		}
		catch (NoSuchMethodException ex) {
			if (logger.isDebugEnabled()) {
				logger.warn("Could not find target method for " + invocation, ex);
			}
			throw ex;
		}
		catch (IllegalAccessException ex) {
			if (logger.isDebugEnabled()) {
				logger.warn("Could not access target method for " + invocation, ex);
			}
			throw ex;
		}
		catch (InvocationTargetException ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Target method failed for " + invocation, ex.getTargetException());
			}
			throw ex;
		}
	}

看到的invoke方法封装了服务器端调用的主体,这个invoke方法在HttpInvokerServiceExporter的基类RemoteInvocationSerializingExporter中实现,服务对象的方法调用完成之后,会把调用结果,通过HTTP响应和对象序列化,传给HTTP调用器客户端,从而完成整个HTTP调用器的远端调用过程,如以下代码所示:
Java代码 复制代码 收藏代码
  1. protected void writeRemoteInvocationResult(   
  2.         HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result)   
  3.         throws IOException {   
  4. //设置Response的ContentType属性,设置为application/x-java-serialized-object   
  5.     response.setContentType(getContentType());   
  6.     writeRemoteInvocationResult(request, response, result, response.getOutputStream());   
  7. }   
  8. //输出到HTTP的Response,然后把Response关闭   
  9. protected void writeRemoteInvocationResult(   
  10.         HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os)   
  11.         throws IOException {   
  12.   
  13.     ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os));   
  14.     try {   
  15.         doWriteRemoteInvocationResult(result, oos);   
  16.         oos.flush();   
  17.     }   
  18.     finally {   
  19.         oos.close();   
  20.     }   
  21. }  
	protected void writeRemoteInvocationResult(
			HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result)
			throws IOException {
	//设置Response的ContentType属性,设置为application/x-java-serialized-object
		response.setContentType(getContentType());
		writeRemoteInvocationResult(request, response, result, response.getOutputStream());
	}
	//输出到HTTP的Response,然后把Response关闭
	protected void writeRemoteInvocationResult(
			HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os)
			throws IOException {

		ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os));
		try {
			doWriteRemoteInvocationResult(result, oos);
			oos.flush();
		}
		finally {
			oos.close();
		}
	}

经过这一系列的处理过程,服务执行结果对象又回到了HTTP的远端调用客户端。在客户端从HTTP响应读取对象之后,它把这个看起来像是在本地实现,其实是由远端服务对象完成的调用结果,交给发起远端调用的客户端调用方法,从而最终完成整个远端调用的过程。这个过程很有特点,它使用了HTTP的请求和响应作为通信通道,在这个通信通道里面,并没有再做进一步的附加的通信协议的封装,而且,在这个处理过程中,使用的都是Java和Spring框架已有的特性,比如,通过IoC的配置,以及代理对象拦截器的封装处理,再加Java的序列化和反序列化,以及在服务器端的Spring MVC框架的使用,通过这些已有的技术实现,让使用者感觉,它的实现风格非常的简洁轻快,整个代码实现,阅读起来,也让人感到非常的赏心悦目。
分享到:
评论

相关推荐

    Spring技术内幕:深入解析Spring架构与设计原理.pdf

    Spring技术内幕:深入解析Spring架构与设计原理 Spring技术内幕 Spring是一个基于Java的开源框架,旨在简化Java企业应用的开发。Spring的目标是提供一个简洁、灵活、可扩展的框架,以帮助开发者快速构建企业级...

    Struts2+技术内幕——深入解析Struts2架构设计与实现原理

    本书《Struts2技术内幕——深入解析Struts2架构设计与实现原理》结合提供的《struts2基础.chm》资料,为我们提供了深入理解Struts2内部机制的机会。 首先,Struts2的核心在于它的拦截器(Interceptor)机制。拦截器...

    Spring技术内幕:深入解析Spring架构与设计原理(第1部分)

    Spring技术内幕 深入解析Spring架构与设计原理1(完整清晰版),一共两部分,这是第一部分 《Spring技术内幕:深入解析Spring架构与设计原理》是Spring领域的问鼎之作,由业界拥有10余年开发经验的资深Java专家亲自...

    Spring技术内幕:深入解析Spring架构与设计原理(第2部分)

    Spring技术内幕:深入解析Spring架构与设计原理(第2部分) 《Spring技术内幕:深入解析Spring架构与设计原理》是Spring领域的问鼎之作,由业界拥有10余年开发经验的资深Java专家亲自执笔!Java开发者社区和Spring...

    Spring技术内幕:深入解析Spring架构与设计原理(第一部分)

    Spring技术内幕 深入解析Spring架构与设计原理1(完整清晰版),一共两部分,这是第一部分 《Spring技术内幕:深入解析Spring架构与设计原理》是Spring领域的问鼎之作,由业界拥有10余年开发经验的资深Java专家亲自...

    Struts2 技术内幕——深入解析Struts2架构设计与实现原理.zip

    这本书《Struts2技术内幕——深入解析Struts2架构设计与实现原理》深入探讨了Struts2的核心机制和设计理念,旨在帮助开发者更好地理解和运用这个框架。 首先,Struts2的出现是为了解决Struts1在MVC模式中的局限性,...

    Spring技术内幕-深入解析_Spring架构与设计原理

    《Spring技术内幕-深入解析_Spring架构与设计原理》是一本深入探讨Spring框架核心机制与设计理念的专业书籍,由资深软件开发专家计文柯撰写。本书不仅适合Java开发者、Spring框架使用者,也对平台开发人员、系统架构...

    Spring技术内幕 - 深入解析Spring架构与设计原理.rar

    《Spring技术内幕 - 深入解析Spring架构与设计原理》这本书是IT领域的经典之作,专为想要深入了解Spring框架的开发者而准备。Spring作为Java领域最广泛应用的轻量级框架,其设计理念和实现机制一直是广大程序员关注...

    Spring技术内幕:深入解析Spring架构与设计原理

    下载频道&gt;资源分类&gt;开发技术&gt;Java&gt;Spring技术内幕:深入解析Spring架构与设计原理 1/2 Spring技术内幕:深入解析Spring架构与设计原理 1/2资源大小:59MB 上传日期:2011-11-15 资源积分:5分 下载次数:30 上 传 者...

    Struts2 技术内幕——深入解析Struts2架构设计.pdf.zip

    总的来说,"Struts2 技术内幕——深入解析Struts2架构设计"这本书将带你深入理解Struts2的工作原理,包括其架构设计、核心组件、配置方式、数据绑定、异常处理以及安全性等方面。通过对这些知识点的掌握,开发者能够...

    Spring技术内幕:深入解析Spring架构与设计原理 1/2

     深入解析spring架构原理与设计思想,探究spring成功的奥秘。  揭开spring源代码的神秘面纱,展示系统阅读开源软件源代码的方法和秘诀。  掌握spring的架构原理与设计思想真的能让开发者如虎添翼吗?  ioc容器...

    Spring技术内幕:深入解析Spring架构与设计原理 2/2

     深入解析spring架构原理与设计思想,探究spring成功的奥秘。  揭开spring源代码的神秘面纱,展示系统阅读开源软件源代码的方法和秘诀。  掌握spring的架构原理与设计思想真的能让开发者如虎添翼吗?  ioc...

    Spring技术内幕:深入解析Spring架构与设计原理(第2版)

    ### Spring技术内幕:深入解析Spring架构与设计原理(第2版) #### 一、Spring框架简介 Spring框架是由Rod Johnson创建的一个开源项目,它最初是为了简化企业级Java应用程序的开发而设计的。随着版本的不断迭代,...

    Spring技术内幕:深入解析Spring架构与设计原理(第2版)

    《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》是一本深入探讨Spring框架核心技术的书籍,由计文柯撰写。这本书详细解析了Spring的设计原理和架构细节,是广大Spring开发者和技术人员不可或缺的学习...

    Spring技术内幕:深入解析Spring架构与设计原理(第2版) 高清

    ### Spring技术内幕:深入解析Spring架构与设计原理 #### Spring框架概述 Spring是一个开源的Java应用框架,由Rod Johnson创建并领导开发。Spring的核心特性包括依赖注入(Dependency Injection, DI)、面向切面...

    SPRING3技术内幕

    Spring技术内幕 深入解析Spring架构与设计原理1(完整清晰版),一共两部分,这是第一部分 《Spring技术内幕:深入解析Spring架构与设计原理》是Spring领域的问鼎之作,由业界拥有10余年开发经验的资深Java专家亲自...

Global site tag (gtag.js) - Google Analytics