JAX-WS从jar中加载WSDL
我正在写一个胖客户端,使用SOAP服务的一些function(错误报告等)
我有JAX-WS工作正常,但默认情况下(至less在NetBeans中)它每次服务初始化时从远程服务器获取WSDL。 我期望这有助于提供一些版本支持等,但这不是我想要的。
我已经将wsdllocation
arg添加到wsimport,将生成的类指向本地资源。 以下片段是来自ApplicationService.java的WSDL资源的URL加载。
baseUrl = net.example.ApplicationService.class.getResource("."); url = new URL(baseUrl, "service.wsdl");
我很确定,在net / example / resources包中,指向存储在jar中的资源应该没有问题,并且jar本身也是按照预期构build的。 然而,该服务将不会加载…具体来说,当我调用ApplicationService.getPort()时,我得到一个NullPointerException;
这可能吗? 还是只是一个疯狂的追逐?
是的,这是绝对有可能的,正如我在通过javax.xml.ws.EndpointReference(一个WS-A相关的类)创build客户端时所做的那样。 我已经添加了WSDL的类path引用到WS-A EndPointReference和JAX-WS的Metro实现加载它就好了。 无论从WS-A EndPointReference或文件或http URL加载WSDL,您的JAX-WS实现都应使用相同的WSDLparsing代码,因为您正在解决的所有问题都是parsingURL。
对你来说最好的办法可能是做如下的事情:
URL wsdlUrl = MyClass.class.getResource( "/class/path/to/wsdl/yourWSDL.wsdl"); Service yourService= Service.create( wsdlUrl, ...);
其中…代表WSDL内的WSDL服务的QName。 现在需要记住的重要一点是您的WSDL需要完整且有效。 这意味着如果您的WSDL导入XSD文件或其他WSDL,则URL必须是正确的。 如果将导入的WSDL和XSD与WSDL文件包含在同一个JAR中,则应该使用相对URL来导入,并将所有导入保留在同一个JAR文件中。 JAR URL处理程序不会将相对URL视为相对于类path的相对关系,而是相对于JAR文件中的相对关系,因此,除非您实现自定义URL处理程序和自己的前缀,否则不能在WSDL中跨JAR运行导入基于类path的导入parsing。 如果您的WSDL导入了外部资源,那没问题,但是如果这些资源移动了,您将自行维护维护问题。 即使在你的类path中使用WSDL的静态拷贝也违背了WSDL,Web服务和JAX-WS的精神,但是有时候这是必要的。
最后,如果您embedded了一个静态的WSDL,我build议您至less使服务端点可configuration用于testing和部署目的。 重新configurationWeb服务客户机端点的代码如下所示:
YourClientInterface client = yourService.getPort( new QName("...", "..."), YourClientInterface.class); BindingProvider bp = (BindingProvider) client; bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://localhost:8080/yourServiceEndpoint");
至less对于最近的JAX-WS,您不需要执行任何模式目录或编程wsdl位置设置。 如果将WSDL放入JAR,然后将wsimport wsdlLocation
设置为JAR中WSDL的相对资源path。 也就是说,JAX-WS使用Java的内置Class.getResource
来加载WSDL。
如果你使用Maven它的东西是这样的:
<plugin> <groupId>org.jvnet.jax-ws-commons</groupId> <artifactId>jaxws-maven-plugin</artifactId> <version>2.3</version> <executions> <execution> <goals> <goal>wsimport</goal> </goals> <!-- Following configuration will invoke wsimport once for each wsdl. --> <configuration> <!--- VERY IMPORTANT THAT THE PATH START WITH '/' --> <wsdlLocation>/com/adamgent/ws/blah.wsdl</wsdlLocation> <wsdlDirectory>${basedir}/src/main/resources/com/adamgent/ws</wsdlDirectory> <wsdlFiles><wsdlFile>blah.wsdl</wsdlFile></wsdlFiles> </configuration> </execution> </executions> </plugin>
对于上面的示例,您将在此处将使用Maven项目布局的WSDL放置在src/main/resources/com/adamgent/ws
。
确保WSDL进入Maven的JAR,如下所示:
<build> <resources> <resource> <directory>src/main/resources</directory> </resource> </resources> ....
现在你的wsimport生成的代码和WSDL都在一个独立的JAR中。 要使用该服务,您不必设置WSDL位置,就像下面这样简单:
BlahService myService = new BlayService_Service().getBlahServicePort();
把这个映射到ANT的wsimport应该是微不足道的。
也许有点迟,但是我发现了一个非常简单的解决scheme,它解决了这个问题,但是这涉及到Service类的生成代码的改变:
如果在Service类中有以下行
baseUrl = net.example.ApplicationService.class.getResource(".");
更改为
baseUrl = net.example.ApplicationService.class.getResource("");
即使在JAR中打包的WSDL也能正常工作。 在这两种情况下都不确定getResource()的确切行为,但是到目前为止,在多个操作系统和Java版本中,我没有遇到这种方法的任何问题。
您所描述的是JAX-WS中的错误: JAX_WS-888 – parsing自定义wsdlLocation的URL的错误代码 。
这是固定的V2.2,所以只要设置wsdlLocation
,你写,现在应该工作。
如果你的类path有“。” 那么Class.getResource(“。”)将返回执行java命令的目录的URL。 否则,它将返回一个null。 相应地调整wsdlocation。
另一个答案是乌斯特新服务(wsdllocation,servicename); 获取服务对象。
这就是我解决问题的方法。
我偶然发现了同样的问题。 JAXWS生成的客户端代码使用MyService.class.getResource(".")
技巧来加载wsdl文件…但是在testing之后,如果类文件位于filesytem目录中,似乎只能工作。 如果类文件在JAR中,则该调用将为URL返回null。
这听起来像是一个JDK中的错误,因为如果你像这样构build你的URL:
final URL url = new URL( MyService.class.getResource( MyService.class.getSimpleName() + ".class"), "myservice.wsdl");
那么它也可以工作,如果类和wsdl捆绑在一个jar子里。
我想大多数人会实际上捆绑在一个jar子里!
在构build客户端jar之前,我replace了WSDL位置。
- 将WSDL复制到类目录。
- 使用classpath将Service类replace成WSDL。
- build立客户端存根。
- jar子存根。
<copy todir="@{dest-dir}/@{dir-package}" verbose="@{verbose}"> <fileset dir="@{dir-wsdl}" includes="*.wsdl,*.xsd" /> </copy> <echo message="Replacing Service to point to correct WSDL path..." /> <replaceregexp match="new URL(.*)" replace='Class.class.getResource("@{name-wsdl}");' flags="gm"> <fileset dir="@{source-dest-dir}"> <include name="@{dir-package}/*Service.java" /> </fileset> </replaceregexp> <replaceregexp match="catch (.*)" replace='catch (Exception ex) {' flags="gm"> <fileset dir="@{source-dest-dir}"> <include name="@{dir-package}/*Service.java" /> </fileset> </replaceregexp>
这是我的解决方法。
我从jar中解压WSDL,并把它写到jar附近的文件中:
File wsdl = new File("../lib/service.wsdl"); InputStream source = getClass().getResource("resources/service.wsdl").openStream(); FileOutputStream out = new FileOutputStream(wsdl); byte[] buffer = new byte[512]; int read; while((read = source.read(buffer)) >= 0) { out.write(buffer, 0, read); }
然后将服务类指向file:../lib/service.wsdl
。
这工作,但我会很感激,如果任何人都可以给我一个更优雅的解决scheme。
这是一个适合我的方法(特别是通过http和https )。 Oracle JDK 1.8.0_51的JAX-WS使用Apache CXF 3.1.1创build的类。
请注意,远程WSDL只能在第一次调用时获得。 根据使用模式(长时间运行的程序),这可能是完全可以接受的。
基础:
- 从远程主机下载WSDL并存储为文件:
wget --output-document=wsdl_raw.xml $WSDL_URL
- 您可能需要
xmllint --format wsdl_raw.xml > wsdl.xml
格式化xmllint --format wsdl_raw.xml > wsdl.xml
以获得更好的格式 - 使用命令行工具生成客户端类:
./cxf/bin/wsdl2java -d output/ -client -validate wsdl.xml
并导入到您的项目
validationWSDL文件中是否存在http和https的服务定义。 在我的情况下,提供商没有一个用于https (但确实接受httpsstream量),我不得不手动添加它。 WSDL中应该包含以下内容:
<wsdl:service name="fooservice"> <wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort"> <soap:address location="http://ws.example.com/a/b/FooBarWebService"/> </wsdl:port> </wsdl:service> <wsdl:service name="fooservice-secured"> <wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort"> <soap:address location="https://ws.example.com/a/b/FooBarWebService"/> </wsdl:port> </wsdl:service>
CXF应该生成一个实现javax.xml.ws.Service
的类,例如Fooservice
,并带有适当的构造函数:
public class Fooservice extends Service { public Fooservice(URL wsdlLocation) { super(wsdlLocation, SERVICE); } public Fooservice(URL wsdlLocation, QName serviceName) { super(wsdlLocation, serviceName); } public Fooservice() { super(WSDL_LOCATION, SERVICE); } ...etc...
在你的代码的某个地方(在这里,一些Groovy更容易阅读),你初始化上面的Service
实例,然后调用一个端口。 在这里,根据名为secure
的标志,我们使用https或http :
static final String NAMESPACE = 'com.example.ws.ab' static final QName SERVICE_NAME_HTTP = new QName(NAMESPACE, 'fooservice') static final QName SERVICE_NAME_HTTPS = new QName(NAMESPACE, 'fooservice-secured') Fooservice wsService File wsdlfile = new File('/somewhere/on/disk/wsdl.xml') // If the file is missing there will be an exception at connect // time from sun.net.www.protocol.file.FileURLConnection.connect // It should be possible to denote a resource on the classpath // instead of a file-on-disk. Not sure how, maybe by adding a handler // for a 'resource:' URL scheme? URI wsdlLocationUri = java.nio.file.Paths(wsdlfile.getCanonicalPath()).toUri() if (secure) { wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTPS) } else { wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP) } SomeServicePort port = wsService.getSomeServicePort() port.doStuff()
另一种方法是将WSDL下载到与用于服务调用的连接分离的连接上(使用tcpdump -n -nn -s0 -A -i eth0 'tcp port 80'
来观察stream量),只需执行以下操作:
URI wsdlLocationUri if (secure) { wsdlLocationUri = new URI('https://ws.example.com/a/b/FooBarWebService?wsdl') } else { wsdlLocationUri = new URI('http://ws.example.com/a/b/FooBarWebService?wsdl') } Fooservice wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP) SomeServicePort port = wsService.getSomeServicePort() port.doStuff()
请注意,如果wsdlLocationUri
指定了https ,则实际上会正确使用https ,尽pipewsService
已使用SERVICE_NAME_HTTP
进行了初始化。 (不知道为什么服务使用检索WSDL资源的scheme?)
这就是它。
要debugging连接,请传递:
-Dcom.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true -Dcom.sun.xml.internal.ws.transport.http.HttpAdapter.dump=true
到命令行上的JVM。 这会将信息从http连接代码写入stdout(不幸的是, java.util.logging
!)。
我的解决scheme是修改生成的服务。 您必须更改标头注释中的wsdlLocation ,而实例块如下所示:
static { URL url = null; url = com.ups.wsdl.xoltws.ship.v1.ShipService.class.getResource("Ship.wsdl"); SHIPSERVICE_WSDL_LOCATION = url; }
我将wsdl文件放在ShipService类旁边的bin目录中
虽然你可以通过一些操作来实现它,但是我build议不要这样做,并保持现在的样子。
Web Service端点提供者应该提供一个WSDL作为其合约的一部分。 您生成的代码应该从服务器本身的WSDL中提取出来。
在WebSphere上进行部署时,您可以从部署UI中将端点更改为其他端点。 其他应用程序服务器你可能需要找出供应商特定的绑定XML来做到这一点。
它只发生在初始化,所以对整个应用程序的影响应该可以忽略不计。