初步实现 WebService 插件

原创
2013/11/22 16:25
阅读数 2.2K

本文是《轻量级 Java Web 框架架构设计》的系列博文。

上文中,我使用了 JAX-WS 发布并调用 Web 服务,虽然已经很简单了,但还是有些冗余的地方,比如:

  1. 接口与实现类上都要使用 @WebService 注解。
  2. 实现类上的 @WebService 注解需要指定 Web 服务名称与接口。
  3. 还要在 WEB-INF 下提供一个 sun-jaxws.xml 配置文件,用于发布 Web Service,需要指定 Web 服务名称、地址与实现。

能否简化一下呢?

我的方案是:只需在接口上定义一个 @WebService 注解,便可发布为 Web 服务。

不过需要借助一个工具,它就是 CXF,它是 Apache 的顶级项目之一。或许有些朋友没有听说过,但它的前身或许会对大家并不陌生,那就是 XFire,已经很老了,现在已停止维护,摇身一变,成为了 CXF。

CXF 与 Spring 集成得非常好,发布或调用 Web 服务只需使用 Java 注解 + Spring 配置即可实现,CXF 将复杂的技术细节给屏蔽了,对于开发人员而言,只需掌握一些基本用法即可。所以在 Java 业界里,尤其是使用了 Spring 作为解决方案的项目,一定都会优先考虑使用 CXF 发布或调用 Web 服务。

可能有朋友会问:必须集成 Spring,才能使用 CXF 吗?

答案是否定的。单独用 CXF 甚至会更加简单,从 API 层面上来讲,更加容易定制与扩展,更容易把玩它!

还是系好您的安全带吧,我们即将起飞!

第一步:在 Maven 中添加 CXF 依赖

CXF 的依赖包比较多,但对于发布 Web 服务而言,至于使用以下配置:

...
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>2.7.7</version>
        </dependency>
...

Maven 真不愧是 Java 世界里最伟大的发明之一,有了它,无需再配置 CXF 的依赖包了,其实它的依赖包很多,我们没必要一个个地去找,Maven 已经帮我们做好了依赖关联关系。

第二步:自定义一个 @WebService 注解

为什么要自定义一个呢?JDK 不是已经提供了一个吗?

其实用 JDK 的也行,只不过这样做是考虑到将来的扩展,比如给该注解添加其他属性等。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebService {

    String value() default "";
}

目前仅提供了唯一的属性 value,默认值为空字符串。它具体有何意义呢?请您继续往下看。

第三步:定制 CXF

该步骤是本文的精华!所以请您务必仔细阅读。

CXF 提供了一个名为 org.apache.cxf.transport.servlet.CXFServlet 的 Servlet,就是靠它来处理 Web 服务请求的,在它内部实现中也耦合了 Spring。看来 CXF 真不愧为 Spring 的好伴侣啊~

如果直接使用 CXFServlet,或许会让 Smart 依赖于 Spring 了,我并不想这样干。虽然 Spring 非常优秀,但不到万不得已,我是不会轻易使用 Sprnig 的。

看了一下 CXFServlet 的源码,发现它还有一个父类,名为 org.apache.cxf.transport.servlet.CXFNonSpringServlet。看到这个名字,我们可以猜想出,这个父类是不依赖于 Spring 的。猎物不费吹灰之力就得到了。

不妨写一个 WebServiceServlet 来扩展 CXFNonSpringServlet 吧:

@WebServlet(name = "ws", urlPatterns = "/ws/*", loadOnStartup = 0)
public class WebServiceServlet extends CXFNonSpringServlet {

    @Override
    protected void loadBus(ServletConfig sc) {
        // 设置 Bus
        setBus(sc);
        // 发布 Web 服务
        publishWebService();
    }
...
}

以上只是一部分代码。首先定义了该 Servlet 的名称、URL 映射、初始化时自动加载(为提高运行时性能,没必要处理请求时进行加载)。然后重写了父类的 loadBus 方法,在该方法中做两件事情:1. 设置 Bus,2. 发布 Web 服务。

Bus 是 CXF 的核心,说通俗一点就是“总线”,可不是“巴士”哦。类似于我们计算机里的 Bus,它把许多部件进行连接。其实 Bus 也是一种设计模式,SOA/ESB 中就是一个最佳实践。对于该技术,本文暂不涉及,有兴趣的朋友,可以去 CXF 官网了解一下,不过官网的文档看起来十分高深莫测,您要有耐心了。

先看如何设置 Bus 吧。其实也就这样了:

...
    private void setBus(ServletConfig sc) {
        super.loadBus(sc);
        Bus bus = getBus();
        BusFactory.setDefaultBus(bus);
    }
...

首先加载 Bus,然后获取 Bus,最后将 Bus 设置为 BusFactory 的默认 Bus。

下面的才是重头戏,如何发布 Web 服务。

...
    private void publishWebService() {
        // 遍历所有标注了 @WebService 注解的接口
        List<Class<?>> wsInterfaceClassList = ClassHelper.getClassListByAnnotation(WebService.class);
        if (CollectionUtil.isNotEmpty(wsInterfaceClassList)) {
            for (Class<?> wsInterfaceClass : wsInterfaceClassList) {
                // 对于接口才能执行以下代码
                if (wsInterfaceClass.isInterface()) {
                    // 获取 value 属性
                    String value = wsInterfaceClass.getAnnotation(WebService.class).value();
                    // 获取 Web 服务地址
                    String wsAddress = getAddress(value, wsInterfaceClass);
                    // 获取 Web 服务实现类(找到唯一的实现类)
                    Class<?> wsImplementClass = IOCHelper.findImplementClass(wsInterfaceClass);
                    // 获取实现类的实例
                    Object wsImplementInstance = BeanHelper.getBean(wsImplementClass);
                    // 发布 Web 服务
                    WebServiceHelper.publishWebService(wsAddress, wsInterfaceClass, wsImplementInstance);
                }
            }
        }
    }
...

首先获取带有 @WebService 注解的接口,然后分别获取 Web 服务的地址、接口、实现类实例,最后将这三个参数传递到 WebServiceHelper 的 publishWebService 方法中,从而发布 Web 服务。

在阅读 WebServiceHelper 代码之前,先看看 getAddress 这个方法是如何获取 Web 服务地址的。

...
    private String getAddress(String value, Class<?> wsInterfaceClass) {
        String address;
        if (StringUtil.isNotEmpty(value)) {
            // 若不为空,则为 value
            address = value;
        } else {
            // 若为空,则为类名
            address = wsInterfaceClass.getSimpleName();
        }
        // 确保最前面只有一个 /
        if (!address.startsWith("/")) {
            address = "/" + address;
        }
        address = address.replaceAll("\\/+", "/");
        return address;
    }
...

该方法判断 @WebService 注解中的 value 属性,若为空(不填),则地址为“/接口名”,否则为用户指定的地址。为了返回正确格式的地址,需确保地址的第一个字符是“/”。

最后再看看 WebServiceHelper 吧,它其实也并不神秘。

import org.apache.cxf.frontend.ClientProxyFactoryBean;
import org.apache.cxf.frontend.ServerFactoryBean;

public class WebServiceHelper {

    // 发布 Web 服务
    public static void publishWebService(String wsAddress, Class<?> wsInterfaceClass, Object wsImplementInstance) {
        ServerFactoryBean factory = new ServerFactoryBean();
        factory.setAddress(wsAddress);               // 地址
        factory.setServiceClass(wsInterfaceClass);   // 接口
        factory.setServiceBean(wsImplementInstance); // 实现
        factory.create();
    }

    // 创建 Web 服务客户端
    public static <T> T createWebClient(String wsAddress, Class<? extends T> interfaceClass) {
        ClientProxyFactoryBean factory = new ClientProxyFactoryBean();
        factory.setAddress(wsAddress);           // 地址
        factory.setServiceClass(interfaceClass); // 接口
        return factory.create(interfaceClass);
    }
}

目前 WebServiceHelper 仅提供这两个方法,一般情况下应该是够用了,不排除后续进行扩展。通过封装 CXF 的 API 应该是一个不错的解决方案,在这里还可以添加更多的 CXF 特性,比如:日志监控、安全加密等。

注意:在创建 Web 服务客户端时,只需提供接口,无需提供实现。

第四步:启动 Tomcat

由于在 WebServiceServlet 的 @WebServlet 注解中定义了 loadOnStartup = 0,所以在启动 Tomcat 时就会自动发布 Web 服务。

启动完毕后,可通过 CXF 提供的控制台进行查看。

控制台地址:http://localhost:8080/smart-sample/ws

注意地址最后的 /ws,这就是在 WebServiceServlet 中配置的 URL 映射。

可见这里已经有一个基于 SOAP 的 Web 服务发布了,可以点击 WSDL 地址查看具体细节。

此外,需要说明的是,CXF 还可以发布基于 REST 的 Web 服务。非常好用,有机会我会和大家分享。

最后一步:调用 Web 服务

不妨使用 WebServiceHelper 来调用这个 Web 服务吧,代码非常精简:

import com.smart.plugin.ws.helper.WebServiceHelper;
import com.smart.sample.service.GreetingService;

public class GreetingServiceClient {

    public static void main(String[] args) throws Exception {
        String wsAddress = "http://localhost:8080/smart-sample/ws/GreetingService";
        GreetingService greetingService = WebServiceHelper.createWebClient(wsAddress, GreetingService.class);

        greetingService.sayHello("Jack");
    }
}

只需提供 Web 服务地址与接口,即可创建客户端对象(greetingService),其实它是一个 Proxy,通过这个 Proxy 来调用目标方法。

运行后,就可以在 Tomcat 控制台下看到 Hello Jack 的输出文字了。

总结

在 Smart 中发布 Web 服务只需做两件事情:

1. 通过 Maven 引入 Smart WebService 插件

...
        <dependency>
            <groupId>com.smart</groupId>
            <artifactId>smart-plugin-ws</artifactId>
            <version>1.0</version>
        </dependency>
...

2. 使用 @WebService 注解定义需要发布的接口

import com.smart.plugin.ws.annotation.WebService;

@WebService
public interface GreetingService {

    void sayHello(String name);
}

发布 Web 服务仅此两步而已,调用 Web 服务也十分方便。您是否觉得这样更加轻量级呢?

展开阅读全文
加载中
点击加入讨论🔥(5) 发布并加入讨论🔥
打赏
5 评论
13 收藏
4
分享
返回顶部
顶部