《Spring技术内幕》学习笔记17——Spring HTTP调用器实现远程调用

1.Spring中,HTTPInvoker(HTTP调用器)是通过基于HTTP协议的分布式远程调用解决方案,和java RMI一样,HTTP调用器也需要使用java的对象序列化机制完成客户端和服务器端的通信。HTTP调用器的远程调用工作原理如下:

(1).客户端:

a.向服务器发送远程调用请求:

远程调用信息——>封装为远程调用对象——>序列化写入到远程调用HTTP请求中——>向服务器端发送。

b.接收服务器端返回的远程调用结果:

服务器端返回的远程调用结果HTTP响应——>反序列化为远程调用结果对象。

(2).服务器端:

a.接收客户端发送的远程调用请求:

客户端发送的远程调用HTTP请求——>反序列化为远程调用对象——>调用服务器端目标对象的目标方法处理。

b.向客户端返回远程调用结果:

服务器端目标对象方法的处理结果——>序列化写入远程调用结果HTTP响应中——>返回给客户端。

接下来我们将从客户端和服务器端分别分析HTTP调用器远程调用的具体实现。

2.HTTP调用器客户端配置:

使用HTTP调用器之前,首先需要对客户端其进行如下的配置:

<!--客户端HTTP调用器代理--> <bean id=”proxy” class=”org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean”> <property name=”serviceUrl”> <value>http://yourhost:8080/远程调用URL</value> </property> <property name=”serviceInterface”> <value>远程调用服务接口全路径</value> </property> </bean> <bean id=”客户端bean” class=”客户端Bean全路径”> <property name=”remoteService”> <ref bean=”proxy”/> </property> </bean>

HTTP调用器客户端代理HttpInvokerProxyFactoryBean中封装远程调用服务URL和服务接口,客户端程序通过HTTP调用代理可以调用实现了指定接口的目标服务端对象。

3.HttpInvokerProxyFactoryBean创建远程调用代理对象:

HTTP调用器客户端代理HttpInvokerProxyFactoryBean是一个实现了Spring FactoryBean接口的IoC容器,其作用是对远程服务客户端封装,源码如下:

public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor implements FactoryBean<Object> { //远程对象的代理 private Object serviceProxy; //在IoC容器注入完成之后调用 public void afterPropertiesSet() { //调用父类容器的回调方法 super.afterPropertiesSet(); //getServiceInterface()方法用于获取配置的远程调用接口 if (getServiceInterface() == null) { throw new IllegalArgumentException("Property 'serviceInterface' is required"); } //使用ProxyFactory代理工厂生成远程代理对象,注意第二个参数this,因为 //HttpInvokerProxyFactoryBean继承了HttpInvokerClientInterceptor, //所以代理对象的拦截器设置为HttpInvokerClientInterceptor this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader()); } //向IoC容器索取被管理对象的方法,获取产生的远程调用代理对象 public Object getObject() { return this.serviceProxy; } //获取对象类型,返回配置的远程调用接口 public Class<?> getObjectType() { return getServiceInterface(); } //是否是单态类型,默认Spring IoC容器产生的都是单态类型 public boolean isSingleton() { return true; } }

通过上面对HttpInvokerProxyFactoryBean源码的分析我们看到,当通过getObject方法向Spring IoC容器索取远程调用对象时,触发afterPropertiesSet回调方法,创建远程调用的代理对象,最后将该远程调用代理对象返回。在创建远程调用代理对象时,使用其父类HttpInvokerClientInterceptor作为远程调用代理对象的拦截器,该拦截器将拦截对代理对象的方法调用。下面我们分析HttpInvokerClientInterceptor代理拦截器对代理对象的方法拦截处理。

4.HttpInvokerClientInterceptor拦截对远程调用代理的方法调用:

当客户端通过HTTP请求调用远程调用代理的方法时,将会触发HttpInvokerClientInterceptor拦截器的invoke方法对当前的请求进行封装处理,将客户端的java对象序列化传输到服务器端,在远程服务器端执行完请求之后,又将处理结果java对象序列化返回给客户端。其源码如下:

public class HttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor implements MethodInterceptor, HttpInvokerClientConfiguration { private String codebaseUrl; //HTTP调用请求执行器 private HttpInvokerRequestExecutor httpInvokerRequestExecutor; public void setCodebaseUrl(String codebaseUrl) { this.codebaseUrl = codebaseUrl; } public String getCodebaseUrl() { return this.codebaseUrl; } public void setHttpInvokerRequestExecutor(HttpInvokerRequestExecutor httpInvokerRequestExecutor) { this.httpInvokerRequestExecutor = httpInvokerRequestExecutor; } //获取HTTP调用请求执行器,如果HTTP调用请求执行器没有设置,则使用 //SimpleHttpInvokerRequestExecutor作为HTTP调用请求执行器 public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() { if (this.httpInvokerRequestExecutor == null) { SimpleHttpInvokerRequestExecutor executor = new SimpleHttpInvokerRequestExecutor(); executor.setBeanClassLoader(getBeanClassLoader()); this.httpInvokerRequestExecutor = executor; } return this.httpInvokerRequestExecutor; } //IoC容器初始化完成回调方法 public void afterPropertiesSet() { //调用父类的初始化回调方法 super.afterPropertiesSet(); //获取HTTP调用请求执行器 getHttpInvokerRequestExecutor(); } //拦截器代理对象方法调用入口,拦截器将客户端对远程调用代理的调用封装为 //MethodInvocation对象。 public Object invoke(MethodInvocation methodInvocation) throws Throwable { if (AopUtils.isToStringMethod(methodInvocation.getMethod())) { return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]"; } //创建远程调用对象,封装了远程调用 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); } } } //执行远程调用入口 protected RemoteInvocationResult executeRequest( RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception { return executeRequest(invocation); } //通过HTTP调用请求执行器执行远程调用 protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception { return getHttpInvokerRequestExecutor().executeRequest(this, invocation); } //将远程调用异常转换成Spring异常 protected RemoteAccessException convertHttpInvokerAccessException(Throwable ex) { if (ex instanceof ConnectException) { throw new RemoteConnectFailureException( "Could not connect to HTTP invoker remote service at [" + getServiceUrl() + "]", ex); } else if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError || ex instanceof InvalidClassException) { throw new RemoteAccessException( "Could not deserialize result from HTTP invoker remote service [" + getServiceUrl() + "]", ex); } else { throw new RemoteAccessException( "Could not access HTTP invoker remote service at [" + getServiceUrl() + "]", ex); } } }

通过上面对HttpInvokerClientInterceptor拦截器的源码分析,我们可以看出,拦截器将客户端对远程调用的HTTP请求封装成了MethodInvocation对象,拦截器的在调用远程调用的代理对象时,又将方法调用封装成了RemoteInvocation远程调用,RemoteInvocation数据对象中封装了调用的具体信息,如方法名、方法参数以及参数类型等。

真正执行远程调用的是HTTP调用请求执行器SimpleHttpInvokerRequestExecutor,下面我们继续分析SimpleHttpInvokerRequestExecutor远程调用的具体过程。

5.SimpleHttpInvokerRequestExecutor远程调用:

SimpleHttpInvokerRequestExecutor封装了基于HTTP协议的远程调用过程,具体源码如下:

public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor { //HTTP调用请求执行器真正进行远程调用的方法,该方法有其父类//AbstractHttpInvokerRequestExecutor的executeRequest方法调用 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); //获取远程调用的结果,校验返回的结果 validateResponse(config, con); InputStream responseBody = readResponseBody(config, con); //将远程调用结果转换成RemoteInvocationResult返回 return readRemoteInvocationResult(responseBody, config.getCodebaseUrl()); } //打开一个HttpURLConnection protected HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException { //getServiceUrl()方法获取配置的远程调用URL,打开一个URL连接 URLConnection con = new URL(config.getServiceUrl()).openConnection(); if (!(con instanceof HttpURLConnection)) { throw new IOException("Service URL [" + config.getServiceUrl() + "] is not an HTTP URL"); } return (HttpURLConnection) con; } //准备HTTP请求连接 protected void prepareConnection(HttpURLConnection con, int contentLength) throws IOException { con.setDoOutput(true); //HTTP调用器只支持POST请求方法 con.setRequestMethod(HTTP_METHOD_POST); //设置HTTP请求头内容类型,设置为:application/x-java-serialized-object con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType()); //设置HTTP请求头内容长度 con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength)); LocaleContext locale = LocaleContextHolder.getLocaleContext(); //设置HTTP请求的Locale if (locale != null) { con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale())); } //设置HTTP请求压缩方式 if (isAcceptGzipEncoding()) { con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP); } } //把序列化对象输出到HTTP请求体中 protected void writeRequestBody( HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos) throws IOException { baos.writeTo(con.getOutputStream()); } //校验远程调用的HTTP响应 protected void validateResponse(HttpInvokerClientConfiguration config, HttpURLConnection con) throws IOException { //如果HTTP响应状态码大于等于300,则证明调用发生错误 if (con.getResponseCode() >= 300) { throw new IOException( "Did not receive successful HTTP response: status code = " + con.getResponseCode() + ", status message = [" + con.getResponseMessage() + "]"); } } //提取远程调用结果的HTTP响应信息 protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con) throws IOException { //如果响应信息是Gzip压缩的,则需要先解压 if (isGzipResponse(con)) { return new GZIPInputStream(con.getInputStream()); } //正常的HTTP响应 else { return con.getInputStream(); } } //是否是Gzip格式压缩 protected boolean isGzipResponse(HttpURLConnection con) { //获取HTTP响应头信息中的压缩方式 String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING); return (encodingHeader != null && encodingHeader.toLowerCase().indexOf(ENCODING_GZIP) != -1); } }

通过对SimpleHttpInvokerRequestExecutor的分析,我们看到,HTTP调用请求执行器的处理逻辑是:首先,打开指定URLHTTP连接,设置连接属性。其次,将封装请求的RemoteInvocation对象序列化到请求体中,请HTTP请求发送到服务器端。最后,从服务器端的HTTP响应中读取输入流,并将响应结果转换成RemoteInvocationResult

将远程调用的HTTP响应转换为RemoteInvocationResult是由AbstractHttpInvokerRequestExecutorreadRemoteInvocationResult方法实现,下面我们将分析其将HTTP响应结果转换成RemoteInvocationResult的实现。

6.AbstractHttpInvokerRequestExecutor其将HTTP响应结果转换成RemoteInvocationResult

AbstractHttpInvokerRequestExecutor中处理远程调用结果,并HTTP响应转换为RemoteInvocationResult的主要方法如下:

//从HTTP响应中读取远程调用结果入口方法 protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl) throws IOException, ClassNotFoundException { //根据给定的输入流和类创建对象输入流 ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl); try { //从对象输入流中读取远程调用结果 return doReadRemoteInvocationResult(ois); } finally { ois.close(); } } //从对象输入流中读取远程调用结果 protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois) throws IOException, ClassNotFoundException { //获取对象输入流中的对象 Object obj = ois.readObject(); if (!(obj instanceof RemoteInvocationResult)) { throw new RemoteException("Deserialized object needs to be assignable to type [" + RemoteInvocationResult.class.getName() + "]: " + obj); } //将获取到的对象封装为RemoteInvocationResult return (RemoteInvocationResult) obj; }

7.HTTP调用器的服务器端配置:

HTTP调用器客户端类似,服务器端也需要进行如下的配置:

<bean name=”/客户端配置的远程调用URL” class=”org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter”> <property name=”service”> <ref bean=”服务器端实现bean”/> </property> <property name=”serviceInterface”> <value>远程调用服务接口全路径</value> </property> </property>

通过对服务器端配置的例子,我们可以看出,真正处理远程调用的服务器端实现是由service属性中指定的服务器端bean提供的,HttpInvokerServiceExporter将远程调用服务接口和服务实现类进行封装,主要提HTTP协议封装和java对象序列化功能。

SpringHttpInvokerServiceExporter是与SpringMVC结合在一起的,它本质上是Spring MVC的一个Controller,客户端发来的远程调用HTTP请求有Spring MVC的中央控制器DispatcherServlet转发到指定URLHttpInvokerServiceExporter上。

8.HttpInvokerServiceExporter导出和执行远程调用服务:

HttpInvokerServiceExporter响应客户端发送的远程调用HTTP请求,它从HTTP请求中读取远程调用并将其反序列化为RemoteInvocation对象,然后调用目标服务对象的目标方法完成远程调用服务,当服务执行完成之后,通过HTTP响应把执行结果对象序列化输出到客户端。器源码如下:

public class HttpInvokerServiceExporter extends RemoteInvocationSerializingExporter implements HttpRequestHandler { //处理客户端发来的远程调用HTTP请求 public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { //从HTTP请求中反序列化出RemoteInvocation远程调用对象 RemoteInvocation invocation = readRemoteInvocation(request); //调用目标服务对象,完成远程调用请求,并创建调用结果 RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy()); //将调用结果写到HTTP响应中 writeRemoteInvocationResult(request, response, result); } catch (ClassNotFoundException ex) { throw new NestedServletException("Class not found during deserialization", ex); } } //从HTTP请求中读取RemoteInvocation远程调用对象入口方法 protected RemoteInvocation readRemoteInvocation(HttpServletRequest request) throws IOException, ClassNotFoundException { //将从HTTP请求中读取远程调用对象 return readRemoteInvocation(request, request.getInputStream()); } //从HTTP请求中读取远程调用对象 protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is) throws IOException, ClassNotFoundException { //根据HTTP请求输入流创建对象输入流 ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is)); try { //从对象输入流中读取远程调用对象 return doReadRemoteInvocation(ois); } finally { ois.close(); } } //获取HTTP请求输入流 protected InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException { return is; } //将远程调用执行结果写到HTTP响应中 protected void writeRemoteInvocationResult( HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result) throws IOException { //设置HTTP响应的内容类型为:application/x-java-serialized-object response.setContentType(getContentType()); //将远程调用结果写到HTTP响应中 writeRemoteInvocationResult(request, response, result, response.getOutputStream()); } //将远程调用执行结果写入HTTP响应中 protected void writeRemoteInvocationResult( HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os) throws IOException { //获取HTTP响应对象输出流 ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os)); try { //将远程调用执行结果写到HTTP响应对象输出流中 doWriteRemoteInvocationResult(result, oos); } finally { oos.close(); } } //获取HTTP响应对象输入流 protected OutputStream decorateOutputStream( HttpServletRequest request, HttpServletResponse response, OutputStream os) throws IOException { return os; } }

通过对HttpInvokerServiceExporter的源码分析,我们可以看出,真正执行远程对象调用的是RemoteInvocationResultresult = invokeAndCreateResult(invocation, getProxy());它调用了RemoteInvocationBasedExporterinvokeAndCreateResult方法调用远程目标对象方法,并创建远程调用执行结果,下面我们继续分析执行服务器端远程调用目标对象方法的实现。

9.RemoteInvocationBasedExporter调用服务器目标对象:

RemoteInvocationBasedExporterinvokeAndCreateResult方法调用服务器目标对象方法,RemoteInvocationBasedExporter源码如下:

public abstract class RemoteInvocationBasedExporter extends RemoteExporter { //远程调用执行器 private RemoteInvocationExecutor remoteInvocationExecutor = new DefaultRemoteInvocationExecutor(); public RemoteInvocationExecutor getRemoteInvocationExecutor() { return this.remoteInvocationExecutor; } protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) { try { //调用服务器端目标对象的方法 Object value = invoke(invocation, targetObject); //根据执行结果创建RemoteInvocationResult return new RemoteInvocationResult(value); } catch (Throwable ex) { return new RemoteInvocationResult(ex); } } //调用目标对象的方法 protected Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (logger.isTraceEnabled()) { logger.trace("Executing " + invocation); } try { //获取远程调用执行器,由远程调用执行器调用目标对象的方法,即通过 //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; } } }

通过上面对RemoteInvocationBasedExporter源码分析我们看到,真正调用目标对象的是DefaultRemoteInvocationExecutorinvoke方法,下面我们继续分析DefaultRemoteInvocationExecutor调用目标对象方法的实现。

10.DefaultRemoteInvocationExecutor调用目标对象的方法实现远程调用:

DefaultRemoteInvocationExecutor用于调用目标对象的指定方法实现远程对象调用服务,其源码如下:

public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor { //调用目标对象的方法 public Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{ Assert.notNull(invocation, "RemoteInvocation must not be null"); Assert.notNull(targetObject, "Target object must not be null"); //调用RemoteInvocation的invoke方法 return invocation.invoke(targetObject); } } RemoteInvocation的invoke方法源码如下: public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { //获取远程调用对象的方法名称和参数类型 Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes); //利用JDK反射机制,调用目标对象指定参数的方法 return method.invoke(targetObject, this.arguments); }

你可能感兴趣的:(spring,object,服务器,service,Class,encoding)