JWS实现WebService
WebService估计大家都有听过或者使用过。Java有几种常用的方式实现webservice,本文主要是讨论JWS实现。
什么是webservice
简单而言,webservice就是通过SOAP协议在Web上提供的服务,使用WSDL文件进行说明。其特点是走SOAP协议而不是http协议,且传输的数据格式是xml而不是字符串。因为xml的特性,webservice具有跨语言跨平台的特点,而且xml可以封装复杂的对象,甚至可以使用xml加密数据。但由于涉及xml解析,速度相对慢一些。
实现webservice的方式
webservice分为服务端和客户端。服务端提供功能接口,客户端通过wsdl文件(xml格式)获取相关信息并调用接口。服务端搭建常用的方式有3种:JWS、Axis(Axis2)、CXF。客户端常用的调用方式有6种:wsimport、JDK提供的相关API、URLConnection、Ajax、axis、cxf。
我们先了解一下JWS提供的一些注解:
@WebService
@WebService:用于指定该类是webservice的服务器类。其属性值有:
serviceName:对外发布的服务名,对应wsdl:service,缺省值为Java类名+Service
endpointInterface:服务端点接口的完整, 指定SEI(Service EndPoint Interface)服务端点接口,一般使用接口在项目的相对路径
name:webservice的名称。在默认情况下,该值是实现XML webservice的类的名称,对应wsdl:portType 。缺省值为 Java 类或接口的非限定名称
portName:webservice的端口名称。对应wsdl:portName。缺省值为WebService.name+Port
targetNamespace:指定名称空间,一般使用接口实现类的包名的反缀
wsdlLocation:指定用于定义webservice的wsdl文档的 Web 地址,默认是发布地址+?wsdl。
@WebMethod
@WebMethod:用于指定服务接口(SEI)上的方法,或JavaBeans 端点的服务器端点实现类。注意@WebMethod必须使用在@WebService注释的类中。其属性值有:
operationName:对应wsdl:opereation。即客户端调用时的方法名,可以理解为隐藏服务端实际的方法名而对外暴露用于调用的方法名。缺省值为方法名
action:定义此操作的行为。对于SOAP绑定,此值将确定SOAPAction头的值。缺省值为Java方法的名称
exclude:指定是否从WebService中排除某一方法。缺省值为false
@WebParam
@WebParam:注释用于定制从单个参数至WebService消息部件和XML元素的映射。注意要用在SEI的方法,或JavaBeans 端点的服务器端点实现类。
name:参数的名称。如果不指定,参数名将会默认逐个替换成arg0、arg1…至argn(假设参数个数为n+1个)
partName:定义用于表示此参数的 wsdl:part属性的名称
targetNamespace:指定参数的XML名称空间。缺省值为WebService的targetNamespace
mode:参数的流向(IN、OUT、INOUT的一种)
header:指定参数是在消息头还是消息体中。缺省值为 false
搭建webservice服务端
先创建一个springboot的项目,具体操作这里就省略了,大伙都懂得怎么搭建的。下面的案例就以查找学生成绩作为例子。
idea提供快速搭建webservice项目的功能,虽然是很方便,但很多情况下都是在已有的项目中引用webservice,那么快速搭建项目的功能就显得不够灵活。这里就省略不说了。
好了,我们看看怎么用JWS搭建webservice服务端吧~
服务接口和服务接口实现类
为了方便后续客户端的调用,这里采用面向接口编程。
import com.javatest.po.StudentScore;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
/*** webservice服务端接口类*/
@WebService
public interface JAXTestService {/**提供调用的方法,使用@WebMethod注释*/@WebMethod(operationName = "getStudentScoreById")StudentScore getStudentScore(@WebParam(name = "id") long id);
}
创建对应的实现类。如果不采用面向接口编程,直接创建服务类也是可以的。
import com.javatest.po.StudentScore;
import com.javatest.service.StudentScoreService;
import com.javatest.webservice.server.JAXTestService;
import org.springframework.beans.factory.annotation.Autowired;
import javax.jws.WebService;
import javax.xml.ws.Endpoint;
/*** webservice服务端实现类。注意,如果使用面向接口,那么在此实现类中必须通过endpointInterface指定服务端接口类*/
@WebService(targetNamespace = "http://impl.server.webservice.javatest.com/", // 命名空间一般为包名的倒置endpointInterface = "com.javatest.webservice.server.JAXTestService"
)
@Service
public class JAXTestServiceImpl implements JAXTestService {@Autowiredprivate StudentScoreService service;@Overridepublic StudentScore getStudentScore(long id) {return service.selectByPrimaryKey(id);}// 发布服务public static void main(String[] args) {// 指定服务urlString url = "http://localhost:9010/javatest/webservice/getJAX";// 指定服务实现类JAXTestService server = new JAXTestServiceImpl();// 通过Endpoint发布服务Endpoint.publish(url, server);}
}
执行main方法就可以发布webservice了。在浏览器中输入http://localhost:9010/javatest/webservice/getJAX?wsdl,如果返回下图,则说明服务发布成功。
这里要注意哦:wsdl文档在执行发布服务的方法后就已经确定,后续手动修改wsdl文档是无法生效的,需要重新发布。
不完美的优化
上述的部署的方式有个弊端,一旦springboot项目部署,这个main方法就无法手动执行了,自然webservice的服务端就无法启动。首先想到的是在启动类上添加webservice,或者在开启服务的同时也开启webservice。但这样会存在端口冲突的问题。在不使用其他技术的前提下,找了很久也没有找到共用端口的方法。当然,用cxf的Springbus就能解决这个问题,这个在后面会讲述。
注意:下面两种方式api接口和webservice不能共用同一个端口,相当于同时开启两个服务。
方法1:直接在启动类上开启webservice服务
注意:SpringApplication.run必须写在webservice服务上面
@SpringBootApplication
@MapperScan("com.javatest.dao")
@ServletComponentScan
public class JavaTestApplication {public static void main(String[] args) {SpringApplication.run(JavaTestApplication.class);// 指定服务url,注意,配置的api接口的端口号与webservice接口的端口号不能一致String url = "http://localhost:9011/javatest/webservice/getJAX";// 指定服务实现类JAXTestService server = new JAXTestServiceImpl();// 通过Endpoint发布服务Endpoint.publish(url, server);}
}
方法2:使用ApplicationContext事件机制。
创建一个ApplicationListener的子类,配置webservice服务。
注意:使用该方法时,webservice的实现类可以用@Service注解。而前述的ApplicationListener子类则必须用@Service,@Component或者@Configuration注解,以便交给Spring容器管理
import com.javatest.webservice.server.impl.JAXTestServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import javax.xml.ws.Endpoint;
@Configuration
public class WebServiceApplication implements ApplicationListener<ContextRefreshedEvent> {private static Logger log = LoggerFactory.getLogger(WebServiceApplication.class);private String wsdlUrl = "http://localhost:9011/javatest/webservice/getJAX";@Overridepublic void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {Endpoint.publish(wsdlUrl,new JAXTestServiceImpl());log.info("webservice服务启动成功,wsdl地址:" + wsdlUrl + "?wsdl");}
}
此时执行springboot的启动类,就能同时部署两个项目(springboot的项目和webservice项目)。上例中,9010提供常规的api接口,9011则提供webservice接口。
利用cxf消息总线发布
如果想将api接口和webservice接口都用同一个端口对外发布,除了使用nginx反向代理之外,也可以使用cxf的bus。此时需要引入cxf的依赖和增加一个配置类:
依赖:
<!--cxf依赖-->
<dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-spring-boot-starter-jaxws</artifactId><version>3.2.4</version>
</dependency>
配置类:
import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.xml.ws.Endpoint;
/**使用cxf发布webservice服务,实现同一端口同时提供API和webservice*/
@Configuration
public class WebServiceConfig implements WebMvcConfigurer {// 新增消息总线@Bean(name = Bus.DEFAULT_BUS_ID)public SpringBus springBus() {return new SpringBus();}// 注入webservice实现类的bean@Beanpublic JAXTestServiceImpl JAXTestService() {return new JAXTestServiceImpl();}// 创建一个endpoint@Beanpublic Endpoint endpointForResources() {EndpointImpl endpoint = new EndpointImpl(springBus(), JAXTestService());endpoint.publish("/getJAX");return endpoint;}
}
在application.yml中添加cxf路径
cxf:path: /webservice
运行启动类就能同时提供api和webservice接口了~~
此时webservice的wsdl文档路径为:http://localhost:9010/javatest/webservice/getJAX?wsdl,即项目路径+cxf路径+endpoint指定的路径
注意:如果项目引用了cxf的技术,那么就无法使用方法1和方法2的方式,否则可能会出现“Cannot find any registered HttpDestinationFactory from the Bus”的报错。但既然都引用了cxf,倒不如直接用cxf发布。
搭建webservice客户端
如果不用axis和cxf的话,常用的客户端搭建方式有wsimport、jdk的api和URLConnection/ajax。
wsimport方式搭建Client
为了测试方便,新建一个springboot项目用于创建客户端,创建方式略。
注意:在生成服务端相关文件时,服务端需要在已启动的状态,否则将无法通过wsdl从服务端中获取相应文件!
使用wsimport也有两种方式:
方式1:调用cmd输入命令生成代码
输入以下命令执行,就可以生成服务端相应的配置和接口文件,将生成的文件放置在客户端相应的文件夹中即可。后面的地址就是服务端的wsdl文档路径。注意:-s和wsdl文档路径之间的点不可省略!!
wsimport -s . http://localhost:9010/javatest/webservice/getJAX?wsdl
但在实际操作中发现,这样输入命令会容易出现导包的错误。
方式2:程序生成代码
以idea为例。
选择ok就会自动下载和生成代码了。
客户端调用类
创建一个controller,对外提供api
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/wsimport")
public class WsImportController {@PostMapping("/getStudentScoreById")public StudentScore getStudentScoreById(long id) {JAXTestServiceImplService implService = new JAXTestServiceImplService();JAXTestService jaxTestService = implService.getJAXTestServiceImplPort();return jaxTestService.getStudentScoreById(id);}
}
启动客户端发送请求,成功调用webservice。
小优化:如果服务端方法较多,JAXTestServiceImplService的创建代码是冗余的,可以将其交给Spring容器管理,再注入到controller中。
使用WebService相关API创建Client
利用Jdk提供的api创建Client,也是需要先用wsimport命令生成代码,但是只需要其中的一些类即可:服务接口类、参数类或返回值类等等。在本示例中,因为方法的返回值是StudentScore类,所以需要的类为JAXTestService接口和StudentScore类。
当然,这两个类也可以手写,如果很熟悉的话:-),不过还是强烈建议用程序/指令生成。
生成的service接口的调整
生成的类放到自定义的位置后,生成的接口(在本例中为JAXTestService)还需要做一定的处理才能使用。首先是ObjectFactory.class是没有用到的,所以对应的注解要注释掉。另外也要将接口中的classname调整为当前的路径,否则会出现类转换出错的错误。
修改后的类的代码如下
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
@WebService(name = "JAXTestService", targetNamespace = "http://server.webservice.javatest.com/")
//@XmlSeeAlso({// ObjectFactory.class
//})
public interface JAXTestService {/*** @param id* @return* returns com.javaWebserviceClient.api.StudentScore*/@WebMethod@WebResult(targetNamespace = "")@RequestWrapper(localName = "getStudentScoreById", targetNamespace = "http://server.webservice.javatest.com/", className = "com.javaWebserviceClient.api.GetStudentScoreById")@ResponseWrapper(localName = "getStudentScoreByIdResponse", targetNamespace = "http://server.webservice.javatest.com/", className = "com.javaWebserviceClient.api.GetStudentScoreByIdResponse")public StudentScore getStudentScoreById(@WebParam(name = "id", targetNamespace = "")long id);
}
客户端调用类
下述的调用类使用了QName来指定wsdl文档中的元素。
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import java.net.URL;
@RestController
@RequestMapping("/api")
public class ApiController {@PostMapping("/getStudentScoreById")public StudentScore getStudentScoreById(long id) throws Exception {URL url = new URL("http://localhost:9010/javatest/webservice/getJAX?wsdl");// 指定命名空间和服务名称// 其中targetNamespace和name可在wdsl文档的<wsdl:definitions>标签中找到QName qName = new QName("http://impl.server.webservice.javatest.com/","JAXTestServiceImplService");// 创建对应的服务Service service = Service.create(url,qName);JAXTestService jaxTestService = service.getPort(JAXTestService.class);return jaxTestService.getStudentScoreById(id);}
}
启动客户端发送请求,成功调用webservice。
使用URLConnection/Ajax方式创建Client
URLConnection和Ajax的实现原理是基本一致的,通过模拟soap请求进行webservice调用,只是一个是后台,一个是前端页面。webservice本身也就是走soap协议,所以用urlconnection和ajax显得更底层一些。而使用的关键就是构造soap请求。
URLConnection方式
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
@RestController
@RequestMapping("/url")
public class URLConnectionController {@PostMapping("/getStudentScoreById")public String getStudentScoreById(long id) throws Exception {URL wsUrl = new URL("http://localhost:9010/javatest/webservice/getJAX?wsdl");HttpURLConnection conn = (HttpURLConnection) wsUrl.openConnection();conn.setDoInput(true);conn.setDoOutput(true);conn.setRequestMethod("POST");conn.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");
OutputStream os = conn.getOutputStream();//请求体String soap = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" " +"xmlns:webs=\"http://server.webservice.javatest.com/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"+ "<soapenv:Body><webs:getStudentScoreById><id>" + id + "</id></webs:getStudentScoreById></soapenv:Body></soapenv:Envelope>";
os.write(soap.getBytes());InputStream is = conn.getInputStream();byte[] b = new byte[1024];int len = 0;StringBuilder sb = new StringBuilder();while ((len = is.read(b)) != -1) {String s = new String(b, 0, len, "UTF-8");sb.append(s);}is.close();os.close();conn.disconnect();return sb.toString();}
}
这里解释一下soap请求如何构建:
整个soap的内容有一半是固定搭配,不同的有以下几点:
xmlns:webs :后面的webs是自定义的字符串,不是固定的词,其值就是wsdl文档中的namespace,用于指代webservice服务器。
<webs:getStudentScoreById>
:webs的含义同上,也就是指webservice服务器。getStudentScoreById也就是要调用的方法名。<id>" + id + "</id>
:这里是为方法的入参赋值。注意标签也不是固定的词!这个在wsdl文档可能会找不到,需要在服务端的接口所定义的方法中找
假如参数没有用@WebParam来指定名称,则默认为arg0、arg1依次到argn(假如方法有n+1个参数)
调用之后的返回值如下,也是一个soap。
ajax调用
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020070312503891.png)类似URLConnection,这里贴上其中一个写法:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ajax调用webservice</title><style type="text/css"></style><script type="text/javascript">var xhr;if (window.XMLHttpRequest) {xhr = new XMLHttpRequest();} else {xhr = new ActiveXObject("Microsoft.XMLHttp");}function sendMsg() {var id = document.getElementById("id");//服务的地址var wsUrl = 'http://localhost:9010/javatest/webservice/getJAX?wsdl';
//请求体var soap = '<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:webs=\"http://server.webservice.javatest.com/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><soapenv:Body><webs:getStudentScoreById><id>' + id + '</id></webs:getStudentScoreById></soapenv:Body></soapenv:Envelope>';
//打开链接xhr.open('POST', wsUrl, true);
//重新设置请求头xhr.setRequestHeader("Content-Type", "text/xml;charset=UTF-8");
//设置回掉函数xhr.onreadystatechange = _back;
//发送请求xhr.send(soap);}
function _back(){if (xhr.readyState == 4) {if (xhr.status == 200) {alert("调用WebService成功了");var ret = xhr.responseXML;var msg = ret.getElementsByTagName('return')[0];document.getElementById("showInfo").innerHTML = msg.textContent;}}}
</script>
</head>
<body>
<input type="button" value="发送soap请求" onclick="sendMsg();"/>
<input type="text" id="id"/>
<div id="showInfo"></div>
</body>
</html>
上面的写法是有跨域的问题,具体的解决方式这里就不详细探讨了。
可以看出,URLConnection和ajax方式调用并不是特别方便。
相似的还有使用httpClient,原理也是发送soap请求,只是发送请求的方式不同而已。
总结
wsimport是最简便的搭建方式,但缺点是提供的接口越多,下载到本地的文件就越多。一旦业务逻辑调整,wsdl文档更新,那么就要执行wsimport指令重新下载,管理相对麻烦一些。
jdk提供的api是相对方便的搭建方式,虽然也要用wsimport下载文件,但是保留的文件却比较少。所以相对的,业务逻辑调整时,要修改的文件会较少,甚至不用修改。
URLConnection和ajax方式个人觉得不太方便,不太建议。最主要是soap请求构建比较麻烦,即便在一些插件的帮助下生成soap请求,写起来也是比较麻烦的。
JWS实现WebService相关推荐
- 【WebService笔记01】使用JWS实现WebService接口的发布和调用
这篇文章,主要介绍如何使用JWS实现WebService接口的发布和调用. 目录 一.JWS实现WebService接口 1.1.JWS发布WebService接口 (1)编写接口 (2)编写实现类 ...
- 基于jws发布webservice服务
基于jws发布webservice服务 用途 用于验证基于jws搭建的webservice服务端与客户端. WebService服务端 1.目录结构 D:. │ pom.xml # maven配置 │ ...
- java jws web_基于Jws的WebService项目
1.服务器端建立 1.1.创建接口 [java] view plaincopy @WebService public interface IWebService { int add(int a, in ...
- java自带JWS开发Webservice服务
java-JWS开发Webservice 个人工作总结–顺便分享给大家 从JDK5开始,JAVA为WebService提供了Jax-ws支持,所以使用该指南需要具备JAVA 的JDK5以上版本 不多说 ...
- 转 真正的轻量级WebService框架——使用JAX-WS(JWS)发布WebService
WebService历来都很受重视,特别是Java阵营,WebService框架和技术层出不穷.知名的XFile(新的如CXF).Axis1.Axis2等. 而Sun公司也不甘落后,从早期的JAX-R ...
- java webservice jws,1 基于jws的webservice项目
use="literal" namespace="http://service.zttc.org/" /> use="literal" ...
- JWS 批注参考WebService注解
下列部分提供了有关标准 (JSR-181) JWS 批注和 WebLogic 特定 JWS 批注的参考文档: JWS 批注标记概述 标准 JSR-181 JWS 批注参考 WebLogic 特定的 J ...
- Java 使用Axis实现WebService实例
在上一篇WebService实例中,基于jdk1.6以上的javax.jws 发布webservice接口.这篇博文则主要用eclipse/myeclipse 使用axis插件进行发布和调用WebSe ...
- 系统开发系列 之MyEclipse创建WebService详细教程和调用教程(spring框架+maven+CXF框架)
1 回顾 [系统开发系列 之MyEclipse创建WebService详细教程和调用教程]介绍了使用JWS实现WebService接口的发布和调用,主要涉及的点有: (1)MyEclipse点击Fil ...
最新文章
- [JavaScript] JavaScript数组挖掘,不只是讲数组哟(2)
- idea resources目录_最全八种IDEA目录类型标注:Mark Dir as Sources/Resources Root
- CIIS 2020专题论坛丨机器智能产业发展蓝图初现
- 神策数据全新服务体系——打造用户行为分析领域服务最高标准
- java16下载_java lombok下载
- php 父子进程通信,PHP 进程及进程间通信
- LeetCode 800. 相似 RGB 颜色
- python四舍五入保留小数点后三位_Python中的“正确”四舍五入到小数点后3位
- 一份完整的问卷模板_如何写出一份优秀的个人简历?
- avalon2学习教程11数据联动
- C#------如何获取本机IP地址
- 实现文本超出显示省略号
- 投资理财之基金二:购买基金的渠道
- 【博客438】Kubernetes IPAM分配IP原理
- 整理:X86架构图示以及各部分解释
- Hark的数据结构与算法练习之归并排序
- 强化学习导论_Example 6.5: Windy Grid-world
- 配置文件和日志文件导出方法
- windows系统运维基础
- 如何取消计算机共享密码设置,win7系统计算机取消共享密码的操作方法