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的内容有一半是固定搭配,不同的有以下几点:

  1. xmlns:webs :后面的webs是自定义的字符串,不是固定的词,其值就是wsdl文档中的namespace,用于指代webservice服务器。

  2. <webs:getStudentScoreById>:webs的含义同上,也就是指webservice服务器。getStudentScoreById也就是要调用的方法名。

  3. <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相关推荐

  1. 【WebService笔记01】使用JWS实现WebService接口的发布和调用

    这篇文章,主要介绍如何使用JWS实现WebService接口的发布和调用. 目录 一.JWS实现WebService接口 1.1.JWS发布WebService接口 (1)编写接口 (2)编写实现类 ...

  2. 基于jws发布webservice服务

    基于jws发布webservice服务 用途 用于验证基于jws搭建的webservice服务端与客户端. WebService服务端 1.目录结构 D:. │ pom.xml # maven配置 │ ...

  3. java jws web_基于Jws的WebService项目

    1.服务器端建立 1.1.创建接口 [java] view plaincopy @WebService public interface IWebService { int add(int a, in ...

  4. java自带JWS开发Webservice服务

    java-JWS开发Webservice 个人工作总结–顺便分享给大家 从JDK5开始,JAVA为WebService提供了Jax-ws支持,所以使用该指南需要具备JAVA 的JDK5以上版本 不多说 ...

  5. 转 真正的轻量级WebService框架——使用JAX-WS(JWS)发布WebService

    WebService历来都很受重视,特别是Java阵营,WebService框架和技术层出不穷.知名的XFile(新的如CXF).Axis1.Axis2等. 而Sun公司也不甘落后,从早期的JAX-R ...

  6. java webservice jws,1 基于jws的webservice项目

    use="literal" namespace="http://service.zttc.org/" /> use="literal" ...

  7. JWS 批注参考WebService注解

    下列部分提供了有关标准 (JSR-181) JWS 批注和 WebLogic 特定 JWS 批注的参考文档: JWS 批注标记概述 标准 JSR-181 JWS 批注参考 WebLogic 特定的 J ...

  8. Java 使用Axis实现WebService实例

    在上一篇WebService实例中,基于jdk1.6以上的javax.jws 发布webservice接口.这篇博文则主要用eclipse/myeclipse 使用axis插件进行发布和调用WebSe ...

  9. 系统开发系列 之MyEclipse创建WebService详细教程和调用教程(spring框架+maven+CXF框架)

    1 回顾 [系统开发系列 之MyEclipse创建WebService详细教程和调用教程]介绍了使用JWS实现WebService接口的发布和调用,主要涉及的点有: (1)MyEclipse点击Fil ...

最新文章

  1. [JavaScript] JavaScript数组挖掘,不只是讲数组哟(2)
  2. idea resources目录_最全八种IDEA目录类型标注:Mark Dir as Sources/Resources Root
  3. CIIS 2020专题论坛丨机器智能产业发展蓝图初现
  4. 神策数据全新服务体系——打造用户行为分析领域服务最高标准
  5. java16下载_java lombok下载
  6. php 父子进程通信,PHP 进程及进程间通信
  7. LeetCode 800. 相似 RGB 颜色
  8. python四舍五入保留小数点后三位_Python中的“正确”四舍五入到小数点后3位
  9. 一份完整的问卷模板_如何写出一份优秀的个人简历?
  10. avalon2学习教程11数据联动
  11. C#------如何获取本机IP地址
  12. 实现文本超出显示省略号
  13. 投资理财之基金二:购买基金的渠道
  14. 【博客438】Kubernetes IPAM分配IP原理
  15. 整理:X86架构图示以及各部分解释
  16. Hark的数据结构与算法练习之归并排序
  17. 强化学习导论_Example 6.5: Windy Grid-world
  18. 配置文件和日志文件导出方法
  19. windows系统运维基础
  20. 如何取消计算机共享密码设置,win7系统计算机取消共享密码的操作方法

热门文章

  1. checkbox列表选择2
  2. Omi Emoji表情包
  3. JAVA笔试题笔记(二)
  4. oracle数据块的大小设置,Oracle数据块的大小
  5. 【原创】技术员 Ghost Win 10 X86 企业贺岁版2018
  6. 国庆节上映的电影有哪些?2014国庆节上映的动画电影盘点
  7. 整样运用计算机考试,计算机一级考试Word的十个应用技巧
  8. 奥维互动地图GEE协议历史影像分析与应用
  9. 猿创征文|磁盘满的本质分析——磁盘空间满与inode节点满
  10. 电脑网络连接为什么常常连接不上