【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