View Javadoc
1   /**
2    * Copyright By Grandsoft Company Limited.  
3    * 2014年3月10日 下午6:31:36
4    */
5   package gboat2.cxf.utils;
6   
7   import gboat2.base.bridge.exception.DefaultGboatNestedException;
8   import gboat2.base.bridge.util.json.JsonUtil;
9   import gboat2.base.bridge.util.xml.JAXBUtil;
10  
11  import java.util.ArrayList;
12  import java.util.LinkedHashMap;
13  import java.util.List;
14  import java.util.Map;
15  
16  import javax.xml.bind.annotation.XmlRootElement;
17  import javax.xml.namespace.QName;
18  
19  import org.apache.commons.collections.MapUtils;
20  import org.apache.commons.lang3.ArrayUtils;
21  import org.apache.commons.lang3.ClassUtils;
22  import org.apache.cxf.common.jaxb.JAXBUtils;
23  import org.apache.cxf.common.jaxb.JAXBUtils.IdentifierType;
24  import org.apache.cxf.endpoint.Client;
25  import org.apache.cxf.endpoint.Endpoint;
26  import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
27  import org.apache.cxf.service.model.BindingInfo;
28  import org.apache.cxf.service.model.BindingMessageInfo;
29  import org.apache.cxf.service.model.BindingOperationInfo;
30  import org.apache.cxf.service.model.MessagePartInfo;
31  import org.apache.cxf.transport.http.HTTPConduit;
32  import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  import org.springframework.beans.BeanUtils;
36  import org.springframework.beans.BeansException;
37  import org.springframework.util.Assert;
38  
39  /**
40   * <p>
41   * 使用 CXF 发布 WebService 服务和调用 WebService 服务的工具类。<br>
42   * <small><b>注:</b>目前只提供了调用 WebService 服务相关的方法,而没提供发布服务相关的方法</small><br>
43   * </p>
44   * 该工具类解决了在 OSGI 环境中直接使用 CXF wsdl2java 生成的代码调用 WebService 时的以下问题:
45   * <ol>
46   * <li>传递复杂对象时,会抛出“<i>javax.xml.ws.WebServiceException: Could not find
47   * wsdl:binding operation info for web method ...</i>”的异常</li>
48   * <li>当 WebService 服务端的接口和实现类在不同的包下,而且实现类没指定 {@code @WebService} 注解的
49   * {@code targetNamespace}
50   * 属性时,会抛出“<i>org.apache.cxf.common.i18n.UncheckedException: No operation was
51   * found with the name ...</i>”的异常。</li>
52   * </ol>
53   * 传递复杂参数(JavaBean)调用 WebService 服务的步骤:
54   * <ol>
55   * <li>使用 CXF 的 wsdl2java 工具生成 Java 代码;</li>
56   * <li>使用刚才生成的 JavaBean 创建对象实例,并赋值;</li>
57   * <li>调用本工具类的 {@link #invokeWebService(String, String, Object...)} 方法,将刚才创建的
58   * JavaBean 实例作为参数传入;</li>
59   * <li>如果返回结果中包含复杂对象(JavaBean),则可以通过 Spring 的 {@link BeanUtils} 或
60   * commons-beanutils 的 {@link org.apache.commons.beanutils.BeanUtils BeanUtils}
61   * 工具类将返回结果转换成刚才通过 wsdl2java 生成的 JavaBean,或是通过 json 工具进行转换(先将结果中的 JavaBean 转换成
62   * json 字符串,再将 json 字符串转换成自己的 JavaBean),这样就可以更方便的读取返回结果中的具体信息了。</li>
63   * </ol>
64   * 
65   * @author <a href="mailto:[email protected]">何明旺</a>
66   * @since 3.0
67   * @date 2014年3月10日
68   */
69  public class CXFUtil {
70  
71      private final static Logger logger = LoggerFactory.getLogger(CXFUtil.class);
72      private final static int CONFIGURE_CLIENT_SIZE = 999999999;
73  
74      /**
75       * 将构造方法设为私有,防止在外部被实例化
76       */
77      private CXFUtil() {
78      }
79      
80      /**
81       * 动态创建 WebService 的 JAX-WS 方式调用客户端实例<br>
82       * 和直接执行 {@code createJaxWsDynamicClient(wsdlUrl, JaxWsDynamicClientFactory.class.getClassLoader())} 返回的结果一样
83       * @param wsdlUrl WSDL 地址
84       * @return
85       */
86      public static Client createJaxWsDynamicClient(String wsdlUrl){
87          return createJaxWsDynamicClient(wsdlUrl, JaxWsDynamicClientFactory.class.getClassLoader());
88      }
89      
90      /**
91       * 动态创建 WebService 的 JAX-WS 方式调用客户端实例
92       * @param wsdlUrl WSDL 地址
93       * @param classLoader 类加载器,调用 CXF 的
94       *            {@link org.apache.cxf.endpoint.dynamic.DynamicClientFactory#createClient(String, ClassLoader)
95       *            DynamicClientFactory#createClient(String, ClassLoader)} 方法动态创建客户端时,将使用此类加载器
96       * @return
97       */
98      public static Client createJaxWsDynamicClient(String wsdlUrl, ClassLoader classLoader) {
99          // 创建 HttpClient 策略
100         HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
101         httpClientPolicy.setAllowChunking(false);
102         httpClientPolicy.setChunkingThreshold(CONFIGURE_CLIENT_SIZE);
103 
104         // 动态创建 WebService 客户端
105         Client client = JaxWsDynamicClientFactory.newInstance().createClient(wsdlUrl, classLoader);
106         ((HTTPConduit) client.getConduit()).setClient(httpClientPolicy);
107         return client;
108     }
109     
110     /**
111      * 获取 CXF 的绑定操作信息的实例
112      * @param client CXF 客户端,可通过 {@link #createJaxWsDynamicClient(String)} 进行创建
113      * @param operation 操作名称(一般和方法名相同),如: sayHello
114      * @return 绑定操作信息的实例。如果没有找到对应的绑定操作,则返回 <code>null</code>
115      */
116     public static BindingOperationInfo getBindingOperationInfo(Client client, String operation){
117         Endpoint endpoint = client.getEndpoint();  
118         BindingInfo bindingInfo = endpoint.getEndpointInfo().getBinding();  
119         QName opName = new QName(endpoint.getService().getName().getNamespaceURI(), operation);  
120         BindingOperationInfo boi = bindingInfo.getOperation(opName);
121         if (boi == null) { // WebService 接口和实现类的 namespace 不相同的情况
122             for (BindingOperationInfo operationInfo : bindingInfo.getOperations()) {
123                 if (operation.equals(operationInfo.getName().getLocalPart())) {
124                     return operationInfo;
125                 }
126             }
127         }
128         return boi;
129     }
130 
131     /**
132      * 获取操作对象的输入参数
133      * @param boi 操作对象
134      * @return 操作的输入参数 Map,key 为参数的名称,value 为参数的类型
135      */
136     public static Map<String, Class<?>> getInputParameters(BindingOperationInfo operationInfo) {
137         return getParameters(operationInfo, true);
138     }
139     
140     /**
141      * 获取操作对象的输出参数
142      * @param boi 操作对象
143      * @return 操作的输出参数 Map,key 为参数的名称,value 为参数的类型
144      */
145     public static Map<String, Class<?>> getOutputParameters(BindingOperationInfo operationInfo) {
146         return getParameters(operationInfo, false);
147     }
148     
149     /**
150      * 获取操作对象的输入或输出参数
151      * @param boi 操作对象
152      * @param input true 表示获取输入参数,false 表示获取输出参数
153      * @return 操作的指定类型参数 Map,key 为参数的名称,value 为参数的类型
154      */
155     private static Map<String, Class<?>> getParameters(BindingOperationInfo boi, boolean input) {
156         BindingOperationInfo unWrappedBoi = boi.isUnwrapped() ? boi : boi.getUnwrappedOperation();
157         BindingMessageInfo bmi = (input ? unWrappedBoi.getInput() : unWrappedBoi.getOutput());
158         List<MessagePartInfo> messageParts = bmi.getMessageParts();
159         Map<String, Class<?>> result = new LinkedHashMap<String, Class<?>>(messageParts.size());
160         for (MessagePartInfo mpi : messageParts) {
161             result.put(mpi.getName().getLocalPart(), mpi.getTypeClass());
162         }
163         return result;
164     }
165     
166     /**
167      * 调用 webservice 服务<br>
168      * 和直接执行
169      * {@code invokeWebService(wsdlUrl, operation, JaxWsDynamicClientFactory.class.getClassLoader(), parameters)}
170      * 返回的结果一样
171      * @param wsdlUrl WSDL 地址
172      * @param operation 要调用的操作(WebService 服务名称)
173      * @param parameters 参数列表,如果没有参数,则可以不传入该参数值
174      * @return WebService 服务端返回的结果
175      * @see #invokeWebService(String, String, ClassLoader, Object...)
176      */
177     public static Object[] invokeWebService(String wsdlUrl, String operation, Object... parameters) {
178         return invokeWebService(wsdlUrl, operation, JaxWsDynamicClientFactory.class.getClassLoader(), parameters);
179     }
180 
181     /**
182      * 调用 webservice 服务
183      * 
184      * @param wsdlUrl WSDL 地址
185      * @param operation 要调用的操作(WebService 服务名称)
186      * @param classLoader 类加载器,调用 CXF 的
187      *            {@link org.apache.cxf.endpoint.dynamic.DynamicClientFactory#createClient(String, ClassLoader)
188      *            DynamicClientFactory#createClient(String, ClassLoader)} 方法动态创建客户端时,将使用此类加载器
189      * @param parameters 参数列表,如果没有参数,则可以不传入该参数值
190      * @return WebService 服务端返回的结果
191      */
192     public static Object[] invokeWebService(String wsdlUrl, String operation,
193             ClassLoader classLoader, Object... parameters) {
194         // 动态创建 WebService 客户端
195         Client client = createJaxWsDynamicClient(wsdlUrl, classLoader);
196         // 调用 WebService 服务
197         return invokeWebService(client, operation, parameters);
198     }
199     
200     /**
201      * 调用 webservice 服务
202      * @param client WebService 客户端实例对象
203      * @param operation 要调用的操作(WebService 服务名称)
204      * @param parameters 参数列表,如果没有参数,则可以不传入该参数值
205      * @return 服务端返回的结果
206      */
207     public static Object[] invokeWebService(Client client, String operation, Object... parameters) {
208         try {
209             BindingOperationInfo boi = getBindingOperationInfo(client, operation);
210             if(boi == null)
211                 throw new DefaultGboatNestedException(
212                         "No operation was found with the name [" + operation + "] of namespace "
213                                 + client.getEndpoint().getService().getName().getNamespaceURI());
214             
215             if(!boi.isUnwrapped()) {
216                 boi = boi.getUnwrappedOperation();
217             }
218             // WebService 服务需要的输入参数的信息
219             Map<String,Class<?>> inputParamInfoMap = getInputParameters(boi);
220             if(ArrayUtils.isNotEmpty(parameters) && MapUtils.isNotEmpty(inputParamInfoMap)) {
221                 List<Class<?>> needTypes = new ArrayList<Class<?>>(inputParamInfoMap.values());
222                 Class<?> needType = null; // 需要的类型
223                 Class<?> actualType = null; // 实际传入的类型
224                 for (int i = 0, size = needTypes.size(); i < size; i++) {
225                     // 如果实际传入的参数个数小于需要的参数个数,且实际传入的参数已经全部被处理完成,则中止循环
226                     if(i >= parameters.length)
227                         break;
228                     
229                     // 如果当前参数值为 null,则什么也不做,直接跳过
230                     if(parameters[i] == null)
231                         continue;
232                     
233                     needType = needTypes.get(i);
234                     actualType = parameters[i].getClass();
235                     // 如果当前参数值为原生类型或者可以强转为需要的参数类型,则什么也不做,直接跳过
236                     if(ClassUtils.isPrimitiveOrWrapper(actualType) || needType.isAssignableFrom(actualType))
237                         continue;
238                     
239                     // 使用需要的类型创建一个新的对象实例,并将当前参数的属性值复制给新创建的实例
240                     Object obj = null;
241                     try {
242                         obj = needType.newInstance();
243                         BeanUtils.copyProperties(parameters[i], obj);
244                     } catch (BeansException e) {
245                         logger.warn(
246                                 "通过 CXF 调用 WebService [{}] 时,将第 [{}] 个参数 [{}] 的属性值复制给目标类型的对象 [{}] 失败,将改用 JSON 转换的方式将其转换为目标类型。",
247                                 boi, i, actualType, needType);
248                         // 如果直接复制失败,则先将源对象转换成 Json 字符串,然后再将 Json 字符串转换成目标对象
249                         String json = JsonUtil.object2Json(parameters[i]);
250                         obj = JsonUtil.json2Object(json, needType);
251                     }
252                     parameters[i] = obj;
253                 }
254             }
255             
256             // 调用 WebService 服务
257             return client.invoke(boi, parameters);
258         } catch (Exception e) {
259             throw new DefaultGboatNestedException("使用 CXF 动态调用 WebService 服务 [" + operation + "] 失败", e);
260         }
261     }
262 
263     /**
264      * 调用 WebService 服务
265      * 
266      * @param wsdlUrl WSDL 地址
267      * @param parameter 经过包装后的请求参数,与 WSDL 文件中 &lt;wsdl:operation name="xxx"&gt;
268      *            相对应的 JavaBean,该类必须有 &#64;XmlRootElement 注解,&#64;XmlRootElement
269      *            注解的 name 属性对应 WSDL 文件中 operation 的 name
270      * @return WebService 服务端的返回结果
271      */
272     public static Object[] invokeWrappedWebService(String wsdlUrl, Object parameter){
273         return invokeWrappedWebService(wsdlUrl, parameter, JaxWsDynamicClientFactory.class.getClassLoader());
274     }
275     
276     /**调用 WebService 服务
277      * 
278      * @param wsdlUrl WSDL 地址
279      * @param parameter 经过包装后的请求参数,与 WSDL 文件中 &lt;wsdl:operation name="xxx"&gt;
280      *            相对应的 JavaBean,该类必须有 &#64;XmlRootElement 注解,&#64;XmlRootElement
281      *            注解的 name 属性对应 WSDL 文件中 operation 的 name
282      * @param classLoader 类加载器,调用 CXF 的
283      *            {@link org.apache.cxf.endpoint.dynamic.DynamicClientFactory#createClient(String, ClassLoader)
284      *            DynamicClientFactory#createClient(String, ClassLoader)} 方法动态创建客户端时,将使用此类加载器
285      * @return WebService 服务端的返回结果
286      */
287     public static Object[] invokeWrappedWebService(String wsdlUrl, Object parameter, ClassLoader classLoader){
288         // 动态创建 WebService 客户端
289         Client client = createJaxWsDynamicClient(wsdlUrl, classLoader);
290         // 调用 WebService 服务
291         return invokeWrappedWebService(client, parameter);
292     }
293     
294     /**
295      * 调用 WebService 服务
296      * 
297      * @param client WebService 客户端实例对象
298      * @param parameter 经过包装后的请求参数,与 WSDL 文件中 &lt;wsdl:operation name="xxx"&gt;
299      *            相对应的 JavaBean,该类必须有 &#64;XmlRootElement 注解,&#64;XmlRootElement
300      *            注解的 name 属性对应 WSDL 文件中 operation 的 name
301      * @return WebService 服务端的返回结果
302      */
303     public static Object[] invokeWrappedWebService(Client client, Object parameter) {
304         Assert.notNull(parameter, "使用经过 CXF 包装后客户端调用 WebService 失败,原因:传入的参数不能为 null");
305         
306         Class<?> paramType = parameter.getClass();
307         XmlRootElement xmlRootElement = paramType.getAnnotation(XmlRootElement.class);
308         Assert.notNull(xmlRootElement, "使用经过 CXF 包装后客户端调用 WebService 失败,原因:传入的参数类型 [" + paramType + "] 必须有 javax.xml.bind.annotation.XmlRootElement 注解");
309         
310         String operation = xmlRootElement.name();
311         if("##default".equals(operation)) {
312             operation = paramType.getSimpleName();
313         }
314         
315         try {
316             BindingOperationInfo boi = getBindingOperationInfo(client, operation);
317             if(boi == null)
318                 throw new DefaultGboatNestedException(
319                         "No operation was found with the name [" + operation + "] of namespace " + client.getEndpoint().getService().getName().getNamespaceURI());
320 
321             QName qname = boi.getName();
322             String packageName = JAXBUtils.namespaceURIToPackage(qname.getNamespaceURI());
323             String simpleClassName = JAXBUtils.nameToIdentifier(qname.getLocalPart(), IdentifierType.CLASS);
324             String className = packageName + "." + simpleClassName;
325 
326             // CXF 动态生成的客户端方法需要的参数类型
327             Class<?> needType = Thread.currentThread().getContextClassLoader().loadClass(className);
328             if(!needType.isAssignableFrom(paramType)){
329                 // 将当前传入的参数转换成与需要的类型相匹配
330                 parameter = JAXBUtil.castClone(parameter, needType);
331             }
332             
333             // 调用 WebService 服务
334             return client.invoke(boi, parameter);
335         } catch (Exception e) {
336             throw new DefaultGboatNestedException("使用经过 CXF 包装后客户端调用 WebService [" + operation + "] 失败", e);
337         }
338     }
339 
340 }