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

时间:2022-04-27 20:11:37

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

(1).客户端:

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

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

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

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

(2).服务器端:

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

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

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

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

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

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

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

  1. <!--客户端HTTP调用器代理-->
  2. <bean id=”proxy” class=”org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean”>
  3. <property name=”serviceUrl”>
  4. <value>http://yourhost:8080/远程调用URL</value>
  5. </property>
  6. <property name=”serviceInterface”>
  7. <value>远程调用服务接口全路径</value>
  8. </property>
  9. </bean>
  10. <bean id=”客户端bean” class=”客户端Bean全路径”>
  11. <property name=”remoteService”>
  12. <ref bean=”proxy”/>
  13. </property>
  14. </bean>

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

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

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

  1. public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor implements FactoryBean<Object> {
  2. //远程对象的代理
  3. private Object serviceProxy;
  4. //在IoC容器注入完成之后调用
  5. public void afterPropertiesSet() {
  6. //调用父类容器的回调方法
  7. super.afterPropertiesSet();
  8. //getServiceInterface()方法用于获取配置的远程调用接口
  9. if (getServiceInterface() == null) {
  10. throw new IllegalArgumentException("Property 'serviceInterface' is required");
  11. }
  12. //使用ProxyFactory代理工厂生成远程代理对象,注意第二个参数this,因为
  13. //HttpInvokerProxyFactoryBean继承了HttpInvokerClientInterceptor,
  14. //所以代理对象的拦截器设置为HttpInvokerClientInterceptor
  15. this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
  16. }
  17. //向IoC容器索取被管理对象的方法,获取产生的远程调用代理对象
  18. public Object getObject() {
  19. return this.serviceProxy;
  20. }
  21. //获取对象类型,返回配置的远程调用接口
  22. public Class<?> getObjectType() {
  23. return getServiceInterface();
  24. }
  25. //是否是单态类型,默认Spring IoC容器产生的都是单态类型
  26. public boolean isSingleton() {
  27. return true;
  28. }
  29. }

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

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

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

  1. public class HttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor
  2. implements MethodInterceptor, HttpInvokerClientConfiguration {
  3. private String codebaseUrl;
  4. //HTTP调用请求执行器
  5. private HttpInvokerRequestExecutor httpInvokerRequestExecutor;
  6. public void setCodebaseUrl(String codebaseUrl) {
  7. this.codebaseUrl = codebaseUrl;
  8. }
  9. public String getCodebaseUrl() {
  10. return this.codebaseUrl;
  11. }
  12. public void setHttpInvokerRequestExecutor(HttpInvokerRequestExecutor httpInvokerRequestExecutor) {
  13. this.httpInvokerRequestExecutor = httpInvokerRequestExecutor;
  14. }
  15. //获取HTTP调用请求执行器,如果HTTP调用请求执行器没有设置,则使用
  16. //SimpleHttpInvokerRequestExecutor作为HTTP调用请求执行器
  17. public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() {
  18. if (this.httpInvokerRequestExecutor == null) {
  19. SimpleHttpInvokerRequestExecutor executor = new SimpleHttpInvokerRequestExecutor();
  20. executor.setBeanClassLoader(getBeanClassLoader());
  21. this.httpInvokerRequestExecutor = executor;
  22. }
  23. return this.httpInvokerRequestExecutor;
  24. }
  25. //IoC容器初始化完成回调方法
  26. public void afterPropertiesSet() {
  27. //调用父类的初始化回调方法
  28. super.afterPropertiesSet();
  29. //获取HTTP调用请求执行器
  30. getHttpInvokerRequestExecutor();
  31. }
  32. //拦截器代理对象方法调用入口,拦截器将客户端对远程调用代理的调用封装为
  33. //MethodInvocation对象。
  34. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  35. if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
  36. return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";
  37. }
  38. //创建远程调用对象,封装了远程调用
  39. RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
  40. //远程调用结果
  41. RemoteInvocationResult result = null;
  42. try {
  43. //远程调用入口
  44. result = executeRequest(invocation, methodInvocation);
  45. }
  46. catch (Throwable ex) {
  47. throw convertHttpInvokerAccessException(ex);
  48. }
  49. try {
  50. //返回远程调用结果
  51. return recreateRemoteInvocationResult(result);
  52. }
  53. catch (Throwable ex) {
  54. if (result.hasInvocationTargetException()) {
  55. throw ex;
  56. }
  57. else {
  58. throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() +
  59. "] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
  60. }
  61. }
  62. }
  63. //执行远程调用入口
  64. protected RemoteInvocationResult executeRequest(
  65. RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {
  66. return executeRequest(invocation);
  67. }
  68. //通过HTTP调用请求执行器执行远程调用
  69. protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception {
  70. return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
  71. }
  72. //将远程调用异常转换成Spring异常
  73. protected RemoteAccessException convertHttpInvokerAccessException(Throwable ex) {
  74. if (ex instanceof ConnectException) {
  75. throw new RemoteConnectFailureException(
  76. "Could not connect to HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
  77. }
  78. else if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError ||
  79. ex instanceof InvalidClassException) {
  80. throw new RemoteAccessException(
  81. "Could not deserialize result from HTTP invoker remote service [" + getServiceUrl() + "]", ex);
  82. }
  83. else {
  84. throw new RemoteAccessException(
  85. "Could not access HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
  86. }
  87. }
  88. }

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

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

5.SimpleHttpInvokerRequestExecutor远程调用:

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

  1. public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
  2. //HTTP调用请求执行器真正进行远程调用的方法,该方法有其父类//AbstractHttpInvokerRequestExecutor的executeRequest方法调用
  3. protected RemoteInvocationResult doExecuteRequest(
  4. HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
  5. throws IOException, ClassNotFoundException {
  6. //打开一个标准的J2SE HttpURLConnection
  7. HttpURLConnection con = openConnection(config);
  8. //准备连接
  9. prepareConnection(con, baos.size());
  10. //远程调用被封装成了RemoteInvocation对象,它通过序列化被写到对应的//HttpURLConnection中
  11. writeRequestBody(config, con, baos);
  12. //获取远程调用的结果,校验返回的结果
  13. validateResponse(config, con);
  14. InputStream responseBody = readResponseBody(config, con);
  15. //将远程调用结果转换成RemoteInvocationResult返回
  16. return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
  17. }
  18. //打开一个HttpURLConnection
  19. protected HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException {
  20. //getServiceUrl()方法获取配置的远程调用URL,打开一个URL连接
  21. URLConnection con = new URL(config.getServiceUrl()).openConnection();
  22. if (!(con instanceof HttpURLConnection)) {
  23. throw new IOException("Service URL [" + config.getServiceUrl() + "] is not an HTTP URL");
  24. }
  25. return (HttpURLConnection) con;
  26. }
  27. //准备HTTP请求连接
  28. protected void prepareConnection(HttpURLConnection con, int contentLength) throws IOException {
  29. con.setDoOutput(true);
  30. //HTTP调用器只支持POST请求方法
  31. con.setRequestMethod(HTTP_METHOD_POST);
  32. //设置HTTP请求头内容类型,设置为:application/x-java-serialized-object
  33. con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType());
  34. //设置HTTP请求头内容长度
  35. con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength));
  36. LocaleContext locale = LocaleContextHolder.getLocaleContext();
  37. //设置HTTP请求的Locale
  38. if (locale != null) {
  39. con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));
  40. }
  41. //设置HTTP请求压缩方式
  42. if (isAcceptGzipEncoding()) {
  43. con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
  44. }
  45. }
  46. //把序列化对象输出到HTTP请求体中
  47. protected void writeRequestBody(
  48. HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos)
  49. throws IOException {
  50. baos.writeTo(con.getOutputStream());
  51. }
  52. //校验远程调用的HTTP响应
  53. protected void validateResponse(HttpInvokerClientConfiguration config, HttpURLConnection con)
  54. throws IOException {
  55. //如果HTTP响应状态码大于等于300,则证明调用发生错误
  56. if (con.getResponseCode() >= 300) {
  57. throw new IOException(
  58. "Did not receive successful HTTP response: status code = " + con.getResponseCode() +
  59. ", status message = [" + con.getResponseMessage() + "]");
  60. }
  61. }
  62. //提取远程调用结果的HTTP响应信息
  63. protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con)
  64. throws IOException {
  65. //如果响应信息是Gzip压缩的,则需要先解压
  66. if (isGzipResponse(con)) {
  67. return new GZIPInputStream(con.getInputStream());
  68. }
  69. //正常的HTTP响应
  70. else {
  71. return con.getInputStream();
  72. }
  73. }
  74. //是否是Gzip格式压缩
  75. protected boolean isGzipResponse(HttpURLConnection con) {
  76. //获取HTTP响应头信息中的压缩方式
  77. String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING);
  78. return (encodingHeader != null && encodingHeader.toLowerCase().indexOf(ENCODING_GZIP) != -1);
  79. }
  80. }

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

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

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

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

  1. //从HTTP响应中读取远程调用结果入口方法
  2. protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl)
  3. throws IOException, ClassNotFoundException {
  4. //根据给定的输入流和类创建对象输入流
  5. ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl);
  6. try {
  7. //从对象输入流中读取远程调用结果
  8. return doReadRemoteInvocationResult(ois);
  9. }
  10. finally {
  11. ois.close();
  12. }
  13. }
  14. //从对象输入流中读取远程调用结果
  15. protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois)
  16. throws IOException, ClassNotFoundException {
  17. //获取对象输入流中的对象
  18. Object obj = ois.readObject();
  19. if (!(obj instanceof RemoteInvocationResult)) {
  20. throw new RemoteException("Deserialized object needs to be assignable to type [" +
  21. RemoteInvocationResult.class.getName() + "]: " + obj);
  22. }
  23. //将获取到的对象封装为RemoteInvocationResult
  24. return (RemoteInvocationResult) obj;
  25. }

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

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

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

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

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

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

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

  1. public class HttpInvokerServiceExporter extends RemoteInvocationSerializingExporter implements HttpRequestHandler {
  2. //处理客户端发来的远程调用HTTP请求
  3. public void handleRequest(HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. try {
  6. //从HTTP请求中反序列化出RemoteInvocation远程调用对象
  7. RemoteInvocation invocation = readRemoteInvocation(request);
  8. //调用目标服务对象,完成远程调用请求,并创建调用结果
  9. RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy());
  10. //将调用结果写到HTTP响应中
  11. writeRemoteInvocationResult(request, response, result);
  12. }
  13. catch (ClassNotFoundException ex) {
  14. throw new NestedServletException("Class not found during deserialization", ex);
  15. }
  16. }
  17. //从HTTP请求中读取RemoteInvocation远程调用对象入口方法
  18. protected RemoteInvocation readRemoteInvocation(HttpServletRequest request) throws IOException, ClassNotFoundException {
  19. //将从HTTP请求中读取远程调用对象
  20. return readRemoteInvocation(request, request.getInputStream());
  21. }
  22. //从HTTP请求中读取远程调用对象
  23. protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is) throws IOException, ClassNotFoundException {
  24. //根据HTTP请求输入流创建对象输入流
  25. ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is));
  26. try {
  27. //从对象输入流中读取远程调用对象
  28. return doReadRemoteInvocation(ois);
  29. }
  30. finally {
  31. ois.close();
  32. }
  33. }
  34. //获取HTTP请求输入流
  35. protected InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException {
  36. return is;
  37. }
  38. //将远程调用执行结果写到HTTP响应中
  39. protected void writeRemoteInvocationResult(
  40. HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result) throws IOException {
  41. //设置HTTP响应的内容类型为:application/x-java-serialized-object
  42. response.setContentType(getContentType());
  43. //将远程调用结果写到HTTP响应中
  44. writeRemoteInvocationResult(request, response, result, response.getOutputStream());
  45. }
  46. //将远程调用执行结果写入HTTP响应中
  47. protected void writeRemoteInvocationResult(
  48. HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os)
  49. throws IOException {
  50. //获取HTTP响应对象输出流
  51. ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os));
  52. try {
  53. //将远程调用执行结果写到HTTP响应对象输出流中
  54. doWriteRemoteInvocationResult(result, oos);
  55. }
  56. finally {
  57. oos.close();
  58. }
  59. }
  60. //获取HTTP响应对象输入流
  61. protected OutputStream decorateOutputStream(
  62. HttpServletRequest request, HttpServletResponse response, OutputStream os) throws IOException {
  63. return os;
  64. }
  65. }

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

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

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

  1. public abstract class RemoteInvocationBasedExporter extends RemoteExporter {
  2. //远程调用执行器
  3. private RemoteInvocationExecutor remoteInvocationExecutor = new DefaultRemoteInvocationExecutor();
  4. public RemoteInvocationExecutor getRemoteInvocationExecutor() {
  5. return this.remoteInvocationExecutor;
  6. }
  7. protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
  8. try {
  9. //调用服务器端目标对象的方法
  10. Object value = invoke(invocation, targetObject);
  11. //根据执行结果创建RemoteInvocationResult
  12. return new RemoteInvocationResult(value);
  13. }
  14. catch (Throwable ex) {
  15. return new RemoteInvocationResult(ex);
  16. }
  17. }
  18. //调用目标对象的方法
  19. protected Object invoke(RemoteInvocation invocation, Object targetObject)
  20. throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  21. if (logger.isTraceEnabled()) {
  22. logger.trace("Executing " + invocation);
  23. }
  24. try {
  25. //获取远程调用执行器,由远程调用执行器调用目标对象的方法,即通过
  26. //DefaultRemoteInvocationExecutor了调用目标对象的方法
  27. return getRemoteInvocationExecutor().invoke(invocation, targetObject);
  28. }
  29. catch (NoSuchMethodException ex) {
  30. if (logger.isDebugEnabled()) {
  31. logger.warn("Could not find target method for " + invocation, ex);
  32. }
  33. throw ex;
  34. }
  35. catch (IllegalAccessException ex) {
  36. if (logger.isDebugEnabled()) {
  37. logger.warn("Could not access target method for " + invocation, ex);
  38. }
  39. throw ex;
  40. }
  41. catch (InvocationTargetException ex) {
  42. if (logger.isDebugEnabled()) {
  43. logger.debug("Target method failed for " + invocation, ex.getTargetException());
  44. }
  45. throw ex;
  46. }
  47. }
  48. }

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

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

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

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