Java系统WebService:Spring与CXF的集成

459 查看

WebService是什么呢?顾名思义,是Web系统提供的服务,其目的呢,往大了说:是系统实现多异构模块协同合作,服务实现SOA(Services oriented Architecture面向服务的体系结构),往小了说:我们的系统模块能够定义一些功能开放给其他模块,也可以调用其他模块的一些功能。

WebService的实现基本原理就是选择一个各异构系统(结构、开发方式、运行环境等都不完全相同)都能处理的交互信息的方式,让各系统模块能够“听懂”彼此的请求,也可以“告诉”彼此自己的请求,进而实现功能的调用,将追求的功能转为一组合适的通用交互API,开发者只需要根据开放出的API在自己的系统中发出合乎要求的请求就可以了。因此,XML这一相对来说通用性非常强的消息传递方式被选为了完成这一任务的“传递语言”:服务发布方用XML来描述自己系统内开放功能的XML格式的API,其中方法的名称、入参、返回值等都在XML中有明确规定,服务调用方则将自己需要功能的方法、入参、返回值等信息通过XML告知服务发布方,如此一来,就实现了WebService的交互。

如果每个系统都要从头开始构建自己的WebService描述,处理服务消息发布、接收等任务的话系统的重用性将大大降低,因此,就像所有的系统级开发一样,在WebService这里也有很多现成的框架来帮助开发者快速部署自己的Web服务。现在的主流WebService服务框架主要有:

1、JWS(Java WebService)是Java语言对WebService服务的一种实现,用来开发和发布服务。而从服务本身的角度来看JWS服务是没有语言界限的。但是Java语言为Java开发者提供便捷发布和调用WebService服务的一种途径。这是最原生的JAVA语言层面上支持的WebService,只不过功能较弱。

2、Axis2是Apache下的一个重量级WebService框架,准确说它是一个Web Services / SOAP / WSDL 的引擎,是WebService框架的集大成者,它能不但能制作和发布WebService,而且可以生成Java和其他语言版WebService客户端和服务端代码。这是它的优势所在。但是,这也不可避免的导致了Axis2的复杂性,使用过的开发者都知道,它所依赖的包数量和大小都是很惊人的,打包部署发布都比较麻烦,不能很好的与现有应用整合为一体。但是如果你要开发Java之外别的语言客户端,Axis2提供的丰富工具将是你不二的选择。

3、XFire是一个高性能的WebService框架,在Java6之前,它的知名度甚至超过了Apache的Axis2,XFire的优点是开发方便,与现有的Web整合很好,可以融为一体,并且开发也很方便。但是对Java之外的语言,没有提供相关的代码工具。XFire后来被Apache收购了,原因是它太优秀了,收购后,随着Java6 JWS的兴起,开源的WebService引擎已经不再被看好,渐渐的都败落了。

4、CXF是Apache旗下一个重磅的SOA简易框架,它实现了ESB(企业服务总线)。CXF来自于XFire项目,经过改造后形成的,就像目前的Struts2来自WebWork一样。可以看出XFire的命运会和WebWork的命运一样,最终会淡出人们的视线。CXF不但是一个优秀的Web Services / SOAP / WSDL 引擎,也是一个不错的ESB总线,为SOA的实施提供了一种选择方案,当然他不是最好的,它仅仅实现了SOA架构的一部分。

在这里,我们使用Spring这一依赖注入框架和CXF这一WebService框架来写一个Demo用以展示WebService构建Java下Web服务的基本过程。

我们使用Eclipse在JDK6下开发,导入的CXF框架版本2.7,合适的版本对环境的搭建非常重要,不匹配的版本无法正常协同工作。

新建Web工程HowIsTheWeather,导入CXF的lib目录下的全部包,其中包括Spring的包,这些包将在Eclipse的默认构建环境下加入项目编译的classpath下。

创建WebService服务接口com.rocking.weather.WeatherService:

@WebService
public interface WeatherService {
    public String getWeather(String city);
}

实现以上接口的实现类com.rocking.weather.WeatherServiceImpl:

@WebService(endpointInterface = "com.rocking.weather.WeatherService", serviceName = "WeatherService")
public class WeatherServiceImpl implements WeatherService{
    @Override
    public String getWeather(String city) {
        //fake weather data:
        String shanghai = "12C,almost sunny";
        String beijing = "7C,most part fog";
        String guangzhou = "20C,a little rain";
        if(city.equals("shanghai"))
            return shanghai;
        else if(city.equals("beijing"))
            return beijing;
        else if (city.equals("guangzhou"))
            return guangzhou;
        else 
            return "no data";
    }
}

在Web.xml中声明基本的CXF的服务servlet和Spring的IOC容器:

  • Spring IOC容器使用ContextLoaderListener监听器来注入需要的bean,bean的配置详情写在classpath下的applicationContext-server.xml(实际也就是src下的applicationContext-server.xml,在构建后classpath下就会出现这个配置文件)。

  • 同所有的servlet一样,servlet存在的目的就是将web前端的URL请求映射到后台的处理器中去,因此web.xml中需要配置映射原则,这里我们可以看到CXFServlet用于映射带有ws标识的URL到自己的处理范围里,也就是说,一旦满足这样模式的URL,都将请求WebService。

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <display-name>HowIsTheWeather</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext-server.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>CXFServlet</servlet-name>
        <display-name>CXFServlet</display-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/ws/*</url-pattern>
    </servlet-mapping>
</web-app>

applicationContext-server.xml则负责Spring容器中的bean注入的详情,这里配置的bean都将在容器启动的时候装配注入进去,jaxws可以看作是一种特殊的bean,WebService服务实体bean,这是CXF框架为Spring提供的集成支持,在这里我们可以看到,当URL的请求地址为/getWeather时,将会调用我们已经注入的服务bean处理这个请求,进而完成外界对系统开放的WebService的调用。

applicationContext-server.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                       http://www.springframework.org/schema/beans/spring-beans.xsd
                       http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
    <jaxws:endpoint id="WeatherService" implementor="com.rocking.weather.WeatherServiceImpl"
        address="/getWeather" />
</beans>

将我们的工程部署到Tomcat服务器中去,启动服务器查看如下URL:http://localhost:8080/HowIsTheWeather/ws
在这里浏览器会返回CXF渲染的web页面用以说明当前的CXFServlet能够导向的服务有哪些:WeatherService使用/getWeather的URL就可以调用。
我们再使用这样的URL来查看这个服务具体的定义:http://localhost:8080/HowIsTheWeather/ws/getWeather?wsdl
这个URL用以通过WSDL(WebService Description Language)查看Web服务:也就是我们开头所说的用通用格式来描述WebService的功能、入参和返回值,使我们的调用者明白服务的使用方法:具体详情可以查看我们的这个服务的WSDL页面。

至此,说明我们的服务已经部署到我们的服务器上了,接下来需要有客户端来调用这个服务,我们仍然使用Spring的依赖注入来处理:

public class Client {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext-client.xml");
        WeatherService weatherService = (WeatherService) context.getBean("client");
        String response = weatherService.getWeather("beijing");
        System.out.println(response);
    }
}

applicationContext-client.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                       http://www.springframework.org/schema/beans/spring-beans.xsd
                       http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
    <bean id="client" class="com.rocking.weather.Client" factory-bean="clientFactory"
        factory-method="create" />
    <bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
        <property name="serviceClass" value="com.rocking.weather.WeatherService" />
        <property name="address"
            value="http://localhost:8080/HowIsTheWeather/ws/getWeather" />
    </bean>
</beans>

这样我们就在同一系统中实现了WebService的调用,但是,我们不得不说这不是我们的要求,因为谁也不会大费周折在项目内部从WebService获取服务。我们的目的是在另一个项目中获取这个项目提供的开放服务,而另一个系统需要的,就是这个服务提供的接口:WeatherService,这样一来,我们可以直接使用这个接口的源文件,或者打成jar包提供给调用方。

在这里,我们新建一个一般的Java项目,将CXF的包导入classpath,将WeatherService接口导入到和原来在服务提供方一样的包路径下(这和jar包的自动路径是一样的),然后配置好Spring的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                       http://www.springframework.org/schema/beans/spring-beans.xsd
                       http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
    <bean id="client" class="com.rocking.weather.client.Client" factory-bean="clientFactory"
        factory-method="create" />
    <bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
        <property name="serviceClass" value="com.rocking.weather.WeatherService" />
        <property name="address"
            value="http://localhost:8080/HowIsTheWeather/ws/getWeather" />
    </bean>
</beans>

或者这样注入:

<jaxws:client id="WeatherService"
                 serviceClass="com.rocking.weather.WeatherService"
                 address="http://localhost:8080/HowIsTheWeather/ws/getWeather"/>

Client客户端可以调用接口,像调用本地方法一样调用远程服务提供的方法。

public class Client {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext-client.xml");
        WeatherService weatherService = (WeatherService) context.getBean("client");
        String response = weatherService.getWeather("shanghai");
        System.out.println(response);
    }
}

以上内容就是我们展示的CXF和Spring集成的WebService开发过程,例子虽小,但是阐明了WebService创建的基本过程。

现在我们回过头来想,我们当初所说的WebService的调用方使用XML格式发送消息接收消息并没有看到啊,我们这里直接使用了导入的本地接口像调用本地方法一样调用远程方法,看来和XML没有什么区别啊,其实这就是WebService框架带来的好处,我们在代码中虽然没有出现XML,但是这些请求会在CXF的帮助下变成XML在服务器端和服务交互,得到的结果也会以类似的样子返回来,只不过我们看到的都是冰层之上的事情了,这也从一个侧面反映了WebService框架开发带来的好处。

另外,我们在服务的WSDL页面上其实已经看到了CXF为我们的代码生成的使用WSDL描述的XML化的服务“说明书”,如果想要更彻底地通过服务源码获取可以移植到客户端的接口,可以使用CXF的wsdl2java工具来生成接口。