简介
时隔多年,再次遇到需要调用WebService的业务,对方给予的wsdl说明文档还是内网的链接,并且设有基础访问权限,即在浏览器打开wsdl链接时需要输入【用户名+密码】登录后方可查看wsdl文档,这需要设置代理(我使用putty完成了代理),本文只记录使用org.apache.cxf调用wsdl的过程
附一张putty的下载链接:
https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html
步骤一 :添加org.apache.cxf的maven引用
<!-- cxf JaxWsDynamicClientFactory 开始 --><dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-rt-frontend-jaxws</artifactId><version>3.5.2</version></dependency><dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-rt-transports-http</artifactId><version>3.5.2</version></dependency><!-- cxf JaxWsDynamicClientFactory 结束 -->
步骤二:访问WSDL
/*** 打开WSDL文件* @return*/private static Bus openWSDL(){Bus bus = BusFactory.getThreadDefaultBus();bus.setExtension((name, address, httpConduit) -> {//设置访问wsdl所需的用户名和密码final AuthorizationPolicy authorization = new AuthorizationPolicy();authorization.setUserName("username");authorization.setPassword("password");httpConduit.setAuthorization(authorization);final HttpAuthSupplier supplier = new DefaultBasicAuthSupplier();httpConduit.setAuthSupplier(supplier);//设置service中location的映射代理IP和端口 final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();httpClientPolicy.setProxyServer("127.0.0.1");httpClientPolicy.setProxyServerPort(50000);httpClientPolicy.setAllowChunking(false);httpClientPolicy.setConnectionTimeout(50000);httpClientPolicy.setReceiveTimeout(50000);httpConduit.setClient(httpClientPolicy);}, HTTPConduitConfigurer.class);return bus;}
步骤三:获取WSDL文档内容
/*** 获取WSDL内容* @return*/private static Map<String,Object> getWSDLContent() {Map<String,Object> wsdl = new HashMap<>();try {Bus bus = openWSDL();ClassLoader loader = Gmm1020Server.class.getClassLoader();// 创建动态客户端JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance(bus);Client client = dcf.createClient("你的wsdl地址",new QName("wsdl中的targetNamespace", "wsdl:service的name"),loader,new QName("wsdl中的targetNamespace", "HTTP_Port"));QName qName = new QName("wsdl中的targetNamespace", "wsdl:operation的name");List<MessagePartInfo> partInfos = client.getEndpoint().getService().getServiceInfos().get(0).getBinding(new QName("wsdl中的targetNamespace", "wsdl:binding的name")).getOperation(qName).getInput().getMessageParts();wsdl.put("client",client);wsdl.put("qname",qName);wsdl.put("messagePartInfo",partInfos);} catch (Exception e) {throw new WebServiceException(e);}return wsdl;}
注意:Bus引用的是上一个方法
Bus bus = openWSDL();
步骤四:调用远程接口(远程过程调用)
/*** 调用远程过程*/public static void call(Map map) {Map<String,Object> wsdl = getWSDLContent();Client client = (Client) wsdl.get("client");List<MessagePartInfo> partInfos = (List<MessagePartInfo>) wsdl.get("messagePartInfo");QName qName = (QName) wsdl.get("qname");String clazzName = partInfos.get(0).getTypeClass().getName();try {Object requestParamObject = Thread.currentThread().getContextClassLoader().loadClass(clazzName).newInstance();Field[] fields = requestParamObject.getClass().getDeclaredFields();for (Field field : fields) {//如果是泛型boolean b = field.getGenericType() instanceof ParameterizedType;if(b && field.getType() == List.class){Type[] types = ((ParameterizedType)field.getGenericType()).getActualTypeArguments();for (Type type : types) {Class aClass = (Class) type;Object obj = aClass.newInstance();List<Object> curElementList = new ArrayList<>();List<Map> params = (List<Map>) map.get(field.getName().toLowerCase());if(!CollectionUtils.isEmpty(params)){for (Map param : params) {writeFiledVal(obj,param);curElementList.add(obj);}}//writeCustomValue(obj);field.setAccessible(true);field.set(requestParamObject,curElementList);}}else if(field.getType().getName().equals("java.lang.String")){//字符串则直接赋值Field f = requestParamObject.getClass().getDeclaredField(field.getName());f.setAccessible(true);f.set(requestParamObject,map.get(f.getName()));}else {//对象类型Class clazz = field.getType();Object obj = clazz.newInstance();Map param = (Map) map.get(field.getName().toLowerCase());writeFiledVal(obj,param);//writeCustomValue(obj);Field f = requestParamObject.getClass().getDeclaredField(field.getName());f.setAccessible(true);f.set(requestParamObject,obj);}}log.info("请求参数:{}",JSON.toJSON(requestParamObject));Object result = client.invoke(qName, requestParamObject);log.info("响应结果:{}",JSON.toJSONString(result,true));} catch (Exception e) {throw new RuntimeException(e);}}
注意:该代码的入参是一个Map对象,该对象中包含了三种类型的属性和一层属性对象的嵌套(java.util.List、java.lang.String、POJO对象)因此该代码只针对三种类型做了解析处理,并不具备所有类型的通用性。
比如:你的入参对象中有java.lang.Integer类型,或者Set ...,那就需要编写相应的解析方式,这是类型解析。
属性对象的嵌套的意思是:如果你的入参对象Map中有个List<POJO>或者直接就是POJO,这就是一层属性嵌套,而如果这个List中的POJO对象又嵌套了其他的POJO对象,这就属于二层解析,即:
List<User> userList = new ArrayLIst();
class User {
private String userName;
private UserDetails userDetails;
}
而或许UserDetails对象中还存着Enterprise对象... 以此嵌套,想要全面解析你就需要使用递归算法。(我想我应该把问题说清楚了)
代码层面:ParameterizedType
Type[] types = ((ParameterizedType)field.getGenericType()).getActualTypeArguments();
这个对象是一个接口,表示参数化类型,这句代码意思是返回这个field的实际类型参数
步骤五:字段赋值
/*** 字段写值* @param obj*/private static void writeFiledVal(Object obj,Map param) {Field[] fields = obj.getClass().getDeclaredFields();for (Field field : fields) {field.setAccessible(true);try {field.set(obj,param.get(field.getName()));} catch (IllegalAccessException e) {throw new RuntimeException(e);}}}/*** 测试使用* 给字段写入自定义值* @param obj*/private void writeCustomValue(Object obj) {Field[] fields = obj.getClass().getDeclaredFields();for (Field field : fields) {field.setAccessible(true);if(!field.getType().isPrimitive()){field.getType().getConstructors();}try {field.set(obj,"1");} catch (IllegalAccessException e) {throw new RuntimeException(e);}}}
说明:writeCustomValue是测试方式,而writeFiledVal多了一个入参Map,主要用来赋值的。
使用的时候,只需要讲你的入参对象转换成map,放入 'call()' 方法即可完成调用
响应结果虽然是服务器告诉我有问题(因为我的数据是乱写的),但是可以看得出来,调用是通了