Spring HttpInvoker 从实战到源码追溯
Spring HttpInvoker 作为 Spring 家族的一员,作为新的远程调用模型。
主要目的是来执行基于 HTTP 的远程调用(轻松穿越防火墙),并使用标准的 JDK 序列化机制。
Http 远程调用框架不是有成熟的 Hessian、Burlap嘛,Spring 团队为什么还要重复造轮子呢?
是因为他们有各自的序列化方式,当数据类型比较复杂时内私有的序列化方法可能会无法胜任。
1.项目实战
分为 server 服务和 api 接口模块,api 模块打包方式为 jar,其中定义对应传递 BO 和接口。
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>
启动服务模块,这时服务就已经发布成功了,是不是很简单?
客户端将 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 方法首先进行远程调用对象的封装,其次发起远程服务请求,最后解析返回结果并封装返回。
追溯这几个过程的时候你会看到,Sprng 代理对象 ProxyFactory、Java 序列对象 ObjectOutputStream、Java Http 连接对象 HttpURLConnection。
HttpInvoker 调优时也记得去关注上述几个对象:https://blog.csdn.net/qian_348840260/article/details/51555864
从服务暴露到服务调用,debug 源码过来底层总是那些熟悉的面孔,只不过 Spring 团队做了出色的封装和合理的抽象。
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); } }