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 从实战到源码追溯的更多相关文章

  1. 阿里P7终于讲完了JDK+Spring+mybatis+Dubbo+SpringMvc+Netty源码

    前言 这里普及一下,每个公司都有职别定级系统,阿里也是,技术岗以 P 定级,一般校招 P5, 社招 P6 起.其实阅读源码也是有很多诀窍的,这里分享几点心得: 首先要会用.你要知道这个库是干什么的,掌 ...

  2. Spring框架之spring-web http源码完全解析

    Spring框架之spring-web http源码完全解析 Spring-web是Spring webMVC的基础,由http.remoting.web三部分组成. http:封装了http协议中的 ...

  3. Spring框架之spring-web web源码完全解析

    Spring框架之spring-web web源码完全解析 spring-web是Spring webMVC的基础,由http.remoting.web三部分组成,核心为web模块.http模块封装了 ...

  4. SpringCloudGateway微服务网关实战与源码分析 - 中

    实战 路由过滤器工厂 路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应.路由过滤器的作用域是特定的路由.SpringCloud Gateway包括许多内置的GatewayFilter ...

  5. 各个版本spring的jar包以及源码下载地址

    各个版本spring的jar包以及源码下载地址,目前最高版本到spring4.1.2,留存备用: http://maven.springframework.org/release/org/spring ...

  6. Spring Boot 2.0系列文章(五):Spring Boot 2.0 项目源码结构预览

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/04/15/springboot2_code/ 项目结构 结构分析: Spring-boot-pr ...

  7. Apache Beam WordCount编程实战及源码解读

    概述:Apache Beam WordCount编程实战及源码解读,并通过intellij IDEA和terminal两种方式调试运行WordCount程序,Apache Beam对大数据的批处理和流 ...

  8. Spring Environment(二)源码分析

    Spring Environment(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Envi ...

  9. 【Java实战】源码解析Java SPI(Service Provider Interface )机制原理

    一.背景知识 在阅读开源框架源码时,发现许多框架都支持SPI(Service Provider Interface ),前面有篇文章JDBC对Driver的加载时应用了SPI,参考[Hibernate ...

随机推荐

  1. Azkaban

    Azkaban安装部署 https://azkaban.github.io/azkaban/docs/2.5/ 安装Azkaban ) 在/opt/module/目录下创建azkaban目录 [kri ...

  2. 065 updateStateByKey的函数API

    一:使用场景 1.应用场景 数据的累加 一段时间内的数据的累加 2.说明 每个批次都输出自己批次的数据, 这个时候,可以使用这个API,使得他们之间产生联系. 3.说明2 在累加器的时候,起到的效果和 ...

  3. Shiro笔记(三)shiroFilter拦截器配置原则

    参考: http://blog.csdn.net/yaowanpengliferay/article/details/17281341

  4. 上传前端webuploader

    多文件上传时,是有几个文件调用几次方法. 可以设置为单线程.

  5. JAVA项目中常用的异常处理情况

    1.数学运算异常( java.lang.arithmeticexception) 程序中出现了除以零这样的运算就会出这样的异常,对这种异常,大家就要好好检查一下自己程序中涉及到数学运算的地方,公式是不 ...

  6. Codeforces 919D Substring 【拓扑排序】+【DP】

    <题目链接> 题目大意:有一个具有n个节点,m条边的有向图,每个点对应一个小写字母,现在给出每个顶点对应的字母以及有向边的连接情况,求经过的某一条路上相同字母出现的最多次数.如果次数无限大 ...

  7. Linux常用命令大全(转)

    (转)Linux常用命令大全(非常全!!!) 最近都在和Linux打交道,感觉还不错.我觉得Linux相比windows比较麻烦的就是很多东西都要用命令来控制,当然,这也是很多人喜欢linux的原因, ...

  8. 算法进阶面试题07——求子数组的最大异或和(前缀树)、换钱的方法数(递归改dp最全套路解说)、纸牌博弈、机器人行走问题

    主要讲第五课的内容前缀树应用和第六课内容暴力递归改动态规划的最全步骤 第一题 给定一个数组,求子数组的最大异或和. 一个数组的异或和为,数组中所有的数异或起来的结果. 简单的前缀树应用 暴力方法: 先 ...

  9. linux学习笔记 ftp命令

    ftp server with sites et up for downloaing files sometimes provides an anonymous ftp account 数据传输 ft ...

  10. C#导出Excel时间格式问题

    Range range = worksheet.get_Range(worksheet.Cells[2, 1], worksheet.Cells[RowCount + 1, ColCount]);ra ...