BRAVE

学技术 学做人 学做事

深入学习 Spring HttpInvoker

深入学习 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作用是向服务端发送请求,进行远程方法调用,获取远程执行结果

有以下几个过程:

  1. 序列化RemoteInvocation, 将ObjectOutputStream写入到ByteArrayOutputStream中
  2. 根据url, 请求连接 openConnection
  3. 请求头的相关设置 :请求方法、Content-Type、Content-Length(ByteArrayOutputStream的大小)
  4. 将ByteArrayOutputStream发送到服务端,ByteArrayOutputStream.writeTo(con.getOutputStream())
  5. 获取服务端响应数据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());
    }

服务端

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

  1. 接受客户端数据,反序列化为RemoteInvocation
  2. 服务的具体实现在服务端,根据RemoteInvocation中封装的方法信息,反射调用相关方法
  3. 将方法执行结果,序列化为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 流程:

  1. 客户端服务代理对象 与服务端建立连接,将RemoteInvocation序列化发送给服务端
  2. 服务端反序列化RemoteInvocation,反射调用具体服务实现的方法,将结果封装到ResultRemoteInvocation中,将其序列化后发送给客户端
  3. 客户端发序列化ResultRemoteInvocation,获取远程调用结果。

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

参考文档

几种通信协议的比较

Spring 5.0.4 文档

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/BabyGoodMorning/article/details/79476356
个人分类: Spring 源码阅读
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭