将 Hessian 集成到 Smart 中
博客专区 > 黄勇 的博客 > 博客详情
将 Hessian 集成到 Smart 中
黄勇 发表于4年前
将 Hessian 集成到 Smart 中
  • 发表于 4年前
  • 阅读 3688
  • 收藏 71
  • 点赞 11
  • 评论 15

新睿云服务器60天免费使用,快来体验!>>>   

摘要: 本文是《轻量级 Java Web 框架架构设计》的系列博文,为您展示如何将 Hessian 集成到 Smart 中。

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

标签: Smart Hessian
  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
黄勇
粉丝 5757
博文 114
码字总数 196279
作品 1
评论 (15)
哈库纳
黄兄,昨晚奋斗到12点!!!太让人敬佩了,文章也写的很精辟。

我写 hasor 整合 hessian 时候,正是由于黄勇 的想法而来。

我在写hasor-web时候,已经为所有 filter servlet做了一层 Dispatcher 因此我那篇文章里没有介绍,中央转发器这块。

强烈推荐 黄勇这个文章,因为比较全面。
最后也非常感谢他和我分享自己的想法。
ForJustice
额,与 PHPRPC 差不多嘛
RyanHoo
虽然我不搞Java Web久矣,你每发博客我都过来瞅一眼~79
大漠真人
+1;
变成马甲了哈哈
这次实在忍不住了,我也说两句:
--------------------------------------
黄兄,你是我的榜样,你让我明白了什么叫真正的程序员:不装B,不自大,不固执,谦虚'谨慎',言语'朴实',技术'内敛'。
黄勇
多谢大家的支持与鼓励!我只想凭借自己i微不足道的能力,为中国开源事业添一块砖。
感谢 @红薯 提供了这么好的平台!
aspboy
非常不错! 好文 ,顶! !!!111
haanya168
不错,好文章,请问有没有办法可以让客户端传递的参数也以二进制文件传递到服务端,这样效率更好,也可以称得上是真正的binary web service框架!
黄勇

引用来自“haanya168”的评论

不错,好文章,请问有没有办法可以让客户端传递的参数也以二进制文件传递到服务端,这样效率更好,也可以称得上是真正的binary web service框架!

感谢您的阅读!请问您是想将参数作为二进制文件从客户端发送到服务端吗?
haanya168

引用来自“黄勇”的评论

引用来自“haanya168”的评论

不错,好文章,请问有没有办法可以让客户端传递的参数也以二进制文件传递到服务端,这样效率更好,也可以称得上是真正的binary web service框架!

感谢您的阅读!请问您是想将参数作为二进制文件从客户端发送到服务端吗?

嗯,客户端传递的信息到服务端的信息也是二进制流的形式传输!
黄勇

引用来自“haanya168”的评论

引用来自“黄勇”的评论

引用来自“haanya168”的评论

不错,好文章,请问有没有办法可以让客户端传递的参数也以二进制文件传递到服务端,这样效率更好,也可以称得上是真正的binary web service框架!

感谢您的阅读!请问您是想将参数作为二进制文件从客户端发送到服务端吗?

嗯,客户端传递的信息到服务端的信息也是二进制流的形式传输!

使用 Hessian 将数据从客户端传递到服务端,不就是二进制流吗?:)
shylock
忍不住要顶两下了。。
李德伦
UserService userService = HessianHelper.createClient(url, UserService.class);这样的话,其他平台使用hessian生成的接口,难道要导入这些相关的包(Hessian相关,还有你这UserService接口)才能使用?
CDD
文章很好,我喜欢
但是我怎么觉得ClassHelper这个类在哪里呢?
xunux
向勇哥学习,之前也用到了hession主要就是项目分多个子项目,项目之间调用就用了hession技术,多了一层remote service接口,其他什么都一样,让人觉得访问的是本地service一样。
×
黄勇
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: