服务端
pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.1.11</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
服务端接口
@WebService(name = "helloWordService",//暴露服务名称 targetNamespace = "http://webservice.server.cxf.springboot.com")//命名空间一般是接口的包名倒序 public interface HelloWord { public String say(String str); public List<Role> getRoleByUser(@WebParam(name = "user1") User user); @XmlJavaTypeAdapter(MyAdapter.class) public Map<String, List<Role>> getAllRole();
@XmlJavaTypeAdapter常用在转换比较复杂的对象时,如map类型或者格式化日期等。使用此注解时,需要自己写一个adapter类继承XmlAdapter抽象类,并实现里面的方法。
@XmlJavaTypeAdapter(value=xxx.class),value为自己定义的adapter
因为CXF不识别Map集合
这里我们自定义了一个map和array相互转换的类
public class MyAdapter extends XmlAdapter<MyRoleArray[], Map<String, List<Role>>> { /** * 适配转换 MyRoleArray[] --> Map<String, List<Role>> */ @Override public Map<String, List<Role>> unmarshal(MyRoleArray[] v) throws Exception { Map<String, List<Role>> map = new HashMap<String, List<Role>>(); for (int i = 0; i < v.length; i++) { MyRoleArray role = v[i]; map.put(role.getKey(), role.getValue()); } return map; } /** * 适配转换 Map<String, List<Role>> --> MyRoleArray[] */ @Override public MyRoleArray[] marshal(Map<String, List<Role>> v) throws Exception { MyRoleArray[] myRoleArray = new MyRoleArray[v.size()]; int i = 0; for (String key : v.keySet()) { myRoleArray[i] = new MyRoleArray(); myRoleArray[i].setKey(key); myRoleArray[i].setValue(v.get(key)); i++; } return myRoleArray; }
这时我们也需要一个数组类MyRoleArray 与Role.class 和User.class 放在entity包下
/** * 自定义实体 CXF可以接受 * * @author zkr-lsy * */ public class MyRoleArray { private String key; private List<Role> value; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public List<Role> getValue() { return value; } public void setValue(List<Role> value) { this.value = value; } }
接口实现类
@WebService(serviceName = "helloWordService",//与接口中指定的name一致 targetNamespace = "http://webservice.server.cxf.springboot.com",//与接口中命名空间一致,一般是接口的包名倒叙 endpointInterface = "com.springboot.cxf.server.webservice.HelloWord"//接口地址 ) @Component public class HelloWordImpl implements HelloWord { public String say(String str) { System.out.println("执行Say"); return "Hello" + str; } public List<Role> getRoleByUser(User user) { List<Role> roleList = new ArrayList<Role>(); if (user != null) { if (user.getUsername().equals("lili") && user.getPassword().equals("123")) { roleList.add(new Role(1, "管理员")); } else if (user.getUsername().equals("qqq") && user.getPassword().equals("123")) { roleList.add(new Role(2, "程序员")); } return roleList; } else { return null; } } public Map<String, List<Role>> getAllRole() { Map<String, List<Role>> map = new HashMap<String, List<Role>>(); List<Role> list = new ArrayList<Role>(); list.add(new Role(1, "管理员")); list.add(new Role(2, "genene")); map.put("abc", list); List<Role> list2 = new ArrayList<Role>(); list2.add(new Role(1, "管3")); list2.add(new Role(2, "gen4")); map.put("liuyl", list2); return map; } }
下面我们需要写一个拦截器,这个拦截器的作用是进行客户端的判断
@Component public class MyInterceptor extends AbstractPhaseInterceptor<SoapMessage> { public MyInterceptor() { super(Phase.PRE_INVOKE); // 在调用方法之前调用自定义拦截器 } /** * 在调用时会有Soap消息,传入解析 权限信息都被封装到Saop消息的Header中 */ public void handleMessage(SoapMessage message) throws Fault { List<Header> headersList = message.getHeaders(); if (headersList == null || headersList.size() == 0) { throw new Fault(new IllegalArgumentException("没有Header,拦截器实施拦截")); } Header firstheader = headersList.get(0); Element element = (Element) firstheader.getObject();// 得到元素 NodeList userNameList = element.getElementsByTagName("userName"); // 获取元素节点 NodeList passwordList = element.getElementsByTagName("password"); // 获取元素节点 if (userNameList.getLength() != 1) { throw new Fault(new IllegalArgumentException("用户名格式不正确")); } if (passwordList.getLength() != 1) { throw new Fault(new IllegalArgumentException("密码格式不正确")); } String userName = userNameList.item(0).getTextContent();//获得用户第一个节点的值 String password = passwordList.item(0).getTextContent();//获得用户第一个节点的值 if (!userName.equals("lili") || !password.equals("123456")) { throw new Fault(new IllegalArgumentException("用户名或密码不正确")); } }
需要注意的是这个拦截器继承 AbstractPhaseInterceptor<SoapMessage> 不能像添加正常拦截器那样使用
public class MyWebConfig extends WebMvcConfigurerAdapter{ @Override public void addInterceptors(InterceptorRegistry registry) { InterceptorRegistration ir =registry.addInterceptor(new XXX); } }
下面我们开始填加拦截器,
添加一个Configuration
/** * 配置cxf服务发布,默认服务在Host:port/services/***路径下 * 这里相当于把Helloword接口发布在了路径/services/helloSay下,helloSay是endpoint.publish("/helloSay");添加的 * wsdl文档路径为http://localhost:8080/services/helloSay?wsdl */ @Configuration public class CxfConfig { @Autowired private Bus bus; @Autowired private HelloWord helloWord; @Autowired private MyInterceptor myInterceptor; @Bean public Endpoint endpoint() { EndpointImpl endpoint = new EndpointImpl(bus, helloWord); endpoint.publish("/helloSay"); endpoint.getInInterceptors().add(new LoggingInInterceptor()); endpoint.getInInterceptors().add(myInterceptor); endpoint.getOutInterceptors().add(new LoggingOutInterceptor()); return endpoint; } }
客户端
抽取了获取客户端的方式ClientUtil
public class ClientUtil { public static Client getClient(String wsdlUrl,String userName,String password) { JaxWsDynamicClientFactory dynamicClientFactory = JaxWsDynamicClientFactory.newInstance(); Client client = dynamicClientFactory.createClient("http://localhost:8080/services/helloSay?wsdl"); client.getOutInterceptors().add(new AddHeaderInterceptor("lili", "123456")); client.getInInterceptors().add(new LoggingInInterceptor());//添加In拦截器,日志拦截器 client.getOutInterceptors().add(new LoggingOutInterceptor());//添加拦截器,日志拦截器 return client; } }
通过添加HeaderInterceptor拦截器,实现在发送SOAP信息之前 添加响应的账号密码到headers中供服务端验证
需要在客户端出的时候拦截所以通过getOutInterceptor拦截
public class AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage> { private String userName; private String passWord; /** * 设置拦截器的阶段 * * @param phase * Phase.XXX 拦截器的阶段 */ 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> headersList = message.getHeaders(); Document doc = DOMUtils.createDocument(); Element element = doc.createElement("authHeader"); Element uElement = doc.createElement("userName"); uElement.setTextContent(userName); Element pElement = doc.createElement("password"); pElement.setTextContent(passWord); element.appendChild(uElement); element.appendChild(pElement); headersList.add(new Header(new QName("cxf"), element)); } }
客户端启动类
在其中分别实现了3个client
分别返回字符串,集合,数组(在服务端接口实现类中getAllRole返回类型为Map<String,List<Role>>,在调用服务的过程中通过我们的MyAdapter.class转换成了数组返回到客户端)
@SpringBootApplication public class SpringbootCxfClientApplication { private static Client client = ClientUtil.getClient( "http://localhost:8080/services/helloSay?wsdl", "lili", "123456"); private static Object[] objects = new Object[0]; public static void main(String[] args) { SpringApplication.run(SpringbootCxfClientApplication.class, args); client2(); client3(); client4(); } public static void client2() { String date = new Date().toString(); try { objects = client.invoke("say", date); System.out.println("返回数据:" + objects[0]); } catch (Exception e) { e.printStackTrace(); } } public static void client3() { User user1 = new User(); user1.setId(1); user1.setUsername("lili"); user1.setPassword("123"); try { objects = client.invoke("getRoleByUser", user1); System.out.println("返回数据:" + objects[0]); List<Role> roleList = (List<Role>) objects[0]; for (Role role : roleList) { System.out.println("id : " + role.getId() + " -- RoleName : " + role.getRoleName()); } } catch (Exception e) { e.printStackTrace(); } } public static void client4() { try { objects = client.invoke("getAllRole"); System.out.println("返回数据:" + objects[0]); MyRoleArrayArray allRole = (MyRoleArrayArray)objects[0]; List<MyRoleArray> myRoleArrayList = allRole.item; for (MyRoleArray myRoleArray : myRoleArrayList) { List<Role> roleList = myRoleArray.getValue(); System.out.println(myRoleArray.getKey() + " : "); for (Role role : roleList) { System.out.println(role.getId() + "---" + role.getRoleName()); } System.out.println(); System.out.println("------------------"); } } catch (Exception e) { e.printStackTrace(); } } }
这时我们需要服务端中的entity类,并且需要添加一个MyRoleArrayArray.class
如果没有MyRoleArrayArray这个类会报错
public class MyRoleArrayArray { public List<MyRoleArray> item; }
只需通过此方法将它转换成一个权限数组的集合,之后通过遍历获取
值得注意的是,和Server有关的这几个类一定要放在以客户端接口的命名空间为包名的包下面,
否则还是会报cannot be cast to 这样的错误
暂时先理解成这样,总感觉不会这么麻烦 ,不过现在是可以运行的
个人观点仅供参考