1、Spring远程调用的设计概览
Spring为使用各种技术的远程支持提供集成类。远程支持简化了由通常的(Spring) pojo实现的支持远程的服务的开发。目前,Spring支持以下远程处理技术:
- Remote Method Invocation (RMI) : 通过使用RmiProxyFactoryBean和RmiServiceExporter, Spring同时支持传统的RMI(使用
java.rmi.Remote
远程接口和java.rmi.RemoteException
)以及通过RMI调用程序进行的透明远程处理(使用任何Java接口)。 - Spring’s HTTP invoker :Spring提供了一种特殊的远程处理策略,它允许通过HTTP进行Java序列化,支持任何Java接口(就像RMI调用程序所做的那样)。相应的支持类是HttpInvokerProxyFactoryBean和HttpInvokerServiceExporter。
- Hessian : 通过使用Spring的HessianProxyFactoryBean和HessianServiceExporter,可以通过Caucho提供的基于http的轻量级二进制协议透明地发布服务。
- JAX-WS : Spring通过JAX-WS为web服务提供远程支持。
- JMS : 通过JmsInvokerServiceExporter和JmsInvokerProxyFactoryBean类支持使用JMS作为底层协议的远程处理。
- AMQP : Spring AMQP项目支持使用AMQP作为底层协议的远程处理。
如果从Spring实现架构的角度来看, Spring对这些方案的具体支持可以体现在如图所示的一系列相关类的设计中。在这个类的层次关系中,首先可以看到的是, Spring为RemotingSupport设计的一系列子类,这一系列的子类是 Spring用来封装远端服务客户端所使用的。相应地,在具体实现上,这些类有一系列的拦截器实现,在这些拦截器中完成了对客户端远端调用的主要封装,和 Spring提供的与具体远端调用实现对应的 FactoryBean一起,构成了远端调用客户端的基础设施。其次,在这个类的层次关系中,还可以看到,在RemoteService下有一系列子类,这些子类是为远端调用的服务器端的实现提供导出服务的,通过这个服务导出的设计支持,客户端可以完成对这些导出的远端服务的调用。
下面对HTTP调用器、Hessian的远端调用、RMI远端调用的实现原理进行分析。在这些实现原理的分析中,大致包含了基本配置、客户端实现和服务器端实现几个基本的部分。在分析中,会看到上图中出现的很多类,比如封装HTTP调用器客户端的HttpInvokerProxyFactoryBean和HttpInvokerClientInterceptor,为Hessian提供客户端封装服务HessianProxyFactoryBean和HessianClientInterceptor,为封装RMI提供客户端服务的RmiProxyFactoryBean和RmiClientInterceptor等。Spring对不同的远端调用进行封装,基本上都采用了类似的模式来完成,比如在客户端,都是通过相关的ProxyFactoryBean和ClientInterceptor来完成的,在服务器端是通过ServiceExporter来导出远端的服务对象的。有了这些统一的命名规则,应用配置和使用远端调用会非常方便,同时,通过对这些 Spring远端调用基础设施实现原理的分析,还可以看到一些常用处理方法的技术实现,比如对代理对象的使用、拦截器的使用、通过 afterPropertiesSet来启动远端调用基础设施的建立等,这些都是在 Spring中常用的技术。
从图中可以看到,在Spring远端调用的客户端已经为常用的远端调用解决方案提供了一系列的ProxyFactoryBean,这些ProxyFactoryBean通过AOP的方式设计了拦截器来封装对远端调用客户端的处理,还会看到在其中都会继承对应的拦截器,这些拦截器作为ProxyFactoryBean的基类,是实现远端调用客户端封装的重点所在。
再看看远端调用的服务器端,其类继承体系如下图所示。
服务器端的设计与客户端的设计不同在于,由于在 Spring远端调用的设计中采用的是B/S结构,也就是说,在服务器端,需要对客户端的请求进行响应,就像在MVC框架中用到的DispatcherServerlet一样,因此会看到和HTTP请求响应相关的设计,比如HttpHandler/HttpRequestHandler的设计等。关于这些设计的具体实现,后面将向大家进行详细分析。
2、Spring Http调用器的实现
2.1、 设计原理和实现过程
作为Spring远端调用的一种实现方式,最为简单的应该是通过HTTP调用器实现;在这种实现中不需要依赖于第三方组件,井且对于远程调用的实现来说,只需要通过HTTP协议就能实现,基于HTTP协议的远程调用的封装,Spring已经完成了。所以这种HTTP调用器的使用,对Spring用户来说,是即开即用的;对用户来说,只需要在IoC容器的Bean定义中,配置好相应的HTTP调用器的客户端,并将服务器端的调用接口通过HttpInvokerServiceExporter导出到MVC模块中,就可以使用了。
在这个模块的设计中,我们可以看到为了支持应用对HTTP调用器的使用,在Spring中设计了HttpInvokerProxyFactoryBean,从名字上就可以看到这是一个ProxyFactoryBean,它会使用Spring AOP来对HTTP调用器的客户端进行封装,既然使用到AOP,就需要设置代理对象井且为代理方法设置拦截器,在HttpInvokerProxyFactoryBean中,设置的拦截器是HttpInvokerClientInterceptor,在这个拦截器中,会封装客户端的基本实现,比如打开HTTP链接,通过HTTP客户端,将请求对象序列化,将请求传递到服务器端;同样地在接收到服务器的HTTP响应后,拦截器会把对象反序列化,并且把远端调用服务器端返回的对象交给应用使用,从而完成一个完整通过HTTP协议来完成远程调用的过程。对于服务器端的设计,也是同样的,这部分设计是通过HttpInvokerServiceExporter来完成的,在这个Exporter中,会导出远端的服务器服务,比如相应的远端服务URL请求,供客户端的服务对象,服务接口配置等,这些都是由HttpInvokerServiceExporter来完成的,而具体说来,这个Exporter,因为需要处理HTTP的服务请求,它的设计是需要依赖Spring的MVC模块的,具体说来,在这个HttpInvokerServiceExporter中,会封装MVC框架的DispatcherServlet,并且设置相应的Controller,这个控制器Controller执行相应的HTTP请求处理,比如,接收服务请求,将服务请求中对象反序列化,交给服务器端的服务对象完成请求,最后把生成的结果通过序列化通过HTTP传回到客户端。
在这个HTTP调用器的设计中,我们可以看到,并不需要依赖其他的第三方模块,主要通过HTTP协议,通过HTTP协议的对象序列化和反序列化,以及Spring的MⅤC模块来处理HTTP请求和响应的。了解了这些,就不难深入地了解HTTP远端调用的实现了,具体的设计和实现过程,我们会在下面的部分向大家详细地分析。
2.2、配置HTTP调用器客户端
顾名思义,HTP调用器是基于HTTP协议提供的一种远端调用方案。使用HTTP调用器和使用Java RMI一样,需要使用Java的序列化机制来完成客户端和服务器端的通信。在Spring中,我们从客户端的基础配置模块HttpInvokerProxyFactoryBean入手,对HTTP调用器的实现原理进行分析。这个HttpInvokerProxyFactoryBean是一个FactoryBean,这个工厂Bean的作用是为客户端HTTP调用器提供服务配置,使用我们熟悉的FactoryBean的设计方式来封装客户端需要的远端代理对象,这是 Spring远端调用模块处理客户端封装的一个模式。下面我们简单回顾一下Httpinvoker的使用,在使用HttpInvoker时,首先需要配置客户端的HttpInvokerProxyFactoryBean,然后需要设置应用Bean对ProxyFactoryBean的配置。
<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
<bean id="yourBean" class="yourClass">
<property name="remoteService" ref="httpInvokerProxy"/>
</bean>
在代码清单中,可以看到对HttpInvokerProxyFactoryBean的使用配置,比如需要配置客户端访问的远端服务的URL地址,设置远端调用服务的服务接口,然后把这个ProxyFactory设置到客户端应用Bean的remoteService,属性中去,有了这个设置,客户端应用就已经准备就绪了,它就可以像调用本地调用一样,享用远端的服务了。
具体来说,在这些对IoC容器的配置中,HttpInvokerProxyFactoryBean中封装了对应的远端服务的信息,比如域名、端口号和服务所在的URL,这些都是访问远端服务调用所需要的信息。同时,由于使用的是HttpInvoker,因此可以看到,在URL中,指定的协议是HTTP协议。对访问远端服务调用的客户端而言,它只要持有HttpInvokerProxyFactoryBean提供的代理对象,就可以方便地使用远端调用,使用起来也很简单。对客户端来说,就像本地调用一样,在远端调用过程中,发生的数据通信以及与远端服务的交互都被 Spring使用 Proxy代理类进行了封装,并对客户端是透明的。具体地说,这些封装实现在HttpInvokerProxyFactoryBean这个FactoryBean生产出来的代理对象中完成,下面看看这个HttpInvokerProxyFactoryBean是怎样工作的。
2.3、HTTP调用器客户端的实现
了解了HTP调用器的基本设置后,下面看看在HttpInvokerProxyFactoryBean,是如何完成对远端服务客户端的封装的。
在如图所示的设计时序中可以看到,在HttpInvokerProxyFactoryBean中设置了serviceProxy对象作为远端服务的本地代理对象。这个代理对象的设置是在依赖注入完成以后,通过afterPropertiesSet来对远端调用进行设置。这个afterPropertiesSet完成的设置包括:使用ProxyFactory生成代理对象、为代理对象设置代理接口方法,并把ProxyFactory生成的代理对象设置给serviceProxy。可以看到,在我们熟悉的FactoryBean的接口方法getobject()的实现中,把这个serviceτoxy对象,也就是生成的代理对象,提供给了访问远端调用的客户端应用。在生成这个AOP的代理对象并设置好为其配置的拦截器之后,远端服务巳经可以工作了,在代理对象对应的远端方法被调用的时候,实际上会调用serviceProxy对象的代理方法。这个代理方法会触发HttpInvokerClientInterceptor的拦截器方法,在拦截器中封装了具体对远端调用,比如通过HTTP协议调用远端方法的处理,这些处理通过调用SimpleHttpInvokerRequestExecutor的executeRequest()方法来实现。而最终的实现是在doExecuteRequest()方法中封装完成的。
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");
//这里使用ProxγFactory来生成远端代理对象,注意这个this,因为HttpInvokerProxyFactoryBean的基类是HttpInvokerClientInterceptor
//所以代理类的拦截器被设置为HttpInvokerClientInterceptor
this.serviceProxy = new ProxyFactory(ifc, this).getProxy(getBeanClassLoader());
}
//FactoryBean生产对象的入口。返回的是serviceProxy对象,这是一个代理对象
@Override
@Nullable
public Object getObject() {
return this.serviceProxy;
}
@Override
public Class<?> getObjectType() {
return getServiceInterface();
}
@Override
public boolean isSingleton() {
return true;
}
}
通过FactoryBean的封装,getobject()实际上取得的是一个代理对象。在代码实现中可以看到,为这个代理对象配置了一个拦截器 HttpInvokerClientInterceptor,在这个拦截器中,拦截了对代理对象的方法调用。我们到拦截器的实现中去看看它具体做了什么:
public class HttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor
implements MethodInterceptor, HttpInvokerClientConfiguration {
@Nullable
private String codebaseUrl;
@Nullable
private HttpInvokerRequestExecutor httpInvokerRequestExecutor;
//将代码库URL设置为从本地未找到的类中下载的类。
//可以由多个url组成,由空格分隔。
//遵循RMI的动态类下载代码库约定。
//与RMI不同,在RMI中,服务器决定类下载的URL(通过“java.rmi.server.codebase”系统属性),
//是客户端决定这里的代码库URL。服务器通常与服务URL相同,只是指向不同的路径。
public void setCodebaseUrl(@Nullable String codebaseUrl) {
this.codebaseUrl = codebaseUrl;
}
@Override
@Nullable
public String getCodebaseUrl() {
return this.codebaseUrl;
}
//将HttpInvokerRequestExecutor实现设置为用于执行远程调用。默认值是SimpleHttpInvokerRequestExecutor。
//或者,考虑使用HttpComponentsHttpInvokerRequestExecutor来满足更复杂的需求。
public void setHttpInvokerRequestExecutor(HttpInvokerRequestExecutor httpInvokerRequestExecutor) {
this.httpInvokerRequestExecutor = httpInvokerRequestExecutor;
}
//返回此远程访问器使用的HttpInvokerRequestExecutor。
//如果尚未初始化执行程序,则创建默认SimpleHttpInvokerRequestExecutor。
public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() {
if (this.httpInvokerRequestExecutor == null) {
SimpleHttpInvokerRequestExecutor executor = new SimpleHttpInvokerRequestExecutor();
executor.setBeanClassLoader(getBeanClassLoader());
this.httpInvokerRequestExecutor = executor;
}
return this.httpInvokerRequestExecutor;
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
//如果需要,急切地初始化默认HttpInvokerRequestExecutor。
getHttpInvokerRequestExecutor();
}
//代理对象的方法调用入口
@Override
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);
}
}
}
//通过HttpInvokerRequestExecutor执行给定的远程调用。
//这个实现委托给executeRequest(RemoteInvocation)。
//可以被覆盖以对特定的原始方法调用作出响应。
protected RemoteInvocationResult executeRequest(
RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {
return executeRequest(invocation);
}
//通过{@link HttpInvokerRequestExecutor}执行给定的远程调用。
//可以在子类中重写,以将不同的配置对象传递给执行程序。
//或者,在这个访问器的子类中添加更多的配置属性:默认情况下,访问器将自己作为配置对象传递给执行器。
protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception {
return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
}
//将给定的HTTP调用程序访问异常转换为适当的Spring RemoteAccessException。
@Nullable
protected RemoteAccessException convertHttpInvokerAccessException(Throwable ex) {
if (ex instanceof ConnectException) {
return new RemoteConnectFailureException(
"Could not connect to HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
}
if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError ||
ex instanceof InvalidClassException) {
return new RemoteAccessException(
"Could not deserialize result from HTTP invoker remote service [" + getServiceUrl() + "]", ex);
}
if (ex instanceof Exception) {
return new RemoteAccessException(
"Could not access HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
}
//对于任何其他可抛出的,例如OutOfMemoryError:让它按原样传播。
return null;
}
}
对于HttpInvokerClientInterceptor拦截器,其触发的拦截行为在invoke()回调方法中实现。在这个回调方法中,所做的处理是通过HTTP请求触发远端服务,在触发远端服务的时候,通过生成一个MethodInvocation对象来封装当前代理方法调用的具体调用场景。然后,代理对象会把这个MethodInvocation对象作为参数,通过HTTP的Java对象序列化机制传输到服务器端,从而交给远端服务去执行;在远端服务执行完成后,服务器端的服务对象会把执行结果返回,这个执行结果会封装在RemoteInvocationResult对象中,同样也会通过HTTP的Java对象序列化机制回送到客户端,从而交由客户端应用来使用。
我们可以看到,RemoteInvocation是由DefaultRemotelnvocationFactory来创建的,这个RemoteInvocation实际上是一个数据对象。这个数据对象中封装了调用的具体信息,比如调用方法名、参数、参数类型等。这些封装都可以在 Remotelnvocation的实现中看到。远端调用的具体实现过程是由executeRequest方法来完成的。在代码中可以看到,是通过使用HttpInvokerRequestExecutor这个对象来触发远端调用的。
可以看到,HttpInvokerRequestExecutor是一个SimpleHttpInvokerRequestExecutor;也就是说,我们可以在SimpleHttpInvokerRequestExecutor中看到executeRequest()方法调用的实现:
public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
private int connectTimeout = -1;
private int readTimeout = -1;
//设置底层URLConnection的连接超时(单位为毫秒)。超时值0指定无限超时。
//URLConnection#setConnectTimeout(int)
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
//设置底层URLConnection的读取超时(单位为毫秒)。超时值0指定无限超时。
//URLConnection#setReadTimeout(int)
public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}
//通过标准的{@link HttpURLConnection}执行给定的请求。
//该方法实现了基本的处理流程:
//实际的工作发生在这个类的模板方法中。
@Override
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());
}
//为给定的远程调用请求打开HttpURLConnection
protected HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException {
URLConnection con = new URL(config.getServiceUrl()).openConnection();
if (!(con instanceof HttpURLConnection)) {
throw new IOException(
"Service URL [" + config.getServiceUrl() + "] does not resolve to an HTTP connection");
}
return (HttpURLConnection) con;
}
//准备给定的HTTP连接。
//默认实现将POST指定为方法,将“application/x-java-serialized-object”指定为“Content-Type”报头,
//将给定的内容长度指定为“Content-Length”报头。
protected void prepareConnection(HttpURLConnection connection, int contentLength) throws IOException {
if (this.connectTimeout >= 0) {
connection.setConnectTimeout(this.connectTimeout);
}
if (this.readTimeout >= 0) {
connection.setReadTimeout(this.readTimeout);
}
connection.setDoOutput(true);
connection.setRequestMethod(HTTP_METHOD_POST);
connection.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType());
connection.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength));
LocaleContext localeContext = LocaleContextHolder.getLocaleContext();
if (localeContext != null) {
Locale locale = localeContext.getLocale();
if (locale != null) {
connection.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, locale.toLanguageTag());
}
}
if (isAcceptGzipEncoding()) {
connection.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
}
}
//将给定的序列化远程调用设置为请求体。
//默认实现只是将序列化的调用写入HttpURLConnection的OutputStream。
//例如,可以重写它来编写特定的编码并可能设置适当的HTTP请求头。
protected void writeRequestBody(
HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos)
throws IOException {
baos.writeTo(con.getOutputStream());
}
//验证HttpURLConnection对象中包含的给定响应,如果它不对应于成功的HTTP响应,则抛出异常。
//默认实现拒绝任何超过2xx的HTTP状态代码,以避免解析响应体并试图从损坏的流中反序列化。
protected void validateResponse(HttpInvokerClientConfiguration config, HttpURLConnection con)
throws IOException {
if (con.getResponseCode() >= 300) {
throw new IOException(
"Did not receive successful HTTP response: status code = " + con.getResponseCode() +
", status message = [" + con.getResponseMessage() + "]");
}
}
//从给定的已执行远程调用请求中提取响应体。
//默认实现只是从HttpURLConnection的InputStream读取序列化调用。
//如果响应被识别为GZIP响应,则InputStream将被包装在GZIPInputStream中。
protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con)
throws IOException {
if (isGzipResponse(con)) {
// GZIP response found - need to unzip.
return new GZIPInputStream(con.getInputStream());
}
else {
// Plain response found.
return con.getInputStream();
}
}
//确定给定的响应是否是GZIP响应。
//默认实现检查HTTP“Content-Encoding”报头是否包含“gzip”(在任何大小写中)。
protected boolean isGzipResponse(HttpURLConnection con) {
String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING);
return (encodingHeader != null && encodingHeader.toLowerCase().contains(ENCODING_GZIP));
}
}
在SimpleHttpInvokerRequestExecutor的实现中,封装了整个HTTP调用器客户端实现的基本过程:首先,它会打开一个HTTP链接,接着它会通过HTTP的对象序列化,把封装好的调用场景,也就是在前面生成的Remotelnvocation传送到服务器端,请求服务响应;其次在服务器端完成服务以后,会把执行结果,以对象序列化的方式回送给HTTP响应(HttpResponse);最后;客户端应用;也就是在这个executeRequest()方法中,会从HTTP响应中读出远端服务的执行结果。在这里,使用了HTTP的请求响应机制来实现对远端方法的访问与执行结果的返回,这个过程,与我们熟悉的 Servlet实现机制大致一样。对比其他的Spring远端调用方案的实现,HttpInvoker的实现特点在于,它使用的是Java虚拟机提供的基本特性,比如HTTP的传输机制,对象的序列化和反序列化,相对于在Spring中提供的像Hessian这样的方案而言,使用HTTP调用器完成远端调用的应用不需要引用第三方类库,因此使用起来是非常方便的。
可以看到,对远端服务执行结果的返回对象的处理是在AbstractHttpInvokerRequestExecutor中实现的。在通过HrTP把对象反序列化之后,会把远端的服务结果封装成 RemoteInvocationResult对象,这部分实现如下所示。
//从给定的InputStream反序列化RemoteInvocationResult对象。
给decorateInputStream一个首先修饰流的机会(例如,用于自定义加密或压缩)。
//通过createObjectInputStream创建 ObjectInputStream并调用doReadRemoteInvocationResult来实际读取对象。
//可以为调用的自定义序列化重写。
protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, @Nullable String codebaseUrl)
throws IOException, ClassNotFoundException {
ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl);
try {
return doReadRemoteInvocationResult(ois);
}
finally {
ois.close();
}
}
//从给定的ObjectInputStream执行调用对象的实际读取。
//默认实现只是调用readObject。
//可以覆盖自定义包装器对象的反序列化,而不是普通调用,例如支持加密的持有者。
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对象的反序列化,从HTTP响应中得到服务执行结果的过程。
看到这里,我们大致了解了在HTTP调用器客户端完成远端调用的基本过程。简单来说,这个过程是这样的:首先由客户端应用调用代理方法,在调用发生以后,代理类会先运行拦截器,对代理的方法调用进行拦截。在拦截器的拦截行为中,先对本地发生的方法调用进行封装,具体来说,就是封装成MethodInvocation对象。然后,把这个MethodInvocation对象,通过序列化和HTTP请求发送到服务器端,在服务器端的处理完成以后,会通过HTTP响应返回处理结果,这个处理结果被封装在RemotelnvocationResult对象中。整个过程是一个典型的HTTP客户机服务器实现的基本过程,只是在这里,传输的数据是序列化的Java对象,而不是那些常见的经过URL请求后得到的HTML页面响应而已。
2.4、配置HTTP调用器远端服务器端
在了解了客户端的实现原理以后,我们来看看在服务器端是怎样实现对远端服务请求的服务响应的。同样,与客户端需要使用loC容器进行配置才能使用远端服务一样,在服务器端也是需要做一些简单的配置。通过这些配置,可以把远端调用服务在服务器端导出,暴露给客户端使用。与客户端的远端调用的配置很类似,在服务器端的配置也很简单,我们在这里简单地回顾一下。
<bean name="/remoteServiceURL" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="serviceBean"/>
<property name="serviceInterface" value="yourInterface"/>
</bean>
在配置中,可以看到,需要设置远端服务对应的URL,还需要设置提供服务的Bean,这个Bean由应用来完成服务的实现。可以看到,在这个设置中还有一个叫servicebean的Bean,调用这个Bean的服务方法,从而完成服务的最终执行。同时,需要在 servicelnterface中,设置提供的服务接口方法,设置 Proxy的代理方法调用。
通过这些简单的配置就可以在服务器端导出远端服务,具体的远端服务是通过service属性中配置的Bean来提供的。这个服务Bean封装在HttpInvokerServiceExporter中,这个HttpInvokerServiceExporter封装了对HTTP协议的处理以及Java对象的序列化功能,然后通过Proxy代理类进行封装,从而成为HTTP调用器服务器端的基础设施。
2.5、HTTP调用器服务器端的实现
在服务器端使用Spring Http远端调用,需要配置HttpInvokerServiceExporter,作为远端服务的服务导出器。在这个服务导出器中,需要配置对应的URL请求,以及需要导出的供客户端使用的服务对象、服务接口等信息的配置。 HttpInvokerServiceExporter的使用是与Spring MVC结合在一起的,实际上是我们熟悉的Spring MVC框架中的一个Controller。在 Spring MVC中,具体的映射和转发是由Dispatche Servlet来完成的;然后,通过这个Spring MVC框架,由配置好的Controller来完成对应的URL的数据处理。下面看看在这个服务导出器中,是如何对客户端发起的远端调用的服务请求进行响应的,处理的时序如图所示。
在这个设计时序中,我们可以看到,一个和客户端访问远端服务的过程相逆的处理过程。具体来说,这个过程是这样的,作为服务器端的HttpInvokerServiceExporter,已经设置好handleRequest()方法接收对服务的HTTP请求。响应这些请求的响应和通过DispatcherServle响应HTTP请求是一样的,在执行需要的服务之前,先会从HTTP请求中读取RemoteInvocation对象。在前面对客户端的实现原理分析中已介绍过这个Remotelnvocation对象了,它封装了访问远端服务的调用场景,比如具体的远端方法名、调用参数等。
在服务器端,Remotelnvocation对象是通过从HTTP请求中反序列化得到的。有了这个RemoteInvocation对象后,会调用配置好的的服务方法来执行请求的远端服务。在服务执行完成以后,通过HTTP响应,把执行结果通过对象的序列化输出到客户端,从而完成整个服务器端的服务过程。从设计时序来看,这个过程很清晰,包括服务请求接受、服务执行,以及最后返回服务结果的完整过程。
public class HttpInvokerServiceExporter extends RemoteInvocationSerializingExporter implements HttpRequestHandler {
//从请求读取远程调用,执行它,并将远程调用结果写入响应。
@Override
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);
}
}
//从给定的HTTP请求读取RemoteInvocation。
//使用HttpServletRequest#getInputStream() servlet请求的输入流委托给
//HttpServletRequest#getInputStream() servlet请求的输入流
protected RemoteInvocation readRemoteInvocation(HttpServletRequest request)
throws IOException, ClassNotFoundException {
return readRemoteInvocation(request, request.getInputStream());
}
//从给定的InputStream反序列化RemoteInvocation对象。
//给decorateInputStream一个首先修饰流的机会(例如,用于自定义加密或压缩)。
//创建CodebaseAwareObjectInputStream和调用doReadRemoteInvocation来实际读取对象。
//可以为调用的自定义序列化重写。
protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is)
throws IOException, ClassNotFoundException {
ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is));
try {
return doReadRemoteInvocation(ois);
}
finally {
ois.close();
}
}
//返回InputStream以用于读取远程调用,潜在地修饰给定的原始InputStream。
//默认实现按原样返回给定的流。
//可以覆盖,例如,用于自定义加密或压缩。
protected InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException {
return is;
}
//将给定的RemoteInvocationResult写入给定的HTTP响应。
protected void writeRemoteInvocationResult(
HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result)
throws IOException {
response.setContentType(getContentType());
writeRemoteInvocationResult(request, response, result, response.getOutputStream());
}
//将给定的RemoteInvocation序列化到给定的OutputStream。
//默认的实现为#decorateOutputStream提供了首先修饰流的机会(例如,用于自定义加密或压缩)。
//为最终的流创建ObjectOutputStream并调用#doWriteRemoteInvocationResult来实际编写对象。
//可以为调用的自定义序列化重写。
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();
}
}
//返回OutputStream以用于编写远程调用结果,潜在地修饰给定的原始OutputStream。
//默认实现按原样返回给定的流。
//可以覆盖,例如,用于自定义加密或压缩。
protected OutputStream decorateOutputStream(
HttpServletRequest request, HttpServletResponse response, OutputStream os) throws IOException {
return os;
}
//装饰一个OutputStream来防止flush()调用,这些调用将被转换为no-ops。
//因为ObjectOutputStream#close()实际上将刷新/删除底层流两次,所以这个FilterOutputStream将防止单个刷新调用。
//多次刷新调用可能会导致性能问题,因为写入没有按应有的方式收集。
//@see <a href="https://jira.spring.io/browse/SPR-14040">SPR-14040</a>
private static class FlushGuardedOutputStream extends FilterOutputStream {
public FlushGuardedOutputStream(OutputStream out) {
super(out);
}
@Override
public void flush() throws IOException {
// Do nothing on flush
}
}
}
从代码实现中可以看到,响应HTTP请求的过程是非常简单明了的。下面来看看怎样通过从请求中反序列化得到Remotelnvocation对象。 Remotelnvocation对象封装了对服务调用的基本信息,比如调用的方法名、参数、参数类型等。在服务器端,得到Remotelnvocation对象的过程在readRemoteInvocation()方法中。从代码中可以看到,这个得到Remotelnvocation数据对象的过程实际上是一个对象反序列化的实现过程。在实现过程中,服务器端首先会从HttpRequest中得到一个对象的输入流;然后,从这个输入流中读取对象;最后,把这个对象转型成Remotelnvocation类型的对象返回。如果得到的对象不是RemoteInvocation对象,还会抛出异常,表示服务器只兼容由HTTP调用器远端调用客户端发起的服务请求。
在通过HTTP请求得到从客户端传过来的Remotelnvocation对象以后,就可以进行服务方法的调用了。服务调用需要的基本信息都封装在 Remotelnvocation对象中。这个服务调用过程是由invokeAndCreateResul()方法来实现的,具体的实现过程如代码所示。
public abstract class RemoteInvocationBasedExporter extends RemoteExporter {
private RemoteInvocationExecutor remoteInvocationExecutor = new DefaultRemoteInvocationExecutor();
//将RemoteInvocationExecutor设置为用于此导出程序。默认是DefaultRemoteInvocationExecutor
//自定义调用执行程序可以从调用中提取更多上下文信息,例如用户凭证。
public void setRemoteInvocationExecutor(RemoteInvocationExecutor remoteInvocationExecutor) {
this.remoteInvocationExecutor = remoteInvocationExecutor;
}
public RemoteInvocationExecutor getRemoteInvocationExecutor() {
return this.remoteInvocationExecutor;
}
//将给定的远程调用应用于给定的目标对象。默认的实现委托给RemoteInvocationExecutor。
//可以在自定义调用行为的子类中重写,也可以在自定义RemoteInvocation子类中应用其他调用参数。
//请注意,最好使用可重用策略的自定义RemoteInvocationExecutor。
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;
}
}
//将给定的远程调用应用于给定的目标对象,将调用结果包装在可序列化的RemoteInvocationResult对象中
//默认实现创建一个普通的RemoteInvocationResult
//可以在自定义调用行为的子类中重写,例如返回额外的上下文信息。请注意,这不在RemoteInvocationExecutor策略的覆盖范围内!
protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
try {
Object value = invoke(invocation, targetObject);
return new RemoteInvocationResult(value);
}
catch (Throwable ex) {
return new RemoteInvocationResult(ex);
}
}
}
这个方法完成的任务就像它的名字一样,由它来启动服务并创建服务执行结果。服务执行结果是通过生成一个 RemoteInvocationResult对象来封装的。
具体的服务执行是由DefaultRemoteInvocationExecutor来完成的,这个执行器执行服务需要的参数有两个:一个是客户端访问场景的数据封装,也就是MethodInvocation对象;另一个,是提供服务的具体服务对象,这个服务对象在IoC容器中配置,并通过依赖注入设置进来。有了MethodInvocation对象封装的调用场景,服务对象就可以根据需要去完成服务了。这个执行器的invoke()方法实现很简单也很巧妙,它把调用交给了Remotelnvocation对象,如下所示。
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);
}
}
绕了一圈,又回到 MethodInvocation的设计中来了,但这里看到的MethodInvocation对象已经是在服务器端的对象了,由于它是服务器端的对象,因此它可以很方便地获取服务器端的服务资源。由Spring实现服务器对象调用也很简单明了,在代码实现中可以看到,使用反射技术来完成方法调用,得到 Method对象,然后,调用Method对象的invoke方法,并在invoke()方法中设置了具体执行服务的目标对象,以及执行方法的输入参数,从而完成远端调用服务的执行。
//对给定的目标对象执行此调用。通常在服务器上接收到远程调用时调用。
public Object invoke(Object targetObject)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
//取得服务对象的调用方法,通过反射完成调用,并得到调用结果返回调用方法名、参数类型,
//以及调用参数都是在客户端封装好,并通过HTTP的Java序列化传递到服务器端
Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes);
return method.invoke(targetObject, this.arguments);
}
服务对象的方法调用完成之后,会把调用结果通过HTTP响应和对象序列化后传递给HTTP调用器客户端,从而完成整个HTTP调用器的远端调用过程,在这里,使用了HTTP响应,传回服务执行结果的过程与处理正常的HTTP响应类似,只有一点需要注意,在使用HttpResponse的输出流之前,需要为输出流设置ContentType属性。把这个属性设置为application/x-java-serialized-object的值,表示此时在流中传输的是Java的序列化对象,在这个传输过程中,利用HTTP作为数据的传输通道。
3、Spring Hession的实现原理
前面介绍了使用HTTP调用器完成远端调用的实现原理,在HTTP调用器的实现中使用的都是Java和Spring框架的已有特性。作为应用平台的 Spring,还为用户使用远端调用提供了其他选择,在这些选择中,包括集成的开源轻量级的第三方远程调用协议实现,比如Caocho公司发布的一个便捷的二进制协议 Hessian。虽然二者之一使用二进制协议,另一个使用XML协议,但它们都建立在使用HTTP协议的基础上,把HTTP作为其传输数据的基本协议。
3.1、设计原理和实现过程
在Spring远端调用模块中,还可以通过第三方的远端调用模块Hessian来完成,在这种模式下, Hessian不但设计了自己的远端处理协议,还封装了对应的客户端和服务器端的交互过程。对于Spring来说,不需要像HTTP调用器那样,在底层的HTTP网络协议
上做一层一层的封装,在这里只需要对Hessian的客户端和服务器端进行相应的封装,把它们封装到Spring的应用模式下即可。
Spring Hessian远端模块的具体设计主要体现在以下两个方面。一方面是客户端的封装,比如HessianProxyFactoryBean,它们也是ProxyFactoryBean,通过AOP来封装Hessian的客户端,这时Hessian的客户端就作为ProxyFactory的代理对象出现。而对Hessian客户端的配置和使用是通过设置好的拦截器(比如HessianClientIntercepter)来完成的。从客户端的设计来看,HessianProxyFactoryBean和HessianClientInterceptor是Spring对Hessian进行封装的主要类。另一方面是在服务器端的设计上,在服务器端, Spring通过为客户提供HessianServiceExporter来简化对Hessian服务器的使用,这些服务器端的服务导出器,可以帮助客户在使用Hessian服务器的时候,在IoC容器的Bean定义中,就可以对Hessian服务器进行配置,这些配置包括服务URL
地址、远端服务的实现以及服务接口定义等。在设计上,通过Spring MVC的DispatcherServlet将服务请求传递到HessianSkeleton服务中,将请求直接交由Hessian处理,完成特定协议的处理和服务对象的调用,并将服务结果封装到特定的
Hessian协议中去,由网络写回到客户端,从而完成一次完整的服务请求和响应。
3.2、Hessian客户端的配置
在使用HessianProxyFactoryBean实现远端调用时,首先同样需要在客户端配置HessianProxyFactoryBean,看到这个ProxyBean,毫无疑问可以得知,Spring又发挥了AOP的强大功能。我们通过一个简单的例子回顾一下这个配置。
<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
可以看到在配置中,需要设置远端调用的服务地址,这个时候,因为Hessian是基于HTTP来完成传输的,所以在这个设置中,需要给出HTTP协议的域名/IP地址、端口号和服务所在的URL地址。而这些服务的URL地址需要和服务器端的服务相对应,同时需要在服务器端导出服务,这样才能让远端调用服务器顺利地响应客户端的服务请求。在服务器端,对服务对象导出的IoC配置如下所示。同样可以看到,对服务URL地址服务对象service,以及服务接口serviceInterface等属性的配置,这些配置都是和客户端的配置相一致的。
<bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
3.3、Hessian客户端的实现
先到HessianProxyFactoryBean的代码中去看一下HessianProxyFactory对Hessian客户端封装的实现,时序设计如图所示。
具体可以看到,先是在HessianProxyFactoryBean的afterPropertiesSet()方法中,通过ProxyFactory的getProxy()方法获取serviceProxy对象,该对象是一个代理对象,代理了配置的远端调用方法。当调用远端调用方法的时候,代理对象serviceProxy会触发拦截器的 invoke()方法。最后通过调用拦截器,触发HessianProxyFactory的执行方法。而HessianProxyFactory是在HessianClientInterceptor的afterPropertiesSet()方法中设置好的。
对应于这个实现时序,我们可以参照源代码实现来了解HessianProxyFactoryBean的实现。在这个HessianFactoryBean中,我们可以看到,需要生成一个代理对象serviceProxy,这个Proxy代理对象是在HessianProxyFactoryBean的afterPropertiesSet()方法中实现的,如果需要使用这个代理对象,通过HessianFactoryBean的getObject()方法就可以得到。
public class HessianProxyFactoryBean extends HessianClientInterceptor implements FactoryBean<Object> {
@Nullable
private Object serviceProxy;
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
//通过ProxyFactory生成代理对象,拦截器使用HessianClientInterceptor,
//因为HessianProxyFactoryBean本身是HessianClientInterceptor的子类,
//所以这里使用this为代理对象设置拦截器, getserviceInterface()取得在BeanDefinition中定义的接口
this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
}
@Override
@Nullable
public Object getObject() {
return this.serviceProxy;
}
@Override
public Class<?> getObjectType() {
return getServiceInterface();
}
@Override
public boolean isSingleton() {
return true;
}
}
可以在代码中看到,FactoryBean的主要功能是完成代理Proxy对象的生成和拦截器的设置。而在通过这个FactoryBean来实现Hessian远端调用的设置中,驱动Hessian的过程被封装在设置的拦截器HessianClientinterceptor中来完成。可以看到,这些设置都是在这个IoC容器的afterPropertiesSet()回调方法中实现的在生成了代理对象和为代理对象设置好拦截器之后,来看看在拦截器中是怎样完成对远端调用的具体封装和实现的。它们在HessianClientInterceptor中实现,如下所示。
//依赖注入完成之后,使用Hessian的准备工作
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
prepare();
}
//初始化此拦截器的Hessian代理。
//调用 createHessianProxy,这里的proxyFactory是Hessian的类HessianProxyFactory
public void prepare() throws RemoteLookupFailureException {
try {
this.hessianProxy = createHessianProxy(this.proxyFactory);
}
catch (MalformedURLException ex) {
throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex);
}
}
//这是调用HessianProxyFactory来生成客户端stub的地方,通过调用create()方法来实现,
//和我们独立使用HessianProxyFactory是一样的
protected Object createHessianProxy(HessianProxyFactory proxyFactory) throws MalformedURLException {
Assert.notNull(getServiceInterface(), "'serviceInterface' is required");
return proxyFactory.create(getServiceInterface(), getServiceUrl(), getBeanClassLoader());
}
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
if (this.hessianProxy == null) {
throw new IllegalStateException("HessianClientInterceptor is not properly initialized - " +
"invoke 'prepare' before attempting any operations");
}
ClassLoader originalClassLoader = overrideThreadContextClassLoader();
try {
//这里是驱动Hessian完成远端调用的入口,这个hessianProxy是由HessianProxyFactory生成的代理对象
//这个hessianProxyFactory是Hessian的类,已经不是Springi框架的内容了
return invocation.getMethod().invoke(this.hessianProxy, invocation.getArguments());
}
catch (InvocationTargetException ex) {
Throwable targetEx = ex.getTargetException();
// Hessian 4.0 check: another layer of InvocationTargetException.
if (targetEx instanceof InvocationTargetException) {
targetEx = ((InvocationTargetException) targetEx).getTargetException();
}
if (targetEx instanceof HessianConnectionException) {
throw convertHessianAccessException(targetEx);
}
else if (targetEx instanceof HessianException || targetEx instanceof HessianRuntimeException) {
Throwable cause = targetEx.getCause();
throw convertHessianAccessException(cause != null ? cause : targetEx);
}
else if (targetEx instanceof UndeclaredThrowableException) {
UndeclaredThrowableException utex = (UndeclaredThrowableException) targetEx;
throw convertHessianAccessException(utex.getUndeclaredThrowable());
}
else {
throw targetEx;
}
}
catch (Throwable ex) {
throw new RemoteProxyFailureException(
"Failed to invoke Hessian proxy for remote service [" + getServiceUrl() + "]", ex);
}
finally {
resetThreadContextClassLoader(originalClassLoader);
}
}
//将给定的Hessian访问异常转换为适当的Spring RemoteAccessException。
protected RemoteAccessException convertHessianAccessException(Throwable ex) {
if (ex instanceof HessianConnectionException || ex instanceof ConnectException) {
return new RemoteConnectFailureException(
"Cannot connect to Hessian remote service at [" + getServiceUrl() + "]", ex);
}
else {
return new RemoteAccessException(
"Cannot access Hessian remote service at [" + getServiceUrl() + "]", ex);
}
}
在这里可以看到,使用了HessianProxyFactory创建的proxy代理对象来完成具体的远端调用,这个Hessian的proxy对象的使用,是通过Method的反射调用来完成的。值得注意的是,这个proxy对象是Hessian的一个实现类,而不是通常看到的Java的Proxy代理对象。对HessianProxyFactory的HessianClientInterceptort设置,是由IoC容器完成注入的,然后在prepare()方法中,也就是在 afterPropertiesSet()方法调用中,实现了 Hessian的poxy对象的创建。
这里使用的HessianProxyFactory是Hessian提供的类,由它来具体完成通过Hessian的远端调用,在完成调用之前,已经通过HessianProxyFactory的create()方法实现了调用前的准备工作。这个准备工作为远端对象创建了客户端的Hession的proxy。有了这个proxy,就可以像调用本地对象方法一样调用这个proxy()的方法,中间的通信和交互过程都由Hessian封装好了。
3.4、Hessian服务器端的实现
我们到HessianServiceExporter中去看看它们是怎样通过HessianServiceExporter完成服务器端的服务导出的,HessianServiceExporter接收handleRequest()方法响应远端客户端发送过来的远端服务请求,然后通过invoke()方法,启动HessianExporter和HessianSkeleton的本地服务调用,设计时序如图所示。
这个类的源代码实现如代码清单所示。
可以看到,它完成的基本上是桥梁工作,通过这个桥梁,HessianServiceExporter把远端服务整合到Spring MVC框架中,将提供的服务封装到HttpRequestHandler的handleRequest()方法中去完成,从而借助Spring MVC的实现完成服务在服务器端的导出。
public class HessianServiceExporter extends HessianExporter implements HttpRequestHandler {
//HessianServiceExporter实际上是Spring MVC框架中的一个Controller,
//通过handleRequest()完成对Request的响应,将得到的服务执行结果输出到response中。
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (!"POST".equals(request.getMethod())) {
throw new HttpRequestMethodNotSupportedException(request.getMethod(),
new String[] {"POST"}, "HessianServiceExporter only supports POST requests");
}
response.setContentType(CONTENT_TYPE_HESSIAN);
try {//这是对服务器端的远端对象方法的调用
invoke(request.getInputStream(), response.getOutputStream());
}
catch (Throwable ex) {
throw new NestedServletException("Hessian skeleton invocation failed", ex);
}
}
}
从代码中可以看到,在准备好HTTP的Response之后,服务器端对服务的调用和执行结果的返回都是由invoke()方法来完成的。 invoke方法是在HessianServiceExporter的基类HessianExporter中实现的。在得到了从HTTP请求中获取的输入流及从HTTP响应中获取的输出流对象之后,通过调用doInvoke()方法来完成服务的执行。在这个doInvoke()方法的实现中,我们可以看到,最终通过对Hessian的使用来完成服务的封装和执行。在这个实现过程中,由于Hessian有不同的实现版本,因此在使用的时候,为了保证与客户端使用的协议的兼容性,需要在Hessian的服务器端对客户端使用的Hessian版本进行判断,然后根据不同版本的使用情况,进行相应的选择和处理。以上的一系列实现过程如下代码所示。
HessianExporter的invoke()方法。
@Override
public void afterPropertiesSet() {
prepare();
}
//这里创建HessianSkeleton,这个HessianSkeleton是Hessian完成服务器端服务的Proxy类
public void prepare() {
checkService();
checkServiceInterface();
this.skeleton = new HessianSkeleton(getProxyForService(), getServiceInterface());
}
//对导出的对象执行调用
public void invoke(InputStream inputStream, OutputStream outputStream) throws Throwable {
Assert.notNull(this.skeleton, "Hessian exporter has not been initialized");
doInvoke(this.skeleton, inputStream, outputStream);
}
//用给定的流实际调用框架
protected void doInvoke(HessianSkeleton skeleton, InputStream inputStream, OutputStream outputStream)
throws Throwable {
ClassLoader originalClassLoader = overrideThreadContextClassLoader();
try {
InputStream isToUse = inputStream;
OutputStream osToUse = outputStream;
if (this.debugLogger != null && this.debugLogger.isDebugEnabled()) {
try (PrintWriter debugWriter = new PrintWriter(new CommonsLogWriter(this.debugLogger))){
@SuppressWarnings("resource")
HessianDebugInputStream dis = new HessianDebugInputStream(inputStream, debugWriter);
@SuppressWarnings("resource")
HessianDebugOutputStream dos = new HessianDebugOutputStream(outputStream, debugWriter);
dis.startTop2();
dos.startTop2();
isToUse = dis;
osToUse = dos;
}
}
if (!isToUse.markSupported()) {
isToUse = new BufferedInputStream(isToUse);
isToUse.mark(1);
}
int code = isToUse.read();
int major;
int minor;
AbstractHessianInput in;
AbstractHessianOutput out;
//判断客户端Hessian的版本,使用不同的AbstractHessianInput和AbstractHessianOutput
if (code == 'H') {
// Hessian 2.0 stream
major = isToUse.read();
minor = isToUse.read();
if (major != 0x02) {
throw new IOException("Version " + major + '.' + minor + " is not understood");
}
in = new Hessian2Input(isToUse);
out = new Hessian2Output(osToUse);
in.readCall();
}
else if (code == 'C') {
// Hessian 2.0 call... for some reason not handled in HessianServlet!
isToUse.reset();
in = new Hessian2Input(isToUse);
out = new Hessian2Output(osToUse);
in.readCall();
}
else if (code == 'c') {
// Hessian 1.0 call
major = isToUse.read();
minor = isToUse.read();
in = new HessianInput(isToUse);
if (major >= 2) {
out = new Hessian2Output(osToUse);
}
else {
out = new HessianOutput(osToUse);
}
}
else {
throw new IOException("Expected 'H'/'C' (Hessian 2.0) or 'c' (Hessian 1.0) in hessian input at " + code);
}
in.setSerializerFactory(this.serializerFactory);
out.setSerializerFactory(this.serializerFactory);
if (this.remoteResolver != null) {
in.setRemoteResolver(this.remoteResolver);
}
//调用HessianSkeleton完成服务,关于Hessianskeleton的使用,可以参考 Hessian的使用说明
try {
skeleton.invoke(in, out);
}
finally {
try {
in.close();
isToUse.close();
}
catch (IOException ex) {
// ignore
}
try {
out.close();
osToUse.close();
}
catch (IOException ex) {
// ignore
}
}
}
finally {
resetThreadContextClassLoader(originalClassLoader);
}
}
可以看到,这里使用了Hessian提供的HessianSkeleton来完成服务的提供。为了使用HessianSkeleton,需要做一些准备工作,这些配置Spring已经为用户封装好了,有了这一层封装,比用户直接使用Hessian来完成远端调用要方便许多。
4、Spring RMI的实现
4.1、设计原理和实现过程
与前面看到的其他远端调用方案实现一样,Spring通过对IoC容器和AOP的使用,为用户提供了基于RMI机制的远端调用服务。在使用RMI实现远端调用服务的时候,它的网络通信实现是基于TCP/IP协议完成的,而不是通过前面看到的HTTP协议,从而完成数据的通信传输。在 Spring的RMI实现中,集成了标准的RMI-JRMP解决方案,这个方案是Java虚拟机实现的一部分,它使用Java序列化来完成对象的传输,是一个Java到Java环境的分布式处理技术,因而不涉及异构平台的处理。 Spring在封装RMI远端调用服务的时候,支持传统的RMI实现方式,但在这种传统实现方式的基础上,还提供了RM调用器的实现方案作为简化方案。这个RMI调用器的实现方式,就像我们前面看到的HTTP调用器的实现那样,通过使用普通的Java业务接口就能够提供远端服务,并不需要实现传统RMI需要的Remote接口,使用起来也非常方便。
在设计中,与前面介绍的HTTP调用器和Hessian远程调用一样,在SpringRMI中,设计了RMIProxyFactoryBean来支持Spring应用对RMI的使用,而对RM基础设施的封装,是通过RmiClientInterceptor来完成的,这个RmiClientIntercepter封装了对RM客户端处理的主要过程,比如,为RMI客户端准备存根作为RMI调用器等,有了这些对RMI客户端的支持,就可以在Spring中方便地使用RMI了。在服务器端的设计中,RMI服务的导出是通过RmiServiceExporter来实现的,这个导出结果同样为RMI调用准备了服务器端的基础设施,比如,设置RM服务参数,通过RMI机制将服务对象导出,并且完成RMI服务的注册等。
可以看到,作为应用平台的Spring,为应用提供RMI的远端服务是通过封装RMI的现有机制来完成的,关于具体的设计和实现过程,下面进行详细分析。
4.2、Spring RMI客户端的配置
在使用 Spring RMI的时候,毫不例外,仍然需要对客户端和服务器端进行相应的配置先从客户端的配置入手,如下代码所示,然后和以前的分析一样,从客户端的实现开始,再接着分析服务器端的实现。在RMI客户端的配置中,沿袭了Spring远端调用方案的一致风格,比如,可以再一次看到对ProxyFactoryBean的使用,只是在RMI客户端的配置中设置的ProxyFactoryBean被换成了 RMIProxyFactoryBean而已。RMIProxyFactoryBean的配置也与其他Spring远端调用方案非常类似,只是在配置serviceUrl时,需要使用RMI作为协议及与此相对应的的通信端口,在这里,使用1099端口,从而通过TCP/IP完成数据的通信和交互。
这些不同是很好理解的,因为在底层的通信支持上,RMI采用了不同的协议和实现方式。
<bean class="example.SimpleObject">
<property name="accountService" ref="accountService"/>
</bean>
<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
4.3、Spring RMI客户端的实现
在Spring RMI客户端中,使用的仍然是我们已经非常熟悉的ProxyFactoryBean,它是RmiProxyFactoryBean,完成的是对RM客户端的封装,比如生成代理对象、査询RMI的存根对象,并通过这个存根对象发起相应的RMIi远端服务请求。远端调用方法实际上调用的是serviceProxy代理对象的代理方法,并会被RmiClientInterceptor拦截器拦截,通过RmiClientInterceptorUtilis的invokeRemoteMethod()方法实现。而RMI实现的配置是在RmiProxyFactoryBean和RmiClientInterceptor的afterProperties()方法中启动设置的,其中,在RmiProxyFactoryBean的afterPropertiesSet()中设置的是serviceProxy代理对象,在这个代理对象中,设置RmiClientInterceptor拦截器,而在RmiClientInterceptor拦截器的afterPropertiesSet()方法中需要准备RMI的基本配置,比如配置存根等,从而最终完成RMI的客户端设置。
public class RmiProxyFactoryBean extends RmiClientInterceptor implements FactoryBean<Object>, BeanClassLoaderAware {
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
public Object getObject() {
return this.serviceProxy;
}
@Override
public Class<?> getObjectType() {
return getServiceInterface();
}
@Override
public boolean isSingleton() {
return true;
}
}
RMI客户端基础设施的封装是由拦截器RmiClientInterceptor来完成的,这个拦截器的设置是在RmiProxyFactoryBean生成的代理对象中完成的。在拦截器中,首先看到的是对stub对象的获取,作为实现RMI的基本准备,获取这个stub的实现是在拦截器的afterPropertiesSet()方法中完成的。在实现中,,Spring还为这个stub对象提供了缓存,从而提高对它的性能。从Spring的代码实现中可以看到,拦截器获取stub的实现如下所示。
//建立RM工基础设施的调用,仍然是在afterPropertiesSet()方法中实现。因为这个
//RmiClientInterceptor实现了InitializingBean接口,所以它会被IoC容器回调
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
prepare();
}
//这里为RMI客户端准备stub,这个stub通过lookupStub()方法获得,并且会在第一次生成之后,放到缓存中去
public void prepare() throws RemoteLookupFailureException {
//是否在初始化时缓存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) {
this.cachedStub = remoteObj;
}
}
}
//创建RMI stub,通常通过查找它。如果“cacheStub”为“true”,调用拦截器初始化;
//否则通过getStub()调用每个调用
默认实现通过以下方式查找服务URL
protected Remote lookupStub() throws RemoteLookupFailureException {
try {
Remote stub = null;
if (this.registryClientSocketFactory != null) {
//指定用于注册表访问的RMIClientSocketFactory。
//不幸的是,由于RMI API的限制,这意味着我们需要自己解析RMI URL并直接执行
//LocateRegistry.getRegistry/Registry.lookup
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 = LocateRegistry.getRegistry(host, port, this.registryClientSocketFactory);
stub = registry.lookup(name);
}
else {
// Can proceed with standard RMI lookup API...
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);
}
}
获取了stub之后,当调用RM客户端的代理方法时,会触发拦截器RmiClientInterceptor的invoke()回调方法。 invoke()回调方法的实现如下所示。
在invoke()回调中,可以看到RMI远端调用的发生,其中Spring采用了两种方式,一种是使用RMI调用器的方式,这种方式和使用HTTP调用器非常类似,是通过一个自定义的Remotelnvocatior来封装调用的场景的,有了这层封装,相当于为RM的远程调用设计了一个RMI调用器来简化通过RM的远端调用实现。
//拦截器对代理对象方法调用的回调,在实现中获取RMI stub并将其委托给doInvoke()方法
//如果配置为在连接失败时刷新,它将在相应的RMI异常上调用refreshAndRetry()
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
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是RmiInvocationHandler实例,
//那么使用RMI调用器来完成这次远端调用,否则,使用传统的RM调用方式
@Nullable
protected Object doInvoke(MethodInvocation invocation, Remote stub) throws Throwable {
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);
}
}
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;
}
}
}
}
4.4、Spring RMI服务器端的配置
了解了Spring RMI客户端的配置和实现原理,下面从了解Spring RMI的服务器端的设置开始去了解Spring RMI服务器端的实现。 Spring RMI服务器端的设置是一个简单示例。
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- does not necessarily have to be the same name as the bean to be exported -->
<property name="serviceName" value="AccountService"/>
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
<!-- defaults to 1099 -->
<property name="registryPort" value="1199"/>
</bean>
在RMI中,基于TCP/IP协议,而不是HTTP来实现基本的网络通信,RMI的网络通信是Java RMI实现的一部分,所以这里不需通过使用 Spring MVC的DispatcherServlet来配置远端服务的服务URL以及控制服务请求的转发。在服务器端的配置中,除了需要指定提供服务的Bean及代理的服务接口之外,还需要通过serviceName属性来配置服务的导出位置,同时使用registryPort来指定RMI监听的TCP/IP端口。
4.5、Spring RMI服务器端的实现
在Spring RMI服务器端,通过 RmiserviceExporter来导出RMI服务,这个类的具体实现如下:
//在afterPropertiesSet()方法中,开始建立RMI服务器端的基础设施,在prepare()方法中实现
@Override
public void afterPropertiesSet() throws RemoteException {
prepare();
}
//初始化此服务导出器,将服务注册为RMI对象。如果不存在,则在指定端口上创建RMI注册表。
public void prepare() throws RemoteException {
//检查提供服务的Bean和serviceName属性是否设置正确,如果没有设置,抛出异常
checkService();
if (this.serviceName == null) {
throw new IllegalArgumentException("Property 'serviceName' is required");
}
// Check socket factories for exported object.
if (this.clientSocketFactory instanceof RMIServerSocketFactory) {
this.serverSocketFactory = (RMIServerSocketFactory) this.clientSocketFactory;
}
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.
if (this.registryClientSocketFactory instanceof RMIServerSocketFactory) {
this.registryServerSocketFactory = (RMIServerSocketFactory) this.registryClientSocketFactory;
}
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.
if (this.registry == null) {
this.registry = getRegistry(this.registryHost, this.registryPort,
this.registryClientSocketFactory, this.registryServerSocketFactory);
this.createdRegistry = true;
}
//这里是取得服务对象的地方,需要根据服务的设置来完成服务对象的获取,
//如果服务对象实现了Java的Remote接口,那么取得的是标准的RMI服务,否则,使用RMI调用器
this.exportedObject = getObjectToExport();
if (logger.isDebugEnabled()) {
logger.debug("Binding service '" + this.serviceName + "' to RMI registry: " + this.registry);
}
// Export RMI object.
if (this.clientSocketFactory != null) {
UnicastRemoteObject.exportObject(
this.exportedObject, this.servicePort, this.clientSocketFactory, this.serverSocketFactory);
}
else {
UnicastRemoteObject.exportObject(this.exportedObject, this.servicePort);
}
// Bind RMI object to registry.
try {
if (this.replaceExistingBinding) {
this.registry.rebind(this.serviceName, this.exportedObject);
}
else {
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;
}
}
在RMI的导出器中,建立RM服务器端的实现,主要集中在prepare()方法中,这个方法在afterPropertiesSet()调用。对于afterPropertiesSet()的使用,我们已经很熟悉了。在建立RM服务器端导出服务基础设施的过程中,首先需要对一些基本的参数设置进行检查,比如,是否设置了服务提供Bean,是否设置了服务位置等。接着,在正确取得服务对象的基础上,导出器会通过RMI机制导出这个服务对象,然后在完成RMI注册以后,客户端就可以开始查询和使用RMI远端服务了。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。