zoukankan      html  css  js  c++  java
  • HttpInvoker-----服务端实现

    前言

    Spring开发小组意识到在RMI服务和基于HTTP的服务(如Hessian和Burlap)之间的空白。一方面,RMI使用Java标准的对象序列化,但很难穿越防火墙;另一方面。Hessian/Burlap能很好的穿过防火墙工作,但是使用自己一套的对象序列化机制。

    就这样,Spring的HttpInvoker应运而生。HttpInvoker是一个新的远程调用模型,作为Spring框架的一部分,来执行基于HTTP的远程调用(让防火墙高兴的事),并使用Java的序列化机制(这是Java程序员高兴的事)。

    HttpInvoker是基于HTTP的远程调用,同时也是以Spring提供的web服务作为基础。

    服务端实现

    服务端的入口为org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter,那么同样的,先来看一下这个类的类图:

    HttpInvokerServiceExporter不仅实现了HttpRequestHandler接口,也实现了InitializingBean接口。分析RMI服务时我们已经了解到了,当某个bean继承自InitializingBean接口的时候,Spring会确保这个bean在初始化时调用其afterPropertiesSet方法,而对于HttpRequestHandler接口,因为我们在配置文件中已经将此接口配置成web服务,那么当有相应请求的时候,Spring的web服务就会将程序引导至HttpRequestHandler的handleRequest方法中,首先,我们从afterPropertiesSet方法开始分析,看看bean的初始化过程中做了哪些逻辑。

    1.创建代理

     跟踪代码,创建代理的核心代码在getProxyForService方法中。

    protected Object getProxyForService() {
            //验证service
            checkService();
            //验证serviceInterface
            checkServiceInterface();
            //使用JDK的方式创建代理
            ProxyFactory proxyFactory = new ProxyFactory();
            //添加代理接口
            proxyFactory.addInterface(getServiceInterface());
    
            if (this.registerTraceInterceptor != null ? this.registerTraceInterceptor : this.interceptors == null) {
                //加入代理的横切面RemoteInvocationTraceInterceptor并记录Exporter名称
                proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
            }
            if (this.interceptors != null) {
                AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
                for (Object interceptor : this.interceptors) {
                    proxyFactory.addAdvisor(adapterRegistry.wrap(interceptor));
                }
            }
            //设置要代理的目标类
            proxyFactory.setTarget(getService());
            proxyFactory.setOpaque(true);
            //创建代理
            return proxyFactory.getProxy(getBeanClassLoader());
        }

     可以看到,初始化的逻辑主要是创建了一个代理,代理中封装了对于特定请求的处理方法以及接口等信息,而这个代理最关键的目的是加入了RemoteInvocationTraceInterceptor增强器,当然创建代理还有其他好处,比如代码优雅、方便扩展等。RemoteInvocationTraceInterceptor中的增强主要是对增强的目标方法进行一些相关信息的日志打印,并没有在此基础上进行任何功能性的增强。

    2.处理来自客户端的request

     当有web请求时,根据配置中的规则会把路径匹配的访问直接引入对应的HttpRequestHandler中。本例中的请求与普通的web请求时有些区别的,因为此处的请求包含着HttpInvoker的处理过程。

    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            try {
                //从request中读取序列化对象
                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);
            }
        }

    在handleRequest函数中,我们很清楚的看到了HttpInvoker处理的大致框架,HttpInvoker服务简单点说就是将请求的方法,也就是RemoteInvocation对象,从客户端序列化并通过Web请求出入服务端,服务端在对传过来的序列化对象进行反序列化还原RemoteInvocation实例,然后通过实例中的相关信息进行相关方法的调用,并将执行结果再次的返回给客户端。从handleRequest函数中我们也可以清晰的看到程序执行的框架结构。

    (1)从request中读取序列化对象

    主要是从HttpServletRequest提取相关的信息,也就是提取HttpServletRequest中的RemoteInvocation对象的序列化信息以及反序列化的过程。

    protected RemoteInvocation readRemoteInvocation(HttpServletRequest request)
                throws IOException, ClassNotFoundException {
    
            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 RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois)
                throws IOException, ClassNotFoundException {
    
            Object obj = ois.readObject();
            if (!(obj instanceof RemoteInvocation)) {
                throw new RemoteException("Deserialized object needs to be assignable to type [" +
                        RemoteInvocation.class.getName() + "]: " + ClassUtils.getDescriptiveType(obj));
            }
            return (RemoteInvocation) obj;
        }

     对于序列化的提取与转换过程其实没有太多需要解释的东西,这里完全是按照标准的方式进行操作,包括创建ObjectInputStream以及从ObjectInputStream中提取对象实例。

    (2)执行调度

    根据反序列化的方式得到的RemoteInvocation对象中的信息,进行方法调用。注意,此时调用的实体并不是服务接口或者服务类,而是之前在初始化的时候构造的封装了服务接口以及服务类的代理。

    完成了RemoteInvocation实例的提取,也就意味着可以通过RemoteInvocation实例中提供的信息进行方法调用了。

    protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
            try {
                //激活代理类中对应Invocation中的方法
                Object value = invoke(invocation, targetObject);
                //封装结果以便于序列化
                return new RemoteInvocationResult(value);
            }
            catch (Throwable ex) {
                return new RemoteInvocationResult(ex);
            }
        }

    上述函数需要说明两个地方。

      ❤  对应方法的激活也就是invoke方法的调用,虽然经过层层环绕,但是最终还是实现了一个我们熟知的调用invocation.invoke(targetObject),也就是执行RemoteInvocation类中的invoke方法,大致的逻辑还是通过RemoteInvocation中对应的方法信息在targetObject上去执行,此方法在分析RMI功能的时候已经分析过,不再赘述。但是对于当前方法的targetObject参数,此targetObject是代理类,调用代理类的时候需要考虑增强方法的调用。

      ❤ 对于返回结果需要使用RemoteInvocationResult进行封装,之所以需要通过使用RemoteInvocationResult类进行封装,是因为无法保证对于所有操作的返回结果都继承Serializable接口,也就是说无法保证所有返回结果都可以直接进行序列化,那么,就必须使用RemoteInvocationResult类进行统一封装。

    (3)将结果的序列化对象写入输出流

    同样这里也包括结果的序列化过程。

    protected void writeRemoteInvocationResult(
                HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result)
                throws IOException {
    
            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(new FlushGuardedOutputStream(decorateOutputStream(request, response, os)));
            try {
                //将序列化对象写入输入流
                doWriteRemoteInvocationResult(result, oos);
            }
            finally {
                oos.close();
            }
        }
    protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos)
                throws IOException {
    
            oos.writeObject(result);
        }

    至此,HttpInvoker的服务器端解析已经结束。

    参考:《Spring源码深度解析》 郝佳 编著:

  • 相关阅读:
    Windows开发,关于通过写代码加载PDB的那些事
    从FreeBSD里面看到的网络协议列表,感觉可以保存一下
    听了几段《双投唐》
    ReactOS 无法显示中文的问题
    ReactOS 代码更新后的编译安装
    都什么年代了,怎么还那种德行
    假冒不伪劣
    ollvm 使用——“Cannot open /dev/random”错误的解决方法
    ollvm 编译
    C/C++ 吐槽第一期:你最讨厌的C/C++里面的数据类型是什么
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/10310241.html
Copyright © 2011-2022 走看看