HttpInvoker-----客户端实现
上篇文章HttpInvoker-----服务端实现分析了服务端的解析以及处理过程后,我们接下来分析对客户端的调用过程,在服务端的调用过程中我们反复的提到需要从HttpServletRequest中提取从客户端传来的RemoteInvocation实例,然后进行相应解析。所以,在客户端,一个比较重要的任务就是构建RemoteInvocation实例,并传送到服务端。HttpInvokerProxyFactoryBean类开始进行分析,下面先看其类图:
从类图中看出,HttpInvokerProxyFactoryBean类同样实现了InitializingBean接口,同时,又实现了FactoryBean和MethodInterceptor。这已经是老生常谈的问题了,这里就不再赘述了。我们还是根据实现InitializingBean接口分析初始化过程中的逻辑。
public void afterPropertiesSet() { super.afterPropertiesSet(); Class<?> ifc = getServiceInterface(); Assert.notNull(ifc, "Property 'serviceInterface' is required"); //创建代理并使用当前方法作为拦截器增强 this.serviceProxy = new ProxyFactory(ifc, this).getProxy(getBeanClassLoader()); }
在afterPropertiesSet中主要创建了一个代理,该代理封装了配置的服务接口,并使用当前类也就是 HttpInvokerProxyFactoryBean作为增强。因为HttpInvokerProxyFactoryBean实现了MethodInterceptor接口,所以可以作为增强拦截器。
同样,又由于HttpInvokerProxyFactoryBean实现了FactoryBean接口,所以通过Spring中普通方式调用该bean时调用的并不是该bean本身,而是此类中getObject方法返回的实例,也就是实例化过程中所创建的代理。
public Object getObject() { return this.serviceProxy; }
那么,HttpInvokerProxyFactoryBean类型bean在初始化过程中创建了封装服务接口的代理,并使用自身作为增强拦截器,然后,又因为实现了FactoryBean接口,所以获取bean的时候返回的其实是创建的代理。那么,汇总上面的逻辑,当调用如下代码时,其实是调用代理类中的服务方法,而在调用代理类中的服务方法时又会使用代理类中加入的增强器进行增强。
分析invoke方法之前,其实我们已经猜出了该方法所提供的主要功能就是将调用信息封装在RemoteInvocation中,发送给服务端并等待返回结果。
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; try { //远程执行方法 result = executeRequest(invocation, methodInvocation); } catch (Throwable ex) { RemoteAccessException rae = convertHttpInvokerAccessException(ex); throw (rae != null ? rae : 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); } } }
上述函数主要有3个步骤:
(1)构建RemoteInvocation实例
因为是代理中增强方法的调用,调用的方法及参数信息会在代理中封装至MethodInvocation实例中,并在增强方法中进行传递,也就意味着当程序进入invoke方法时其实是已经 包含了调用的接口的相关信息的,那么,首先要做的就是将MethodInvocation中的信息提取并构建RemoteInvocation实例。
(2)远程执行方法
(3)提取结果
考虑到序列化的问题,在Spring中约定使用HttpInvoker方式进行远程方法调用时,结果使用RemoteInvocationResult进行封装,那么在提取结果后还需要从封装的结果中提取对应的结果。
而在这三个步骤中最为关键的就是远程方法的执行。执行远程调用的首要步骤就是将调用方法的实例写入输出流中。
protected RemoteInvocationResult executeRequest( RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception { return executeRequest(invocation); }
protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception { return getHttpInvokerRequestExecutor().executeRequest(this, invocation); }
public final RemoteInvocationResult executeRequest( HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws Exception { //获取输出流 ByteArrayOutputStream baos = getByteArrayOutputStream(invocation); if (logger.isDebugEnabled()) { logger.debug("Sending HTTP invoker request for service at [" + config.getServiceUrl() + "], with size " + baos.size()); } return doExecuteRequest(config, baos); }
在doExecuteRequest方法中真正实现了对远程方法的构造与通信,与远程方法的连接功能实现中,Spring引入了第三方JAR:HttpClient。HttpClient是Apache Jakarta Common下的子项目,可以用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。
protected RemoteInvocationResult doExecuteRequest( HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws IOException, ClassNotFoundException { //创建HttpPost HttpPost postMethod = createHttpPost(config); //设置含有方法的输出流到post中 setRequestBody(config, postMethod, baos); try { //执行方法并等待结果响应 HttpResponse response = executeHttpPost(config, getHttpClient(), postMethod); //验证 validateResponse(config, response); //提取返回的输入流 InputStream responseBody = getResponseBody(config, response); //从输入流中提取结果 return readRemoteInvocationResult(responseBody, config.getCodebaseUrl()); } finally { postMethod.releaseConnection(); } }
接下来我们逐步分析客户端实现的逻辑。
(1)创建HttpPost
由于对于服务端方法的调用是通过post方式进行的,那么首先要做的就是构建HttpPost,构建HttpPost过程中可以设置一些必要的参数。
protected HttpPost createHttpPost(HttpInvokerClientConfiguration config) throws IOException { HttpPost httpPost = new HttpPost(config.getServiceUrl()); RequestConfig requestConfig = createRequestConfig(config); if (requestConfig != null) { httpPost.setConfig(requestConfig); } LocaleContext localeContext = LocaleContextHolder.getLocaleContext(); if (localeContext != null) { Locale locale = localeContext.getLocale(); if (locale != null) { httpPost.addHeader(HTTP_HEADER_ACCEPT_LANGUAGE, locale.toLanguageTag()); } } if (isAcceptGzipEncoding()) { httpPost.addHeader(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP); } return httpPost; }
(2)设置RequestBody
构建好PostMethod实例后便可以将存储RemoteInvocation实例的序列化对象的输出流设置进去,当然这里要注意的是传入的ContentType类型,一定要传入application/x-java-serialized-object以保证服务端解析时会按照序列化对象的解析方式进行解析。
protected void setRequestBody( HttpInvokerClientConfiguration config, HttpPost httpPost, ByteArrayOutputStream baos) throws IOException { ByteArrayEntity entity = new ByteArrayEntity(baos.toByteArray()); entity.setContentType(getContentType()); httpPost.setEntity(entity); }
(3)执行远程方法
通过HttpClient所提供的方法来直接执行远程方法。
protected HttpResponse executeHttpPost( HttpInvokerClientConfiguration config, HttpClient httpClient, HttpPost httpPost) throws IOException { return httpClient.execute(httpPost); }
(4)远程相应验证
对于HTTP调用的响应码处理,大于300则是非正常调用的响应码。
protected void validateResponse(HttpInvokerClientConfiguration config, HttpResponse response) throws IOException { StatusLine status = response.getStatusLine(); if (status.getStatusCode() >= 300) { throw new NoHttpResponseException( "Did not receive successful HTTP response: status code = " + status.getStatusCode() + ", status message = [" + status.getReasonPhrase() + "]"); } }
(5)提取响应信息
从服务器返回的输入流可能是经过压缩的,不同的方式采用不同的方法进行提前。
protected InputStream getResponseBody(HttpInvokerClientConfiguration config, HttpResponse httpResponse) throws IOException { if (isGzipResponse(httpResponse)) { return new GZIPInputStream(httpResponse.getEntity().getContent()); } else { return httpResponse.getEntity().getContent(); } }
(7)提取返回结果
提取结果的流程主要是从输入流中提取响应的序列化信息。
protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, @Nullable String codebaseUrl) throws IOException, ClassNotFoundException { ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl); try { return doReadRemoteInvocationResult(ois); } finally { ois.close(); } }
参考:《Spring源码深度解析》 郝佳 编著: