Spring源码深度解析(郝佳)-学习-HttpInvoker使用及源码解析

  Spring开发小组意识到在RMI服务和基于HTTP的服务如(Hessian和Burlap)之间的空白,一方面,RMI使用Java标准对象序列化,很难穿越防火墙,另一方面,Hessian/Burlap能很好的穿过防火墙工作,但是使用自己的私有的一套磁序列化机制。
  就这样,Spring的HttpInvoker应运而生,HttpInvoker是一个新的远程调用模型,作为Spring框架的一部分,来执行基于HTTP的远程调用(让防火墙高兴的事情),并使用Java序列化机制 (让程序员高兴的事情)。
  我们首先来看看HttpInvoker的使用示例,HttpInvoker是基于HTTP的远程调用,同时使用Spring中提供的web服务作为基础,所以我们测试需要首先搭建web工作。

使用示例
1.创建对外接口
public interface HttpInvokerTestI {
    String getTestPo(String desp);
}

public class HttpInvokerTestImpl implements HttpInvokerTestI {
    @Override
    public String getTestPo(String desp) {

        return "getTestPo " + desp;
    }
}
2.创建服务端配置文件web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee"
         version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee">
    <display-name>spring_tiny</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring_1_100/config_71_80/spring77_httpinvoker/spring77_service.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>spring_tiny</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring_tiny</servlet-name>
        <url-pattern>*.service</url-pattern>
    </servlet-mapping>
    
</web-app>
3. 在WEB-INF下创建spring_tiny-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
       
    <!-- 在Spring的httpInvoker服务 -->
    <bean id="httpInvokerServiceExporter"
          class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <!--需要发布的实现类 -->
        <property name="service" ref="httpinvokertest" />
        <property name="serviceInterface" value="com.spring_1_100.test_71_80.test77_spring_httpinvoker.service.HttpInvokerTestI" />
    </bean>
    
    <!-- 将特定的请求映射到具体的hessianservice -->
    <bean id="urlMapping1"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/httpinvokertest.service">httpInvokerServiceExporter</prop>
            </props>
        </property>
    </bean>

    <bean id="httpinvokertest" class="com.spring_1_100.test_71_80.test77_spring_httpinvoker.service.HttpInvokerTestImpl"></bean>
</beans>
4.配置Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
5.添加tomcat配置

在这里插入图片描述
  记得选择Local tomcat 。
在这里插入图片描述
  在添加Deployment时,记得选第一个Artifact…

在这里插入图片描述
  选择要发布的war包时,记得选带 exploded后缀的war包
在这里插入图片描述
  去除项目名称,方便使用http://localhost:8080/直接访问,而不需要在访问时url加上项目名称,如【http://localhosttt:8080/spring_tiny_war_exploded/】】

在这里插入图片描述
  项目启动需要用户自己去下载和配置jdk和tomcat,这里就不再赘述了。
【注意】spring_tiny这个项目,我可能经常需要测试其他的用例,可能web.xml和spring_tiny_servlet.xml经常会变,如果你要测试httpInvoker,请将备份的文件web_77.xml和spring_tiny-servlet_77.xml的内容分别覆盖掉web.xml和spring_tiny_servlet.xml两个文件内容,再启动项目进行测试。
在这里插入图片描述
  至此,服务端的httpInvoker服务己经搭建完全了,启动web工程后就可以使用我们搭建的HttpInvoker服务了,以上代码实现了将远程传的字符串参数处理加入了"getTestPo"前缀的功能,服务端搭建完基于Web服务的HttpInvoker后,客户端还需要使用一定的配置才能进行远程调用。

5. 创建测试端配置client.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="remoteService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
        <property name="serviceUrl" value="http://localhost:8080/httpinvokertest.service"></property>
        <property name="serviceInterface" value="com.spring_1_100.test_71_80.test77_spring_httpinvoker.service.HttpInvokerTestI"></property>
    </bean>
</beans>
6.测试客户端
public class Test77_Client {

    public static void main(String[] args) {
        ApplicationContext ct = new ClassPathXmlApplicationContext("classpath:spring_1_100/config_71_80/spring77_httpinvoker/spring77_client.xml");

        HttpInvokerTestI httpInvokerTestI = (HttpInvokerTestI) ct.getBean("remoteService");
        System.out.println(httpInvokerTestI.getTestPo("dddd"));

    }
}

结果输出:
在这里插入图片描述

  dddd 是我们传入的参数,而getTestPo 则是在服务端添加的字符串,当然上面的服务搭建与测试过程中都是在一台机器上运行的,如果需要在不同的机器上进行测试,还需要读者对服务端相关的接口打成jar包并添加到客户端的服务器上。

服务端的实现

  对于Spring中的HttpInvoker月服务的实现,我们还是首先从服务端进行分析。
  根据spring_tiny-servlet.xml中的配置,我们分析入口类应该是org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter,那么同样,根据这个类的分析其入口函数,如下图所示
在这里插入图片描述
  通过层次关系我们看到了HttpInvokerServiceExporter类实现了InitializingBean接口以及HttpRequestHandler接口,分析RMI服务时我们己经了解到了,当某个bean继承自InitializingBean接口的时候,Spring会确保这个bean在初始化的时候调用其afterPropertiesSet方法,而对于HttpRequestHandler接口,因为我们配置中己经将此接口配置成Web服务,那么当有相应的请求时,Spring的Web服务就会将程序引导至HttpRequestHandler的handleRequest方法中,首先,我们从afterPropetiesSet方法开始分析,看看bean的初始化过程中做了哪些逻辑。

1. 创建代理
public void afterPropertiesSet() {
    prepare();
}

public void prepare() {
    this.proxy = getProxyForService();
}

protected Object getProxyForService() {
	//验证service
    checkService();
    //验证serviceInterface
    checkServiceInterface();
    //使用JDK的方式创建代理
    ProxyFactory proxyFactory = new ProxyFactory();
    //添加代理接口
    proxyFactory.addInterface(getServiceInterface());
    if (this.registerTraceInterceptor != null ?
            this.registerTraceInterceptor.booleanValue() : this.interceptors == null) {
		//加入代理横切面RemoteInvocationTraceInterceptor并记录Exporter名称
        proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
    }
    if (this.interceptors != null) {
        AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
        for (int i = 0; i < this.interceptors.length; i++) {
            proxyFactory.addAdvisor(adapterRegistry.wrap(this.interceptors[i]));
        }
    }
    //设置要代理的目标类
    proxyFactory.setTarget(getService());
    proxyFactory.setOpaque(true);
    //创建代理
    return proxyFactory.getProxy(getBeanClassLoader());
}

  通过将上面3个方法串联,可以看到,初始化过程实现的逻辑主要是创建一个代理中封装了对于我写请求的处理方法以及接口等信息,而这个代理最关键的上的是加入了RemoteInvocationTraceInterceptor增强器,当然创建代理还有其他的好处的,比如代码优雅,方便扩展等,RemoteInvocationTraceInterceptor中的增强主要是对增强的目标方法进行一些相关的日志打印,并没有在此基础上进行任何功能的增强,那么这个代理空间是在什么时候使用的呢?暂时留下悬念,接下来分析当有Web请求HttpRequestHandler的handlerRequest方法的处理。

2. 处理来自客户端的request

  当有Web请求时,根据配置中的规则会把路径匹配的访问直接引入到对应的HttpRequestHandler中,本例中的Web请求与普通的Web请求是有些区别的,因为在此处的请求包含着HttpInvoker的处理过程。

public void handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    try {
    	//从request中读取序列化数据
        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);
    }
}

  在handlerRequest函数中,我们清楚的看到了HttpInvoker处理的大致框架,HttpInvoker服务简单的来说就是将请求的方法,也就是RemoteInvocation对象,从客户端序列化并通过Web请求出入服务端,服务端在对传过来的序列化对象进行反序列化还原RemoteInvocation实例,然后通过实例中的相关信息进行相关的方法的调用,并将执行结果再次的返回给客户端,从handleRequest函数中我们也可以清晰地看到程序执行的框架结构 。

  1. 从request中读取序列化对象。

  主要是从HttpServletRequest提取相关的信息,也就是提取HttpServcletRequest中的RemoteInvocation对象的序列化信息以及反序列化的过程。

protected RemoteInvocation readRemoteInvocation(HttpServletRequest request)
        throws IOException, ClassNotFoundException {

    return readRemoteInvocation(request, request.getInputStream());
}

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;
}

  对于序列化提取与转换过程其实并没有太多需要解释的东西,这里完全是标准的方式进行操作的,包括创建ObjectInputStream以及从ObjectInputStream中提取对象实例。

  1. 执行调用。

  根据反序列化方式得到的RemoteInvocation对象中的信息,进行方法调用,注意,此时调用的实体并不是服务接口或服务类,而是之前在初始化时候构造的封装了服务接口以及服务类的代理。
  完成了RemoteInvocation实例的提取,也就意味着可以通过RemoteInvocation实例提供的信息进行方法调用了。

protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
    try {
	    //激活代理类中对应的incocation中的方法
        Object value = invoke(invocation, targetObject);
        //封装结果以便于序列化
        return new RemoteInvocationResult(value);
    }
    catch (Throwable ex) {
        return new RemoteInvocationResult(ex);
    }
}

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;
    }
}

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);
}


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

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

  这段函数有两点需要说明的地方。

  • 对应方法的激活也就是invoke方法的调用,虽然经过层层环绕,但是最终还是实现了一个我们熟知的调用invocation.invoke(targetObject),也就是执行RemoteInvocation类中的invoke方法,大致的逻辑还是通过RemoteInvocation中对应的方法信息在targetObject上去执行,此方法在分析RMI功能的时候己经分析过,不再赘述,但是在对于当前方法的targetObject参数,此targetObject是代理类,调用代理类的时候要考虑增强方法的调用,这是读者需要注意的地方。
  • 对于返回结果需要使用RemoteInvocationResult进行封装,之所以需要通过RemoteInvocationResult类进行封装,是因为无法保证对于所有操作的返回结果都继承Serializable接口,也就是说无法保证所有的返回结果都可以直接进行序列化,那么就必需到什么ReMoteInvocationResult进行统一封装。
  1. 将结果的序列化对象写入输出流。
    同样这里也包括序列化过程。
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();
    }
}

protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos)
        throws IOException {

    oos.writeObject(result);
}
客户端实现

  分析了服务端的解析以及处理过程后,我们接下来分析客户端的调用过程,在服务端调用的分析是我们反复提到需要从HttpServletRequest中提取从客户端传来的RemoteInvocation实例,然后进行相应的解析,所以,在客户端,一个比较重要的任务就是构建RemoteInvocation实例,并传送到服务端,根据配置文件中的信息,我们还是首先锁定HttpInvokerProxyFactoryBean类,并查看其层次结构 。
在这里插入图片描述
  从层次结构中我们看到,HttpInvokerProxyFactoryBean类同样实现了InitializingBean接口,同时,又实现了FactoryBean以及MethodInterceptro,这己经是老生常谈的问题了,实现这几个接口以及这几个接口在Spring中会有什么作用就不再赘述了,我们还是根据实现了的初始化过程的逻辑。

public void afterPropertiesSet() {
    super.afterPropertiesSet();
    if (getServiceInterface() == null) {
        throw new IllegalArgumentException("Property 'serviceInterface' is required");
    }
    //创建代理并使用当前方法为拦截器增强
    this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
}

  在afterPropertiesSet中主要创建了一个代理,该代理封装了配置的服务接口,并使用当前类也就是HttpInvokerProxyFactoryBean作为增强,因为HttpInvokerProxyFactoryBean实现了MethodInterceptor方法,所以可以作为增强拦截器。
  同样,又由于HttpInvokerProxyFactoryBean实现了FactoryBean接口,所以通过Spring中普通方式调用该bean时调用的并不是该bean本身,而是此类中getObject方法返回的实例,也就是实例化过程中所创建的代理 。

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

  那么综合之前的使用示例,我们再次回顾一下,HttpInvokeProxyFactoryBean类型的bean在初始化过程中创建的封装服务接口的代理,并使用自身作为增强拦截器,然后因为实现了FactoryBean接口,所以获取Bean提时候返回的其实是创建的代理 ,那么,汇总上面的逻辑,当调用如下代码时,其实是调用代理类中的服务方法,而在调用代理类中的服务方法时又会使用代理类中加入的增强器进行增强。
  ApplicationContext ct = new ClassPathXmlApplicationContext(“classpath:spring_1_100/config_71_80/spring77_httpinvoker/spring77_client.xml”);
  HttpInvokerTestI httpInvokerTestI = (HttpInvokerTestI) ct.getBean(“remoteService”);
  System.out.println(httpInvokerTestI.getTestPo(“dddd”));   这时,所有的逻辑分析其实己经被转向到了对于增强器也就是HttpInvokerProxyFactoryBean类本身的invoke方法的分析了。
  在分析invoke方法分析之前,其实我们己经猜测出该方法所提供的主要功能就是将调用的信息封装在RemoteInvocation中,发送给服务端并等待返回结果。

public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
        return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";
    }
	//封装methodName,parameterTypes,arguments 到RemoteInovcation中
    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);
        }
    }
}

protected RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
    return getRemoteInvocationFactory().createRemoteInvocation(methodInvocation);
}

public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
    return new RemoteInvocation(methodInvocation);
}

public RemoteInvocation(MethodInvocation methodInvocation) {
    this.methodName = methodInvocation.getMethod().getName();
    this.parameterTypes = methodInvocation.getMethod().getParameterTypes();
    this.arguments = methodInvocation.getArguments();
}

  函数主要有3个步骤。

  1. 构建RemoteInvocation实例

  因为是代理中增强方法调用,调用的方法及参数信息会在代理中封装至MethoInvocation实例中,并在增强器中进行传递,也就意味着当程序进入invoke方法时其实是己经包含了调用的接口的相关信息的,那么,首先要做的就是将MethodInvocation中的信息提取并构建RemoteInvocation。

  1. 执行远程方法
  2. 提取结果

  考虑到序列化的问题,在Spring中约定使用HttpInvoker方式进行方法的调用,结果使用RemoteInvocationResult进行封装,那么在提取结果后还需要从封装的结果中提取对应的结果。
  而这3个步骤最关键的就是远程方法的执行,执行远程方法的首先步骤就是将调用的方法的实例写入到输入流中。

protected RemoteInvocationResult executeRequest(
        RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {

    return executeRequest(invocation);
}

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

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);
}

  在doExecuteRequest方法中真正的实例了对远程方法的构造及通信,与远程方法的连接功能实现中,Spring引入了第三方JAR:HttpClient,HttpClient是Apache Jakarta Common 下的子项目,可以用来提供高效,最新的,功能丰富的支持HTTP协义的客户端编程工具包,并且它支持HTTP协义最新的版本和建义,对HTTP Client 的可以自己去参考更多的资料和文档。

protected RemoteInvocationResult doExecuteRequest(
        HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
        throws IOException, ClassNotFoundException {
	//创建Connection
    HttpURLConnection con = openConnection(config);
    //执行POST方法
    prepareConnection(con, baos.size());
    writeRequestBody(config, con, baos);
    //验证
    validateResponse(config, con);
    InputStream responseBody = readResponseBody(config, con);

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

  

  1. 创建connection
protected HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException {
	//设置需要访问的url
    URLConnection con = new URL(config.getServiceUrl()).openConnection();
    if (!(con instanceof HttpURLConnection)) {
        throw new IOException("Service URL [" + config.getServiceUrl() + "] is not an HTTP URL");
    }
    return (HttpURLConnection) con;
}

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("POST");
    connection.setRequestProperty("Content-Type", getContentType());
    connection.setRequestProperty("Content-Length", Integer.toString(contentLength));

    LocaleContext localeContext = LocaleContextHolder.getLocaleContext();
    if (localeContext != null) {
        Locale locale = localeContext.getLocale();
        if (locale != null) {
        	//加入Accept-Language属性
            connection.setRequestProperty("Accept-Language", StringUtils.toLanguageTag(locale));
        }
    }
    if (isAcceptGzipEncoding()) {
    	//加入Accept-Encoding属性
        connection.setRequestProperty("Accept-Encoding", "gzip");
    }
}
  1. 执行远程调用
protected void writeRequestBody(
        HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos)
        throws IOException {

    baos.writeTo(con.getOutputStream());
}
  1. 远程相应验证

  对于HTTP调用响应码处理,大于300则是非正常调用码。

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() + "]");
    }
}
  1. 提取响应信息

  从服务器中返回的输入流可能是经过压缩的,不同的方式采用不同的办法进行提前。

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();
    }
}
  1. 提取返回结果
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;
}

  整个调用过程其实并不复杂,客户端将方法名称,参数类型,方法参数值封装成一个RemoteInvocation对象,通过http请求发送综给服务端,服务端接收到这个请求,反序列化RemoteInvocation对象,根据RemoteInvocation对象中的方法名称和方法参数类型定位出要调用的方法,再通过反射传入RemoteInvocation对象中的方法参数调用此方法,将返回值封装成一个可序列化对象RemoteInvocationResult返回给客户端,客户端得到序列化RemoteInvocationResult对象,获取其value即可,我们写一个简单的伪代码来说明一个整个过程。

  1. 服务端代码
@RestController
public class HttpInvokerServerController {

    @RequestMapping("/httpinvokertest.service")
    public String test(String methodName,Class<?> [] paramterTypes,Object [] args) throws Exception{
        Method method = HttpInvokerTestImpl.class.getDeclaredMethod(methodName,paramterTypes);
        return method.invoke(args) + "";
    }
}
  1. 客户端代码
public class HttpInvokerClientTest {
    protected static final Logger logger = LoggerFactory.getLogger(HttpInvokerClientTest.class);
    public static void main(String[] args) {
        doGet("http://localhost:8080/httpinvokertest.service?methodName&xxxx",1000);
    }

    public static String doGet(String url, int timeout) {
        logger.info("doGet url = {}", url);
        BufferedReader in = null;
        OutputStreamWriter out = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            URLConnection conn = realUrl.openConnection();
            conn.setConnectTimeout(timeout);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            logger.error("发送失败" + e);
            return "";
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        logger.info("doGet result = {}", result);
        return result;
    }
}
总结 :

  整个过程,我们来总结一下RMI和HttpInvoker的异同。
不同点:

  • 客户端和服务端数据传输方式不同,RMI使用rmi协义来传输数据,HttpInvoker使用Http协义传输数据。
  • RMI 客户端拿到Remote对象后,直接远程调用,HttpInvoker 客户端和服务端请求参数和返回参数都有一个序列化和反序列化的过程。

相同点:

  • 客户端和服务端都实现了InitializingBean接口,在bean的afterPropertiesSet()方法中初始化bean数据
  • 服务端都使用代理来追踪日志
  • 服务端都是通过获取方法名称,方法参数 通过反射来定位调用的方法的。
  • 客户端都实现了FactoryBean接口,通过getObject返回代理类
  • 客户端都是在代理类的内部完成服务端的调用以及返回值的处理

  今天的源码分析就到这里了,有问题跟我反馈,我们一起来解决问题,一起进步。

本文的github地址是
https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_1_100/test_71_80/test77_spring_httpinvoker

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值