背景:
在分布式计算中,经常涉及到多服务器之间不同进程的通信与计算交互,所以就需要用到远端调用。比如可以基于Http进行调用,或者WebService技术,或者RMI。通过这种技术,就可以进行服务能力的扩展,实现分布式计算。
Spring为远端调用的实现提供了许多不同的方案,比如Http调用器、第三方远端调用库Hessian/Burlap、RMI、基于Java RMI的解决方案等。这几种方案的套路其实都差不多,接下来我们就只看一下Http调用器的设计和实现原理。
Spring Http调用器
这个调用器,顾名思义,是基于HTTP协议的远程调用,分为客户端和服务端。客户端的功能是打开Http连接,将请求对象序列化,将请求发送给服务端。对于服务端来说,负责接收请求,将请求中对象反序列化,处理请求,把结果序列化之后通过http返回到客户端。接下来就看下具体的代码。
Http调用器客户端
客户端的服务配置是通过HttpInvokerProxyFactoryBean实现的,下面是HttpInvokerProxyFactoryBean的定义:
public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor
implements FactoryBean
HttpInvokerProxyFactoryBean间接实现了InitializingBean接口,重写了afterPropertiesSet方法。在afterPropertiesSet方法中,生成了一个代理对象,赋值给了serviceProxy。在new ProxyFactory的时候,构造函数第二个参数,传入了this,这个参数是接收一个Interceptor对象的,HttpInvokerProxyFactoryBean继承了HttpInvokerClientInterceptor,所以这个Interceptor对象就是HttpInvokerClientInterceptor。这个参数会作为一个Advice设置到代理对象中。
根据之前分析过的Spring AOP的实现原理,Advice会设置为代理对象的拦截器,在调用目标对象方法是,会触发代理对象中的拦截器方法,所以,我们再看下HttpInvokerClientInterceptor中的invoke代码:
@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) {
throw convertHttpInvokerAccessException(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);
}
}
}
在invoke方法中,会先根据MethodInvocation生成一个RemoteInvocation对象,对于这个对象的生成,一直跟代码的话,就会发现,其实就是new了这么个对象,设置了methodName,parameterTypes,arguments这三个字段。这些字段封装了调用的具体信息,比如调用的方法名,参数,参数类型。
接下来,会调用executeRequest方法,这个方法就是远端调用的具体实现。下面就看下这个方法:
protected RemoteInvocationResult executeRequest(
RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {
return executeRequest(invocation);
}
protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception {
return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
}
@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);
}
@Override
protected RemoteInvocationResult doExecuteRequest(
HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
throws IOException, ClassNotFoundException {
// 打开http连接
HttpURLConnection con = openConnection(config);
prepareConnection(con, baos.size());
writeRequestBody(config, con, baos);
validateResponse(config, con);
// 获取http响应的IO流
InputStream responseBody = readResponseBody(config, con);
return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
}
这里涉及到的方法比较多,在第二个方法那里,会通过getHttpInvokerRequestExecutor方法,获取一个httpInvokerRequestExecutor,这个httpInvokerRequestExecutor是SimpleHttpInvokerRequestExecutor。之后,会调用到SimpleHttpInvokerRequestExecutor的doExecuteRequest方法中。
在doExecuteRequest方法中,逻辑也比较清晰,会先通过openConnection打开一个HTTP连接,然后会把序列化的对象发送给服务器端,当服务器返回之后,再从HTTP响应中读取返回的结果。
最后再看下readRemoteInvocationResult方法:
protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, 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() + "]: " + obj);
}
return (RemoteInvocationResult) obj;
}
这个方法就是通过java的反序列化,得到服务的执行结果,返回RemoteInvocationResult的对象。
分析到这里,关于Http调用器的客户端就分析完了,回顾下大体的流程:客户端调用代理对象的方法,触发代理对象中的拦截器,执行拦截器中的invoke方法,在拦截器的invoke方法中,先把对本地的方法调用进行封装,封装成RemoteInvocation对象,记录了调用的方法名、参数等信息。接下来,把RemoteInvocation对象序列化,通过HTTP请求发送给服务端。等到服务端返回HTTP响应后,再从响应中通过反序列化得到结果,封装成RemoteInvocationResult返回。这里用到的序列化和反序列化,都是java本身提供的方法。
Http调用器服务端
分析完Http调用器的客户端,再来看下Http调用器的服务端,Http调用器的服务端需要配置HttpInvokerServiceExporter,这个是服务的导出器,在HttpInvokerServiceExporter中,通过handleRequest接收HTTP请求,接下来就看下这个方法:
/**
* Reads a remote invocation from the request, executes it,
* and writes the remote invocation result to the response.
* @see #readRemoteInvocation(HttpServletRequest)
* @see #invokeAndCreateResult(org.springframework.remoting.support.RemoteInvocation, Object)
* @see #writeRemoteInvocationResult(HttpServletRequest, HttpServletResponse, RemoteInvocationResult)
*/
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// 从请求中反序列化得到RemoteInvocation
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);
}
}
该方法中,先通过readRemoteInvocation方法,从request中反序列化得到RemoteInvocation对象,这个对象里面就存放了调用的方法以及调用参数等信息。之后,通过invokeAndCreateResult方法,对服务进行调用,返回一个RemoteInvocationResult的结果。最后,通过writeRemoteInvocationResult方法,设置返回的对象。接下来,对这几个方法详细分析一下。
protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is)
throws IOException, ClassNotFoundException {
ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is));
try {
return doReadRemoteInvocation(ois);
}
finally {
ois.close();
}
}
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() + "]: " + obj);
}
return (RemoteInvocation) obj;
}
首先是readRemoteInvocation方法,一直跟代码,会看到上面这两个方法,这两个方法的逻辑就是从request中得到ObjectInputStream对象,然后反序列化为RemoteInvocation对象。
接下来再看invokeAndCreateResult方法的详细逻辑:
/**
* Apply the given remote invocation to the given target object, wrapping
* the invocation result in a serializable RemoteInvocationResult object.
* The default implementation creates a plain RemoteInvocationResult.
* Can be overridden in subclasses for custom invocation behavior,
* for example to return additional context information. Note that this
* is not covered by the RemoteInvocationExecutor strategy!
* @param invocation the remote invocation
* @param targetObject the target object to apply the invocation to
* @return the invocation result
* @see #invoke
*/
protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
try {
Object value = invoke(invocation, targetObject);
return new RemoteInvocationResult(value);
}
catch (Throwable ex) {
return new RemoteInvocationResult(ex);
}
}
/**
* Apply the given remote invocation to the given target object.
* The default implementation delegates to the RemoteInvocationExecutor.
*
Can be overridden in subclasses for custom invocation behavior,
* possibly for applying additional invocation parameters from a
* custom RemoteInvocation subclass. Note that it is preferable to use
* a custom RemoteInvocationExecutor which is a reusable strategy.
* @param invocation the remote invocation
* @param targetObject the target object to apply the invocation to
* @return the invocation result
* @throws NoSuchMethodException if the method name could not be resolved
* @throws IllegalAccessException if the method could not be accessed
* @throws InvocationTargetException if the method invocation resulted in an exception
* @see RemoteInvocationExecutor#invoke
*/
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;
}
}
@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);
}
/**
* Perform this invocation on the given target object.
* Typically called when a RemoteInvocation is received on the server.
* @param targetObject the target object to apply the invocation to
* @return the invocation result
* @throws NoSuchMethodException if the method name could not be resolved
* @throws IllegalAccessException if the method could not be accessed
* @throws InvocationTargetException if the method invocation resulted in an exception
* @see java.lang.reflect.Method#invoke
*/
public Object invoke(Object targetObject)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes);
return method.invoke(targetObject, this.arguments);
}
顺着方法一直跟踪,可以看到上面这几个方法,虽然方法多,但是也没啥复杂的,经过一系列调用之后,本质还是通过反射机制来对目标方法进行调用。
最后,再看下writeRemoteInvocationResult方法:
/**
* Write the given RemoteInvocationResult to the given HTTP response.
* @param request current HTTP request
* @param response current HTTP response
* @param result the RemoteInvocationResult object
* @throws IOException in case of I/O failure
*/
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(decorateOutputStream(request, response, os));
try {
doWriteRemoteInvocationResult(result, oos);
}
finally {
oos.close();
}
}
方法会设置response的ContentType为"application/x-java-serialized-object",表示在流中传输的是Java序列化对象。之后在writeRemoteInvocationResult方法中,把result对象写入到HTTP的response中。
到这里,Http调用器的服务端的逻辑就分析完了,总结一下,就是获取到请求,反序列化解析,通过反射调用,然后再封装结果到response中。
参考资料:
1.《Spring技术内幕》 计文柯 著