• 【Hessian】轻量级分布式通信组件


    参考自简书

    https://www.jianshu.com/p/9136aa36cffb

    案例场景为单向通信

    A 和 B两个应用服务, B需要调用A的接口完成业务需求

    那么A服务角色就是服务端,提供给B服务,B服务角色就是客户端,请求A服务接口

    服务端 8081

    客户端 8082

    新建两个SpringBoot的Web服务

    导入Hessian组件包

            <!-- hessian -->
            <dependency>
                <groupId>com.caucho</groupId>
                <artifactId>hessian</artifactId>
                <version>4.0.38</version>
            </dependency>

    服务端提供:

    1、业务接口

    package cn.cloud9.hessianserver.service;
    
    public interface HelloHessian {
        String hello(String message);
    }

    2、业务实现

    package cn.cloud9.hessianserver.service;
    
    import org.springframework.stereotype.Service;
    
    @Service("HelloHessian")
    public class HelloHessianImpl implements HelloHessian{
    
        @Override
        public String hello(String message) {
            return "From Hessian Server, Message: " + message;
        }
    }

    3、Hessian服务转换配置

    package cn.cloud9.hessianserver.config;
    
    import cn.cloud9.hessianserver.service.HelloHessian;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.remoting.caucho.HessianServiceExporter;
    
    import javax.annotation.Resource;
    
    @Configuration
    public class HessianServerConfiguration {
    
        @Resource
        HelloHessian helloHessian;
    
        /**
         * 1. HessianServiceExporter是由Spring.web框架提供的Hessian工具类,能够将bean转化为Hessian服务
         * 2. @Bean(name = "/helloHessian.do")加斜杠方式会被spring暴露服务路径,发布服务。
         * @return
         */
        @Bean("/helloHessian.do")
        public HessianServiceExporter exportHelloHessian() {
            HessianServiceExporter exporter = new HessianServiceExporter();
            exporter.setService(helloHessian);
            exporter.setServiceInterface(HelloHessian.class);
            return exporter;
        }
    
    }

    除此以外,服务端无任何配置

    客户端提供:

    1、和Hessian服务中一样声明的接口

    package cn.cloud9.hessianclient.service;
    
    public interface HelloHessian {
        String hello(String message);
    }

    2、代理工厂将接口实现化

    package cn.cloud9.hessianclient.util;
    import com.caucho.hessian.client.HessianProxyFactory;
    
    public class HessianProxyFactoryUtil {
    
        /**
         *  获取调用端对象
         * @param clazz 实体对象泛型
         * @param url 客户端url地址
         * @param <T>
         * @return 业务对象
         */
        public static <T> T getHessianClientBean(Class<T> clazz,String url) throws Exception {
            // 客户端连接工厂,这里只是做了最简单的实例化,还可以设置超时时间,密码等安全参数
            HessianProxyFactory factory = new HessianProxyFactory();
    
            return (T)factory.create(clazz,url);
        }
    }

    3、调用即实现

    package cn.cloud9.hessianclient.controller;
    
    import cn.cloud9.hessianclient.service.HelloHessian;
    import cn.cloud9.hessianclient.util.HessianProxyFactoryUtil;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/test")
    public class TestController {
    
        @GetMapping("/hello")
        public String hello() {
            // 服务器暴露出的地址
            String url = "http://localhost:8081/helloHessian.do";
            String msg = null;
            // 客户端接口,需与服务端对象一样
            try {
                HelloHessian helloHessian = HessianProxyFactoryUtil.getHessianClientBean(HelloHessian.class,url);
                msg =  helloHessian.hello("你好");
    
                System.out.println(msg);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return msg;
        }
    }

    两个服务开启后,访问客户端的服务URL

    localhost:8082/test/hello

    访问成功

    复盘一下:

    服务端 提供了 接口规范,接口实现,Hessian服务实例

    客户端 提供 接口规范, Hessian服务转换, 服务地址 + (接口名?)

    如果不是字符串,需要传递复杂的对象类型,则需要进行序列化处理

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User implements Serializable {
        private Integer id;
        private String name;
        private Integer age;
        private String email;
    }

    Hessian服务接口新增一个方法

    List<User> getUserList2();

    业务实现:

        @Override
        public List<User> getUserList2() {
            return userMapper.selectList(new QueryWrapper<>());
        }

    如果服务端的User类没有实现序列化接口,则会报错

    PO的User类实现序列化接口后可以正常传输

    [{"age":18,"email":"test1@baomidou.com","id":1,"name":"Jone"},{"age":20,"email":"test2@baomidou.com","id":2,"name":"Jack"},{"age":28,"email":"test3@baomidou.com","id":3,"name":"Tom"},{"age":21,"email":"test4@baomidou.com","id":4,"name":"Sandy"},{"age":24,"email":"test5@baomidou.com","id":5,"name":"Billie"}]
    [User(id=1, name=Jone, age=18, email=test1@baomidou.com), User(id=2, name=Jack, age=20, email=test2@baomidou.com), User(id=3, name=Tom, age=28, email=test3@baomidou.com), User(id=4, name=Sandy, age=21, email=test4@baomidou.com), User(id=5, name=Billie, age=24, email=test5@baomidou.com)]

    关于Hessian序列化的潜在问题:

    同名子类序列化报错问题

    https://www.cnblogs.com/yfyzy/p/7197679.html

    在项目中的应用:

    权限系统是一个独立的服务

    酒店端系统需要开发一个PC桌面端的应用程序

    PC应用程序的登录接口通过 酒店端Web提供

    那酒店程序没有权限的功能,需要找到权限系统要这个数据

    所以权限系统提供了一个远程访问接口给其它服务调用

    这里就采用了Hessian来实现

    一、生产者配置

    首先这里的权限系统是Portal-Server

    关于Hessian提供的服务规范在Portal-New-Client包里面

    先来看客户端包的定义:

    可以发现,只有PO和接口

    接口这里我只展示我需要调用的这个方法

    package cn.ymcd.portal_new.service;
    
    import java.util.List;
    
    import cn.ymcd.portal_new.dto.AppDTO;
    import cn.ymcd.portal_new.dto.AreaDTO;
    import cn.ymcd.portal_new.dto.FuncDTO;
    import cn.ymcd.portal_new.dto.OrgDTO;
    import cn.ymcd.portal_new.dto.UserDTO;
    
    /**
     * portal所有功能接口,供rpc远程调用,供2019-7提供的新框架使用
     * 
     * @projectName:portal-new-client
     * @author:lianss
     * @date:2019年7月22日 下午2:49:58
     * @version 1.0
     */
    public interface IPortalNewService {
        
        /**
         * 根据ID获取用户信息(只包含用户基本信息)
         * @param id 用户主键ID
         * @return
         * @author:lianss
         * @createTime:2019年1月24日 上午9:23:40
         */
        UserDTO findUser(String id);
    }

    然后Portal-Server作为服务端

    实现了这个服务规范,并且将服务实现交给Hessian组件进行了封装

    服务的实现:

    package cn.ymcd.portal.rpc;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import cn.ymcd.portal_new.dto.*;
    import com.google.common.collect.Lists;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.util.CollectionUtils;
    
    import com.alibaba.fastjson.JSON;
    
    import cn.ymcd.comm.log.LogFactory;
    import cn.ymcd.comm.log.YmcdLogger;
    import cn.ymcd.portal.area.search.AreaSearchForm;
    import cn.ymcd.portal.area.service.AreaService;
    import cn.ymcd.portal.org.search.OrgSearchForm;
    import cn.ymcd.portal.org.service.OrgService;
    import cn.ymcd.portal.service.IPortalService;
    import cn.ymcd.portal.user.search.UserSearchForm;
    import cn.ymcd.portal.user.service.UserService;
    import cn.ymcd.portal_new.service.IPortalNewService;
    
    /**
     * portal rpc服务,提供给spring boot+mybatis框架使用
     * 
     * @projectName:portal-server
     * @author:lianss
     * @date:2019年7月22日 下午4:05:26
     * @version 1.0
     */
    @Service("portalNewService")
    public class PortalNewServiceImpl implements IPortalNewService {
        protected YmcdLogger _logger = LogFactory.getLogger(getClass());
    
        @Autowired
        private IPortalService portalService;
        @Autowired
        private UserService userService;
        @Autowired
        private OrgService orgService;
        @Autowired
        private AreaService areaService;
        
        @Override
        public UserDTO findUser(String id) {
            cn.ymcd.portal.dto.UserDTO user = portalService.findUser(id);
            
            return copyPorperties(user, UserDTO.class);
        }
    }

    Hessian配置:

    package cn.ymcd.portal.rpc;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.remoting.caucho.HessianServiceExporter;
    
    import cn.ymcd.portal.service.IPortalService;
    import cn.ymcd.portal_new.service.IPortalNewService;
    
    /**
     * 通过Hessian把portal service发布出去
     * 
     * @projectName:portal-server
     * @author:lianss
     * @date:2019年1月24日 下午7:15:10
     * @version 1.0
     */
    @Configuration
    public class HessianServiceConfig {
    
        @Autowired
        private IPortalNewService portalNewService;
        
        @Bean(name = "/portal_new/service")
        public HessianServiceExporter portalNewService() {
            HessianServiceExporter exporter = new PortalHessianServiceExporter();
            exporter.setService(portalNewService);
            exporter.setServiceInterface(IPortalNewService.class);
    
            return exporter;
        }
    }


    二、消费者配置

    回到酒店端服务,酒店端消费这个服务

    首先需要客户端组件:

            <!-- hessian -->
            <dependency>
                <groupId>com.caucho</groupId>
                <artifactId>hessian</artifactId>
                <version>4.0.38</version>
            </dependency>
            <!-- provider -->
            <dependency>
                <groupId>cn.ymcd.portal</groupId>
                <artifactId>portal-new-client</artifactId>
                <version>1.0.5</version>
            </dependency>

    然后配置客户端如何调用生产者的服务:

    这里生产者和消费者的服务的关系是固定的,没有说双方既是生产者又做消费者

    package cn.ymcd.aisw.config;
    
    import cn.ymcd.portal_new.service.IPortalNewService;
    import cn.ymcd.wss.util.log.YmcdLogger;
    import com.caucho.hessian.client.HessianProxyFactory;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.remoting.caucho.HessianProxyFactoryBean;
    
    /**
     * 通过Hessian获取tcp service
     *
     * @version 1.0
     * @projectName:wssapi
     * @author:wangkun
     * @date:2020年6月3日 下午7:02:12
     */
    @Configuration
    public class HessianTcpClientConfig {
    
        private static final YmcdLogger LOGGER = new YmcdLogger(HessianTcpClientConfig.class);
    
        @Value("${tcp.server.url}")
        private String tcpServiceUrl;
    
        @Value("${tcp.server.timeout:60000}")
        private long timeout;
    
        @Bean("portalNewService")
        public HessianProxyFactoryBean tcpClient() {
            HessianProxyFactoryBean factory = new HessianProxyFactoryBean();
            if (StringUtils.isNotBlank(tcpServiceUrl)) {
                HessianProxyFactory proxyFactory = new HessianProxyFactory();
                factory.setProxyFactory(proxyFactory);
                factory.setOverloadEnabled(true);
                factory.setServiceUrl(tcpServiceUrl + "/portal_new/service");
                // 连接超时时间
                factory.setConnectTimeout(timeout);
                // 读取超时时间
                factory.setReadTimeout(timeout);
                factory.setServiceInterface(IPortalNewService.class);
            } else {
                LOGGER.info("wssapi tcp.service.url参数为空或未配置!");
            }
            return factory;
        }
    }

    这里的代理工厂的获取服务Bean方法和上面的案例不太一样

    只有最简陋的getObject方法,拿到对象之后需要自己进行强转处理

    package cn.ymcd.aisw.common.controller;
    
    import cn.ymcd.aisw.common.Constant;
    import cn.ymcd.aisw.room.service.IMerchantDictService;
    import cn.ymcd.comm.security.user.LoginUserContext;
    import cn.ymcd.comm.security.user.UserContext;
    import cn.ymcd.portal_new.dto.UserDTO;
    import cn.ymcd.portal_new.service.IPortalNewService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.remoting.caucho.HessianProxyFactoryBean;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * @projectName: aisw-root
     * @author: cloud9
     * @date: 2022年03月24日 15:02
     * @version: 1.0
     */
    @RestController
    @RequestMapping("${sys.path}/common")
    public class CommonController {
    
        @Autowired
        IMerchantDictService iMerchantDictService;
    
        @Autowired
        HessianProxyFactoryBean portalNewService;/**
         * /sys/common/loginInterface
         * 提供给桌面小程序的登录接口?
         * @param
         * @return cn.ymcd.portal_new.dto.UserDTO
         * @author cloud9
         * @createTime 2022/3/28 13:34
         *
         */
        @GetMapping("/loginInterface")
        public UserDTO getCurrentUserInfo() {
    
            // HessianProxy[http://localhost:8077/portal/portal_new/service]
            IPortalNewService proxyBean = (IPortalNewService)portalNewService.getObject();
            // UserDTO user = proxyBean.findUser(LoginUserContext.getUser().getId());
    
            UserDTO user = proxyBean.findUser(Constant.TEST_SYS_USER_ID);
    
            return user;
        }
    }

    服务日志打印:

    权限服务和酒店端服务都是本地运行的

    2022-03-28 13:39:05.737 [http-nio-8083-exec-1] INFO  org.springframework.web.servlet.DispatcherServlet -
                    Completed initialization in 21 ms
    HessianProxy[http://localhost:8077/portal/portal_new/service]
    null
  • 相关阅读:
    Educational Codeforces Round 59 (Rated for Div. 2)E. Vasya and Binary String 区间dp
    MySQL语法大全
    D. Buy a Ticket(优先队列+dijkstra)
    Two Sets(并查集分类)
    KMP浅显易懂
    深度理解链式前向星
    快速幂(幂运算取模的logn算法)
    hdu---1950---Bridging signals解题报告(求Lis n*logn贪心+二分搜索)
    dp背包
    线段树模板
  • 原文地址:https://www.cnblogs.com/mindzone/p/15957545.html
Copyright © 2020-2023  润新知