WebService-CXF

来源:互联网 发布:笑果软件融合 编辑:程序博客网 时间:2024/05/18 01:10
 

WebService-CXF

注:如果只想开发一个简单的WebService,蓝色标注的文字可以不看。

红色字体是需要注意的地方。

 

简介:Apache CXF  ==  Celtix + Xfire, CXF 继承了 Celtix 和 XFire 两大开源项目的精华,提供了对JAX-WS 全面的支持. CXF 既支持 WSDL 优先开发,也支持从 Java 的代码优先开发模式。容易使用: CXF 设计得更加直观与容易使用。有大量简单的 API 用来快速地构建代码优先的 Services,各种 Maven 的插件也使集成更加容易,支持 JAX-WS API ,支持 Spring 2.0 更加简化的 XML 配置方式,等等。支持二进制和遗留协议:CXF 的设计是一种可插拨的架构,既可以支持 XML ,也可以支持非 XML 的类型绑定,比如:JSON 和 CORBA.

 

优点:

1 可支持代码生成:

Java to WSDL;WSDL to Java;XSD to WSDL;WSDL to XML;WSDL to SOAP;WSDL to Service;

2 Apache CXF 提供方便的Spring 整合方法,可以通过注解、Spring 标签式配置来暴露WebServices

 

CXF开发:

CXF适合与Spring一起开发,JAVA 中共有三种WebService 规范,分别是JAXM&SAAJ、JAX-WS(JAX-RPC)、JAX-RS。这里的JAX-WS 和JAX-RS 规范我们采用Apache CXF 作为实现,CXF 是Objectweb Celtix和Codehaus XFire 合并而成。CXF 的核心是org.apache.cxf.Bus(总线),类似于Spring 的

ApplicationContext,Bus 由BusFactory 创建,默认是SpringBusFactory 类,可见默认CXF是依赖于Spring 的,Bus 都有一个ID,默认的BUS 的ID 是cxf。

在这里简单说下注意事项:当你使用的是JDK1.5 的时候你就必须要有jaxws-api-2.0.jar这个包的支持.如果使用的是JDK1.6 就不用使用这个包了。因为1.6 里已经有了相关的实现。

CXF与JDK1.6版本开发时代码冲突的解决方法:

如果是java应用程序:在<java-home>/lib/endorsed中将jaxb-api.jar和jaxb-impl.jar放入这里。

如果是java web项目:在<tomcat安装目录>/common/endorsed中将jaxb-api.jar和jaxb-impl.jar放入这里。

注:具体的目录可以通过System.out.println(System.getProperty("java.endorsed.dirs"));

这句代码查看。

Java-Webservice-JAX Ws的开发:

分为Service和Client两部分,Server 公开Web 服务,Client 调用Web 服务,JAX-WS 的服务端、客户端双方传输数据使用的SOAP 消息格式封装数据。

服务端示例:

1 需要一个Web服务的接口:例如IhelloService:

@WebService

public interface IHelloService {

Customer selectMaxAgeStudent(Customer c1, Customer c2);

Customer selectMaxLongNameStudent(Customer c1, Customer c2);

}

注解@WebService 就标注了这个接口的方法将公开为Web 服务,使用了这个注解的接口的所有方法都将公开为Web 服务的操作,如果你想屏蔽某个方法,可以使用方法注解@Method 的exclude=true.

我们也通常把公开为Web服务的接口叫做SEI(Service EndPoint Interface)服务端点接口.

2 接口的实现类:

public class HelloServiceImpl implements IHelloService {

@Override

public Customer selectMaxAgeStudent(Customer c1, Customer c2) {

if (c1.getBirthday().getTime() > c2.getBirthday().getTime())

return c2;

else

return c1;

}

@Override

public Customer selectMaxLongNameStudent(Customer c1, Customer c2)

{

if (c1.getName().length() > c2.getName().length())

return c1;

else

return c2;

}

}

是如果你的实现类还实现了其他的接口,那么你需要在实现类上使@WebService 注解的endpointInterface 属性指定那个接口是SEI(全类名)

3 Customer类:

@XmlRootElement(name = "Customer")

public class Customer {

private long id;

private String name;

private Date birthday;

public long getId() {

return id;

}

public void setId(long id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public Date getBirthday() {

return birthday;

}

public void setBirthday(Date birthday) {

this.birthday = birthday;

}

}

@XmlRootElement   将一个Java类映射为一段XML的根节点

参数:name            定义这个根节点的名称
          namespace   定义这个根节点命名空间

4 发布Web服务(java应用程序的发布方式)

public class SoapServer {

public static void main(String[] args) {

Endpoint.publish("http://127.0.0.1:8080/helloService",

new HelloServiceImpl());

}

}

注意我们发布Web 服务使用的是javax.xml.ws.*包中的EndPoint 的静态方法publish().

访问http://127.0.0.1:8080/helloService?wsdl , 如果你已经看到WSDL,那么表示我们的Web 服务发布成功了。我没在没有Tomcat这样的Web服务器,而直接运行一个main方法为什么能发布呢?因为CXF内置了Jetty(Servlet容器),因此也可以发布。

常用注解:

@WebService

       argetNamespace 属性指定你想要的名称空间(<wsdl:definitions … 中targetNamespace的值)

operationName 属性值指定XXX和XXXResponse 元素中XXX 的值。其中XXX 是方法名称

例如:  <wsdl:message name="getClassByJson">

              <wsdl:part element="tns:getClassByJson" name="parameters" />

      </wsdl:message>

<wsdl:message name="getClassByJsonResponse">

         <wsdl:part element="tns:getClassByJsonResponse" name="parameters" />

       </wsdl:message>

 

@ WebResult

Name属性指定,方法的返回值

@WebParam

Name属性指定,参数名

mode 属性 由javax.jws.WebParam.Mode 枚举指定,表示参数的流向,默认是IN,也就是输入参数,还可以是OUT、INOUT 类型。

如果是OUT、INOUT 类型的参数类型,这样的方法参数将会被当做返回值在Web 服务调用完成后返回给你,客户端生成代码时会被转变为javax.xml.ws.Holder<T>类型,注意不要导错包,不是javax.xml.rpc.Holder 类型(JDK1.6 已经没有这个类型,但是MyEclipse 中还是会有,小心导入这个已经不使用的类型)。

 

@XmlRootElement   将一个Java类映射为一段XML的根节点

参数:name            定义这个根节点的名称
          namespace   定义这个根节点命名空间

例如:<xs:element name="Customer" type="tns:customer" />

 

@XmlAccessorType  定义映射这个类中的何种类型需要映射到XML。可接收四个参数,分别是:
      XmlAccessType.FIELD:映射这个类中的所有字段到XML
      XmlAccessType.PROPERTY:映射这个类中的属性(get/set方法)到XML
      XmlAccessType.PUBLIC_MEMBER:将这个类中的所有public的field或property同时映射到XML(默认)
      XmlAccessType.NONE:不映射

 

@XmlElement  指定一个字段或get/set方法映射到XML的节点。如,当一个类的XmlAccessorType 被标注为PROPERTY时,在某一个没有get/set方法的字段上标注此注解,即可将该字段映射到XML。

参数:defaultValue  指定节点默认值
         name             指定节点名称
         namespace    指定节点命名空间
         required         是否必须(默认为false)
         nillable           该字段是否包含 nillable="true" 属性(默认为false)
         type               定义该字段或属性的关联类型

@XmlAttribute  指定一个字段或get/set方法映射到XML的属性。
参数:name             指定属性名称
         namespace    指定属性命名空间
         required         是否必须(默认为false)

@XmlTransient  定义某一字段或属性不需要被映射为XML。如,当一个类的XmlAccessorType 被标注为PROPERTY时,在某一get/set方法的字段上标注此注解,那么该属性则不会被映射。

@XmlType  定义映射的一些相关规则
参数:propOrder        指定映射XML时的节点顺序
         factoryClass     指定UnMarshal时生成映射类实例所需的工厂类,默认为这个类本身
         factoryMethod  指定工厂类的工厂方法
         name               定义XML Schema中type的名称
         namespace      指定Schema中的命名空间

@XmlElementWrapper  为数组元素或集合元素定义一个父节点。如,类中有一元素为List items,若不加此注解,该元素将被映射为
    <items>...</items>
    <items>...</items>
这种形式,此注解可将这个元素进行包装,如:
    @XmlElementWrapper(name="items")
    @XmlElement(name="item")
    public List items;
将会生成这样的XML样式:
    <items>
        <item>...</item>
        <item>...</item>
    </items>

@XmlJavaTypeAdapter  自定义某一字段或属性映射到XML的适配器。如,类中包含一个接口,我们可以定义一个适配器(继承自 javax.xml.bind.annotation.adapters.XmlAdapter类),指定这个接口如何映射到XML。

@XmlSchema 配置整个包的namespace,这个注解需放在package-info.java文件中。

jaxb编码:

           JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            InputStreamReader reader=new InputStreamReader(inputStream,"GBK");   //在此修改编码
            return unmarshaller.unmarshal(reader);

@javax.jws.Oneway

是一个标识性注解,没有任何属性,它表示公开的Web 服务的方法

没有任何返回值,不允许有 OUT 类型的参数,不允许抛出非运行时异常。如果条件不符JAX-WS 规范要求应该报告错误,但是CXF 的策略是如果方法存在返回值,生成客户端时将被改为void;如果方法参数含有OUT 类型,生成客户端时将被忽略;如果方法含有INOUT类型参数,生成客户端时将只作为IN 类型参数被保留。

 

分析发布后生成的WSDL的解析:

(1.)<wsdl:definitions …这个是WSDL 的根元素,我们要关心的是三个属性,name 属性值为公开的Web 服务的接

口的实现类+Service Service(上例中为name="HelloServiceImplService");targetNamespace 指定目标名称空间,targetNamespace 的值被后面

的xmlns:tns 属性作为值, 默认是使用接口实现类的包名的反缀

(targetNamespace="http://server.soap.ilkj.net/" …

xmlns:tns="http://server.soap.ilkj.net/"),

你可以使用@WebService 注解的targetNamespace 属性指定你想要的名称空间。

 

(2.)<wsdl:types …

这个元素会通过<xs:element … 声明几个复杂数据类型的元素。

一般首先你看到的是Web 服务中的方法参数、返回值所涉及的所有复杂(complex)类型的

元素定义<xs:element …,其中name 属性值是这个复杂类型的JAXB 注解的name 属性值,

type 属性是tns:+JAXB 注解的name 属性值的全小写形式(上例中的方法参数、返回值只涉

及一个复杂类型Customer,Customer 的@XmlRootElement 注解的name 属性值为Customer,

因此你会看到<xs:element name="Customer" type="tns:customer" />)。

再向下你会看到XXX 元素和XXXResponse 元素,其中XXX 是方法名称(你可以使用

@WebMethod 的operationName 属性值指定XXX 的值),XXX 是对方法参数的封装,

XXXResponse 是对返回值的封装。上例中你会看到

<xs:element name="selectMaxAgeStudentMethod"

type="tns:selectMaxAgeStudentMethod" />

<xs:element name="selectMaxAgeStudentMethodResponse"

type="tns:selectMaxAgeStudentMethodResponse" />

<xs:element name="selectMaxLongNameStudent"

type="tns:selectMaxLongNameStudent" />

<xs:element name="selectMaxLongNameStudentResponse"

type="tns:selectMaxLongNameStudentResponse" />

 

最 后你会看到一组<xs:complexType … 元素, 这个元素通过name 属性关联到<xs:element … ,它为前面定义的元素指定封装的具体内容(通过子元素<xs:sequence … 指定),上例中方法参数的复杂类型指定如下形式:

<xs:complexType name="selectMaxAgeStudentMethod">

<xs:sequence>

<xs:element minOccurs="0" name="arg0" type="tns:customer" />

<xs:element minOccurs="0" name="arg1" type="tns:customer" />

</xs:sequence>

</xs:complexType>

我们看到方法参数名称为arg0、arg1、…,如果你想指定方法参数的名字在方法参数前使用

@WebParam 的name 属性指定值,同样,方法的返回值同样可以使用@WebResult 注解指定

相关的属性值。

例如:

@WebResult(name = "method")

Customer selectMaxAgeStudent(@WebParam(name = "c1") Customer c1,

@WebParam(name = "c2") Customer c2);

 

(3.)<wsdl:message …

这个元素将输入参数(方法参数)和响应结果(方法返回值)、受检查的异常信息包装为消息。

 

(4.)<wsdl:portType …

这个元素指定Web 服务的端口类型(Web 服务会被发布为EndPoint 端点服务),它的name属性默认为接口名称(你可以使用@WebService 注解的name 属性指定值)。这个元素包含了一系列的<wsdl:operation …子元素指定该端点服务包含了那些操作( 方法) ,

<wsdl:operation …的子元素<wsdl:input …、<wsdl:output …指定操作的输入输出(通过属性message 绑定到前面声明过的消息)。

 

(5.)<wsdl:binding …

这个元素将前面最终的端点服务绑定到SOAP 协议(你可以看出来WSDL 从上到下依次有着依赖关系),其中的<soap:xxx … 的style、use 分别可以使用SOAPBinding 注解的style、use 属性指定值、<wsdl:operation … 指定公开的操作(方法)。这部分XML 指定最终发布的Web 服务的SOAP 消息封装格式、发布地址等。

 

(6.)<wsdl:service …

这个元素的name 属性指定服务名称(这里与根元素的name 属性相同),子元素<wsdl:port…的name 属性指定port 名称,子元素<soap:address … 的location 属性指定Web 服务的地址。

 

利用WSDL生成客户端代码的方法:

你可以在命令行将当前目录切换到CXF 的bin 目录,然后运行wsdl2java –h 查看这个批处理命令的各个参数的作用,常用的方式就是wsdljava –p 包路径 –d 目标文件夹 wsdl 的url地址。现在我们将前面的WSDL生成客户端代码:

wsdl2java -p net.ilkj.soap.client –d E:\ http://127.0.0.1:8080/helloService?wsdl

你会在E 盘根目录找到生成的客户端代码,然后将它复制到Eclipse 工程即可使用。

我们上面的WSDL 会生成如下所示的客户端代码:

Customer.java

HelloServiceImplService.java

IHelloService.java

ObjectFactory.java

package-info.java

SelectMaxAgeStudent.java

SelectMaxAgeStudentResponse.java

SelectMaxLongNameStudent.java

SelectMaxLongNameStudentResponse.java

其中package-info.java、ObjectFactory.java 是JAXB 需要的文件;HelloServiceImplService.java

继承自javax.xml.ws.Service 类,用于提供WSDL 的客户端视图,里面使用的是大量

javax.xml.ws.*包中的注解;剩下的类是Web 服务的接口、方法参数、响应值的类。

 

客户端

在 CXF 中使用JaxWsProxyFactoryBean 客户端代理工厂调用Web 服务,代码如下所示:

public class SoapClient {

public static void main(String[] args) throws ParseException {

JaxWsProxyFactoryBean soapFactoryBean = new

JaxWsProxyFactoryBean();

//输出日志的拦截器

soapFactoryBean.getInInterceptors().add(new LoggingInInterceptor());

soapFactoryBean.getOutInterceptors().add(new LoggingOutInterceptor());

soapFactoryBean.setAddress("http://127.0.0.1:8080/helloService");

soapFactoryBean.setServiceClass(IHelloService.class);

Object o = soapFactoryBean.create();

IHelloService helloService = (IHelloService) o;

Customer c1 = new Customer();

c1.setId(1);

c1.setName("A");

GregorianCalendar calendar = (GregorianCalendar)

GregorianCalendar

.getInstance();

calendar

.setTime(new

SimpleDateFormat("yyyy-MM-dd").parse("1989-01-28"));

c1.setBirthday(new XMLGregorianCalendarImpl(calendar));

Customer c2 = new Customer();

c2.setId(2);

c2.setName("B");

calendar

.setTime(new

SimpleDateFormat("yyyy-MM-dd").parse("1990-01-28"));

c2.setBirthday(new XMLGregorianCalendarImpl(calendar));

System.out.println(helloService.selectMaxAgeStudent(c1, c2).getName());

}

}

 

客户端访问Web 服务时我们使用了CXF 的JaxWsProxyFactoryBean 来进行操作,其实你也可以使用标准的JAX-WS 的API 完成客户端调用。

例如:

QName qName = new QName("http://server.soap.ilkj.net/",

"HelloServiceImplService");

HelloServiceImplService helloServiceImplService =new HelloServiceImplService(

new URL("http://127.0.0.1:8080/ws/services/helloService?wsdl"),

qName);

IHelloService helloService = (IHelloService) helloServiceImplService

.getPort(IHelloService.class);

 

 

CXF 与Spring:

CXF 可以很好的与Spring 整合,这样可以为我们省去很多的代码,你需要的是简单的Spring配置即可。

I.                    CXF发布在Web服务器:

我们前面都是使用CXF 自带的Jetty 发布Web 服务,如果我们的Web 服务在Web 服务器中编写,那么使用现有的Web 服务器自然是更好的选择。

通常CXF 在Web 服务器发布Web 服务都是通过Spring 容器完成,但是CXF 也可以完全脱离Spring 容器独立运行在Web 容器中, 你需要书写一个类覆盖

org.apache.cxf.transport.servlet.CXFNonSpringServlet 的loadBus 方法指定BUS 以及发布你的Web 服务。我们的Web 应用上下文是/ws。

 

不用Spring时编写自己的CXFNonSpringServlet并配置web.xml

(1.)web.xml:

<servlet>

<servlet-name>CXFServlet</servlet-name>

<servlet-class>

net.ilkj.servlet.MyCXFNonSpringServlet

</servlet-class>

<init-param>

<param-name>/helloService</param-name>

<param-value>

net.ilkj.soap.server.HelloServiceImpl

</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>CXFServlet</servlet-name>

<url-pattern>/services/*</url-pattern>

</servlet-mapping>

你可以配置多个初始化参数,指定你要发布的Web服务实现类。

 (2.)MyCXFNonSpringServlet.java:

public class MyCXFNonSpringServlet extends CXFNonSpringServlet {

/**

*

*/

private static final long serialVersionUID = 1930791254280865620L;

@Override

public void loadBus(ServletConfig servletConfig) throws

ServletException {

super.loadBus(servletConfig);

Bus bus = this.getBus();

BusFactory.setDefaultBus(bus);

// 获取在web.xml中配置的要发布的所有的Web服务实现类并发布Web服务

Enumeration<String> enumeration = getInitParameterNames();

while (enumeration.hasMoreElements()) {

String key = enumeration.nextElement();

String value = getInitParameter(key);

try {

Class clazz = Class.forName(value);

try {

Endpoint.publish(key, clazz.newInstance());

} catch (InstantiationException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

}

为了测试结果,你可以暂时先删除lib 目录下Spring 的所有jar 文件,然后我们访问http://127.0.0.1:335/ws/services/helloService?wsdl,我们看到Web 服务发布成功。

 

2 使用Spring发布SOAP方式的Web服务:

使用Spring 发布Web 服务很简单,首先将web.xml 改为如下的形式:

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/beans.xml</param-value>

</context-param>

<listener>

<listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

<servlet>

<servlet-name>CXFServlet</servlet-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>/services/*</url-pattern>

</servlet-mapping>

我们看到CXFServlet 负责截获/services/*的请求,Web 服务的SEI 由Spring 的上下文加载监听器来完成查找。那么下面我们来配置Spring,beans.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"

xmlns:jaxrs="http://cxf.apache.org/jaxrs"

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

http://cxf.apache.org/jaxrs

http://cxf.apache.org/schemas/jaxrs.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-extension-jaxrs-binding.xml" />

<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

<jaxws:endpoint id="helloServiceWs" address="/helloService"

implementor="#helloService" />

<bean id="helloService"

class="net.ilkj.soap.server.HelloServiceImpl" />

</beans>

我们注意到这里引入了两个新的名称空间jaxws、jaxrs,因为CXF 实现了Spring 的NamespaceHandler 接口,实现这个接口可以在Spring 中增加额外的配置。

那么jaxws 自然是配置SOAP 方式的Web 服务,你可以看到有jaxws:server、jaxws:endpoint、jaxws:client 三个元素,jaxws:server 和jaxws:endpoint 是等效的,都用于发布Web 服务,出现jaxws:endpoint 的原因是JAX-WS 规范中使用EndPoint 发布Web 服务(前面使用过这种方式),CXF 为了和JAX-WS 对应,提供了这个与jaxws:server 功能一样的配置元素;jaxrs是REST 方式的Web 服务,有jaxrs:server、jaxrs:client 两个元素。你也可以使用implementorClass 属性直接指向一个类,而不是像上面那样引用一个Bean 的名字。

我们启动Web 服务器,访问http://127.0.0.1:335/ws/services/helloService?wsdl 地址,如果你

看到WSDL,那么表示我们的Web 服务发布成功。你也可以使用下面的方式发布Web 服务:

<jaxws:server id="helloServiceWs" address="/helloService">

<jaxws:serviceBean>

<ref bean="helloService" />

</jaxws:serviceBean>

</jaxws:server>

同样,你也可以使用serviceClass 属性指向一个类,而不是使用子元素jaxws:serviceBean 引用一个Bean 的名字。无论是哪种方式,指向的目标都是SEI 的实现类。

II.                  使用Spring开发SOAP方式的客户端:

<jaxws:client id="helloServiceClient"

address="http://127.0.0.1:335/ws/services/helloService"

serviceClass="net.ilkj.soap.client.IHelloService"/>

这里属性serviceClass 指向客户端生成的接口,这里就不能指向一个Bean 的名字,因为接

口是不能被实例化的。然后你就可以向访问Spring 容器中的Bean 一样去访问这个CXF 的

客户端代理(JaxWsClientProxy),下面是我们在JSP 中书写的调用代码:

IHelloService helloService =

(IHelloService) WebApplicationContextUtils

.getWebApplicationContext(application)

.getBean("helloServiceClient");

out.print(helloService.selectMaxAgeStudent(c1, c2).getName());

 

更多关于CXF(JAX-WS规范)的令牌,异常处理以及异步调用请参照“JAVA的WebService支持.pdf”文档。双击下面文档打开。

原创粉丝点击