服务调用方案(Spring Http Invoker) - 我们到底能走多远系列(40)

时间:2023-06-27 10:37:20

我们到底能走多远系列(40)

扯淡:

   判断是否加可以效力于这家公司,一个很好的判断是,接触下这公司工作几年的员工,了解下生活工作状态,这就是你几年后的状态,如果满意就可以考虑加入了。

主题:

  场景:项目A作为主项目,业务实现完整,项目B需要调用项目A中的部分服务,那么项目A就需要提供出服务出来。实现分布式调用的方法有很多,这里介绍一下利用Spring Http Invoker 来实现的服务提供和调用。

  demo地址:摸我

  如果你对快速用springmvc搭建web应用感兴趣:摸我

  

  阅读Spring的源码基本的流程实现是这样的:  

服务调用方案(Spring Http Invoker) - 我们到底能走多远系列(40)

下面就详细分析一下这个流程的实现:

1,服务请求流程分析
对远程服务的配置例子如下:
 <bean id="remoteDemoService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl">
<value>http://localhost:7777/remote/demoService</value>
</property>
<property name="serviceInterface">
<value>com.witown.open.demo.remote.service.DemoService</value>
</property>
</bean>

首先看的是HttpInvokerProxyFactoryBean类,它继承FactoryBean,所以先来看一看这个FactoryBean。

在spring中可以利用FactoryBean来产生一些自定义配置的bean。
如果一个类继承 FactoryBean 用户可以自己定义产生实例对象的方法只要实现他的 getObject 方法。然而在 Spring 内部这个 Bean 的实例对象是 FactoryBean,通过调用这个对象的 getObject 方法就能获取用户自定义产生的对象,正是这样获得bean的机制,我们可以在getObject 方法中自定义一些操作,从而为 Spring 提供了很好的扩展性。
public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor
implements FactoryBean<Object> {
// 代理对象
private Object serviceProxy;
// 初始化后执行方法
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (getServiceInterface() == null) {
throw new IllegalArgumentException("Property 'serviceInterface' is required");
} // 使用AOP代理工厂创建代理对象,对这个代理对象的所有方法调用最后都会被拦截
// 调用接口的任何一个方法时都会被拦截去变成http请求
this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
}
// 获得bean方法
public Object getObject() {
return this.serviceProxy;
}
public Class<?> getObjectType() {
return getServiceInterface();
}
public boolean isSingleton() {
return true;
}
}

这样DemoService 的调用都会被HttpInvokerClientInterceptor拦截,拦截的方法调用会去执行HttpInvokerClientInterceptor中的invoke方法,这个方法明了的体现了客户端的执行流程的三个步骤:

public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";
}
// 1,把方法执行信息封装成RemoteInvocation
RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
RemoteInvocationResult result = null;
try {
// 2,发起http请求,把方法执行信息传过去,正常的话,服务器会返回结果
result = executeRequest(invocation, methodInvocation);
}
catch (Throwable ex) {
throw convertHttpInvokerAccessException(ex);
}
try {
// 3,解析返回的结果,转化成java对象
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);
}
}
}
step 1,MethodInvocation是客户端调用的方法,spring的AOP实现了这个接口,第一步就是吧这个对象转变成RemoteInvocation,它是Serializable 的一边后面对这个对象的序列化,RemoteInvocation封装了什么:
 public class RemoteInvocation implements Serializable {
//方法名
private String methodName;
// 类
private Class[] parameterTypes;
// 参数
private Object[] arguments;
// 步骤1中实际就是调用了这个构造函数而已
public RemoteInvocation(MethodInvocation methodInvocation) {
this.methodName = methodInvocation.getMethod().getName();
this.parameterTypes = methodInvocation.getMethod().getParameterTypes();
this.arguments = methodInvocation.getArguments();
}
// ...
}
step 2,调用链下去会调用到AbstractHttpInvokerRequestExecutor的executeRequest方法,这个方法又分成两步:1,将远程调用信息对象转成流,2,将流写入http请求
  public final RemoteInvocationResult executeRequest(HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws Exception {
// RemoteInvocation(远程调用对象),转成可以传输的OutputStream,以便写入http请求中
ByteArrayOutputStream baos = getByteArrayOutputStream(invocation);
if (logger.isDebugEnabled()) {
logger.debug("Sending HTTP invoker request for service at [" + config.getServiceUrl() +
"], with size " + baos.size());
}
// 将流写入http请求,发送请求,并接收响应
return doExecuteRequest(config, baos);
}

继续追踪getByteArrayOutputStream方法,发现使用了ObjectOutputStream最终调用了writeObject方法来将对象转化成流,下面是一段ObjectOutputStream的描述:

     Java中ObjectInputStream 与 ObjectOutputStream这两个包装类可用于输入流中读取对象类数据和将对象类型的数据写入到底层输入流 。ObjectInputStream 与 ObjectOutputStream 类所读写的对象必须实现了 Serializable 接口。需要注意的是:对象中的 transient 和 static 类型的成员变量不会被读取和写入 。
转换好之后得到的是ByteArrayOutputStream ,此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()和 toString()获取数据。
将流写入http请求,发送请求,并接收响应:
protected RemoteInvocationResult doExecuteRequest(
HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
throws IOException, ClassNotFoundException {
// 打开远程的HTTP连接
HttpURLConnection con = openConnection(config); // 设置HTTP连接信息
prepareConnection(con, baos.size()); // 把准备好的序列化的远程方法调用对象的字节流写入到HTTP请求体中
writeRequestBody(config, con, baos); // 校验HTTP响应
validateResponse(config, con); // 获得HTTP相应体的流对象
InputStream responseBody = readResponseBody(config, con);
// 读取远程调用结果对象并返回
return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
}

最后的读取远程调用结果对象并返回:

protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
// 获得Object
Object obj = ois.readObject();
if (!(obj instanceof RemoteInvocationResult)) {
throw new RemoteException("Deserialized object needs to be assignable to type [" +
RemoteInvocationResult.class.getName() + "]: " + obj);
}
// 服务端对返回的对象封装成RemoteInvocationResult!
return (RemoteInvocationResult) obj;
}

到这里基本完成了,客户端请求远程对象方法的流程。

2,服务提供者流程分析
 
既然对方用http请求,那作为spring,自然是会给予Spring mvc来实现的啦。
从例子的配置中就可以看到:拦截所有/remote/*请求,来调用服务。
<servlet>
<servlet-name>remote</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/config/remote-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>remote</servlet-name>
<url-pattern>/remote/*</url-pattern>
</servlet-mapping>

remote-servlet.xml文件中配置了处理请求的service:

< bean name= "/demoService" class ="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter" >
< property name= "service" ref = "demoServiceImpl"/>
< property name= "serviceInterface" value ="com.witown.open.demo.service.DemoService" />
</bean >

HttpInvokerServiceExporter继承了HttpRequestHandler,所以请求从handleRequest方法开始。

整个请求到响应的流程像编码解码,很普遍的流程,查看下面代码,步骤十分清晰。
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { try {
// 解析请求,获得RemoteInvocation
RemoteInvocation invocation = readRemoteInvocation(request);
// 根据RemoteInvocation里的信息:方法,参数,执行,把返回的结果封装成RemoteInvocationResult
RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy());
// 把结果写入响应
writeRemoteInvocationResult(request, response, result);
}
catch (ClassNotFoundException ex) {
throw new NestedServletException("Class not found during deserialization", ex);
}
}

内部调用的代码经过前面请求调用发起的流程学习,就会很好理解了。
至此,整个流程就完整了。

牵涉知识点,有兴趣可以扩展学习:
1,spring FactoryBean
2,spring aop拦截机制
3,序列化
4,java发起http请求
总结:spring的这个封装很不错,在实际项目中使用方便,研读源码时可以遇到一些常用的知识领域,会有收获!

让我们继续前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不会成功。