【Spring 源码深度解析】11远程服务

1 RMI

Java 远程方法调用,即 Java RMI(Remote Method Invocation),是 Java 编程语言里一中用于实现远程过程调用的程序编程接口。使客户机上运行的程序可以调用远程服务器上的对象。其宗旨就是尽可能简化远程接口对象的使用。

Java RMI 极大得依赖于接口。在需要创建一个远程对象时,通过传递一个接口来隐藏底层的细节。客户端得到的远程对象句柄正好与本地的根代码连接,由后者通过网络通信。这样,开发者只需要关心如果通过自己的接口句柄发送消息。

1.1 使用示例

在 Spring 中也提供了对 RMI 的支持。
1)建立 RMI 对外接口

public interface HelloRMIService {
	int getAdd(int a, int b);
}

2)建立接口实现类

public class HelloRMIServiceImpl implements HelloRMIService {
	@Override
	public int getAdd(int a, int b) {
		return a + b;
	}
}

3)建立服务端配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 服务端-->
	<bean id="helloRMIServiceImpl" class="org.springframework.test.rmi.HelloRMIServiceImpl" />
	<!-- 将类作为一个 RMI 服务-->
	<bean id="myRMI" class="org.springframework.remoting.rmi.RmiServiceExporter">
		<property name="service" ref="helloRMIServiceImpl" />
		<property name="serviceName" value="helloRMI" />
		<property name="serviceInterface" value="org.springframework.test.rmi.HelloRMIService" />
		<property name="registryPort" value="9999" />
	</bean>
</beans>

4)建立服务端测试类

public class Main {
	public static void main(String[] args) {
		new ClassPathXmlApplicationContext("test/rmi/bean.xml");
	}
}

5)完成服务端配置后,建立测试环境和测试代码。首先建立测试端配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="myClient" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
		<property name="serviceUrl" value="rmi://127.0.0.1:9999/helloRMI" />
		<property name="serviceInterface" value="org.springframework.test.rmi.HelloRMIService" />
	</bean>
</beans>

6)编写测试端代码

public class ClientTest {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("test/rmi/bean2.xml");
		HelloRMIService client = context.getBean("myClient", HelloRMIService.class);
		System.out.println(client.getAdd(1, 3));
	}
}

1.2 服务端实现

在配置文件中定义了两个 bean, 一个是对接口实现类的发布,一个是对 RMI 服务的发布。使用 RmiServiceExporter 进行封装,其中包括服务类,服务名,服务接口,服务端口等属性。此类是 RMI 的关键类。在该类中进行了一些初始化的设置。查看其类继承图,可以知道其中几个相关类的作用。
在这里插入图片描述
主要接口的作用:

  • BeanClassLoaderAware:保证在实现该接口的 bean 初始化时调用其 setBeanClassLoader 方法。
  • InitializingBean:保证实现该接口的 bean 实例化后调用 afterProperties 方法。
  • DisposableBean:保证实现该接口的 bean 销毁时调用 destory 方法。

其中 afterProperties 是 RmiServiceExporter 类功能的初始化方法。

// RmiServiceExporter.java
public void afterPropertiesSet() throws RemoteException {
	prepare();
}

public void prepare() throws RemoteException {
	// 检查验证 service 存在
	checkService();

	if (this.serviceName == null) {
		throw new IllegalArgumentException("Property 'serviceName' is required");
	}

	// Check socket factories for exported object.
	// 如果用户在配置文件配置了 clientSocketFactory 或者 serverSocketFactory 的处理
	/*
	 * 如果配置中的 clientSocketFactory 同时又实现了 RMIServerSocketFactory 接口那么会忽略
	 * 配置中的 serverSocketFactory 而使用 clientSocketFactory 代替
	 */
	if (this.clientSocketFactory instanceof RMIServerSocketFactory) {
		this.serverSocketFactory = (RMIServerSocketFactory) this.clientSocketFactory;
	}
	// clientSocketFactory 和 serverSocketFactory 要么同时出现要么都不出现
	if ((this.clientSocketFactory != null && this.serverSocketFactory == null) ||
			(this.clientSocketFactory == null && this.serverSocketFactory != null)) {
		throw new IllegalArgumentException(
				"Both RMIClientSocketFactory and RMIServerSocketFactory or none required");
	}

	// Check socket factories for RMI registry.
	/*
	 * 如果配置中的 registryClientSocketFactory 同时又实现了 RMIServerSocketFactory 接口那么会忽略
	 * 配置中的 registryServerSocketFactory 而使用 registryClientSocketFactory 代替
	 */
	if (this.registryClientSocketFactory instanceof RMIServerSocketFactory) {
		this.registryServerSocketFactory = (RMIServerSocketFactory) this.registryClientSocketFactory;
	}
	// 不允许出现只配置 registryClientSocketFactory 却没有配置 registryServerSocketFactory 的情况
	if (this.registryClientSocketFactory == null && this.registryServerSocketFactory != null) {
		throw new IllegalArgumentException(
				"RMIServerSocketFactory without RMIClientSocketFactory for registry not supported");
	}

	this.createdRegistry = false;

	// Determine RMI registry to use.
	// 确定 RMI registry
	if (this.registry == null) {
		this.registry = getRegistry(this.registryHost, this.registryPort,
			this.registryClientSocketFactory, this.registryServerSocketFactory);
		this.createdRegistry = true;
	}

	// Initialize and cache exported object.
	// 初始化以及缓存导出的 Object
	// 此时通常情况下是使用 RmiInvocationWrapper 封装的 JDK 代理类,切面为
	// RemoteInvocationTraceInterceptor
	this.exportedObject = getObjectToExport();

	if (logger.isDebugEnabled()) {
		logger.debug("Binding service '" + this.serviceName + "' to RMI registry: " + this.registry);
	}

	// Export RMI object.
	if (this.clientSocketFactory != null) {
		/*
		 * 使用由给定的套接字工厂指定的传送方式导出远程对象,以便能够接收传入的调用
		 * clientSocketFactory:进行远程对象调用的客户端套接字工厂
		 * serverSocketFactory:进行远程调用的服务端套接字工厂
		 */
		UnicastRemoteObject.exportObject(
				this.exportedObject, this.servicePort, this.clientSocketFactory, this.serverSocketFactory);
	}
	else {
		// 导出 remote object,以使它能够接收特定端口的调用
		UnicastRemoteObject.exportObject(this.exportedObject, this.servicePort);
	}

	// Bind RMI object to registry.
	try {
		if (this.replaceExistingBinding) {
			this.registry.rebind(this.serviceName, this.exportedObject);
		}
		else {
			// 绑定服务名称到 remote object,外界调用 serviceName 的时候会被 exportedObject 接收
			this.registry.bind(this.serviceName, this.exportedObject);
		}
	}
	catch (AlreadyBoundException ex) {
		// Already an RMI object bound for the specified service name...
		unexportObjectSilently();
		throw new IllegalStateException(
				"Already an RMI object bound for name '"  + this.serviceName + "': " + ex.toString());
	}
	catch (RemoteException ex) {
		// Registry binding failed: let's unexport the RMI object as well.
		unexportObjectSilently();
		throw ex;
	}
}

上述方法主要逻辑:
1)验证 service:此处的 service 对应的是配置中类型为 service 属性,是实现类而不是接口。
2)处理用户自定义的 SocketFactory 属性
在 RmiServiceExporter 中提供了4个套接字工厂配置,分别是 clientSocketFactory,serverSocketFactory,registryClientSocketFactory,registryServerSocketFactory。
(1)registryClientSocketFactory,registryServerSocketFactory 用于主机与 RMI 服务器之间连接的创建,即当使用 LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory) 方法创建 Registry 实例时会在 RMI 主机使用 serverSocketFactory 创建套接字等待连接,而客户端与 RMI 主机通信时会使用 clientSocketFactory 创建连接套接字。
(2)clientSocketFactory,serverSocketFactory 同样是创建套接字,但是使用的位置不同,其用于导出远程对象,serverSocketFactory 用于在服务端建立套接字等待客户端连接,而 clientSocketFactory 用于调用端建立套接字发起连接。
3)根据配置参数获取 registry
4)构造对外发布的实例:构建对外发布的实例,当外界通过注册的服务名调用响应的方法时,RMI 会将请求引入此类来处理。
5)发布实例。

1.2.1 获取 registry

由于底层的封装,可以使用 LocateRegistry.createRegistry 创建 Registry 实例即可。如果 RMI 注册主机与发布的服务不在同一机器上,那么需要调用LocateRegistry.getRegistry(String host, int port,RMIClientSocketFactory csf)去远程获取 Registry 实例。

protected Registry getRegistry(String registryHost, int registryPort,
		@Nullable RMIClientSocketFactory clientSocketFactory, @Nullable RMIServerSocketFactory serverSocketFactory)
		throws RemoteException {

	// 如果 RMI 注册主机与发布的服务不在同一机器上,需要远程获取 Registry 实例
	if (registryHost != null) {
		// Host explicitly specified: only lookup possible.
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
		}
		// 尝试获取远程实例
		Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
		testRegistry(reg);
		return reg;
	}

	else {
		// 本地创建 RMI 的 Registry 实例。
		return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
	}
}

如果不是从另外的服务器上获取 registry,则需要创建本地的 Registry 实例。alwaysCreateRegistry 属性为 true 时,进行检测是否已经建立了对指定端口的连接,如果以建立则复用已经创建的示例,否则重新创建。如果 clientSocketFactory 为 null 则使用默认的连接工厂创建。

protected Registry getRegistry(int registryPort,
		@Nullable RMIClientSocketFactory clientSocketFactory, @Nullable RMIServerSocketFactory serverSocketFactory)
		throws RemoteException {

	if (clientSocketFactory != null) {
		// alwaysCreateRegistry 该参数的作用是在获取 Registry 实例的时候首先测试是否以及建立了对指定端口的
		// 的连接,如果已经建立则复用已经建立的实例,否则重新创建。
		if (this.alwaysCreateRegistry) {
			logger.debug("Creating new RMI registry");
			// 使用 clientSocketFactory 创建 Registry
			return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for RMI registry at port '" + registryPort + "', using custom socket factory");
		}
		synchronized (LocateRegistry.class) {
			try {
				// Retrieve existing registry.
				// 复用测试
				Registry reg = LocateRegistry.getRegistry(null, registryPort, clientSocketFactory);
				testRegistry(reg);
				return reg;
			}
			catch (RemoteException ex) {
				logger.trace("RMI registry access threw exception", ex);
				logger.debug("Could not detect RMI registry - creating new one");
				// Assume no registry found -> create new one.
				return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);
			}
		}
	}
	// 使用默认的连接工厂
	else {
		return getRegistry(registryPort);
	}
}

如果创建 Registry 时不需要指定自定义的套接字工厂,就直接调用 LocateRegistry.createRegistry(int port) 创建。

protected Registry getRegistry(int registryPort) throws RemoteException {
	// 直接创建
	if (this.alwaysCreateRegistry) {
		logger.debug("Creating new RMI registry");
		return LocateRegistry.createRegistry(registryPort);
	}
	if (logger.isDebugEnabled()) {
		logger.debug("Looking for RMI registry at port '" + registryPort + "'");
	}
	synchronized (LocateRegistry.class) {
		try {
			// Retrieve existing registry.
			// 复用测试,如果已经对指定端口进行绑定,则复用之前创建过的实例。
			Registry reg = LocateRegistry.getRegistry(registryPort);
			testRegistry(reg);
			return reg;
		}
		catch (RemoteException ex) {
			logger.trace("RMI registry access threw exception", ex);
			logger.debug("Could not detect RMI registry - creating new one");
			// Assume no registry found -> create new one.
			// 使用默认套接字工厂创建新的实例
			return LocateRegistry.createRegistry(registryPort);
		}
	}
}

1.2.2 初始化将要导出的实体对象

当请求某个 RMI 服务时,RMI 会根据注册的服务名称,将请求引导至远程对象处理类中,这个处理类是使用 getObjectToExport() 进行创建。

// RmiBasedExporter.java
protected Remote getObjectToExport() {
	// determine remote object
	// 如果配置的 service 的属性对应的类实现了 Remote 接口且没有配置 serviceInterface 属性
	if (getService() instanceof Remote &&
			(getServiceInterface() == null || Remote.class.isAssignableFrom(getServiceInterface()))) {
		// conventional RMI service
		return (Remote) getService();
	}
	else {
		// RMI invoker
		// 其它类型的对 service 进行封装
		if (logger.isDebugEnabled()) {
			logger.debug("RMI service [" + getService() + "] is an RMI invoker");
		}
		// RmiInvocationWrapper 间接实现了 Remote 接口
		return new RmiInvocationWrapper(getProxyForService(), this);
	}
}

请求处理类的的初始化主要处理规则为:如果配置的 service 的属性对应的类实现了 Remote 接口且没有配置 serviceInterface 属型,那么直接使用 service 作为处理类;否则使用 RmiInvocationWrapper 对 service 的代理类和当前类即 RmiServiceExporter 进行封装。

经过这样的封装,客户端和服务端便可以达成一致协议,当客户端检测到是 RmiInvocationWrapper 类型的 stub 的时候会调用 invoke 方法,在 invoke 方法中会使用代理类进行进一步处理。

当请求 RMI 服务时会由注册表 Registry 实例将请求转向之前注册的处理类去处理,即 RmiInvocationWrapper,然后由 invoke 方法进行处理。但是在该方法中不是直接使用 service 方法,而是通过代理再次将 service 封装。其中原因是,在创建代理的时添加了一个增强拦截器 RemoteInvocationTraceInterceptor,目的是为了对方法调用进行打印跟踪,当时如果直接在 invoke 方法中进行硬编码这些日志,会使代码看起来不优雅,而且耦合度高,使用代理的方法可以解决这样的问题,并且易于扩展。

// RemoteExporter.java
protected Object getProxyForService() {
	// 验证 service
	checkService();
	// 验证 serviceInterface
	checkServiceInterface();

	// 代理工厂 JDK 方式
	ProxyFactory proxyFactory = new ProxyFactory();
	// 添加代理接口
	proxyFactory.addInterface(getServiceInterface());

	// 如果设置了 registerTraceInterceptor 属性,则依据其属性判断是否添加默认拦截器
	// 否则根据是否添加了自定义的拦截器判断是否需要添加默认拦截器
	if (this.registerTraceInterceptor != null ? this.registerTraceInterceptor : this.interceptors == null) {
		// 默认添加的 拦截器
		// 加入代理的横切面 RemoteInvocationTraceInterceptor -- 用于打印方法调用的跟踪日志
		proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
	}
	if (this.interceptors != null) {
		// 加入拦截器 DefaultAdvisorAdapterRegistry
		AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
		for (Object interceptor : this.interceptors) {
			// interceptor -- 实现 Advice 接口 --> MethodInterceptor
			proxyFactory.addAdvisor(adapterRegistry.wrap(interceptor));
		}
	}

	// 设置要代理的目标类
	proxyFactory.setTarget(getService());
	proxyFactory.setOpaque(true);

	// 创建代理
	return proxyFactory.getProxy(getBeanClassLoader());
}

1.2.3 RMI 服务激活调用

由于之前 bean 初始化的时候做了服务名称绑定this.registry.bind(this.serviceName,this.exportedObject),其中 exportedObject 是被 RmiInvocationWrapper 进行封装的,即当其他服务器调用 serviceName 的 RMI 服务时, Java 会为我们封装其内部操作,而直接会将代码转向 RmiInvocationWrapper 的 invoke 方法中。

// RmiInvocationWrapper.java
public Object invoke(RemoteInvocation invocation) 
	throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {

	return this.rmiExporter.invoke(invocation, this.wrappedObject);
}

其中的 this.rmiExporter 是 RmiServiceExporter,invocation 为包含这需要激活的方法参数,而 wrappedObject 是之前封装的代理类。

// RmiBasedExporter.java
protected Object invoke(RemoteInvocation invocation, Object targetObject)
	throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

	return super.invoke(invocation, targetObject);
}

// RemoteInvocationBasedExporter.java
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.debug("Could not find target method for " + invocation, ex);
		}
		throw ex;
	}
	catch (IllegalAccessException ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("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;
	}
}

委托给 DefaultRemoteInvocationExecutor 的 invoke 方法

// DefaultRemoteInvocationExecutor.java
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);
}

// RemoteInvocation.java
public Object invoke(Object targetObject)
	throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

	// 反射获取要执行的方法
	Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes);
	// 反射执行
	return method.invoke(targetObject, this.arguments);
}

1.3 客户端实现

根据客户端的配置,入口类为 RmiProxyFactoryBean,同样查看其类的层次接口。
在这里插入图片描述
该类实现的比较重要的类为 FactoryBean,BeanClassLoaderAware,MethodInterceptor,InitializingBean 类。 其中实现了 InitializingBean,会在初始化 bean 时调用 afterProperties 方法进行初始化。

// RmiProxyFactoryBean.java
public void afterPropertiesSet() {
	// 父类逻辑
	super.afterPropertiesSet();
	Class<?> ifc = getServiceInterface();
	Assert.notNull(ifc, "Property 'serviceInterface' is required");
	// 根据设置的接口创建代理,并使用当前类 this 作为增强器
	this.serviceProxy = new ProxyFactory(ifc, this).getProxy(getBeanClassLoader());
}

同时,RmiProxyFactoryBean 又实现了 FactoryBean 接口,在获取 bean 时使用 getObject 方法获取实例。

// RmiProxyFactoryBean.java
public Object getObject() {
	return this.serviceProxy;
}

所以,当获取该 bean 时,首先通过 afterPropertiesSet 方法创建代理类,并使用当前类作为增强方法,而在调用该 bean 时其实返回的是代理类,既然调用的是代理类,那么又会使用当前 bean 作为增强器进行增强,即会调用 RmiProxyFactoryBean 的父类 RmiClientInterceptor 的 invoke 方法。

首先查看 RmiClientInterceptor 的 afterProperties 方法。

// RmiClientInterceptor.java
public void afterPropertiesSet() {
	super.afterPropertiesSet();
	prepare();
}

继续查看 UrlBasedRemoteAccessor 的 afterPropertiesSet 方法。

// UrlBasedRemoteAccessor.java
public void afterPropertiesSet() {
	if (getServiceUrl() == null) {
		throw new IllegalArgumentException("Property 'serviceUrl' is required");
	}
}

在该类中只完成了对 serviceUrl 的验证。因此逻辑是在 RmiClientInterceptor 的 prepare 方法中实现的。所以客户端的试下都是在 prepare() 方法中实现。继续分析 prepare() 方法。

1.3.1 通过代理拦截并获取 stub

在 UrlBasedRemoteAccessor 的 afterPropertiesSet 方法中完成了对 serviceUrl 验证。那么在 prepare 方法中完成了什么功能?

public void prepare() throws RemoteLookupFailureException {
	// Cache RMI stub on initialization?
	// 如果配置了 lookupStubOnStartup 属性便会在启动时寻找 stub
	if (this.lookupStubOnStartup) {
		// 寻找本地存根
		Remote remoteObj = lookupStub();
		if (logger.isDebugEnabled()) {
			if (remoteObj instanceof RmiInvocationHandler) {
				logger.debug("RMI stub [" + getServiceUrl() + "] is an RMI invoker");
			}
			else if (getServiceInterface() != null) {
				boolean isImpl = getServiceInterface().isInstance(remoteObj);
				logger.debug("Using service interface [" + getServiceInterface().getName() +
					"] for RMI stub [" + getServiceUrl() + "] - " +
					(!isImpl ? "not " : "") + "directly implemented");
			}
		}
		if (this.cacheStub) {
			// 将获取 stub 缓存
			this.cachedStub = remoteObj;
		}
	}
}

如果 lookupStubOnStartup 的属性为 true,那么获取 stub 的工作就会在系统启动时被执行缓存,从而提高响应速度。获取 stub 是 RMI 应用中的关键步骤,有两种方法可以进行。

  • 使用自定义的套接字工厂。如果使用这种方式,需要在构造 Registry 实例时将自定义的套接字工厂传入,并使用 Registry 中提供的 lookup方法来获取对应的 stub。
  • 直接使用 RMI 提供的标准方法:Naming.lookup(getServiceUrl())。
// RmiClientInterceptor.java
protected Remote lookupStub() throws RemoteLookupFailureException {
	try {
		Remote stub = null;
		// 如果自定义了套接字工厂,使用自定义的套接字工厂构建 Registry 实例
		if (this.registryClientSocketFactory != null) {
			// RMIClientSocketFactory specified for registry access.
			// Unfortunately, due to RMI API limitations, this means
			// that we need to parse the RMI URL ourselves and perform
			// straight LocateRegistry.getRegistry/Registry.lookup calls.
			URL url = new URL(null, getServiceUrl(), new DummyURLStreamHandler());
			String protocol = url.getProtocol();
			// 验证传输协议
			if (protocol != null && !"rmi".equals(protocol)) {
				throw new MalformedURLException("Invalid URL scheme '" + protocol + "'");
			}
			// 主机
			String host = url.getHost();
			// 端口
			int port = url.getPort();
			// 服务名称
			String name = url.getPath();
			// 截取名称
			if (name != null && name.startsWith("/")) {
				name = name.substring(1);
			}
			// 创建 Registry 实例
			Registry registry = LocateRegistry.getRegistry(host, port, this.registryClientSocketFactory);
			// 查找 stub
			stub = registry.lookup(name);
		}
		else {
			// Can proceed with standard RMI lookup API...
			// 如果没有自定义,使用默认创建获取 stub
			stub = Naming.lookup(getServiceUrl());
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Located RMI stub with URL [" + getServiceUrl() + "]");
		}
		return stub;
	}
	catch (MalformedURLException ex) {
		throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex);
	}
	catch (NotBoundException ex) {
		throw new RemoteLookupFailureException(
				"Could not find RMI service [" + getServiceUrl() + "] in RMI registry", ex);
	}
	catch (RemoteException ex) {
		throw new RemoteLookupFailureException("Lookup of RMI stub failed", ex);
	}
}

这里的 registryClientSocketFactory 与之前服务端的套接字工厂类似,这里的 registryClientSocketFactory 用来连接 RMI 服务器,用户通过实现 RmiClientSocketFactory 接口来控制用于连接的 socket 的各种参数。

1.3.2 增强进行远程连接

在 RmiProxyFactoryBean 的 bean 初始化时,创建了代理并将自身作为增强器加入了代理(其父类 RmiClientInterceptor 实现了 MethodInterceptor)。那么当客户端调用代理的接口中的某个方法时,就首先执行 RmiProxyFactoryBean 中的 invoke 方法。由 RmiClientInterceptor 方法实现。

// RmiClientInterceptor.java
public Object invoke(MethodInvocation invocation) throws Throwable {
	// 获取服务器中对应注册的 remote 对象,通过序列化传输
	Remote stub = getStub();
	try {
		// 激活方法
		return doInvoke(invocation, stub);
	}
	catch (RemoteConnectFailureException ex) {
		return handleRemoteConnectFailure(invocation, ex);
	}
	catch (RemoteException ex) {
		if (isConnectFailure(ex)) {
			return handleRemoteConnectFailure(invocation, ex);
		}
		else {
			throw ex;
		}
	}
}

当客户端使用接口进行方法调用时是通过 RMI 获取 stub 的,然后在通过 stub 中封装的信息进行服务器的调用,这个 stub 就是在构建服务器时发布的对象,那么,客户端调用时最关键的一步就是进行 stub 的获取了。

// RmiClientInterceptor.java
protected Remote getStub() throws RemoteLookupFailureException {
	// 如果有缓存
	if (!this.cacheStub || (this.lookupStubOnStartup && !this.refreshStubOnConnectFailure)) {
		return (this.cachedStub != null ? this.cachedStub : lookupStub());
	}
	else {
		synchronized (this.stubMonitor) {
			if (this.cachedStub == null) {
				// 获取 stub
				this.cachedStub = lookupStub();
			}
			return this.cachedStub;
		}
	}
}

当获取到 stub 后就可以进行远程方法的调用了。 Spring 中对于远程方法的调用其实是分两种情况进行考虑。

  • 获取到的是 RmiInvocationHandler 类型,从服务端获取的如果是该类型,表明服务端也同样使用了 Spring 构建,那么自然会使用 Spring 中的约定。Spring 中的处理方式委托给 doInvoke 方法。
  • 获取到的不是 RmiInvocationHandler 类型,则表明服务端构建 RMI 服务可能是通过普通方法或者借助于 Spring 外的低三方插件,这种的处理方式是反射。在 invocation 中包含了需要调用方法的各种信息,包括方法名以及参数,而调用的实体就是 stub,那么就可以通过反射进行远程调用。
// RmiClientInterceptor.java
protected Object doInvoke(MethodInvocation invocation, Remote stub) throws Throwable {
	// 获取到的是 RmiInvocationHandler 类型,从服务端获取的如果是该类型,
	// 表明服务端也同样使用了 Spring 构建,那么自然会使用 Spring 中的约定。
	// Spring 中的处理方式委托给 doInvoke 方法。
	if (stub instanceof RmiInvocationHandler) {
		// RMI invoker
		try {
			return doInvoke(invocation, (RmiInvocationHandler) stub);
		}
		catch (RemoteException ex) {
			throw RmiClientInterceptorUtils.convertRmiAccessException(
				invocation.getMethod(), ex, isConnectFailure(ex), getServiceUrl());
		}
		catch (InvocationTargetException ex) {
			Throwable exToThrow = ex.getTargetException();
			RemoteInvocationUtils.fillInClientStackTraceIfPossible(exToThrow);
			throw exToThrow;
		}
		catch (Throwable ex) {
			throw new RemoteInvocationFailureException("Invocation of method [" + invocation.getMethod() +
					"] failed in RMI service [" + getServiceUrl() + "]", ex);
		}
	}
	// 获取到的不是 RmiInvocationHandler 类型,
	// 则表明服务端构建 RMI 服务可能是通过普通方法或者借助于 Spring 外的低三方插件,这种的处理方式是反射。
	// 在 invocation 中包含了需要调用方法的各种信息,包括方法名以及参数,
	// 而调用的实体就是 stub,那么就可以通过反射进行远程调用。
	else {
		// traditional RMI stub
		try {
			return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, stub);
		}
		catch (InvocationTargetException ex) {
			Throwable targetEx = ex.getTargetException();
			if (targetEx instanceof RemoteException) {
				RemoteException rex = (RemoteException) targetEx;
				throw RmiClientInterceptorUtils.convertRmiAccessException(
						invocation.getMethod(), rex, isConnectFailure(rex), getServiceUrl());
			}
			else {
				throw targetEx;
			}
		}
	}
}

在服务端,Spring 将 RMI 导出的 Object 封装成了 RmiInvocationHandler(RmiInvocationWrapper)进行发布,那么客户端在获取 stub 的时候是包含了远程连接信息的代理类 RmiInvocationHandler,即当调用 RmiInvocationHandler 中的方法时会使用 RMI 中提供的代理进行远程连接,而此时,Spring 将代码引至 RmiInvocationHandler 接口的 invoke 方法。

// RmiClientInterceptor.java
protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
	throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {

	if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
		return "RMI invoker proxy for service URL [" + getServiceUrl() + "]";
	}

	// 将 methodInvocation 中的方法名及参数等信息重新封装到 RemoteInvocation
	// 并通过远程代理方法直接调用
	return invocationHandler.invoke(createRemoteInvocation(methodInvocation));
}

2 HttpInvoker

在 RMI 服务和基于 HTTP 服务(如 Hessian 和 Burlap)。一方面,RMI 使用标准的对象序列化,但是很难穿越防火墙;另一方面,Hessian/Burlap 能很好的穿过防火墙,但使用自己私有的一套对象序列化机制。所以 HttpInvoker 是一个新的远程调用模型,作为 Spring 的一部分,来执行基于 HTTP 的远程调用,并使用 Java 的序列化机制。

2.1 使用示例

1)创建对外接口

public interface IHttpInvokerTest {
	String getTestPo(String desp);
}

2)创建接口实现类

public class HttpInvokerTestImpl implements IHttpInvokerTest {
	@Override
	public String getTestPo(String desp) {
		return "getTestPo " + desp;
	}
}

3)创建服务端配置文件 applicationContext-server.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean name="httpInvokerTest" class="org.springframework.test.httpinvoker.HttpInvokerTestImpl" />
</beans>

4)在 WEB-INF 下创建 remote-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean name="/hit" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
		<property name="service" value="httpInvokerTest" />
		<property name="serviceInterface" value="org.springframework.test.httpinvoker.IHttpInvokerTest" />
	</bean>
</beans>

5)创建测试端 client.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean id="remoteService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
		<property name="serviceUrl" value="http://localhost:8080/httpInvokerTest/remoting/hit" />
		<property name="serviceInterface" value="org.springframework.test.httpinvoker.IHttpInvokerTest" />
	</bean>
</beans>

6)创建测试类

public class Test {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("test/httpinvoker/client.xml");
		IHttpInvokerTest test = (IHttpInvokerTest) context.getBean("remoteService");
		System.out.println(test.getTestPo("asd"));
	}
}

2.2 服务端实现

对于 Spring 中 HttpInvoker 服务的实现,先从服务端进行分析。 依据 remote-servlet.xml 中的配置,入口类为 HttpInvokerServiceExporter。查看类的层次结构图。
在这里插入图片描述
该类实现了 InitializingBean 接口以及 HttpRequestHandler 接口。
*InitializingBean:实现该接口,Spring 会在 bean 初始化时调用 afterPropertiesSet 方法。

  • HttpRequestHandler:实现该接口,在配置中以及将此接口配置成了 web 服务,当有相应的请求时,会将程序引导至 handleRequest 方法中。

2.2.1 创建代理

// RemoteInvocationSerializingExporter.java
public void afterPropertiesSet() {
	prepare();
}

public void prepare() {
	this.proxy = getProxyForService();
}

// RemoteExporter.java
protected Object getProxyForService() {
	// 验证 service
	checkService();
	// 验证 serviceInterface
	checkServiceInterface();

	// 代理工厂 JDK 方式
	ProxyFactory proxyFactory = new ProxyFactory();
	// 添加代理接口
	proxyFactory.addInterface(getServiceInterface());

	// 如果设置了 registerTraceInterceptor 属性,则依据其属性判断是否添加默认拦截器
	// 否则根据是否添加了自定义的拦截器判断是否需要添加默认拦截器
	if (this.registerTraceInterceptor != null ? this.registerTraceInterceptor : this.interceptors == null) {
		// 默认添加的 拦截器
		// 加入代理的横切面 RemoteInvocationTraceInterceptor -- 用于打印方法调用的跟踪日志
		proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
	}
	if (this.interceptors != null) {
		// 加入拦截器 DefaultAdvisorAdapterRegistry
		AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
		for (Object interceptor : this.interceptors) {
			// interceptor -- 实现 Advice 接口 --> MethodInterceptor
			proxyFactory.addAdvisor(adapterRegistry.wrap(interceptor));
		}
	}

	// 设置要代理的目标类
	proxyFactory.setTarget(getService());
	proxyFactory.setOpaque(true);

	// 创建代理
	return proxyFactory.getProxy(getBeanClassLoader());
}

通过上述 3 个方法,可以看到,初始化过程中实现的逻辑主要是创建了一个代理,代理中封装了对于特定请求的处理方法以及接口等信息,而这个代理的关键目的是加入了 RemoteInvocationTraceInterceptor 增强器。主要是对增强的目标方法进行一些相关信息的日志打印。

2.2.2 处理来自客户端的 request

当有 web 请求时,根据配置中的规则会把路径匹配的访问直接引入对应的 HttpRequestHandler 中。本例中的 web 请求与普通的 web 请求是有区别的,因此此处的请求包含着HttpInvoker 的处理过程。

// HttpInvokerServiceExporter.java
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	try {
		// 从 request 中读取序列化对象
		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);
	}
}

在 handleRequest 方法中,将请求的方法即 RemoteInvocation 对象,从客户端序列化并提供 web 请求出入服务端,服务端在对传过来的序列化对象进行反序列化还原 RemoteInvocation 示例,然后通过实例中的相关信息进行相关方法的调用,并将结果返回给客户端。程序执行的框架如下。

1)从 request 中读取序列化对象
主要是从 HttpServletRequest 提取相关信息,即 RemoteInvocation 对象的序列化信息以及反序列化的过程。

// HttpInvokerServiceExporter.java
protected RemoteInvocation readRemoteInvocation(HttpServletRequest request)
	throws IOException, ClassNotFoundException {

	return readRemoteInvocation(request, request.getInputStream());
}

protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is)
	throws IOException, ClassNotFoundException {

	// 创建对象输入流
	ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is));
	try {
		// 从输入流中读取序列化对象
		return doReadRemoteInvocation(ois);
	}
	finally {
		ois.close();
	}
}

// RemoteInvocationSerializingExporter.java
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;
}

2)执行调用
根据反序列化方式得到的 RemoteInvocation 对象中的信息,进行方法调用。此时调用的实体并不是服务接口或服务类,而是之前在初始化时构造的封装了服务接口以及服务类的代理。完成了 RemoteInvocation 实例的提取,就可以通过 RemoteInvocation 实例中提供的信息进行方法调用了。

// RemoteInvocationBasedExporter.java
protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
	try {
		// 激活代理类中对应 invocation 中的方法
		Object value = invoke(invocation, targetObject);
		// 封装结果以便于序列化
		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 {
		// 使用执行器执行
		return getRemoteInvocationExecutor().invoke(invocation, targetObject);
	}
	catch (NoSuchMethodException ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Could not find target method for " + invocation, ex);
		}
		throw ex;
	}
	catch (IllegalAccessException ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("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;
	}
}

该方法需要说明的地方。

  • 对应方法的激活也就是 invoke 方法的调用,经过层层环绕,最终还是实现了一个熟知的调用 invocation.invoke(targetObject),也就是执行 RemoteInvocation 类中的 invoke 方法。但是,在对应当前方法的 targetObject 参数,此 targetObject 是代理类,调用代理类的时候需要考虑之增强方法的调用。
  • 对于返回结果需要使用 RemoteInvocationResult 进行封装,之所以需要进行封装,因为无法保证所以操作的返回结果都继承了 Serializable 接口,所以需要使用 RemoteInvocationResult 类进行同一封装。

3)将结果的序列化对象写入输入流
同样这里也包括结果的序列化过程。

// HttpInvokerServiceExporter.java
protected void writeRemoteInvocationResult(
	HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result)
		throws IOException {

	response.setContentType(getContentType());
	writeRemoteInvocationResult(request, response, result, response.getOutputStream());
}

protected void writeRemoteInvocationResult(
	HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os)
		throws IOException {

	// 获取输入流
	ObjectOutputStream oos =
			createObjectOutputStream(new FlushGuardedOutputStream(decorateOutputStream(request, response, os)));
	try {
		// 将序列化对象写入输入流
		doWriteRemoteInvocationResult(result, oos);
	}
	finally {
		oos.close();
	}
}

protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos)
	throws IOException {

	oos.writeObject(result);
}

2.3 客户端实现

在客户端一个比较重要的任务就是构建 RemoteInvocation 实例,并传送到服务端。根据配置信息,入口类为 HttpInvokerProxyFactoryBean 类。查看其层次结构图。
在这里插入图片描述
该类实现了 FactoryBean,InitializingBean,MethodInterceptor 接口。实现了 InitializingBean 接口的初始化逻辑。

首先分析 InitializingBean 接口。

public void afterPropertiesSet() {
	super.afterPropertiesSet();
	Class<?> ifc = getServiceInterface();
	Assert.notNull(ifc, "Property 'serviceInterface' is required");
	// 代理类
	this.serviceProxy = new ProxyFactory(ifc, this).getProxy(getBeanClassLoader());
}

// HttpInvokerClientInterceptor
public void afterPropertiesSet() {
	super.afterPropertiesSet();

	// Eagerly initialize the default HttpInvokerRequestExecutor, if needed.
	getHttpInvokerRequestExecutor();
}

// HttpInvokerClientInterceptor
public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() {
	if (this.httpInvokerRequestExecutor == null) {
		SimpleHttpInvokerRequestExecutor executor = new SimpleHttpInvokerRequestExecutor();
		executor.setBeanClassLoader(getBeanClassLoader());
		this.httpInvokerRequestExecutor = executor;
	}
	return this.httpInvokerRequestExecutor;
}

在 afterPropertiesSet 方法中主要创建了一个代理,该代理封装了配置的服务接口,并使用当前类即 HttpInvokerClientInterceptor(HttpInvokerProxyFactoryBean 的父类)作为增强。同样其有实现了 FactoryBean 接口,所以调用 getBean 时,返回 getObject 中的实例。

public Object getObject() {
	return this.serviceProxy;
}

因此,当使用以下代码时,会转向 HttpInvokerClientInterceptor 的 invoke 方法。

ApplicationContext context = new ClassPathXmlApplicationContext("test/httpinvoker/client.xml");
IHttpInvokerTest test = (IHttpInvokerTest) context.getBean("remoteService");
System.out.println(test.getTestPo("asd"));

这时,所有的逻辑以及转向了对于增强器也就是 HttpInvokerClientInterceptor 中 invoke 方法的执行。该方法的主要功能就是将调用信息封装在 RemoteInvocation 中,发送给服务端等待返回结果。

// HttpInvokerClientInterceptor.java
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 实例。
因为是代理中增强方法的调用,调用的方法及参数会在代理中封装至 RemoteInvocation 实例中,并在增强器中进行传递,也就是意味着当程序进入 invoke 方法时已经包含了调用的接口的相关信息,所有首先要做的是将 MethodInvocation 中的信息提取并构建 RemoteInvocation。
2)远程执行方法。
3)提取结果。
考虑到序列化的问题,在 Spring 中约定使用 HttpInvoker 方式进行远程方法调用时,结果使用 RemoteInvocationResult 进行封装,那么在提取结果后还需要从封装的结果中提取对应的结果。该3个步骤中最关键的就是远程方法的执行。

而在这 3 个步骤中最关键的就是远程方法的执行。执行远程调用的首要步骤就是将调用方法的实例写入输出流中。

// HttpInvokerClientInterceptor.java
protected RemoteInvocationResult executeRequest(
		RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {

	return executeRequest(invocation);
}

protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception {
	// 默认是 SimpleHttpInvokerRequestExecutor
	// 可以在配置文件中设置成 HttpComponentsHttpInvokerRequestExecutor
	return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
}

// SimpleHttpInvokerRequestExecutor - AbstractHttpInvokerRequestExecutor
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。

// SimpleHttpInvokerRequestExecutor Java 自带的 http 请求实现
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());
}

HttpComponentsHttpInvokerRequestExecutor 三方 jar 的实现。

// HttpComponentsHttpInvokerRequestExecutor.java
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,构建过程中可以设置一些必要的参数。

// HttpComponentsHttpInvokerRequestExecutor.java
protected HttpPost createHttpPost(HttpInvokerClientConfiguration config) throws IOException {
	// 设置需要访问的 url
	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) {
			// 加入 Accept-Language 属性
			httpPost.addHeader(HTTP_HEADER_ACCEPT_LANGUAGE, locale.toLanguageTag());
		}
	}

	if (isAcceptGzipEncoding()) {
		// 加入 Accept-Encoding 属性
		httpPost.addHeader(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
	}

	return httpPost;
}

2)设置 RequestBody。
构建好 PostMethed 实例后便可以将存储 RemoteInvocation 实例的序列化对象的输入流设置进去。需要声明 ContentType 为 application/x-java-serialized-object。

protected void setRequestBody(
		HttpInvokerClientConfiguration config, HttpPost httpPost, ByteArrayOutputStream baos)
		throws IOException {

	ByteArrayEntity entity = new ByteArrayEntity(baos.toByteArray());
	// 将序列化流加入到 postMethod 中,
	// 声明 ContentType 为 application/x-java-serialized-object
	entity.setContentType(getContentType());
	httpPost.setEntity(entity);
}

3)执行远程方法。
提供 HttpClient 所提供的方法来执行原方法。

protected HttpResponse executeHttpPost(
		HttpInvokerClientConfiguration config, HttpClient httpClient, HttpPost httpPost)
		throws IOException {
	
	// 调用 HttpClient 的方法执行
	return httpClient.execute(httpPost);
}

4)远程相应验证。
对于 HTTP 调用的响应码处理,大于 300 则是非正常调用的响应码

protected void validateResponse(HttpInvokerClientConfiguration config, HttpResponse response)
		throws IOException {

	StatusLine status = response.getStatusLine();
	// 大于 300 则是非正常调用的响应码
	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 {
	
	// 经过 GZip 压缩
	if (isGzipResponse(httpResponse)) {
		return new GZIPInputStream(httpResponse.getEntity().getContent());
	}
	else {
		return httpResponse.getEntity().getContent();
	}
}

6)提取返回结果。
主要是从输入流中提取响应的序列化信息。

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 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值