一、CXF拦截器
为了让程序员能访问、并修改CXF
框架所生成的SOAP
消息,CXF
提供了拦截器。
拦截器可以加载服务器端,也可以加载客户端,而拦截器分为In
拦截器和Out
拦截器。
1.1、对于在服务器端添加拦截器(Inter_Server
工程)
MyService.java
package org.fkjava.cxf.ws.server;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import javax.xml.ws.Endpoint;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import org.fkjava.cxf.ws.HelloWorld;
import org.fkjava.cxf.ws.impl.HelloWorldWs;
public class MyService {
public static void main(String[] args) throws IOException {
HelloWorld hw = new HelloWorldWs();
//调用此方法发布WebService
EndpointImpl ep = (EndpointImpl)Endpoint.publish("http://localhost:8080/myService", hw);
ep.getInInterceptors().add(new LoggingInInterceptor(new PrintWriter(new FileWriter("in.txt"))));//添加In拦截器
ep.getOutInterceptors().add(new LoggingOutInterceptor(new PrintWriter(new FileWriter("out.txt"))));//添加Out拦截器
System.out.println("Hello World!");
}
}
说明:
- (1)首先获取
Endpoint
的publish
方法的返回值; - (2)调用该方法的返回值的
getInInterceptor
、getOutInterceptor
方法来获取In、Out
拦截器列表,接下来就可以添加拦截器了。 - (3)这里我们是使用的
CXF
给我们定义好的拦截器,但是这个拦截器在API
文档中是查不到的,我们将相关输入和输出信息输出到文件中。添加的拦截器中(如LoggingInInterceptor
)如果不给参数,则相关数据打印在控制台,这里我们让其输出到相关的文件中去。
注意:这里如果不想太麻烦,直接加入所有的依赖包。同时客户端不需要改变,因为我们需要的操作没有改变。
1.2 测试
这里我们使用工程WS_Client03
对上面才改造服务端进行测试。在输出的文件中我们可以看到一些信息。在测试方法中我们总共使用的3个操作,于是应该有6个SOAP
消息,从输出的文件中我们确实可以看到6个SOAP
消息,在in.txt
文件中前面两条先不管。我们从中截取出一段出来进行分析:
in.txt
张三
out.txt
张三您好!现在的时间是: Mon Jun 27 21:18:11 CST 2016
说明:从这里我们可以看出SOAP
消息的结构
-
SOAP
信息的根元素是Envelope
,此元素包含两个子元素:Header
(默认情况下没有,即不是强制出现的,其是由程序员控制添加,主要用于携带一些额外的信息,如用户名和密码)和Body
。 - 对于
Body
子元素:(1)如果调用正确,其内容应该遵守wsdl
所要求的格式;(2)如果调用错误(比如我们使用http://localhost:8080/myService
调用,不指明服务名),其内容就是Fault
子元素。
1.3 在客户端添加拦截器(工程Inter_Client
)
客户端的做法和服务端的做法基本一样。
MyClient.java
package org.fkjava.cxf.ws.client;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.endpoint.Client;
import org.fkjava.cxf.ws.Cat;
import org.fkjava.cxf.ws.Entry;
import org.fkjava.cxf.ws.HelloWorld;
import org.fkjava.cxf.ws.StringCat;
import org.fkjava.cxf.ws.User;
import org.fkjava.cxf.ws.impl.HelloWorldWs;
public class MyClient {
public static void main(String[] args) throws IOException {
HelloWorldWs factory = new HelloWorldWs();
HelloWorld hw = factory.getHelloWorldWsPort();
Client client = ClientProxy.getClient(hw) ;//调用此方法,以远程WebService的代理为参数
client.getInInterceptors().add(new LoggingInInterceptor(new PrintWriter(new FileWriter("in.txt"))));
client.getOutInterceptors().add(new LoggingOutInterceptor(new PrintWriter(new FileWriter("out.txt"))));
System.out.println(hw.sayHi("张三"));
User user = new User();
user.setId(30);//只要名字和密码相同则认为是同一个用户,所以这里给30没关系
user.setName("大熊");
user.setPassword("111");
List cats = hw.getCatsByUser(user);
for(Cat cat : cats){
System.out.println(cat.getName());
}
StringCat sc = hw.getAllCats();
for(Entry entry : sc.getEntries()){
System.out.println(entry.getKey() + entry.getValue().getName());
}
}
}
说明:首先我们使用ClientProxy
类得到Client
,之后就可以通过此类得到相关的拦截器列表了。同样测试之后我们也可以在相关的输出文件中看到输出的SOAP
消息。
二、自定义拦截器(工程Auth_Server
)
2.1服务端
这里我们是要自定义一个拦截器来进行用户名和密码的检查,于是只需要在服务器端加一个In
拦截器即可。
MyServer.java
package org.fkjava.cxf.ws.server;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import javax.xml.ws.Endpoint;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import org.fkjava.cxf.ws.HelloWorld;
import org.fkjava.cxf.ws.auth.AuthInterceptor;
import org.fkjava.cxf.ws.impl.HelloWorldWs;
public class MyServer {
public static void main(String[] args) throws IOException {
HelloWorld hw = new HelloWorldWs();
//添加一个自定义的In拦截器,负责检查用户和密码
EndpointImpl ep = (EndpointImpl)Endpoint.publish("http://localhost:8080/myService", hw);
ep.getInInterceptors().add(new AuthInterceptor());//添加In拦截器
System.out.println("Hello World!");
}
}
说明:这里的AuthInterceptor
就是我们自定义的拦截器。我们需要实现Interceptor
接口,而实际上,我们一般会继承AbstractPhaseInterceptor
。
AuthInterceptor.java
package org.fkjava.cxf.ws.auth;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
//通过PhaseInterceptor指定拦截器在哪个阶段起作用
public class AuthInterceptor extends AbstractPhaseInterceptor {
public AuthInterceptor() {
//下面的常量表示在调用之前让拦截器起作用
super(Phase.PRE_INVOKE);//显式调用父类有参构造器,因为AbstractPhaseInterceptor没有无参构造器
}
//实现自己的拦截器的时候需要实现此方法,其中的形参就是被拦截到的SOAP消息
@Override
public void handleMessage(SoapMessage msg) throws Fault {
System.out.println("********" + msg);//从这里可以看到已经拦截到了SOAP消息
}
}
说明:这里我们需要实现handleMessage
方法,同时由于AbstractPhaseInterceptor
抽象类没有无参构造函数,所以我们必须显式调用有参构造函数。其中参数表示让拦截器起作用的阶段,这里是调用之前拦截。拦截到的就是SOAP
消息,之后我们需要修改和解析消息,这里我们先验证是否拦截到了SOAP
消息,我们可以使用客户端进行访问来测试。会打印出相应的的消息。
解析SOAP消息:
AuthInterceptor.java
package org.fkjava.cxf.ws.auth;
import java.util.List;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
//通过PhaseInterceptor指定拦截器在哪个阶段起作用
public class AuthInterceptor extends AbstractPhaseInterceptor {
public AuthInterceptor() {
//下面的常量表示在调用之前让拦截器起作用
super(Phase.PRE_INVOKE);//显式调用父类有参构造器,因为AbstractPhaseInterceptor没有无参构造器
}
//实现自己的拦截器的时候需要实现此方法,其中的形参就是被拦截到的SOAP消息
@Override
public void handleMessage(SoapMessage msg) throws Fault {
System.out.println("********" + msg);//从这里可以看到已经拦截到了SOAP消息
List headers = msg.getHeaders();//得到SOAP的所有HEADER
//如果没有HEADER
if(headers == null || headers.size() < 1){
throw new Fault(new IllegalArgumentException("没有头信息"));
}
//加入要求第一个HEADER中携带了用户名和密码信息
Header firstHeader = headers.get(0);//得到第一个HEADER
Element ele = (Element) firstHeader.getObject();//得到HEADER的内容
NodeList usernames = (NodeList) ele.getElementsByTagName("username");//我们要求有一个username的标签
NodeList passwords = (NodeList) ele.getElementsByTagName("password");
if(usernames.getLength() != 1){
throw new Fault(new IllegalArgumentException("用户名的格式不对"));
}
if(passwords.getLength() != 1){
throw new Fault(new IllegalArgumentException("密码的格式不对"));
}
String username = usernames.item(0).getTextContent();//得到第一个userId元素里的文本内容,以该内容作为用户名
String password = passwords.item(0).getTextContent();
//实际项目中应该去查询数据库,检查该用户名密码是否能够被授权
if(!(username.equals("大熊") && password.equals("111"))){
throw new Fault(new IllegalArgumentException("用户名或密码不正确"));
}
//放行。。。这里不需要我们管
}
}
说明:
- 1.首先我们得到一个
Header
列表。然后进行判断,如果确实有Header
元素则我们取出列表中的第一个Header
,因为会有很多Header
,而这里其实只有一个,但是我们认为我们的用户名和密码信息放在第一个Header
中。 - 2.下面我们需要从头中解析出用户名和密码,当然用户名和密码信息所在的元素肯定是和客户端设置的一致,解析出来判断之后我们只需放行即可。
2.2 客户端(工程Auth_Client
)
MyClient.java
package org.fkjava.cxf.ws.client;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.endpoint.Client;
import org.fkjava.cxf.ws.Cat;
import org.fkjava.cxf.ws.Entry;
import org.fkjava.cxf.ws.HelloWorld;
import org.fkjava.cxf.ws.StringCat;
import org.fkjava.cxf.ws.User;
import org.fkjava.cxf.ws.auth.AddHeaderInterceptor;
import org.fkjava.cxf.ws.impl.HelloWorldWs;
public class MyClient {
public static void main(String[] args) throws IOException {
HelloWorldWs factory = new HelloWorldWs();
HelloWorld hw = factory.getHelloWorldWsPort();
Client client = ClientProxy.getClient(hw) ;//调用此方法,以远程WebService的代理为参数
client.getOutInterceptors().add(new AddHeaderInterceptor("大熊", "111"));
client.getOutInterceptors().add(new LoggingOutInterceptor());
System.out.println(hw.sayHi("张三"));
User user = new User();
user.setId(30);//只要名字和密码相同则认为是同一个用户,所以这里给30没关系
user.setName("大熊");
user.setPassword("111");
List cats = hw.getCatsByUser(user);
for(Cat cat : cats){
System.out.println(cat.getName());
}
StringCat sc = hw.getAllCats();
for(Entry entry : sc.getEntries()){
System.out.println(entry.getKey() + entry.getValue().getName());
}
}
}
说明:客户端我们是加一个Out
拦截器,即在输出SOAP
消息的时候使用拦截器加上用户名和密码等信息头,这里我们还使用了一个CXF
提供的拦截器主要是为了便于通过控制台查看一些信息。
自定义拦截器:
AddHeaderInterceptor.java
package org.fkjava.cxf.ws.auth;
import java.util.List;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class AddHeaderInterceptor extends AbstractPhaseInterceptor {
private String username ;
private String password;
public AddHeaderInterceptor(String username, String password) {
super(Phase.PREPARE_SEND);//这里表示准备发送SOAP消息的时候调用此拦截器
this.username = username;
this.password = password;
}
@Override
public void handleMessage(SoapMessage msg) throws Fault {
List headers = msg.getHeaders();
Document document = DOMUtils.createDocument();//创建一个Document对象
Element ele = document.createElement("authHeader");//创建一个元素,这个名字随便
Element usernameEle = document.createElement("username");//创建一个元素,注意和服务端元素名字一致
usernameEle.setTextContent(username);//将相关的值设置进去
Element passwordEle = document.createElement("password");//创建一个元素
passwordEle.setTextContent(password);
ele.appendChild(usernameEle);
ele.appendChild(passwordEle);
//生成了一个如下的代码片段
/*
* username
* password
*
* */
//把ele元素包装成Header类,然后添加到SOAP消息的Header列表中
headers.add(new Header(new QName("fkjava"), ele));//这里的QName参数值随便设置
}
}
说明:当然拦截器中我们肯定要将用户名和密码传递进去。和服务端的实现方式是一样的,只是客户端是需要添加xml
代码片段。测试之后我们发现在控制台可以看到这样的信息:
大熊
111
张三
说明:这样便成功了。而
中的内容不受我们控制,是由WSDL
决定的。