以下是废话:
写文章的原因是要给医院方面写一个考核相关的接口,让护士等人员的手持设备可以实时访问,反馈结果数据。
对于比较成熟(老)的框架,我没兴趣,于是锁定了相对流行和新的框架,RESTEasy和Apache CXF。一个是jboss的一个是Apache的,因为习惯使用spring,个人也觉得apache的东西一直都比较牛叉,所以决定用CXF。
这里我会说明服务器方面接口怎么写,也会说明客户端(调用者)方面怎么调用。
1:新建maven项目
我使用的是idea,步骤为:file-new-project-选择maven-选择maven-archetype-webapp-根据向导一步步完成。
2:导入相关包
在pom.xml文件内加入如下包,然后刷新maven项目。第三个是一个类似tomcat的服务器,方便测试。
<!-- https://mvnrepository.com/artifact/org.apache.cxf/cxf-rt-frontend-jaxws -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.cxf/cxf-core -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-core</artifactId>
<version>3.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.cxf/cxf-rt-transports-http-jetty -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>3.2.1</version>
</dependency>
3:编写接口
注意需要加webservice注解来标识,这里是要实现的三个学习使用的接口。
分为简单类型返回,较复杂类型返回,cxf不支持的类型怎么写适配器然后返回。
/**
* Created by gcc on 2018/2/6.
*/
@WebService
public interface HelloWord {
public String say(String something);
public List<Roles> getRoleByUsers(Users users);
/**
* 获取所有用户以及对应的角色,返回类型map需要适配转化。需要加注解
*/
@XmlJavaTypeAdapter(MapAdapter.class)
public Map<String,List<Roles>> getRoles();
}
4:实现接口
say方法实现:获取数据并打印,然后返回修改后的字符串
getRoleByUsers实现:通过用户实体返回对应权限集合
getRoles实现:返回复杂类型,这里实现代码简单,但因为cxf不支持这种返回类型,需要写适配类。
这里省略Roles和Users实体类,实体有id,名字和密码属性即可
/**
* Created by Administrator on 2018/2/6.
*/
@WebService
public class HelloWordImpl implements HelloWord {
public String say(String something) {
System.out.println("get:"+something);
return "you post"+something;
}
//开放一个,提供用户获取权限的接口
public List<Roles> getRoleByUsers(Users users) {
List<Roles> rolesList = new ArrayList<Roles>();
//用户测试,模拟数据
if("gcc".equals(users.getUsername()) & "123".equals(users.getPassword())){
rolesList.add(new Roles(1,"super manger"));
}else if("admin".equals(users.getUsername()) & "123".equals(users.getPassword())){
rolesList.add(new Roles(2,"system manger"));
}else{
rolesList.add(new Roles(3,"normal user"));
}
return rolesList;
}
public Map<String, List<Roles>> getRoles() {
Map<String,List<Roles>> map=new HashMap<String,List<Roles>>();
List<Roles> roleList1=new ArrayList<Roles>();
roleList1.add(new Roles(1,"super manger"));
roleList1.add(new Roles(2,"system manger"));
map.put("gcc", roleList1);
List<Roles> roleList2=new ArrayList<Roles>();
roleList2.add(new Roles(3,"normal user"));
map.put("jack", roleList2);
return map;
}
}
实现map类型的适配转化,这里通过数组进行转化。
public class MapAdapter extends XmlAdapter<MyRole[], Map<String,List<Roles>>>{
/**
* 适配器转换,MyRole[] -> Map<String, List<Role>>
*/
@Override
public Map<String, List<Roles>> unmarshal(MyRole[] v) throws Exception {
Map<String, List<Roles>> map=new HashMap<String,List<Roles>>();
for(int i=0;i<v.length;i++){
MyRole r=v[i];
map.put(r.getKey(), r.getValue());
}
return map;
}
/**
* 适配器转换 Map<String, List<Role>> -> MyRole[]
*/
@Override
public MyRole[] marshal(Map<String, List<Roles>> v) throws Exception {
MyRole[] roles=new MyRole[v.size()];
int i=0;
for(String key:v.keySet()){
roles[i]=new MyRole();
roles[i].setKey(key);
roles[i].setValue(v.get(key));
i++;
}
return roles;
}
}
下面是转化时需要使用的entity类
/**
* cxf不能接受map,需要使用适配器自己定义
*/
public class MyRole {
private String key;
private List<Roles> value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public List<Roles> getValue() {
return value;
}
public void setValue(List<Roles> value) {
this.value = value;
}
}
5:编写测试代码
这里先忽略拦截器代码,稍后解释。
第一步获得接口对象,第二步设置访问地址(本机地址),之后见代码注释,在所有设置完成后暴露接口。若在暴露接口后写设置编译没问题,但设置内容是无效的。
public class ServiceCxf {
public static void main(String[] args) {
System.out.println("webService start");
HelloWord helloWordImpl = new HelloWordImpl();
String addr = "http://192.168.0.122/helloWordGcc";
// Endpoint.publish(addr,helloWordImpl);
JaxWsServerFactoryBean serverFactoryBean = new JaxWsServerFactoryBean();//cxf初始化服务工厂
serverFactoryBean.setAddress(addr);//设置地址
serverFactoryBean.setServiceClass(HelloWord.class);//设置接口类
serverFactoryBean.setServiceBean(helloWordImpl);//设置实现类
serverFactoryBean.getInInterceptors().add(new LoggingInInterceptor()); // 添加in拦截器 日志拦截器
serverFactoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); // 添加out拦截器 日志拦截器
serverFactoryBean.getInInterceptors().add(new MyInterceptor());//自定义拦截器,用于接口调用的权限验证
serverFactoryBean.create();//暴露接口
System.out.println("webService started");
}
}
6:浏览器查看结果
http://192.168.0.122/helloWordGcc?wsdl
浏览器地址输入设置的访问地址,追加?wsdl,表示显示wsdl格式内容。wsdl就是web service description language
到此客户端接口完成,但我们不能就这样使用,因为鬼也不知道会不会有人写个死循环一直调用你的接口。所以需要写一个拦截器,在调用之前进行权限拦截。
代码如下:
public class MyInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
public MyInterceptor() {
super(Phase.PRE_INVOKE); // 在调用方法之前调用自定拦截器
}
@SuppressWarnings("null")
public void handleMessage(SoapMessage message) throws Fault {
List<Header> headers=message.getHeaders();
if(headers==null && headers.size()==0){
throw new Fault(new IllegalArgumentException("没有Header,拦截器实施拦截"));
}
Header firstHeader=headers.get(0);
Element ele=(Element) firstHeader.getObject();
NodeList uList=ele.getElementsByTagName("userName");
NodeList pList=ele.getElementsByTagName("password");
if(uList.getLength()!=1){
throw new Fault(new IllegalArgumentException("用户名格式不对"));
}
if(pList.getLength()!=1){
throw new Fault(new IllegalArgumentException("密码格式不对"));
}
String userName=uList.item(0).getTextContent();
String password=pList.item(0).getTextContent();
if(!userName.equals("gcc")||!password.equals("123")){
throw new Fault(new IllegalArgumentException("用户名或者密码错误!"));
}
}
}
到此服务器端基本任务已经完成,下面介绍客户端如何调用接口。
1:新建maven项目
另起一个项目作为客户端调用使用。
2:生成代码
这里介绍一个神奇的工具,apache-cxf-3.2.1:http://cxf.apache.org/download.html去cxf官网下载即可,这文件夹下面的bin目录下有一个wsdl2java.bat文件,将到bin为止的路径配置到环境变量,方便cmd使用。简单使用为:cd到文件生成路径,然后写wsdl2java,然后粘贴访问路径(例如:http://192.168.0.122/helloWordGcc?wsdl),具体使用方法各位百度即可。
3:编写客户端测试代码
将文件生成在main的java目录下,生成代码很容易理解,这里不再介绍。
获取实现对象就可以调用接口方法。当然也需要编写通过拦截器验证的代码。
代码如下:
public class Client {
public static void main(String[] args) throws Exception {
//基本使用
HelloWordService helloWordService = new HelloWordService();
HelloWord helloWord = helloWordService.getHelloWordPort();
// System.out.println(helloWord.say("你好"));
org.apache.cxf.endpoint.Client client= ClientProxy.getClient(helloWord);
client.getOutInterceptors().add(new AddHeaderInterceptor("gcc","123")); // 添加自定义拦截器,用于获得调用接口的权限
//返回类型相对复杂的使用
Users users = new Users();
users.setUsername("gcc");
users.setPassword("123");
List<Roles> rolesList = helloWord.getRoleByUsers(users);
for (Roles roles:rolesList){
System.out.println(roles.getId()+":"+ roles.getRolename());
}
//需要适配的返回类型使用
MyRoleArray arrys = helloWord.getRoles();
List<MyRole> myRoles = arrys.getItem();
for (int i = 0; i <myRoles.size() ; i++) {
System.out.println(myRoles.get(i).getKey());
for(Roles list:myRoles.get(i).getValue()){
System.out.println(list.getId()+":"+list.getRolename());
}
}
}
}
拦截器代码如下:
public class AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private String userName;
private String password;
public AddHeaderInterceptor(String userName,String password) {
super(Phase.PREPARE_SEND); // 准备发送SOAP消息的时候调用拦截器
this.userName=userName;
this.password=password;
}
public void handleMessage(SoapMessage message) throws Fault {
List<Header> headerList=message.getHeaders();
Document doc=DOMUtils.createDocument();
Element ele=doc.createElement("authHeader");//节点名字自定义
Element uElement=doc.createElement("userName");//对应验证的拦截器
uElement.setTextContent(userName);
Element pElement=doc.createElement("password");
pElement.setTextContent(password);
ele.appendChild(uElement);
ele.appendChild(pElement);
headerList.add(new Header(new QName("gcc"),ele));//key-valuex形式,key的name自定义
}
}
至此,非spring的服务器端和客户端的使用方法多已经阐述完毕。
敬礼!
咳咳,差点忘了还要整合spring
在spring项目中修改三处即可:
1:web.xml中添加:
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/webservice/*</url-pattern>
</servlet-mapping>
2:applicationContext.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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.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-servlet.xml"/>
<!-- 自动扫描 -->
<context:component-scan base-package="com.gcc.webservice" />
<!-- 定义服务提供者,在指定非拦截的路径下,追加HelloWorld路径,即执行名字为helloWorld的bean类 -->
<jaxws:endpoint
implementor="#helloWorld"
address="/HelloWorld">
<!-- 添加in拦截器 -->
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
<bean class="com.gcc.interceptor.MyInterceptor"/>
</jaxws:inInterceptors>
<!-- 添加out拦截器 -->
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
</jaxws:outInterceptors>
</jaxws:endpoint>
</beans>
3:给需要暴露的接口实现添加为bean,让spring扫描到。
@Component("helloWorld")
@WebService
public class HelloWordImpl implements HelloWord {
public String say(String something) {
System.out.println("get:"+something);
return "you post"+something;
}
…………
暂时说明到此,若有追加,后续补充……
感谢各位顾客观赏