使用CXF调用WSDL
简介
时隔多年,再次遇到需要调用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()' 方法即可完成调用
响应结果虽然是服务器告诉我有问题(因为我的数据是乱写的),但是可以看得出来,调用是通了