规范只是接口,如JAX-WS和JAX-RS有CXF具体实现,有三种规范:JAXM&SAAJ(暴露了更多细节),JAX-WS和JAX-RS(rest风格的服务规范)。
底层使用JAXB,JAX-RPC已被JAX-WS替换,位于javax.xml.ws.* 提供api操作web服务。 WS-MetaData:是JAX-WS的依赖规范,位于javax.jws.*
(使用@Method和@WebService 标注接口为web服务)
@WebService public interface IHelloService { Customer selectMaxAgeStudent(Customer c1, Customer c2); Customer selectMaxLongNameStudent(Customer c1, Customer c2); }
类如果实现有多个接口,要使用 @WebService endpointInterface 指定哪个接口是SEI
SEI中参数的类,使用JAXB注解告诉CXF 完成XML和Java Object间处理,如@XmlRootElement。
@XmlRootElement(name = "Customer") public class Customer { private long id; private String name; private Date birthday; }
使用 javax.xml.ws.Endpint.publish 发布服务,通过 url?wsdl可查看生成的wsdl文件
public static void main(String[] args) { Endpoint.publish("http://127.0.0.1:8080/helloService", new HelloServiceImpl()); }
访问 http://127.0.0.1:8080/helloService?wsdl地址
name(实现类+Service),targetNamespace Xs:element: web service中complex数据类型的元素定义 Xxx(方法名称)和XxxResponse:对参数和返回值的元素定义 通过name关联到 xs:element,为前面定义的元素指定具体的封装内容 将参数、返回值和异常封装为消息 Name为接口名称 绑定webservice到SOAP协议,指定消息封装格式、发布地址 Name: 服务名称, 可以在SEI中指定参数和返回值的名称,如: 1. 根据wsdl生成java文件 常用的方式就是 wsdljava –p 包路径–d 目标文件夹 wsdl 的url,如 wsdl2java -p net.ilkj.soap.client –d E:\ http://127.0.0.1:8080/helloService?wsdl 2. 使用JaxWsProxyFactoryBean创建webservice的代理进行调用。 当wsdl可以访问,webservice 不一定能访问,还和web service的实现有关,wsdl只是接口的一种xml表示 将【实例】中的发布改为使用 JaxWsServerFactoryBean,加入日志拦截器到输入和输出拦截器中。 效果如下: Inbound Message输出的是服务器端接收到的 SOAP 信息,Outbound Message输出的服务器端响应的 SOAP 信息,SOAP 的 Headers:{}的前面是 SOAP 消息的标识、编码方式、MIME类型,Headers:{}熟悉 HTTP 应该很容易看懂这里面的消息报头的作用,Headers:{}后面的Payload(有效负载,也叫净荷)的 XML 就是 SOAP 消息的真正内容,我们看到 SOAP 消息内容被封装为 对参数使用 @WebParam(name=’c2’ mode=Mode.OUT) 可定义参数显示的名称为c2,且为out类型的. Mode可以有IN,OUT,INOUT类型,对于后两种会作为返回值,客户端生成代码是Holder @javax.jws.Oneway 表示公开的web service的方法没有任何返回值 在实现类中访问请求相关的Message Context,只要使用 javax.annotation.Resouce标注就可使用该接口,如下: @Resource Private WebServiceContext context; //调用 context.getMessageContext() 后以Map形式进行遍历,得到属性值,使用mCOntext. getScope得到属性范围 使用标准的JAX-WS来完成客户端调用,例: // 使用wsdl中的targetNamespace和 QName qName=new QName("http://server.soap.ilkj.net/","HelloServiceImplService"); //实现javax.xml.ws.Service的客户端视图类 HelloServiceImplService helloServiceImplService=new HelloServiceImplService( new URL("http://127.0.0.1:8080/ws/services/helloService?wsdl"),qName); //找到端口服务接口 IHelloService helloService=(IHelloService)helloServiceImplService.getPort(IHelloService.class); 使用javax.xml.ws.WebFault注解,这样异常就会在 注意:自定义异常包含异常消息msg和封装错误信息的bean,这个bean必须使用JAXB注释。 自定义异常必须有getFaultInfo()返回封装具体错误信息的bean 在消息中传送二进制信息,需要XOP传输二进制数据。否则附件会被base64编码传递,会答很多。 @XmlRootElement(name = "Customer") @XmlAccessorType(XmlAccessType.FIELD) //标注xml和java转换时只关注字段 public class Customer { private long id; private String name; private Date birthday; @XmlMimeType("application/octet-stream") private DataHandler imageData; 生成的wsdl如下: Spring如下: 这段内容加到 Java 代码: 在服务端、客户端获取javax.xml.ws.soap.SoapBinding 实例,然后调用它的 setMTOMEnabled(true)方法。 服务端: rs.setImageData(new DataHandler(new FileDataSource( new File("c:"+ File.separator + "18.jpg")))); 客户端: String attachmentMimeType = helloService.selectMaxLongNameStudent(c1, c2).getImageData().getDataSource().getContentType(); REST中重要的两个概念就是资源定位和资源操作,而HTTP 协议恰好完整的提供了这两个要点,HTTP 协议中的 URI 可以完成资源定位,GET、POST、PUT DELETE等方法可以完成资源操作,因此 REST 完全依赖 HTTP 协议就可以完成 Web 服务,而不像SOAP 协议那样只利用HTTP 的传输特性,定位与操作由 SOAP 协议自身完成。 说明: 1.这个REST 的服务接口的最终响应结果是 XML(@Produces注解标注,这个注解可以包含一组字符串,默认值是*/*,它指定 REST 服务的响应结果的 MIME 类型,例如:application/xml、application/json、image/jpeg 等),你也可以同时返回多种类型,但具体生成结果时使用哪种格式取决于ContentType。CXF默认返回的是JSON 字符串。 2.访问方法URI是/student/1/info?name=Andrew-Lee、/student/1/info2?name=Fetion,由@Path注解组合而来; 3.@QueryParam注解用于指定将 URL上的查询参数传递给使用这个注解的属性值; 4.@PathParam注解用于指定将URL上的路径参数作为使用这个注解的属性值。 5.@GET 注解指定方法对应于 Http 的 GET 请求。 实现和参数同JAXWS ((HttpURLConnection)new URL(“***”).openConnection()).getInputStream()获取 1. REST面向资源的服务,SOAP面向活动的服务。 2. REST 简单易用,效率高,SOAP 成熟度较高,安全性较好。 通过Response返回http响应代码、响应头或者是一种实体 public class StudentServiceImpl implements IStudentService { public Response getStudent(long id, String name) { Student s = new Student(); s.setId(id); s.setName(name); try { s.setBirthday(new SimpleDateFormat("yyyy-MM-dd") .parse("1983-04-26")); } catch (ParseException e) { e.printStackTrace(); } return Response.ok(s).build(); //响应实体 } public Response getStudent(String name) { return Response.status(Response.Status.BAD_REQUEST).build(); //响应代码 } } HttpResponse response = httpclient.execute(get); StatusLine st = response.getStatusLine(); if (st.getStatusCode() == HttpServletResponse.SC_OK) { InputStream ins = response.getEntity().getContent(); 使用@javax.ws.rs.core.Context 注解将 UriInfo, SecurityContext, HttpHeaders, Providers, Request, ContextResolver, HttpServletRequest, HttpServletResponse, ServletContext, ServletConfig 实例注入到服务实现类 Apache-Components-Client 要比 java.net.*下面的 API 要来得简单),其实pache.cxf.jaxrs.client.WebClient用起来更加简单。例: WebClient client = WebClient.create("http://127.0.0.1:8080/ws/services/student/1/"); Student student = client.path("info/matrix;id=2;name=m.j").accept( "application/xml").get(Student.class); System.out.println(student.getName()); org.springframework.web.context.ContextLoaderListener org.apache.cxf.transport.servlet.CXFServlet 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"> serviceClass="net.ilkj.soap.client.IHelloService"/> IHelloService helloService =BeanUtils.getBean(“helloServiceClient”); WS-Addressing: 与传输协议的隔离,寻址方式采用基于消息的路由,实现会话状态的保存 WS-Reliable Messaging: 可靠消息传递 WS-Security和WS-Policy、WS-Trust : 安全策略和信任机制 Apache的WSS4J实现了Ws-Security,WSS4J依赖于SAAJ。 CXF 中使用拦截器机制完成 WSS4J 功能的支持,你只需要初始化 WSS4JInInterceptor(对 应的还有一个 WSS4JOutInterceptor)实例并添加相关信息即可 value="net.ilkj.soap.server.security.ServerPasswordCallbackHandler" /> Constructor-arg中传入的参数在org.apache.ws.security.handler.WSHandlerConstants和 org.apache.ws.security.WSConstants中的常量列表中查找。例如:上面的第一组键值对 action 和 UsernameToken 都是 WSHandlerConstants 中的常量,表示验证机制是用户姓名令牌,也就是使用传统的用户名和密码机制。第二组的键值对分别是 WSHandlerConstants 和WSConstants中的常量,表示密码类型是文本,还可以是WSConstants.PASSWORD_DIGEST (密码会被加密为 MD5)。第三组键值对的键表示服务器端验证密码的回调处理类,这个 类必须JAVA安全认证框架中的 javax.security.auth.callback.CallbackHandler类 public class ServerPasswordCallbackHandler implements CallbackHandler { public final static String USER = "Fetion2"; public final static String PASSWORD = "Fetion"; @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { WSPasswordCallback wspassCallback = (WSPasswordCallback) callbacks[0]; System.out.println(wspassCallback.getIdentifier() + "\t" + wspassCallback.getPassword()); if (wspassCallback.getIdentifier().equals(USER) && wspassCallback.getPassword().equals(PASSWORD)) { // undo } else { throw new WSSecurityException("No Permission!"); } } } class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"> value="net.ilkj.soap.client.security.ClientPasswordCallbackHandle r" /> address="http://127.0.0.1:335/ws/services/helloService" serviceClass="net.ilkj.soap.client.IHelloService"> public class ClientPasswordCallbackHandler implements CallbackHandler { public final static String USER = "Fetion2"; public final static String PASSWORD = "Fetion"; @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { WSPasswordCallback wspassCallback = (WSPasswordCallback) callbacks[0]; wspassCallback.setIdentifier(USER); wspassCallback.setPassword(PASSWORD); } } 1. 客户端和服务端配置文件 beans.xml中只需要将passwordType的值变为PasswordDigest即可 2. 客户端代码不变 3. 服务器端代码 @Override public void handle(Callback[]callbacks)throws IOException, UnsupportedCallbackException{ WSPasswordCallback wspassCallback=(WSPasswordCallback)callbacks[0]; System.out.println(wspassCallback.getIdentifier()+"\t" +wspassCallback.getPassword()); if(WSConstants.PASSWORD_TEXT. equals(wspassCallback.getPasswordType())){ if(wspassCallback.getIdentifier().equals(USER) &&wspassCallback.getPassword().equals(PASSWORD)){ //undo }else{ throw new WSSecurityException("No Permission!"); } }else{ System.out.println(wspassCallback.getIdentifier()); //一般使用这个用户名到数据库中查询其密码,然后再设置到password属性,WSS4J会自动比较客户端传来的值和你设置的这个值。 wspassCallback.setPassword(PASSWORD); } } 分别在两端生成相应的公钥和私钥, 批储量文件如下: (1.)generateKeyPair.bat: rem@echo off echo alias%1 echo keypass%2 echo keystoreName%3 echo KeyStorePass%4 echo keyName%5 keytool-genkey-alias%1-keypass%2-keystore%3-storepass%4-dname"cn=%1"-keyalg RSA keytool-selfcert-alias%1-keystore%3-storepass%4-keypass%2 keytool-export-alias%1-file%5-keystore%3-storepass%4 (2.) generateServerKey: call generateKeyPair.bat apmserver apmserverpass serverStore.jks keystorePass serverKey.rsa pause call generateKeyPair.bat apmclient apmclientpass clientStore.jks keystorePass clientKey.rsa pause keytool-import-alias apmserver-file serverKey.rsa-keystore clientStore.jks-storepass keystorePass pausekeytool-import-alias apmclient-file clientKey.rsa-keystore serverStore.jks-storepass keystorePass 为两个jks分别建立相应的properties文件,如下: org.apache.ws.security.crypto.provider=org.apache.ws.security.components. pto.Merlin org.apache.ws.security.crypto.merlin.keystore.type=jks org.apache.ws.security.crypto.merlin.keystore.password=keystorePass #org.apache.ws.security.crypto.merlin.alias.password=apmserverpass org.apache.ws.security.crypto.merlin.keystore.alias=apmserver org.apache.ws.security.crypto.merlin.file=serverStore.jks client_sign.properties: org.apache.ws.security.crypto.provider=org.apache.ws.security.components. pto.Merlin org.apache.ws.security.crypto.merlin.keystore.type=jks org.apache.ws.security.crypto.merlin.keystore.password=keystorePass #org.apache.ws.security.crypto.merlin.alias.password=apmclientpass org.apache.ws.security.crypto.merlin.keystore.alias=apmclient org.apache.ws.security.crypto.merlin.file=clientStore.jks class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"> value="net.ilkj.soap.server.security.ServerPasswordCallbackHandle r"/> value="server_sign.properties"> class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"> value="net.ilkj.soap.client.security.ClientPasswordCallbackHandle r"/> value="client_sign.properties"> public class ClientPasswordCallbackHandler implements CallbackHandler{ @Override public void handle(Callback[]callbacks)throws IOException, UnsupportedCallbackException{ WSPasswordCallback wspassCallback=(WSPasswordCallback) callbacks[0]; wspassCallback.setPassword("apmclientpass");} } helloService.selectMaxAgeStudent(c1,c2).getName() 如果日志中有很多的 前面的CXFServlet和CXFNonSpringServlet就是一种ServletTransport,CXF使用这两个传输端口发布Web服务。 可以配置http的相关设置,如超时时间、SSL相关设置、是否启用缓存等 CXF通过拦截器(Interceptor)和特征(Feature)扩展自己的功能,例如:WS-Addressing功能实用Feature实现,日志、WS-Security使用Interceptor实现。 我们也可以编写自己的拦截器注册到CXF中完成特定的功能。CXF中的所有拦截器都要事先org.apache.cxf.inrerceptor.Interceptor 不需要等待服务端的返回 创建 async_binding.xml文件 xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" wsdlLocation="http://127.0.0.1:8080/ws/services/helloService?wsdl" xmlns="http://java.sun.com/xml/ns/jaxws"> 给wsdl2java传入-b async_binding.xml 生成客户端的异步stub接口,其中关于异步调用的方法如下: public Response selectMaxLongNameStudentAsync(Customer arg0,Customer arg1); public Future>selectMaxLongNameStudentAsync(Customer arg0, Customer arg1, AsyncHandler Polling: (返回结果为Response Callback:(返回结果是Future,称为回调方法,需额外编写AsyncHandler的回调方法) 代码实例: HelloAsyncHandler类: public class HelloAsynchHandler implements AsyncHandler<SelectMaxAgeStudentResponse> { private SelectMaxAgeStudentResponse reply; @Override public void handleResponse(Response<SelectMaxAgeStudentResponse> res) { try { System.out.println("handleResponse called"); reply = res.get(); } catch (Exception ex) { ex.printStackTrace(); } } public Customer getResponse() { return reply.getReturn(); } } 客户端调用代码: //Callback HelloAsynchHandler helloAsyncHandler=new HelloAsynchHandler(); Future>response=helloService.selectMaxAgeStudentAsync(c1,c2,helloAsyncHandler); System.out.println("Other Things..."); while(!response.isDone()){ Thread.sleep(100); } resp=helloAsyncHandler.getResponse(); System.out.println("Server responded through callback with:"+resp.getName()); System.out.println("-----------------------------"); //polling method Response<SelectMaxAgeStudentResponse>selectMaxAgeStudentResponse=helloService.selectMaxAgeStudentAsync(c1,c2); System.out.println("Other Things..."); while(!selectMaxAgeStudentResponse.isDone()){ Thread.sleep(100); } SelectMaxAgeStudentResponse reply=selectMaxAgeStudentResponse.get(); System.out.println("Server responded through polling with:"+reply.getReturn().getName()); System.exit(0); 你访问的Web服务传回来的SOAP消息中的XML可能无法正确解析成你的客户端对象,或者你要对SOAP消息中的XML做一些处理,在javax.xm.soap.*包中 客户端:创建SOAP链接——》创建SOAP消息——》增加数据——》发送消息——》对SOAP消息应答 定义了发送和接收消息的API,相当于web服务的服务器端,位于javax.messaging.*包中。 将一个servlet发布为一个web service的地址,要求servlet如下: 例:public class MyJAXMServlet extends JAXMServlet implements ReqRespListener{ 在onMessage中实现业务方法 JAXM发布的Web服务比较简单,完全省略了WSDL,这也就是说,你用这种方式发布Web服务,必须把要接收的Soap消息的内容说明发布出来(有点儿类似于REST风格的OpenAPI),这样客户端才知道如何组装你想要的SOAP消息。从这里你也可以看出来,HTTP协议与SOAP消息是基于SOAP的基本组成,WSDL是完全可以没有的,WSDL的作用是异构平台为了方便使用自己的语言特性的中间桥梁。 只能使用SoapConnection的call()方法调用。实例如下:
@WebResult(name = "method")
Customer selectMaxAgeStudent(@WebParam(name = "c1") Customer c1,
@WebParam(name = "c2") Customer c2);
客户端
public static void main(String[] args) throws ParseException {
JaxWsProxyFactoryBean soapFactoryBean = new JaxWsProxyFactoryBean();
soapFactoryBean.setAddress("http://127.0.0.1:8080/helloService");
soapFactoryBean.setServiceClass(IHelloService.class);
IHelloService helloService = (IHelloService) soapFactoryBean.create();
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());
}
SOAP消息格式
1: public static void main(String[] args) {
2: JaxWsServerFactoryBean soapFactoryBean = new JaxWsServerFactoryBean();
3: soapFactoryBean.getInInterceptors().add(new LoggingInInterceptor());
4: soapFactoryBean.getOutInterceptors().add(new LoggingOutInterceptor());
5: // 注意这里是实现类不是接口
6: soapFactoryBean.setServiceClass(HelloServiceImpl.class);
7: soapFactoryBean.setAddress("http://127.0.0.1:8080/helloService");
8: soapFactoryBean.create();
9:
10: }
11:
输入和输出参数
服务端 SEI:
@WebService
public interface IHelloService {
boolean selectMaxAgeStudent(@WebParam(name = "c1") Customer c1, @WebParam(name = "c2") Customer c2,
@WebParam(name = "c3", mode = Mode.OUT) Holder
Customer selectMaxLongNameStudent(Customer c1, Customer c2);
}
客户端调用代码:
public static void main(String[] args) throws ParseException {
JaxWsProxyFactoryBean client = new JaxWsProxyFactoryBean();
client.setAddress("http://127.0.0.1:335/ws/services/helloService");
client.setServiceClass(IHelloService.class);
IHelloService helloService = (IHelloService) client.create();
….
Holder
helloService.selectMaxAgeStudent(c1, c2, ch);
Web service context
客户端视图
异常处理
@WebFault(name="HelloServiceException")
public class HelloServiceException extends Exception{
private static final long serialVersionUID=1562884941631450124L;
private HelloServiceFault details;
public HelloServiceException(String msg){
super(msg);
}
public HelloServiceException(String msg,HelloServiceFault details)
{
super(msg);
this.details=details;
}
public HelloServiceException(HelloServiceFault details){
super();
this.details=details;
}
public HelloServiceFault getFaultInfo(){return details;
}
@XmlRootElement(name="HelloServiceFault")
public static class HelloServiceFault{
private String t;
public HelloServiceFault(){
}
public HelloServiceFault(String t){
this.t=t;
}
public String getT(){
return t;
}
public void setT(String t){
this.t=t;
}
}
MTOM
1. 创建二进制属性
2. 服务器和客户端启用XTOM:
3.传输数据
JAXRS
实例
1. 定义SEI
@Path(value = "/student/{id}")
@Produces("application/xml")
public interface IStudentService {
@GET
@Path(value = "/info")
Student getStudent(@PathParam("id") long id, @QueryParam("name") String name);
@GET
@Path(value = "/info2")
Student getStudent(@QueryParam("name") String name);
}
2. 实现和参数
3. 发布服务
public static void main(String[] args) {
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
sf.setResourceClasses(StudentServiceImpl.class);
sf.setAddress("http://localhost:335/");
sf.create();
}
4. 客户端访问
使用httpclient访问
和JAXWS区别
方法返回值
1. 服务接口
@Path(value = "/student/{id}")
@Produces("application/xml")
public interface IStudentService {
@GET
@Path(value = "/info")
Response getStudent(@PathParam("id") long id,
@QueryParam("name") String name);
@GET
@Path(value = "/info2")
Response getStudent(@QueryParam("name") String name);
}
2. 服务实现
3. 客户端访问
异常处理
参数处理
生命周期
Context注释
Webclient
CXF和spring
服务端
1. Web.xml的配置
2. Beans.xml实现
客户端
Spring发布rest风格
SOAP的WS-*规范
用户名令牌机制
1. 服务器端配置:
2. 服务器端java
3. 客户端配置
4. 客户端代码
MD5传递
证书
1. 生成客户端和服务端的证书文件
2. 得到clientStore.jks和serverStore.jks文件
3. 修改beans.xml 文件
4. 密码回调处理类
5. 调用webservice
Transport
客户端设置:
服务端设置:
拦截器特征
JAX-WS的异步调用
1. 生成客户端接口
2. 两种异步调用方式:
SAAJ
应用场景
使用方法
JAXM
发布
调用
XSLT
文件实例
My First Words