Spring系列-Spring远端调用原理分析

背景:
在分布式计算中,经常涉及到多服务器之间不同进程的通信与计算交互,所以就需要用到远端调用。比如可以基于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 {

	private Object serviceProxy;


	@Override
	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		if (getServiceInterface() == null) {
			throw new IllegalArgumentException("Property 'serviceInterface' is required");
		}
		this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
	}


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

	@Override
	public Class getObjectType() {
		return getServiceInterface();
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

}

 
  

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技术内幕》 计文柯 著

你可能感兴趣的:(Spring)