Hessian,啥东西?
第一次见到这个单词的时候,我真不知道它是什么意思,虽然我的英语功底已经相当牛逼了。最后查了一下有道词典才知道,原来 Hessian 就是“麻布袋子”啊!
我真搞不懂,为什么一个麻布袋子就能通过 HTTP 发送二进制数据?可能麻布袋子不是一般的袋子,因为它密密麻麻,不是一般的数据可以穿透它,除了二进制数据 0 和 1。
如果您不想使用笨重的 SOAP WebService,也不想使用流行的 REST WebService,或许当您看到 Hessian 的功能后,它一定会让您惊呆!
一般我们是这样玩的,在服务端发布 Hessian 服务,让客户端调用已发布的 Hessian 服务。
请不要把 Hessian 想象得过于复杂与神秘,其实它不过是一个麻布袋子而已。
在服务端我们可以这样来发布 Hessian 服务:
@WebServlet("/hessian/user_service")
public class UserServiceImpl extends HessianServlet implements UserService {
@Override
public User login(String username, String password) {
return DataSet.select(User.class, "username = ? and password = ?", username, password);
}
}
我们可以将 Service 实现类发布为 Servlet(在类上使用 @WebServlet 注解,Servlet 3 规范,无需配置 web.xml 文件),并且让它继承 HessianServlet 类,那么 Hessian 服务就发布啦!
有没有搞错?真的这么简单?—— 没有搞错!果真就这么简单!
客户端如何调用呢?
public class UserServiceHessianTest {
@Test
public void loginTest() throws Exception {
String username = "admin";
String password = "admin";
String url = "http://localhost:8080/smart-sample/hessian/user_service";
HessianProxyFactory factory = new HessianProxyFactory();
UserService userService = (UserService) factory.create(UserService.class, url);
User user = userService.login(username, password);
Assert.assertNotNull(user);
}
}
我们使用 HessianProxyFactory 这个工厂类,借助 UserService 接口与一个 HTTP 请求 URL,就可以创建客户端代理对象,通过这个代理对象来调用目标方法。
简单而优雅!我只能这样来形容 Hessian 了,而且它还安全且高效!因为我们通过 HTTP 传递的实际上是二进制数据,而并非文本数据。
想进一步了解 Hessian 可以阅读一下它的官网:http://hessian.caucho.com/
官网还提供了一个入门指南,也不错哦!http://hessian.caucho.com/doc/hessian-overview.xtp
当您打开 Hessian 官网,在您眼前一定会看到:
hessian binary web service protocol
The Hessian binary web service protocol makes web services usable without requiring a large framework, and without learning yet another alphabet soup of protocols. Because it is a binary protocol, it is well-suited to sending binary data without any need to extend the protocol with attachments.
翻译一下大概是说:Hessian 是一种二进制 WebService 协议,它无需借助一个牛逼框架来使用 WebService,也无需学习其它乱七八糟的协议。因为它是一种二进制协议,它非常适合于发送二进制数据,没有任何必要来对现有协议进行扩展。
看来体育老师没有白教我英语,我总算一口气翻译出来了。
它宣称自己是 WebService,怪不得它敢说自己是跨平台的,而且针对许多主流的开发语言都有相应的技术实现。
看来作为一名开发人员,错过了 Hessian 实属不幸啊!但错过了 Hessian 与 Smart 的集成,那就更为不幸了。
众所周知,Smart 的 Service 一般都是封装业务逻辑的地方,包括复杂的计算与数据库操作,可以进行控制事务,也可以进行数据缓存,还可以进行方法拦截。这么好的东西,如果带上了 Servlet 这顶帽子,似乎对于它真的有些亏!
为了不失 Smart Service 的种种特性,我们需要进行一些巧妙的架构设计,就可以解决这个问题。
怎么做呢?
借鉴 DispatcherServlet 的思想,我们也可以搞一个 HessianDispatcherServlet 出来,让它拦截所有的 Hessian 请求,根据 URL 来分发到指定的 Service 上。
说起来简单,做起来如何呢?
第一步:创建 @Hessian 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Hessian {
String value(); // Hessian URL
}
我们创建了一个 @Hessian 注解,可将它标注在接口上,它只有一个 value 属性,用于设置 Hessian URL。
帽子有了,带在头上看看效果如何呢?
第二步:配置需要发布的 Hessian 服务
@Hessian("/user_service")
public interface UserService {
User login(String username, String password);
}
我们需要发布哪个 Service,就将这个 @Hessian 帽子戴在谁的头上。
需要说明的是,如果戴在了某个 Service 接口头上的话,那么该接口的所有方法都会被发布出来,其实这也正是我们想做的事情。
需要注意的是,接口方法的参数或返回值都必须可被序列化,数据类型肯定是可以的,但对于 JavaBean 而言,我们必须实现 Serializable 接口。我们这里的 User 是一个 Smart Entity,它一定是实现了 Serializable 接口的(由于 User 继承于 BaseEntity,它继承于 BaseBean,它实现了 Serializable 接口)。
第三步:创建 HessianDispatcherServlet
@WebServlet(urlPatterns = HessianConstant.URL_PREFIX + "/*", loadOnStartup = 0)
public class HessianDispatcherServlet extends HessianServlet {
// 定义一个 Hessian Servlet Map,用于管理 Hessian URL 与 Hessian Servlet 之间的映射关系
private final Map<String, HessianServlet> hessianServletMap = new HashMap<String, HessianServlet>();
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// 获取所有标注了 @Hessian 注解的类(接口)
List<Class<?>> hessianInterfaceList = ClassHelper.getClassListByAnnotation(Hessian.class);
if (CollectionUtil.isNotEmpty(hessianInterfaceList)) {
// 遍历所有 Hessian 接口
for (Class<?> hessianInterface : hessianInterfaceList) {
// 获取 Hessian URL
String url = hessianInterface.getAnnotation(Hessian.class).value();
// 获取 Hessian 接口的实现类
Class<?> implClass = IOCHelper.findImplementClass(hessianInterface);
// 获取实现类实例
Object implInstance = BeanHelper.getBean(implClass);
// 创建 Hessian Servlet
HessianServlet hessianServlet = new HessianServlet();
hessianServlet.setHomeAPI(hessianInterface); // 设置接口
hessianServlet.setHome(implInstance); // 设置实现类实例
hessianServlet.init(config); // 初始化 Servlet
// 将 Hessian URL 与 Hessian Servlet 放入 Hessian Servlet Map 中
hessianServletMap.put(HessianConstant.URL_PREFIX + url, hessianServlet);
}
}
}
@Override
public void service(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// 获取请求 URL
HttpServletRequest req = (HttpServletRequest) request;
String url = WebUtil.getRequestPath(req);
// 从 Hessian Servlet Map 中获取 Hessian Servlet
HessianServlet hessianServlet = hessianServletMap.get(url);
if (hessianServlet != null) {
// 执行 Servlet
hessianServlet.service(request, response);
}
}
}
为了让 Hessian URL 与其它 URL 不同,我们故意给它增加了一个前缀,在常量接口 HessianConstant 中提供了一个 URL_PREFIX 常量,代码如下:
public interface HessianConstant {
String URL_PREFIX = "/hessian";
}
其实 URL_PREFIX 叫什么名字真的无所谓,关键是需要有别于其它普通 URL 才行,以免被 Smart 的 DispatcherServlet 给截获了。
通过阅读 HessianDispatcherServlet 代码及其相关注释,不难理解:
最核心的就是 Map<String, HessianServlet> hessianServletMap,有了它就能保证不同的 Hessian URL 可以映射到不同的 Hessian Servlet 上。
我们通过遍历所有带有 @Hessian 注解的接口,来找到它们各自的实现类。通过创建 HessianServlet 实例,并设置 Home API(接口)与 Home(实现类实例),最后一定要调用 init 方法来初始化 Servlet。
在 HessianDispatcherServlet 的 service 方法中,只是通过 URL 找到对应的 HessianServlet,并调用它的 service 方法来执行 Servlet。
通过以上三个步骤,就可以实现 Smart Hessian 插件了,我们只需要在 Maven 的 pom.xml 中这样做即可使用该插件:
...
<dependency>
<groupId>com.smart</groupId>
<artifactId>smart-plugin-hessian</artifactId>
<version>${smart.version}</version>
</dependency>
...
您可以在 Smart Sample 中尝试一下该功能,如果您打算将 Hessian 集成到您自己的框架中,相信本文会为您提供一些帮助。
如果您也和我一样有些洁癖,不喜欢看到太多的代码细节,或许您会这样提供一个 Hessian 客户端 API:
public class HessianHelper {
private static final Logger logger = LoggerFactory.getLogger(HessianHelper.class);
@SuppressWarnings("unchecked")
public static <T> T createClient(String hessianURL, Class<T> interfaceClass) {
T client = null;
try {
HessianProxyFactory factory = new HessianProxyFactory();
client = (T) factory.create(interfaceClass, hessianURL);
} catch (MalformedURLException e) {
logger.error("创建 Hessian 客户端出错!", e);
}
return client;
}
}
有了 HessianHelper 我们的 Hessian 客户端编写起来会更加轻松:
public class UserServiceHessianTest {
@Test
public void loginTest() throws Exception {
String username = "admin";
String password = "admin";
String url = "http://localhost:8080/smart-sample/hessian/user_service";
UserService userService = HessianHelper.createClient(url, UserService.class);
User user = userService.login(username, password);
Assert.assertNotNull(user);
}
}
客户端需要知道的就两样东西:接口与 URL。Hessian 这麻布袋子还不错吧?
非常感谢 哈库纳 提供的技术指导!有了他的帮助,让我少走了许多弯路。恰巧他今天也写了一篇关于 Hessian 的文章,借此向大家推荐一下:
http://my.oschina.net/u/1166271/blog/187509
相关代码链接
Smart Hessian Plugin 代码:http://git.oschina.net/huangyong/smart-plugin-hessian
Smart Framework 代码:http://git.oschina.net/huangyong/smart-framework
Smart Sample 代码:http://git.oschina.net/huangyong/smart-sample