自己动手整合 Hessian 到 Hasor 中发布服务

ta8210 2013-12-27

    这篇是承接《轻量级 Java 开发框架 设计》系列Blog文的后续文章,本文将介绍一下如何将 Hessian 整合进 Hasor。

   Hessian 是一种通信协议,使用 Hessian 可以实现跨平台的方法调用,有点类似 SOAP。您可以使用下面任意一种语言通过 Hessian 协议进行互相调用,Hessian 本身使用的是二进制传输格式。

    好了废话不多说,这篇文章是源于和 OSC 上的朋友进行讨论最后得出的,现在拿出来和大家分享一下。

   首先我们假想这样一段代码用来发布 Hessian 服务:

@HessianService("/testBean")
public class HessianBean {
    public String sayHello() {
        System.out.println("aaa");
        return "aaa";
    }
}

然后使用下面这样的代码调用这个服务。

@HessianClient("http://127.0.0.1:8082/testBean")
public interface IHessianBean {
    public String sayHello();
}

public static void main(String[] args) throws MalformedURLException {
  IHessianBean bean = (IHessianBean) HessianPlugin.getPropxy(IHessianBean.class);
  System.out.println(bean.sayHello());
}

--------------------------------------------------------

    Hessian 发布服务是使用一个 HessianServlet 进行的例如下面代码:
HessianServlet hessianServlet = new HessianServlet();
hessianServlet.setAPIClass(HessianBean.class);
hessianServlet.setHome(hessianBean);

    在没有容器支持的情况下使用 Hessian 发布服务需要写一个 Servlet 类继承自 HessianServle类然后设置 APIClass 和 Home 属性。其中 APIClass 表示发布的服务类型,Home 表示发布的服务对象。

    当写完扩展类之后将其配置到 web.xml 下,如果是 Servlet3.0 容器加上 @WebServlet  注解就可以了。

    下面看一看如何让 Hasor 支持 Hessian,首先 Hasor 发布 Servlet 是通过 WebApiBinder 接口。得到这个接口有两种方式。

    第一种:通过 Hasor 的 WebPlugin 扩展机制,在loadPlugin 时候得到它。
    第二种:通过 Hasor 的 WebModule 扩展机制,当 init 阶段可以得到它。

    现在无论你是选用了那种方式我们假设您已经得到了 WebApiBinder 接口。接下来就是通过上面这段代码创建一个 Hessian Servlet 并注册到 Hasor 中便可以了。代码看上去应该是这个样子的:

@Plugin
public class TestHessian extends AbstractWebHasorPlugin {
    public void loadPlugin(WebApiBinder apiBinder) {
        HessianBean hessianBean = new HessianBean();
        //
        HessianServlet hessianServlet = new HessianServlet();
        hessianServlet.setAPIClass(HessianBean.class);
        hessianServlet.setHome(hessianBean);
        //
        apiBinder.serve("/hessian/test").with(hessianServlet);
    }
    public static void main(String[] args) throws MalformedURLException {
        String url = "http://127.0.0.1:8082/hessian/test";
        HessianProxyFactory factory = new HessianProxyFactory();
        // IHello为调用的服务接口,url为hessian服务url  
        IHessianBean bean = (IHessianBean) factory.create(IHessianBean.class, url);
        System.out.println(bean.sayHello());
    }
}

    其中 Main 方法是用于测试 这个服务发布是否成功。

    HessianBean 和 IHessianBean 之间一个是用于发布服务的服务对象,另一个是服务抽象接口。根据 Java Hessian Api 特性两者可以没有继承关系。下面是它们的代码:

public class HessianBean {
    public String sayHello() {
        System.out.println("aaa");
        return "aaa";
    }
}

public interface IHessianBean {
    public String sayHello();
}

    启动 web 程序,并运行 main 方法可以看到控制台上打印出 aaa,客户端也打印出一份 aaa。

--------------------------------------------------------

    或许各位同学觉得,这样写太死了不能满足我们那么灵活的要求。倘若我需要发布其它 Services 岂不要重复写很多代码,这样写也不是很优雅!

    要想实现上面这样的目标我们需要自己解析 @HessianService 注解,然后发布 HessianServlet 服务。接下来我们开发一个 HessianPlugin 类用于创建 Hessian客户端对象。

    我们知道通过 Java 反射机制可以轻松获取标记到 Class 上的注解信息,然后运用这些信息去做发布服务和调用服务。值得庆幸的是服务发布和调用代码在上面已经先给出了。那么接下来就是反射形式取得注解信息然后发布服务了。

    首先定义一个 @HessianService 注解,所有标记了这个注解的类都被列入 Hessian Bean。

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface HessianService {
    public String value();
}

    其次创建一个 Hasor Web Plugin 插件。

@Plugin
public class HessianPlugin extends AbstractWebHasorPlugin {
    public void loadPlugin(WebApiBinder apiBinder) {
        
    }
}

    接下来我们需要知道都有哪些类标记了 HessianServices 注解,这个工作可以通过 Hasor 提供的方法取得。

Set<Class<?>> servicesSet = apiBinder.getClassSet(HessianService.class);

    getClassSet 方法是由 Environment 接口直接提供,Hasor 在加载插件之前这个接口对象会被创建。该方法会自动扫描 classpath 路径中所有类,并匹配被扫描的类。扫描匹配规则如下:

    1.被扫描的类是否为被测试类的(子类)。
    2.被扫描的类中是否实现了被测试类型表示的接口(接口实现)。
    3.如果被测试的类型是注解,则判断被扫描的类中是否标记了该注解(注解)。

    通过上面这个方法就可以得到我们需要的那些类,接下来取得注解的信息注册 Hessian 服务。

@Plugin
public class HessianPlugin extends AbstractWebHasorPlugin {
    public void loadPlugin(WebApiBinder apiBinder) {
        Set<Class<?>> servicesSet = apiBinder.getClassSet(HessianService.class);
        for (Class<?> hessian : servicesSet) {
            //
            HessianService hessAnno = hessian.getAnnotation(HessianService.class);
            if (hessAnno == null || StringUtils.isBlank(hessAnno.value()))
                continue;
            String pushPath = hessAnno.value();
            pushPath = (pushPath.charAt(0) != '/') ? ("/" + pushPath) : pushPath;
            //
            HessianServlet hessianServlet = new HessianServlet();
            hessianServlet.setAPIClass(hessian);
            hessianServlet.setHome(hessian.newInstance());
            apiBinder.serve(pushPath).with(hessianServlet);
            //
        }
    }
}

    OK,到此为止发布服务的工作就算完成了。或许“hessian.newInstance()”这么草草的通过反射创建 Services 会让服务类丢失 Guice IoC/Aop 强大功能的支持,但是我们的确完成了发布工作。

    如果想要发布的服务类对象是通过 Guice 创建而来的自然就更加完美了,这样 Hessian 服务类也可以享受到 IoC/Aop 的待遇。

 

@Plugin
public class HessianPlugin extends AbstractWebHasorPlugin {
    public void loadPlugin(WebApiBinder apiBinder) {
        Set<Class<?>> servicesSet = apiBinder.getClassSet(HessianService.class);
        // 1.注册
        Map<Class<?>, HessianServlet> serviceMap = new HashMap<Class<?>, HessianServlet>();
        for (Class<?> serviceType : servicesSet) {
            //
            HessianService hessAnno = serviceType.getAnnotation(HessianService.class);
            if (hessAnno == null || StringUtils.isBlank(hessAnno.value()))
                continue;
            String pushPath = hessAnno.value();
            pushPath = (pushPath.charAt(0) != '/') ? ("/" + pushPath) : pushPath;
            //
            HessianServlet serviceServlet = new HessianServlet();
            //serviceServlet.setAPIClass(hessian);
            //serviceServlet.setHome(hessian.newInstance());
            serviceMap.put(serviceType, serviceServlet);
            apiBinder.serve(pushPath).with(serviceServlet);
            //
        }
        // 2.初始化
    }
}

    新的插件类改为如上样子,我们通过 serviceMap 保存 Hessian 服务和具体 HessianServlet 之间的映射关系。

    通过 AppContextAware 接口取得 AppContext 对象然后再设置 HessianServlet的 APIClass 和 Home 属性即可 。

    PS:Hasor 会在 Start 的第一时间通知 AppContextAware 接口并将 AppContext 输送进来,此时 AppContext  已经准备好可以通过它创建或者获取 Bean,其它模块还尚未 Start。

    下面是我们发布服务的最终完整插件代码,它只有一个类:

@Plugin
public class HessianPlugin extends AbstractWebHasorPlugin {
    public void loadPlugin(WebApiBinder apiBinder) {
        Set<Class<?>> servicesSet = apiBinder.getClassSet(HessianService.class);
        // 1.注册
        final Map<Class<?>, HessianServlet> serviceMap = new HashMap<Class<?>, HessianServlet>();
        for (Class<?> serviceType : servicesSet) {
            //
            HessianService hessAnno = serviceType.getAnnotation(HessianService.class);
            if (hessAnno == null || StringUtils.isBlank(hessAnno.value()))
                continue;
            String pushPath = hessAnno.value();
            pushPath = (pushPath.charAt(0) != '/') ? ("/" + pushPath) : pushPath;
            //
            HessianServlet serviceServlet = new HessianServlet();
            serviceMap.put(serviceType, serviceServlet);
            apiBinder.serve(pushPath).with(serviceServlet);
        }
        // 2.初始化
        apiBinder.registerAware(new AppContextAware() {
            public void setAppContext(AppContext appContext) {
                for (Entry<Class<?>, HessianServlet> ent : serviceMap.entrySet()) {
                    Class<?> serviceType = ent.getKey();
                    HessianServlet serviceServlet = ent.getValue();
                    serviceServlet.setAPIClass(serviceType);
                    // 通过 AppContext 创建
                    serviceServlet.setHome(appContext.getInstance(serviceType));
                }
            }
        });
    }
}

    下面代码就是测试程序:

//发布服务
@HessianService("/testBean")
public class HessianBean {
    @Inject
    private AppContext appContext;
    //
    public long sayHello() {
        long t = appContext.getStartTime();
        System.out.println(t);
        return t;
    }
}

//定义客户端接口
public interface IHessianBean {
    public long sayHello();
}

//客户端调用
public static void main(String[] args) throws MalformedURLException {
    String url = "http://127.0.0.1:8082/testBean";
    HessianProxyFactory factory = new HessianProxyFactory();
    // IHello为调用的服务接口,url为hessian服务url  
    IHessianBean bean = (IHessianBean) factory.create(IHessianBean.class, url);
    System.out.println(bean.sayHello());
}

--------------------------------------------------

    还记得前面说的

public static void main(String[] args) throws MalformedURLException {
  IHessianBean bean = (IHessianBean) HessianPlugin.getPropxy(IHessianBean.class);
  System.out.println(bean.sayHello());
}

    这种形式调用客户端么?下面是 getPropxy 的方法代码,将这个代码放入 HessianPlugin 类中即可:

// 创建 Hessian 客户端调用
public static <T> T getPropxy(Class<T> propxyFaces) throws MalformedURLException {
    HessianClient hessAnno = propxyFaces.getAnnotation(HessianClient.class);
    if (hessAnno == null || StringUtils.isBlank(hessAnno.value()))
        return null;
    return getPropxy(propxyFaces, hessAnno.value());
}
// 创建 Hessian 客户端调用
public static <T> T getPropxy(Class<T> propxyFaces, String url) throws MalformedURLException {
    if (propxyFaces.isInterface() == false)
        throw new ClassCastException("propxyFaces is not Interface.");
    HessianProxyFactory factory = new HessianProxyFactory();
    return (T) factory.create(propxyFaces, url);
}

--------------------------------------------------

    整合 Hessian 就这么多,如果您有更好的想法可以在此基础上扩展,通过 Hasor 整合 Hessian 就是这么简单。2个注解一个插件就可以搞定!

    代码位于:

http://git.oschina.net/zycgit/hasor/tree/master/examples/src/main/java/net/test/project/common/hessian

    这里是 @黄勇 写的一篇基于 @WebServlet 注解下集成 Hessian 与大家分享一下。
http://my.oschina.net/huangyong/blog/187561

--------------------------------------------------

目前的开发代码存放于(包括Demo程序)
    Github:    https://github.com/zycgit/hasor
    git@OSC: http://git.oschina.net/zycgit/hasor

非常感谢您百忙之中抽出时间来看这一系博文。可以通过Maven 中央仓库网站  http://search.maven.org/ 搜索 Hasor 下载 hasor 的相关代码。

Global site tag (gtag.js) - Google Analytics