深入学习 Spring HttpInvoker
2018-03-07 20:26
609 查看
深入学习 Spring HttpInvoker
远程通信协议
RPC 远程过程调用,是一个计算机通信协议,允许一台计算机的程序,调用另一台计算机的子程序。如果涉及到的软件采用面向对象编程,亦可称为远程方法调用(如 Java RMI)RMI 一般指Java RMI (Java Remote Method Invocation) Java远程方法调用,只适用于Java程序之间的通信。而且 RMI需要开设防火墙端口
Hessian 比较精简高效,可以跨语言使用,而且协议规范公开,可以针对任意语言开发对其协议的实现(Java C++ .net python…) 数据类型有限,处理复杂对象时,速度稍慢。
HttpInvoker 是Spring提供的一种基于HTTP的Java远程调用的方法
HttpInvoker 配置
服务端
web.xml<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <servlet> <servlet-name>accountExporter</servlet-name> <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>accountExporter</servlet-name> <url-pattern>/remoting/AccountService</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
applicationContext.xml
<bean id="accountService" class="remoting.service.impl.AccountServiceImpl"/> <bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <!--具体服务类型--> <property name="service" ref="accountService"/> <!--服务接口--> <property name="serviceInterface" value="remoting.service.AccountService"/> </bean>
或者如下配置
web.xml
<servlet> <servlet-name>remoting</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>remoting</servlet-name> <url-pattern>/remoting/*</url-pattern> </servlet-mapping>
dispatcher-servlet.xml
<bean id="accountService" class="remoting.service.impl.AccountServiceImpl"/> <bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="remoting.service.AccountService"/> </bean>
客户端
客户端只包含Account、以及服务接口AccountService, 但是没有AccountService的具体实现。remoting.xml
<bean id="accountService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> <!--服务URL--> <property name="serviceUrl" value="http://localhost:8080/remoting/AccountService"/> <!--服务接口--> <property name="serviceInterface" value="remoting.service.AccountService"/> </bean>
import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ImportResource; import org.springframework.context.support.AbstractApplicationContext; import remoting.domain.Account; import remoting.service.AccountService; @ImportResource(locations={"classpath:remoting.xml"}) public class Client { @Autowired public AccountService accountService; public void setAccountService(AccountService accountService) { this.accountService = accountService; } public static void main(String[] args) throws FileNotFoundException, IOException { AbstractApplicationContext context = new AnnotationConfigApplicationContext(Client.class); Client client = context.getBean(Client.class); Account account = new Account(); account.setName("123456"); client.accountService.insertAccount(account); List<Account> accounts = client.accountService.listAccounts(); for (Account acc : accounts) System.out.println(acc.getName()); context.close(); } }
HttpInvoker 执行流程分析
客户端
HttpInvokerProxyFactoryBean实现了FactoryBean接口,用于获取Bean对象,而Bean对象的创建,是以代理的方式创建的
package org.springframework.beans.factory; public interface FactoryBean<T> { T getObject() throws Exception; Class<?> getObjectType(); default boolean isSingleton() { return true; } }
package org.springframework.remoting.httpinvoker; public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor implements FactoryBean<Object> { @Nullable private Object serviceProxy; @Override public void afterPropertiesSet() { super.afterPropertiesSet(); Class<?> ifc = getServiceInterface(); Assert.notNull(ifc, "Property 'serviceInterface' is required"); // 服务代理对象的创建 this.serviceProxy = new ProxyFactory(ifc, this).getProxy(getBeanClassLoader()); } // 获取服务代理对象 @Override @Nullable public Object getObject() { return this.serviceProxy; } // 服务接口 @Override public Class<?> getObjectType() { return getServiceInterface(); } // 单例类型 @Override public boolean isSingleton() { return true; } }
代理对象在调用方法时,会执行invoker方法:
根据methodInvocation,可以得知我们要远程调用的方法 的方法名、参数类型以及参数,将其封装到RemoteInvocation中。
调用httpInvokerRequestExecutor.executeRequest(this, invocation) 获取远程调用结果RemoteInvocationResult.
public class RemoteInvocation implements Serializable { /** use serialVersionUID from Spring 1.1 for interoperability */ private static final long serialVersionUID = 6876024250231820554L; private String methodName; private Class<?>[] parameterTypes; private Object[] arguments; private Map<String, Serializable> attributes; // setter getter 以及构造方法 略 public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes); return method.invoke(targetObject, this.arguments); } @Override public String toString() { return "RemoteInvocation: method name '" + this.methodName + "'; parameter types " + ClassUtils.classNamesToString(this.parameterTypes); } }
public class RemoteInvocationResult implements Serializable { /** Use serialVersionUID from Spring 1.1 for interoperability */ private static final long serialVersionUID = 2138555143707773549L; @Nullable private Object value; @Nullable private Throwable exception; @Nullable public Object recreate() throws Throwable { if (this.exception != null) { Throwable exToThrow = this.exception; if (this.exception instanceof InvocationTargetException) { exToThrow = ((InvocationTargetException) this.exception).getTargetException(); } RemoteInvocationUtils.fillInClientStackTraceIfPossible(exToThrow); throw exToThrow; } else { return this.value; } } }
public class HttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor implements MethodInterceptor, HttpInvokerClientConfiguration { private HttpInvokerRequestExecutor httpInvokerRequestExecutor; @Override 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; 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); } } } protected RemoteInvocationResult executeRequest( RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception { return executeRequest(invocation); } protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception { return getHttpInvokerRequestExecutor().executeRequest(this, invocation); } }
HttpInvokerRequestExecutor作用是向服务端发送请求,进行远程方法调用,获取远程执行结果
有以下几个过程:
序列化RemoteInvocation, 将ObjectOutputStream写入到ByteArrayOutputStream中
根据url, 请求连接 openConnection
请求头的相关设置 :请求方法、Content-Type、Content-Length(ByteArrayOutputStream的大小)
将ByteArrayOutputStream发送到服务端,ByteArrayOutputStream.writeTo(con.getOutputStream())
获取服务端响应数据con.getInputStream() 将其反序列化为ResultRemoteInvocation对象。而ResultRemoteInvocation封装了远程方法调用的返回值
RemoteInvocation对象的序列化和ResultRemoteInvocation对象的反序列化过程在
AbstractHttpInvokerRequestExecutor中实现
序列化过程
// AbstractHttpInvokerRequestExecutor.java protected void writeRemoteInvocation(RemoteInvocation invocation, OutputStream os) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(decorateOutputStream(os)); try { doWriteRemoteInvocation(invocation, oos); } finally { oos.close(); } } protected OutputStream decorateOutputStream(OutputStream os) throws IOException { return os; } protected void doWriteRemoteInvocation(RemoteInvocation invocation, ObjectOutputStream oos) throws IOException { oos.writeObject(invocation); }
反序列化过程
// AbstractHttpInvokerRequestExecutor.java protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, @Nullable String codebaseUrl) throws IOException, ClassNotFoundException { ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl); try { return doReadRemoteInvocationResult(ois); } finally { ois.close(); } } protected InputStream decorateInputStream(InputStream is) throws IOException { return is; } protected ObjectInputStream createObjectInputStream(InputStream is, @Nullable String codebaseUrl) throws IOException { return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), codebaseUrl); } 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() + "]: " + ClassUtils.getDescriptiveType(obj)); } return (RemoteInvocationResult) obj; }
这里使用了Java模板方法的设计模式,doExecuteRequest交给子类实现
// AbstractHttpInvokerRequestExecutor.java @Override 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); } protected ByteArrayOutputStream getByteArrayOutputStream(RemoteInvocation invocation) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE); writeRemoteInvocation(invocation, baos); return baos; } // 抽象方法 具体实现交给子类 protected abstract RemoteInvocationResult doExecuteRequest( HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws Exception;
// SimpleHttpInvokerRequestExecutor.java protected RemoteInvocationResult doExecuteRequest( HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws IOException, ClassNotFoundException { HttpURLConnection con = openConnection(config); prepareConnection(con, baos.size()); // 向服务端发送数据 writeRequestBody(config, con, baos); validateResponse(config, con); // 读取服务端数据 InputStream responseBody = readResponseBody(config, con); return readRemoteInvocationResult(responseBody, config.getCodebaseUrl()); }
服务端
而服务端过程与客户端相反:接受客户端数据,反序列化为RemoteInvocation
服务的具体实现在服务端,根据RemoteInvocation中封装的方法信息,反射调用相关方法
将方法执行结果,序列化为ResultRemoteIvocation, 发送给客户端
HttpRequestHandlerServlet,用于接收远程调用相关请求。HttpRequestHandler接口,定义了请求的处理方法。这里注入的是
HttpInvokerServiceExporter.java
public class HttpRequestHandlerServlet extends HttpServlet { @Nullable private HttpRequestHandler target; @Override public void init() throws ServletException { WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); this.target = wac.getBean(getServletName(), HttpRequestHandler.class); } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Assert.state(this.target != null, "No HttpRequestHandler available"); LocaleContextHolder.setLocale(request.getLocale()); try { this.target.handleRequest(request, response); } catch (HttpRequestMethodNotSupportedException ex) { String[] supportedMethods = ex.getSupportedMethods(); if (supportedMethods != null) { response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", ")); } response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage()); } finally { LocaleContextHolder.resetLocaleContext(); } } }
// HttpInvokerServiceExporter.java public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { RemoteInvocation invocation = readRemoteInvocation(request); RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy()); writeRemoteInvocationResult(request, response, result); } catch (ClassNotFoundException ex) { throw new NestedServletException("Class not found during deserialization", ex); } }
RemoteInvocation对象的反序列化和ResultRemoteInvocation对象的序列化在
RemoteInvocationSerializingExporter中实现
// RemoteInvocationSerializingExporter.java protected ObjectInputStream createObjectInputStream(InputStream is) throws IOException { return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), isAcceptProxyClasses()); } //序列化还原 protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois) throws IOException, ClassNotFoundException { Object obj = ois.readObject(); if (!(obj instanceof RemoteInvocation)) { throw new RemoteException("Deserialized object needs to be assignable to type [" + RemoteInvocation.class.getName() + "]: " + ClassUtils.getDescriptiveType(obj)); } return (RemoteInvocation) obj; } protected ObjectOutputStream createObjectOutputStream(OutputStream os) throws IOException { return new ObjectOutputStream(os); } // 序列化RemoteInvocationResult protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos) throws IOException { oos.writeObject(result); }
方法的反射调用过程, 也就是根据RemoteInvocation创建ResultRemoteInvocation的过程:
public abstract class RemoteInvocationBasedExporter extends RemoteExporter { private RemoteInvocationExecutor remoteInvocationExecutor = new DefaultRemoteInvocationExecutor(); protected Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (logger.isTraceEnabled()) { logger.trace("Executing " + invocation); } try { 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; } } protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) { try { Object value = invoke(invocation, targetObject); return new RemoteInvocationResult(value); } catch (Throwable ex) { return new RemoteInvocationResult(ex); } } }
public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor { @Override 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"); return invocation.invoke(targetObject); } }
总结
Spring HttpInvoker 流程:客户端服务代理对象 与服务端建立连接,将RemoteInvocation序列化发送给服务端
服务端反序列化RemoteInvocation,反射调用具体服务实现的方法,将结果封装到ResultRemoteInvocation中,将其序列化后发送给客户端
客户端发序列化ResultRemoteInvocation,获取远程调用结果。
Spring HttpInvoker 采用Java序列化的方式进行传输,要保证服务端和客户端的相关代码的版本一致,否则容易出现java.io.InvalidClassException: ; incompatible types for field 此类错误。
参考文档
几种通信协议的比较Spring 5.0.4 文档
相关文章推荐
- Java学习之路-Spring的HttpInvoker学习
- java之httpinvoker的深入学习-httpinvoker动态生成(思考)
- 关于spring的httpInvoker学习
- Spring HTTP Invoker 学习小记
- Spring HTTP Invoker 学习小记
- Spring+SpringMVC+MyBatis深入学习及搭建(五)——动态sql
- Spring+SpringMVC+MyBatis深入学习及搭建(一)——MyBatis的基础知识
- 深入学习Spring框架之一Spring的简要概括
- Spring+SpringMVC+MyBatis深入学习及搭建(六)——MyBatis关联查询
- 深入学习spring-boot系列(二)--使用spring-data-jpa
- 学习笔记-spring-mybatis-jsoup-http-client小说站点爬虫(1)--获取小说站点章节列表
- 深入学习spring-boot系列(三)--使用thymeleaf模板
- Spring HTTP Invoker使用介绍
- Spring Remoting by HTTP Invoker Example--reference
- Spring中HttpInvoker远程方法调用总结
- HTTP协议深入学习之问题解惑
- Spring HTTP invoker RPC
- spring源码学习之路---深入AOP(终)
- Spring HTTP Invoker使用介绍
- Spring Httpinvoker简单Demo