• Spring HttpInvoker 从实战到源码追溯


       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 团队做了出色的封装和合理的抽象。

       至此全文结束,文中如有纰漏,还望斧正。

  • 相关阅读:
    [程序员代码面试指南]栈和队列-单调栈结构(单调栈)
    快学Scala第一部分
    Add Digits
    Nim Game
    将分布式中多台节点的日志信息集中到一个节点上
    Eclipse调试的一些小技巧
    Maven的常用命令
    Eclipse插件本地扩展安装
    Spark应用程序的运行框架
    Spark运行各个时间段的解释
  • 原文地址:https://www.cnblogs.com/java-class/p/9876689.html
Copyright © 2020-2023  润新知