将 Hessian 集成到 Smart 中

烂漫一生 提交于 2019-12-03 21:42:56

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!