您的位置:首页 > 理论基础 > 计算机网络

深入学习 Spring HttpInvoker

2018-03-07 20:26 609 查看

深入学习 Spring HttpInvoker

远程通信协议

RPC 远程过程调用,是一个计算机通信协议,允许一台计算机的程序,调用另一台计算机的子程序。如果涉及到的软件采用面向对象编程,亦可称为远程方法调用(如 Java RMI)

RMI 一般指Java RMI (Java Remote Method Invocation) Java远程方法调用,只适用于Java程序之间的通信。而且 RMI需要开设防火墙端口

Hessian 比较精简高效,可以跨语言使用,而且协议规范公开,可以针对任意语言开发对其协议的实现(Java C++ .net python…) 数据类型有限,处理复杂对象时,速度稍慢。

HttpInvoker 是Spring提供的一种基于HTTP的Java远程调用的方法

HttpInvoker 配置

服务端

web.xml

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>

<servlet>
<servlet-name>accountExporter</servlet-name>
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>accountExporter</servlet-name>
<url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>


applicationContext.xml

<bean id="accountService" class="remoting.service.impl.AccountServiceImpl"/>
<bean name="accountExporter"    class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<!--具体服务类型-->
<property name="service" ref="accountService"/>
<!--服务接口-->
<property name="serviceInterface" value="remoting.service.AccountService"/>
</bean>


或者如下配置

web.xml

<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>


dispatcher-servlet.xml

<bean id="accountService" class="remoting.service.impl.AccountServiceImpl"/>
<bean name="/AccountService"    class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="remoting.service.AccountService"/>
</bean>


客户端

客户端只包含Account、以及服务接口AccountService, 但是没有AccountService的具体实现。

remoting.xml

<bean id="accountService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<!--服务URL-->
<property name="serviceUrl" value="http://localhost:8080/remoting/AccountService"/>
<!--服务接口-->
<property name="serviceInterface" value="remoting.service.AccountService"/>
</bean>


import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.AbstractApplicationContext;

import remoting.domain.Account;
import remoting.service.AccountService;
@ImportResource(locations={"classpath:remoting.xml"})
public class Client {

@Autowired
public AccountService accountService;

public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}

public static void main(String[] args) throws FileNotFoundException, IOException {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(Client.class);
Client client = context.getBean(Client.class);
Account account = new Account();
account.setName("123456");
client.accountService.insertAccount(account);
List<Account> accounts = client.accountService.listAccounts();
for (Account acc : accounts)
System.out.println(acc.getName());

context.close();
}
}


HttpInvoker 执行流程分析

客户端



HttpInvokerProxyFactoryBean实现了FactoryBean接口,用于获取Bean对象,而Bean对象的创建,是以代理的方式创建的

package org.springframework.beans.factory;
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}


package org.springframework.remoting.httpinvoker;
public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor implements FactoryBean<Object> {

@Nullable
private Object serviceProxy;

@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
Class<?> ifc = getServiceInterface();
Assert.notNull(ifc, "Property 'serviceInterface' is required");
// 服务代理对象的创建
this.serviceProxy = new ProxyFactory(ifc, this).getProxy(getBeanClassLoader());
}

// 获取服务代理对象
@Override
@Nullable
public Object getObject() {
return this.serviceProxy;
}
// 服务接口
@Override
public Class<?> getObjectType() {
return getServiceInterface();
}
// 单例类型
@Override
public boolean isSingleton() {
return true;
}

}


代理对象在调用方法时,会执行invoker方法:

根据methodInvocation,可以得知我们要远程调用的方法 的方法名、参数类型以及参数,将其封装到RemoteInvocation中。

调用httpInvokerRequestExecutor.executeRequest(this, invocation) 获取远程调用结果RemoteInvocationResult.

public class RemoteInvocation implements Serializable {

/** use serialVersionUID from Spring 1.1 for interoperability */
private static final long serialVersionUID = 6876024250231820554L;

private String methodName;

private Class<?>[] parameterTypes;

private Object[] arguments;

private Map<String, Serializable> attributes;

// setter getter 以及构造方法 略

public Object invoke(Object targetObject)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes);
return method.invoke(targetObject, this.arguments);
}

@Override
public String toString() {
return "RemoteInvocation: method name '" + this.methodName + "'; parameter types " +
ClassUtils.classNamesToString(this.parameterTypes);
}

}


public class RemoteInvocationResult implements Serializable {

/** Use serialVersionUID from Spring 1.1 for interoperability */
private static final long serialVersionUID = 2138555143707773549L;

@Nullable
private Object value;

@Nullable
private Throwable exception;

@Nullable
public Object recreate() throws Throwable {
if (this.exception != null) {
Throwable exToThrow = this.exception;
if (this.exception instanceof InvocationTargetException) {
exToThrow = ((InvocationTargetException) this.exception).getTargetException();
}
RemoteInvocationUtils.fillInClientStackTraceIfPossible(exToThrow);
throw exToThrow;
}
else {
return this.value;
}
}
}


public class HttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor
implements MethodInterceptor, HttpInvokerClientConfiguration {

private HttpInvokerRequestExecutor httpInvokerRequestExecutor;

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";
}

RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
RemoteInvocationResult result;

try {
result = executeRequest(invocation, methodInvocation);
}
catch (Throwable ex) {
RemoteAccessException rae = convertHttpInvokerAccessException(ex);
throw (rae != null ? rae : ex);
}

try {
return recreateRemoteInvocationResult(result);
}
catch (Throwable ex) {
if (result.hasInvocationTargetException()) {
throw ex;
}
else {
throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() +
"] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
}
}
}
protected RemoteInvocationResult executeRequest(
RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {
return executeRequest(invocation);
}

protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception {
return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
}
}




HttpInvokerRequestExecutor作用是向服务端发送请求,进行远程方法调用,获取远程执行结果

有以下几个过程:

序列化RemoteInvocation, 将ObjectOutputStream写入到ByteArrayOutputStream中

根据url, 请求连接 openConnection

请求头的相关设置 :请求方法、Content-Type、Content-Length(ByteArrayOutputStream的大小)

将ByteArrayOutputStream发送到服务端,ByteArrayOutputStream.writeTo(con.getOutputStream())

获取服务端响应数据con.getInputStream() 将其反序列化为ResultRemoteInvocation对象。而ResultRemoteInvocation封装了远程方法调用的返回值

RemoteInvocation对象的序列化和ResultRemoteInvocation对象的反序列化过程在
AbstractHttpInvokerRequestExecutor
中实现

序列化过程

// AbstractHttpInvokerRequestExecutor.java
protected void writeRemoteInvocation(RemoteInvocation invocation, OutputStream os) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(decorateOutputStream(os));
try {
doWriteRemoteInvocation(invocation, oos);
}
finally {
oos.close();
}
}

protected OutputStream decorateOutputStream(OutputStream os) throws IOException {
return os;
}

protected void doWriteRemoteInvocation(RemoteInvocation invocation, ObjectOutputStream oos)     throws IOException {
oos.writeObject(invocation);
}


反序列化过程

// AbstractHttpInvokerRequestExecutor.java

protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, @Nullable String codebaseUrl)
throws IOException, ClassNotFoundException {

ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl);
try {
return doReadRemoteInvocationResult(ois);
}
finally {
ois.close();
}
}

protected InputStream decorateInputStream(InputStream is) throws IOException {
return is;
}
protected ObjectInputStream createObjectInputStream(InputStream is, @Nullable String codebaseUrl) throws IOException {
return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), codebaseUrl);
}

protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois)
throws IOException, ClassNotFoundException {

Object obj = ois.readObject();
if (!(obj instanceof RemoteInvocationResult)) {
throw new RemoteException("Deserialized object needs to be assignable to type [" +
RemoteInvocationResult.class.getName() + "]: " + ClassUtils.getDescriptiveType(obj));
}
return (RemoteInvocationResult) obj;
}


这里使用了Java模板方法的设计模式,doExecuteRequest交给子类实现

// AbstractHttpInvokerRequestExecutor.java
@Override
public final RemoteInvocationResult executeRequest(
HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws Exception {

ByteArrayOutputStream baos = getByteArrayOutputStream(invocation);
if (logger.isDebugEnabled()) {
logger.debug("Sending HTTP invoker request for service at [" + config.getServiceUrl() +
"], with size " + baos.size());
}
return doExecuteRequest(config, baos);
}

protected ByteArrayOutputStream getByteArrayOutputStream(RemoteInvocation invocation) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE);
writeRemoteInvocation(invocation, baos);
return baos;
}
// 抽象方法 具体实现交给子类
protected abstract RemoteInvocationResult doExecuteRequest(
HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
throws Exception;


// SimpleHttpInvokerRequestExecutor.java
protected RemoteInvocationResult doExecuteRequest(
HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
throws IOException, ClassNotFoundException {

HttpURLConnection con = openConnection(config);
prepareConnection(con, baos.size());
// 向服务端发送数据
writeRequestBody(config, con, baos);
validateResponse(config, con);
// 读取服务端数据
InputStream responseBody = readResponseBody(config, con);

return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
}


服务端

而服务端过程与客户端相反:

接受客户端数据,反序列化为RemoteInvocation

服务的具体实现在服务端,根据RemoteInvocation中封装的方法信息,反射调用相关方法

将方法执行结果,序列化为ResultRemoteIvocation, 发送给客户端

HttpRequestHandlerServlet,用于接收远程调用相关请求。HttpRequestHandler接口,定义了请求的处理方法。这里注入的是
HttpInvokerServiceExporter.java


public class HttpRequestHandlerServlet extends HttpServlet {

@Nullable
private HttpRequestHandler target;

@Override
public void init() throws ServletException {
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
this.target = wac.getBean(getServletName(), HttpRequestHandler.class);
}

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

Assert.state(this.target != null, "No HttpRequestHandler available");

LocaleContextHolder.setLocale(request.getLocale());
try {
this.target.handleRequest(request, response);
}
catch (HttpRequestMethodNotSupportedException ex) {
String[] supportedMethods = ex.getSupportedMethods();
if (supportedMethods != null) {
response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
}
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
}
finally {
LocaleContextHolder.resetLocaleContext();
}
}

}




// HttpInvokerServiceExporter.java

public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

try {
RemoteInvocation invocation = readRemoteInvocation(request);
RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy());
writeRemoteInvocationResult(request, response, result);
}
catch (ClassNotFoundException ex) {
throw new NestedServletException("Class not found during deserialization", ex);
}
}


RemoteInvocation对象的反序列化和ResultRemoteInvocation对象的序列化在
RemoteInvocationSerializingExporter
中实现

// RemoteInvocationSerializingExporter.java

protected ObjectInputStream createObjectInputStream(InputStream is) throws IOException {
return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), isAcceptProxyClasses());
}

//序列化还原
protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois)
throws IOException, ClassNotFoundException {

Object obj = ois.readObject();
if (!(obj instanceof RemoteInvocation)) {
throw new RemoteException("Deserialized object needs to be assignable to type [" +
RemoteInvocation.class.getName() + "]: " + ClassUtils.getDescriptiveType(obj));
}
return (RemoteInvocation) obj;
}

protected ObjectOutputStream createObjectOutputStream(OutputStream os) throws IOException {
return new ObjectOutputStream(os);
}
// 序列化RemoteInvocationResult
protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos)
throws IOException {

oos.writeObject(result);
}


方法的反射调用过程, 也就是根据RemoteInvocation创建ResultRemoteInvocation的过程:

public abstract class RemoteInvocationBasedExporter extends RemoteExporter {

private RemoteInvocationExecutor remoteInvocationExecutor = new         DefaultRemoteInvocationExecutor();

protected Object invoke(RemoteInvocation invocation, Object targetObject)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

if (logger.isTraceEnabled()) {
logger.trace("Executing " + invocation);
}
try {
return getRemoteInvocationExecutor().invoke(invocation, targetObject);
}
catch (NoSuchMethodException ex) {
if (logger.isDebugEnabled()) {
logger.warn("Could not find target method for " + invocation, ex);
}
throw ex;
}
catch (IllegalAccessException ex) {
if (logger.isDebugEnabled()) {
logger.warn("Could not access target method for " + invocation, ex);
}
throw ex;
}
catch (InvocationTargetException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Target method failed for " + invocation, ex.getTargetException());
}
throw ex;
}
}

protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
try {
Object value = invoke(invocation, targetObject);
return new RemoteInvocationResult(value);
}
catch (Throwable ex) {
return new RemoteInvocationResult(ex);
}
}

}


public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor {

@Override
public Object invoke(RemoteInvocation invocation, Object targetObject)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{

Assert.notNull(invocation, "RemoteInvocation must not be null");
Assert.notNull(targetObject, "Target object must not be null");
return invocation.invoke(targetObject);
}

}


总结

Spring HttpInvoker 流程:

客户端服务代理对象 与服务端建立连接,将RemoteInvocation序列化发送给服务端

服务端反序列化RemoteInvocation,反射调用具体服务实现的方法,将结果封装到ResultRemoteInvocation中,将其序列化后发送给客户端

客户端发序列化ResultRemoteInvocation,获取远程调用结果。

Spring HttpInvoker 采用Java序列化的方式进行传输,要保证服务端和客户端的相关代码的版本一致,否则容易出现java.io.InvalidClassException: ; incompatible types for field 此类错误。

参考文档

几种通信协议的比较

Spring 5.0.4 文档
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: