1.1.1.1 基于 HTTP 请求处理器流程的实现
这一节,我们将分析 Spring Web MVC 中的另外一个流程,它是用来支持基于 HTTP 协议的远程调用的流程。它并不是应用到一个简单的 HTTP 请求,它使用 Servlet 通过 HTTP 协议导出一个服务,服务的客户端可以通过 HTTP 协议使用和调用此服务。 Spring Web MVC 支持基于 HTTP 协议的三个类型的远程调用,基于 Burlap 的远程调用 , 基于 Hessian 的远程调用和基于序列化的远程调用。这里我们只分析基于序列化的远程调用,在后面小节中我们将分析所有远程调用流程的架构实现。
既然这是一个远程调用流程的实现,那么必然存在客户端和服务器端的实现,客户端是通过一个工厂 Bean 的实现导出服务器接口的代理,代理的实现把对方法的调用封装成远程调用对象并且进行序列化,通过配置的 HTTP URL 把序列化的远程调用对象通过 HTTP 请求体传入到服务器,服务器解析序列化的远程调用对象,然后通过反射调用远程调用对象里包含的方法,最后发送返回值到客户端。如下流程图所示,
图表 4 ‑ 23
首先我们深入分析基于 HTTP 请求处理器流程的远程调用的客户端的实现体系结构。如下类图所示,
图表 4 ‑ 24
客户端的实现是通过 AOP 代理拦截方法调用,然后,将传入的方法参数和正在调用的方法封装到远程调用对象并且序列化为字节流。最后,通过 HTTP 调用器请求执行器写入到远程主机的 HTTP 服务中的。下面我们根据流程的先后顺序对实现代码进行分析,如下代码注释,
当在客户端的 Spring 环境中配置一个 HTTP 服务,首先我们需要声明一个 HTTP 唤起器代理工厂 Bean(HttpInvokerProxyFactoryBean) ,然后指定需要代理的接口类和服务器端服务的 URL 。如下 Spring 配置文件所示,
- <bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
- <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
- <property name="serviceInterface" value="example.AccountService"/>
- </bean>
HTTP 唤起器代理工厂 Bean 实现了工厂 Bean 接口,当它被初始化后进行 Bean 连接的时候,它会提供一个代理对象。如下代码注释,
- // 这是一个工厂Bean,返回一个代理对象,用于拦截对于提供的接口方法的调用,然后,实现远程调用
- public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor
- implements FactoryBean<Object> {
- // 返回的代理对象
- private Object serviceProxy;
- // Spring环境中的Bean的占位符方法,初始化后属性后,构造代理对象
- @Override
- public void afterPropertiesSet() {
- super.afterPropertiesSet();
- // 将要代理的接口为空,抛出异常,停止处理
- if (getServiceInterface() == null) {
- throw new IllegalArgumentException("Property 'serviceInterface' is required");
- }
- // 使用AOP代理工厂创建代理对象,对这个代理对象的所有方法调用最后都会被拦截,然后调用到超类对MethodInterceptor的实现方法invoke()
- // 如果将要代理类是接口,则使用JDK的动态代理,如果将要代理类是类类型,则需要使用CGLIB产生子类创建代理
- this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
- }
- // 返回代理对象
- public Object getObject() {
- return this.serviceProxy;
- }
- // 返回代理类型
- public Class<?> getObjectType() {
- return getServiceInterface();
- }
- // 既然是服务,则是单例模式
- public boolean isSingleton() {
- return true;
- }
- }
产生了代理对象后,客户端会根据业务需要调用代理对象的服务方法。当调用任何方法的时候,代理机制会统一的调用到超类 HTTP 唤起器客户拦截器 (HttpInvokerClientInterceptor) 中对方法拦截器接口 (MethodInterceptor) 中的 invoke 方法的是实现。如下代码所示,
- // 当调用一个代理对象的方法,AOP代理机制会将调用信息,包括调用的方法,参数等封装成MethodInvocation对象传递给MethodInterceptor的实现里
- public Object invoke(MethodInvocation methodInvocation) throws Throwable {
- // 如果正在调用toString()方法,则返回服务URL信息
- if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
- return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";
- }
- // 创建远程调用对象,远程调用对象是方法调用对象的一个简单封装
- RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
- RemoteInvocationResult result = null;
- try {
- // 使用HTTP调用器请求执行器调用远程的HTTP服务,并且获得返回结果
- 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 RemoteAccessException convertHttpInvokerAccessException(Throwable ex) {
- // HTTP连接错误
- if (ex instanceof ConnectException) {
- throw new RemoteConnectFailureException(
- "Could not connect to HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
- }
- // 是否有返回值对象是不可解析的类型
- else if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError ||
- ex instanceof InvalidClassException) {
- throw new RemoteAccessException(
- "Could not deserialize result from HTTP invoker remote service [" + getServiceUrl() + "]", ex);
- }
- // 其他未知调用过程中的异常
- else {
- throw new RemoteAccessException(
- "Could not access HTTP invoker remote service at [" + getServiceUrl() + "]", ex);
- }
- }
- public class RemoteInvocationResult implements Serializable {
- // 如果存在异常则重现异常,否则返回远程结果
- public Object recreate() throws Throwable {
- // 如果异常存在
- if (this.exception != null) {
- Throwable exToThrow = this.exception;
- // 如果是唤起目标异常,则取得发生在方法调用时真正的异常
- if (this.exception instanceof InvocationTargetException) {
- exToThrow = ((InvocationTargetException) this.exception).getTargetException();
- }
- // 添加客户端异常堆栈
- RemoteInvocationUtils.fillInClientStackTraceIfPossible(exToThrow);
- // 抛出异常
- throw exToThrow;
- }
- // 否则返回服务器端结果
- else {
- return this.value;
- }
- }
- }
我们看到拦截器方法除了实现对远程 HTTP 服务的调用外,还对返回结果进行了处理。如果在调用过程中产生任何连接异常,结果解析异常等,则直接翻译这些异常并且抛出。然后,有些情况下,一个调用在服务器端产生了异常,异常信息通过返回的调用结果返回给客户端,这样客户端需要解析这些异常,并且重现他们。
我们也理解,对远程 HTTP 服务的调用是通过 HTTP 调用器请求执行器实现的。它将远程调用序列化成对象流后,通过 HTTP 连接类的支持,写入到远程服务。当远程服务处理这个请求后,返回了远程调用结果对象,并且通过反序列化解析远程调用结果对象。如下代码所示,
- // 代理方法
- protected RemoteInvocationResult executeRequest(
- RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {
- return executeRequest(invocation);
- }
- protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception {
- // 调用HTTP调用器请求执行器实现远程调用的
- return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
- }
程序分析到这里,我们理解所有远程调用的处理过程是通过 HTTP 调用器请求执行器实现的。 HTTP 调用器请求执行器并不是一个单一的实现,它是一个接口,并且有两个常用的实现,一个使用 JDK 自带的 HTTP 客户端进行通讯,而另外一个使用 Apache Commons 的 HTTP 客户端,如下类图所示,
图表 4 ‑ 25
为了使我们的分析简单明白,我们这里仅仅分析基于 JDK 自带的 HTTP 客户端进行通讯的实现。首先,我们分析一下如下抽象 HTTP 唤起器请求执行器( AbstractHttpInvokerRequestExecutor )的实现。如下代码注释,
- 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);
- }
- // 抽象方法供子类实现
- protected abstract RemoteInvocationResult doExecuteRequest(
- HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
- throws Exception;
- protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl)
- throws IOException, ClassNotFoundException {
- // 从HTTP请求体的输入流创建对象输入流
- ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl);
- try {
- // 代理到其他方法进行读操作
- return doReadRemoteInvocationResult(ois);
- }
- finally {
- ois.close();
- }
- }
- // 代理方法,子类可以客户换
- protected InputStream decorateInputStream(InputStream is) throws IOException {
- return is;
- }
- protected ObjectInputStream createObjectInputStream(InputStream is, String codebaseUrl) throws IOException {
- // 使用特殊的对象输入流,如果本地不能解析放回结果中的某个类,则试图通过网络URL配置的资源解析类
- return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), codebaseUrl);
- }
- 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;
- }
如此可见,对于一个远程调用抽象 HTTP 唤起器请求执行器实现了整个流程框架,但是留下了一个抽象方法,这个方法是提供给子类使用不同的方式与 HTTP 远程服务通信的。下面我们分析简单 HTTP 唤起器请求执行器( SimpleHttpInvokerRequestExecutor )是如何通过 JDK 自带的 HTTP 客户端来完成这个任务的。如下代码所示,
- // 通过JDK自带的HTTP客户端实现基于HTTP协议的远程调用
- public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
- @Override
- 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 HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException {
- // 打开远程服务的URL连接
- URLConnection con = new URL(config.getServiceUrl()).openConnection();
- // 这必须是基于HTTP协议的服务
- if (!(con instanceof HttpURLConnection)) {
- throw new IOException("Service URL [" + config.getServiceUrl() + "] is not an HTTP URL");
- }
- return (HttpURLConnection) con;
- }
- protected void prepareConnection(HttpURLConnection con, int contentLength) throws IOException {
- // 我们需要写入远程调用的序列化对象的二进制流,所以,需要使用HTTP POST方法,并且需要输出信息到服务器
- con.setDoOutput(true);
- con.setRequestMethod(HTTP_METHOD_POST);
- // 内容类型是application/x-java-serialized-object,长度是程调用的序列化对象的二进制流的长度
- con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType());
- con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength));
- // 如果地域信息存在,则设置接收语言
- LocaleContext locale = LocaleContextHolder.getLocaleContext();
- if (locale != null) {
- con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));
- }
- // 如果接受压缩选项,则使用压缩的HTTP通信
- if (isAcceptGzipEncoding()) {
- con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
- }
- }
- protected void writeRequestBody(
- HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos)
- throws IOException {
- // 远程调用的序列化对象的二进制流写到HTTP连接的输出流中
- baos.writeTo(con.getOutputStream());
- }
- protected void validateResponse(HttpInvokerClientConfiguration config, HttpURLConnection con)
- throws IOException {
- // 如果接受HTTP响应代码出错,则抛出异常终止处理,说明服务器没有开启,或者服务配置错误
- if (con.getResponseCode() >= 300) {
- throw new IOException(
- "Did not receive successful HTTP response: status code = " + con.getResponseCode() +
- ", status message = [" + con.getResponseMessage() + "]");
- }
- }
- 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();
- }
- }
- protected boolean isGzipResponse(HttpURLConnection con) {
- // 通过HTTP头判断是否这是一个压缩过的HTTP相应
- String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING);
- return (encodingHeader != null && encodingHeader.toLowerCase().indexOf(ENCODING_GZIP) != -1);
- }
- }
由此可见,简单 HTTP 唤起器请求执行器通过 JDK 自带的 URL 连接类实现了远程调用所需要 HTTP 通信的流程。这个流程支持 HTTP 请求体的压缩,并且能够使用地域信息进行一定程度的客户化。
代码分析到这里,我们理解一个远程调用( RemoteInvocation )对象写入了 HTTP 服务中后,服务器就会返回一个远程调用结果( RemoteInvocationResult )对象。下面我们分析服务器端是如何实现这个过程的。如下类图所示,
图表 4 ‑ 26
服务器端对基于 HTTP 请求处理器流程的实现仍然是建立在 Spring Web MVC 的实现架构中的。它和基于简单控制器流程的实现和基于注解控制器流程的实现非常相似,同样有处理器映射,处理器适配器和处理器的实现,但是唯一不同的是它并没有特殊的处理器映射的实现,因为客户端是通过配置的 URL 查找和调用服务器端的服务的,所以简单基于简单控制器流程的实现中的 Bean 名 URL 处理器映射在这里可以被完全重用。如下流程图所示,
图表 4 ‑ 27
下面我们根据上图中流程的先后顺序分析它的实现。首先,派遣器 Servlet 接收到一个包含有远程调用序列化的二进制流的 HTTP 请求,它会使用配置的处理器映射查找能够处理当前 HTTP 请求的处理器。通常情况下, Bean 名 URL 处理器映射会返回配置的 HTTP 调用器服务导出器 (HttpInvokerServiceExporter) 。如下配置代码所示,
- <bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
- <property name="service" ref="accountService"/>
- <property name="serviceInterface" value="example.AccountService"/>
- </bean>
Bean 名 URL 处理器映射会发现这个 Bean 是一个处理器 Bean, 然后使用 Bean 作为 URL 注册此处理器。客户端 HTTP 请求发送到这个 URL, 作为总控制器的派遣器 Servlet 要求 Bean 名 URL 处理器映射解析当前请求所需要的处理器,于是返回配置的 HTTP 调用器服务导出器。
既然 HTTP 调用器服务导出器实现了 HTTP 请求处理器接口 (HttpRequestHandler) , HTTP 请求处理器适配器 (HttpRequestHandlerAdapter) 支持这个类型的处理器,所以,调用的控制流通过 HTTP 请求处理器适配器传递给 HTTP 调用器服务导出器。如下代码注释,
- // 用于适配HTTP请求处理器的处理器适配器,主要用于实现基于HTTP的远程调用
- public class HttpRequestHandlerAdapter implements HandlerAdapter {
- public boolean supports(Object handler) {
- // 支持类型任何实现了接口HttpRequestHandler的处理器
- return (handler instanceof HttpRequestHandler);
- }
- public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- // 传递控制给HttpRequestHandler,不需要返回值,响应是在HttpRequestHandler直接生成的
- ((HttpRequestHandler) handler).handleRequest(request, response);
- return null;
- }
- public long getLastModified(HttpServletRequest request, Object handler) {
- // 通用的实现HttpInvokerServiceExporter不支持最后修改操作
- if (handler instanceof LastModified) {
- return ((LastModified) handler).getLastModified(request);
- }
- return -1L;
- }
- }
程序分析到这里,我们看到包含有远程调用序列化的二进制流的 HTTP 请求传递给 HTTP 调用器服务导出器,它将从序列化的二进制流中解析远程调用对象,进而解析方法调用对象,最后使用反射调用配置的服务中相应的方法,返回结果给客户端。如下代码所示,
- // 方法没有返回值,响应是在处理请求时生成和返回给客户端的
- public void handleRequest(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- try {
- // 从请求中读取远程调用对象
- 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);
- }
- }
上述方法中可以看到实现的总体流程,这个流程分为三个步骤,一个步骤是读取远程调用对象,第二个步骤是调用相关的服务方法,第三个步骤则是写入返回的结果。
第一个步骤,读取远程调用对象代码注释如下,
- protected RemoteInvocation readRemoteInvocation(HttpServletRequest request)
- throws IOException, ClassNotFoundException {
- // 从HTTP请求体中的流中读取远程调用对象
- 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 InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException {
- // 占位符方法,仅仅返回HTTP请求体中的流对象
- return is;
- }
- protected ObjectInputStream createObjectInputStream(InputStream is) throws IOException {
- // 特殊的对象输入流,可以配置客户化的类加载器,如果某些类不存在,可以视图通过一个配置的URL加载此类
- return new CodebaseAwareObjectInputStream(is, getBeanClassLoader(), null);
- }
- protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois)
- throws IOException, ClassNotFoundException {
- // 读取HTTP请求体的序列化的对象
- 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;
- }
第二个步骤,调用相关的服务方法代码注释如下所示,
- protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
- try {
- // 事实上调用服务方法
- 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 class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor {
- 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 class RemoteInvocation implements Serializable {
- public Object invoke(Object targetObject) throws NoSuchMethodException,
- IllegalAccessException, InvocationTargetException {
- // 通过远程调用对象包含的方法名和参数类型找到服务对象的方法
- Method method = targetObject.getClass().getMethod(this.methodName,
- this.parameterTypes);
- // 调用找到的方法
- return method.invoke(targetObject, this.arguments);
- }
- }
第三个步骤,写入返回的结果的代码注释如下所示,
- protected void writeRemoteInvocationResult(
- HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result)
- throws IOException {
- // 设置内容类型为application/x-java-serialized-object
- 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);
- //
- 将缓存刷新到HTTP响应体中
- oos.flush();
- }
- finally {
- oos.close();
- }
- }
- protected ObjectOutputStream createObjectOutputStream(OutputStream os) throws IOException {
- // 创建简单的对象输出流,用于序列化远程调用结果对象
- return new ObjectOutputStream(os);
- }
- protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos)
- throws IOException {
- // 既然远程调用结果对象是可序列化的,直接写远程调用结果对象到对象流
- oos.writeObject(result);
- }
如上分析,我们知道服务端的实现是讲客户端传递过来的远程调用的序列化对象进行反序列化,根据远程调用对象信息调用业务逻辑方法,最后同样以序列化的方法将调用结果传递回客户端。
事实上,还有另外一种配置方法配置基于 HTTP 请求处理器流程的实现方式。这种方法不需要任何的处理器映射和处理器适配器的实现,而是使用特殊的 HTTP 请求处理器 Servlet ( HttpRequestHandlerServlet )。这个 Servlet 会将 HTTP 请求直接发送给相应的 HTTP 调用器服务导出器进行处理。如下图所示,
图表 4 ‑ 28
如下代码所示,
- public class HttpRequestHandlerServlet extends HttpServlet {
- private HttpRequestHandler target;
- @Override
- public void init() throws ServletException {
- // 取得根Web应用程序环境
- WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
- // 取得和此Servlet具有同名的Bean,这个Bean必须是HttpRequestHandler接口的实现
- this.target = (HttpRequestHandler) wac.getBean(getServletName(), HttpRequestHandler.class);
- }
- @Override
- protected void service(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- // 设置地域信息
- LocaleContextHolder.setLocale(request.getLocale());
- try {
- // 讲控制流传递给HttpRequestHandler
- this.target.handleRequest(request, response);
- }
- // 既然我们重写了service方法,那么所有的HTTP方法的请求都可能会发送到这里,所以HttpRequestHandler的实现需要检查是否当前请求使用了支持的HTTP方法
- catch (HttpRequestMethodNotSupportedException ex) {
- String[] supportedMethods = ((HttpRequestMethodNotSupportedException) ex).getSupportedMethods();
- if (supportedMethods != null) {
- // 如果异常中保存了支持的HTTP方法信息
- response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
- }
- // 发送方法禁止错误状态
- response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
- }
- finally {
- LocaleContextHolder.resetLocaleContext();
- }
- }
- }
根据上面的这个简单 Servlet 的实现,我们可以这样配置我们的远程调用导出器。如下代码所示,
跟环境
- <bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
- <property name="service" ref="accountService"/>
- <property name="serviceInterface" value="example.AccountService"/>
- </bean>
web.xml
- <servlet>
- <servlet-name>accountExporter</servlet-name>
- <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>accountExporter</servlet-name>
- <url-pattern>/remoting/AccountService</url-pattern>
- </servlet-mapping>
我们可以看到这种实现方法涉及组件会更少,不需要处理器映射和处理器适配器的参与,直接实现了 Servlet 到处理器适配器的调用,相对于第一种方法而言可以称为是一个捷径。但是它需要单独配置一个 Servlet ,除此之外, HTTP 请求处理器 Servlet 仅仅实现了 HTTP Servlet, 它并不拥有专用的子环境,所以需要在根环境声明服务导出 Bean 。