Spring HttpInvoker 从实战到源码追溯
Spring HttpInvoker 作为 Spring 家族中老牌远程调用模型 (RPC 框架),深受开发者喜爱。
其主要目的是来执行基于 HTTP 的远程调用(轻松穿越防火墙),并使用标准的 JDK 序列化机制。
Http 远程调用框架不是有成熟的 Hessian、Burlap嘛,Spring 团队为什么还要重复造轮子呢?
因为它们有各自的序列化方式,首先无法保证统一和规范性,其次无法保证序列化比较复杂的数据类型。
但 Spring HttpInvoker 因与 Spring 难舍难分,无法跨平台也无法跨语言,服务和客户端必须得使用 Spring。
仅从上手程度来说,Spring HttpInvoker 优于其他的服务框架,所以有利有弊,权衡者在你。
本文试从项目实例入手描述 Spring HttpInvoker 的使用,在进行源码分析带你了解底层 Java 技术。
1.项目实战

maven 依赖 spring-web 即可, 上图为实例工程分为 server 服务模块、 api 接口模块。
api 模块打包方式为 jar,其中定义接口和传递的业务实体, server 模块打包方式为 war,编写业务服务实现。
接口定义如下:
public interface UserService {
    /**
     * 通过ID获取用户
     *
     * @param uuid 用户ID
     * @return 用户实体
     */
    User getUserById(String uuid);
}
接口返回的业务实体属性,还需你根据具体业务拿捏,实现类:
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(String uuid) {
        User user = new User();
        user.setUuid(uuid);
        user.setName("Orson");
        user.setPasswd("xyxy");
        user.setSex("F");
        user.setPhone("13974856211");
        user.setPhoto("/photo/user/xyxy.gif");
        user.setEmail("954875698@qq.com");
        user.setCreateBy("orson");
        return user;
    }
}
Spring 配置服务如下:
    <bean id="userServiceImpl" class="com.rambo.httpinvoker.server.impl.UserServiceImpl" />
    <bean id="userServiceInvoker" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="userServiceImpl" />
        <property name="serviceInterface" value="com.rambo.httpinvoker.api.UserService" />
    </bean>
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/userService">userServiceInvoker</prop>
            </props>
        </property>
    </bean>
web.xml 配置:
<servlet>
<servlet-name>service</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-httpinvoke-server.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>service</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
配置 tomcat 或 jetty 启动服务模块,这时服发布成功,是不是很简单?
客户端将 api 依赖进去,spring 稍做下配置就可以在客户端中使用对应的服务。
<!-- 客户端使用 HttpInvokerProxyFactoryBean 代理客户端向服务器端发送请求,请求接口为 UserService 的服务 -->
<bean id="userService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://${server.url}/service/userService"/>
<property name="serviceInterface" value="com.rambo.httpinvoker.api.UserService"/>
</bean>
demo 项目地址:https://gitee.com/LanboEx/rmi-demo.git
2.源码分析
源码分析时从客户端和服务端配置两个对象 HttpInvokerServiceExporter、HttpInvokerProxyFactoryBean下手。

HttpInvokerServiceExporter 继承 HttpRequestHandler 并实现 handleRequest 方法。
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            RemoteInvocation invocation = this.readRemoteInvocation(request);
            RemoteInvocationResult result = this.invokeAndCreateResult(invocation, this.getProxy());
            this.writeRemoteInvocationResult(request, response, result);
        } catch (ClassNotFoundException var5) {
            throw new NestedServletException("Class not found during deserialization", var5);
        }
    }
首先从 http 请求中读取远程调用对象,然后调用服务对应方法并组织执行结果,最后将执行结果写入到 http 返回。
这几个过程你追溯到底层代码,你会发现 Java ObjectInputStream 反序列化对象、Java Method 反射对象。

HttpInvokerProxyFactoryBean 实现 FactoryBean 接口并继承 HttpInvokerClientInterceptor,spring ioc 托管该类并初始化对应属性后返回该类代理。
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
        if (this.getServiceInterface() == null) {
            throw new IllegalArgumentException("Property 'serviceInterface' is required");
        } else {
            this.serviceProxy = (new ProxyFactory(this.getServiceInterface(), this)).getProxy(this.getBeanClassLoader());
        }
    }
注意获取代理类时传入的拦截器参数为 this 即为父类 HttpInvokerClientInterceptor。
该拦截器 invoke 方法首先进行远程调用对象的封装,其次发起远程服务请求,最后解析返回结果并封装返回。
追溯这几个过程的时候你会看到,cgb 代理拦截器 MethodInterceptor、Java 序列对象 ObjectOutputStream、Java Http 连接对象 HttpURLConnection。
HttpInvoker 调优时也记得去关注上述几个对象:https://blog.csdn.net/qian_348840260/article/details/51555864
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
            return "HTTP invoker proxy for service URL [" + this.getServiceUrl() + "]";
        } else {
            RemoteInvocation invocation = this.createRemoteInvocation(methodInvocation);
            RemoteInvocationResult result;
            try {
                result = this.executeRequest(invocation, methodInvocation);
            } catch (Throwable var7) {
                RemoteAccessException rae = this.convertHttpInvokerAccessException(var7);
                throw (Throwable)(rae != null ? rae : var7);
            }
            return this.recreateRemoteInvocationResult(result);
        }
    }
从服务暴露到服务调用,debug 源码过来底层总是那些熟悉的面孔,只不过 Spring 团队做了出色的封装和合理的抽象。
至此全文结束,文中如有纰漏,还望斧正。
Spring HttpInvoker 从实战到源码追溯的更多相关文章
- 阿里P7终于讲完了JDK+Spring+mybatis+Dubbo+SpringMvc+Netty源码
		
前言 这里普及一下,每个公司都有职别定级系统,阿里也是,技术岗以 P 定级,一般校招 P5, 社招 P6 起.其实阅读源码也是有很多诀窍的,这里分享几点心得: 首先要会用.你要知道这个库是干什么的,掌 ...
 - Spring框架之spring-web http源码完全解析
		
Spring框架之spring-web http源码完全解析 Spring-web是Spring webMVC的基础,由http.remoting.web三部分组成. http:封装了http协议中的 ...
 - Spring框架之spring-web web源码完全解析
		
Spring框架之spring-web web源码完全解析 spring-web是Spring webMVC的基础,由http.remoting.web三部分组成,核心为web模块.http模块封装了 ...
 - SpringCloudGateway微服务网关实战与源码分析 - 中
		
实战 路由过滤器工厂 路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应.路由过滤器的作用域是特定的路由.SpringCloud Gateway包括许多内置的GatewayFilter ...
 - 各个版本spring的jar包以及源码下载地址
		
各个版本spring的jar包以及源码下载地址,目前最高版本到spring4.1.2,留存备用: http://maven.springframework.org/release/org/spring ...
 - Spring Boot 2.0系列文章(五):Spring Boot 2.0 项目源码结构预览
		
关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/04/15/springboot2_code/ 项目结构 结构分析: Spring-boot-pr ...
 - Apache Beam WordCount编程实战及源码解读
		
概述:Apache Beam WordCount编程实战及源码解读,并通过intellij IDEA和terminal两种方式调试运行WordCount程序,Apache Beam对大数据的批处理和流 ...
 - Spring Environment(二)源码分析
		
Spring Environment(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Envi ...
 - 【Java实战】源码解析Java SPI(Service Provider Interface )机制原理
		
一.背景知识 在阅读开源框架源码时,发现许多框架都支持SPI(Service Provider Interface ),前面有篇文章JDBC对Driver的加载时应用了SPI,参考[Hibernate ...
 
随机推荐
- Codeforces 514C Watto and Mechanism 【Trie树】+【DFS】
			
<题目链接> 题目大意:输入n个单词构成单词库,然后进行m次查询,每次查询输入一个单词(注意这些单词只由a,b,c构成),问该单词库中是否存在与当前查询的单词有且仅有一个字符不同的单词. ...
 - [ 中危 ] 发布处存在CSRF及CSRF设想
			
漏洞存在于菜品发布处,使用A账号在添加/发布菜品的时候拦截数据包,使用burpsuite构造 CSRF的POC,再用B账号打开该HTML POC ,生成菜品. 该CSRF400RMB,主要因为是核心业 ...
 - Spring Boot 项目实战(四)集成 Redis
			
一.前言 上篇介绍了接口文档工具 Swagger 及项目监控工具 JavaMelody 的集成过程,使项目更加健壮.在 JAVA Web 项目某些场景中,我们需要用缓存解决如热点数据访问的性能问题,业 ...
 - 深入理解JS防抖与节流
			
参考博客:JS防抖和节流,感谢作者的用心分享 日常开发过程中,滚动事件做复杂计算频繁调用回调函数很可能会造成页面的卡顿,这时候我们更希望把多次计算合并成一次,只操作一个精确点,JS把这种方式称为deb ...
 - linux 学习笔记  cpio命令
			
1 文件或目录打包 打包有如下多种情况 A>包含子目录打包 find /usr/lib -print /cpio -o >/uo/temp1.cpio 将/usr/lib目录下的文件与子目 ...
 - linux 硬盘分区与格式化挂载 (二)
			
1. 文件系统的挂载与卸载(详见linux系统管理P406)1) 掌握挂载的定义:挂载指将一个设备(通常是存储设备)挂接到一个已存在的目录上.2) 掌握mount命令的功能:实现文件系统的挂载.3) ...
 - Java性能调优zz
			
写Java也有n年了,现在还是有不少的坏的代码习惯,也通过学习别人的代码学到了不少好的习惯.这篇文章主要是整理的资料.留给自己做个警戒,提示以后写代码的时候注意!在文章的后面,会提供整理的原材料下载. ...
 - BigDecimal使用中的一些注意事项
			
Java的商业计算,不能用float和double,因为他们无法进行精确计算.但是Java的设计者给编程人员提供了一个很有用的类BigDecimal,他可以完善float和double类无法进行精确计 ...
 - Scrapy基础(五) ------css选择器基础
			
基本语法: * 选择所有节点#container 选择id为container的节点.container 选择所有class包含contai ...
 - 【开源GPS追踪】 之 服务器端opengts安装
			
国内大多数GPS追踪/定位 平台都是基于opengts 二次开发的,opengts 是一款开源的gps 跟踪软件. 下面摘自百度百科: OpenGTS ™(“GPS跟踪系统”)是第一个可用的开源项目, ...