RPC 远程过程调用,是一个计算机通信协议,允许一台计算机的程序,调用另一台计算机的子程序。如果涉及到的软件采用面向对象编程,亦可称为远程方法调用(如 Java RMI)
RMI 一般指Java RMI (Java Remote Method Invocation) Java远程方法调用,只适用于Java程序之间的通信。而且 RMI需要开设防火墙端口
Hessian 比较精简高效,可以跨语言使用,而且协议规范公开,可以针对任意语言开发对其协议的实现(Java C++ .net python…) 数据类型有限,处理复杂对象时,速度稍慢。
HttpInvoker 是Spring提供的一种基于HTTP的Java远程调用的方法
web.xml
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring/applicationContext.xmlparam-value>
context-param>
<servlet>
<servlet-name>accountExporterservlet-name>
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>accountExporterservlet-name>
<url-pattern>/remoting/AccountServiceurl-pattern>
servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-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>remotingservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring/dispatcher-servlet.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>remotingservlet-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">
<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 accounts = client.accountService.listAccounts();
for (Account acc : accounts)
System.out.println(acc.getName());
context.close();
}
}
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 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对象的序列化和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());
}
而服务端过程与客户端相反:
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 流程:
Spring HttpInvoker 采用Java序列化的方式进行传输,要保证服务端和客户端的相关代码的版本一致,否则容易出现java.io.InvalidClassException: ; incompatible types for field 此类错误。
几种通信协议的比较
Spring 5.0.4 文档