开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构: Web Service 1】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/75/detail/15839
Web Service 1
内容介绍:
一、Apache Solr
二、Elasticsearch
一、Apache Solr
APACHE SOLRTM8.4.1
- is the popular, blazing-fast, open source enterprise search platform builton Apache LuceneTM".
- is highly reliable, scalable and fault tolerant, providing distributed indexing, replication and load-balanced querying, automated failoverand recovery, centralized configuration and more.
Solr powers the search and navigation features of many of the world'slargest internet sites.
通过 Lucene 讲解全能搜索工具基本原理,实际商用还有 SOLR 工具,网站上查看 S OLR 是与 Lucene 放在一起,在Lucene 之上构建的搜索引擎,支持分布式的部署,在分布式基础上做一些常规的分布式常见的一些功能,比如负载均衡、自动容错和恢复,有多个副本,整个索引分布在多台机器上,比较适合一个比较大系统,索引文件本身非常的多、尺寸很大,但一台机器上没有办法去存储的这种系统,有很多商业的系统都在使用它,比如较为出名的有一个百货的网站 Sears、BESTBUY
1.Solr
Launch Solr in SolrCloud Mode
下载解压后,通过- $ ./bin/solr start -e cloud 方式运行
图片比较模糊,明白原理即可。在运行的时,启动是会询问跑几个实例,跑两个实例就会指定这两个的端口在哪里,有一个默认推荐几,按照默认推荐写即可。
运行后,实际上取了两个实例,这两个实例分别在这个8983和7574两个端口上,有个控制台,打开控制台,像这数据库的客户端终端
会提示连接成功,可以在 techproducts 选择索引,上面装好后有一个 gettingstarted。如果按照官网的例子,可以生成一个 techproducts 索引,q 这边是搜索条件,*:*代表搜索的键值对,后面代码都是要搜索的键值对
{
" responseHeader" : {
" zkConnected" :true,
"status" : 0,
"QTime" : 151,
"params" : {
"q" : "*:* ",
"_" : "1586393129209"}},
"response" : { " numFound" : 2, "start " : 0 , "maxscore" :1.0 , " docs" : [
{
"id" : "e3a1ab3a-9a37-4a1b-8751-11da9d6b00b7",
"name" : "Amazon Kindle Paperwhite" ,
" _version_" =1662821704548745216},
{
"id" : "0500b99f-b893-45cf-a090-22ealad3db97",
name" : "Amazon Kindle Paperwhite" ,
"_version_" :1662821719178477568}]
}}
*:*表示搜索所有东西,搜索就可以把所有东西全部找出来,找到了两个东西,是 Amazon Kindle Paperwhite
以编程的方式来操作,有一个工程专门去做
public class TechProduct {
@Field
public String i
d
;
@Field
public String name;
public TechProduct(String id,String name){
this.id = id;
this.name = name;
}
public TechProduct() {
}
定义 TechProduct 里面的内容,是一组 Product,每一组 Product 中有一个 id,有一个名字,放的都是键值对,即前面看到的 id 与 name,"_version_" :1662821719178477568}]
下划线开头和结尾的 version 是 solr 自动加上的,自己做一个内部的一个版本号控制。实际上就是有这样的一个对象,这样的类其实没有什么特殊,有两个属性,有 get 这样的方法,用 field 加了@notation,@notation就是solr它的客户端,客户端提供的,会去处理,会把它映射成 solr 里面的内容,第二个是对象绑定,对象绑定的程序是他创建了一个新的定义的 TechProduct,用 solr 的客户端添加进去 product,添加到 TechProducts索引里,有创建的Kindle给添加进去,添加进去之后跟数据库操作一样,需要 commit 一下,才能真正重建数据库。
client.commit( collection: "techproducts");
final SolrQuery query = new SolrQuery( q: ""*:*"");
query.addField("id" );
query.addField( "name" );
query.setSort( field: "id", SolrQuery. ORDER.asc);
commit 之后马上创建 query,query 条件仍然是所有的,将内容打印出来,得到之后排序,按照 id 升序的方式排序,执行 query 之后得到 response,response 实际上返回的内容是一组,应该过来的是字符串,把它组装成刚才定义的TechProduct,相当于从 TechProduct 索引把所有的信息拿回来,组装成一个 TechProduct 的一个列表,拿到列表之后,剩下是常规加的 java 代码或便利里面的每一个 Product 输出它的内容
执行一次之后的结果,可以看到往里面插入一些元素,前面代码插入了一个新的 Product,结果就是新插入那个Product。整个按照 key 的字符串的字典去升序排序,所以看到零开头在最前面,k 开头在最后面,以上就是操作过程,得到 solr 客户端是如何得到,刚才起了两个节点,一个是 master,一个是 still,master 有两个端口一个是8983,一个是7574
public static Solrclient getSolrclient() {
final String solrUrl = "http: //localhost:8983/solr";
return new HttpSolrClient.Builder(solrUrl)
.withConnectionTimeout( 10000)
.withSocketTimeout(60000)
.build();
}
solrUrl = "http: //localhost:8983/solr",生成客户端,上面要可以加上 Timeout 防止长时间的占用链接,以上是solr的编程来实现客户端的一个代码,那这边是在绑定,还可以对他的内容做重新的索引,代码跟刚才是类似的,前面要得到客户端,底下做一些查询,查询会有一些属性,即查询字符串、查询索引文件,是否按照什么排序,然后去执行相应的操作,commit 后得到 TechProduct,这是在做查询,底下把查询回来的东西输出一下,这是 solr的一个客户端
2.Using SolrJ
·
SolrJ
- is an API that makes it easy for applications written in Java (or anylanguage based on the JVM) to talk to Solr.
·
For projects built with Maven, place the following in
your pom
.
xml:
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj< / artifactId>
<version>8.4.1</version>
</ dependency>
客户端在开发时,在 pom 文件中必须要依赖 solr 客户端,然后引入,在 mvnrepository 网站上,不理解可以搜索,会出现一系列东西,
·
Querying.java
public class Querying {
public static void main(String[] args) throws
IO
Exception, SolrServerException {
final SolrClient client = getSolrClient();
final Map<String , String> queryParamMap = new HashMap<String, String>();
queryParamMap.put("q","*:*"");
queryParamMap.put("fl" , "id, name");
queryParamMap.put("sort" , "id asc");
MapSolrParams queryParams = new MapSolrParams(queryParamMap);
final QueryResponse response = client.query("techproducts" , queryParams);
final SolrDocumentList documents = response.getResults();
System.out.printIn("Found " + documents.getNumFound() + " documents");
for (SolrDocument document : documents) {
final String id = (String)document.getFirstValue("id");
Final
String
name=(String)document.getFirstValue("name");
system.out.printIn("id : " + id + " ; name: " + name);
}
}
客户端可以看到代码与 Lucene 不一样,但基本道理一样,其实底层就是一个 Lucene,对这个东西做了一个封装,写法可能与 Lucene 有差异,也是创建一个 map 里面放进查询的条件,针对它执行一个 query,得到一个response,response 本身应该是个字符串,可以直接拿到里面的内容去处理,把它当成一个 SolrDocument,直接拿字符串键值对去处理
public static SolrClient getSolrClient() {
final String solrUrl = "http://localhost:8983/solr";
return new HttpSolrClient.Builder(solrUr
l
)
.withConnectionTimeout(10000)
.withSocketTimeout(60000)
.build();
}
}
也可以组成 TechProduct 对象处理,上图是做实验时插入比较多的记录做的一次搜索,如果在第一次下载Lucene,在运行的时候要求创建 TechProducts,例子带有一个索引,它本身创建出来就有很多,有五十多个,觉得太多把它删掉,重新搞一下只有两个
3.Indexing
·
Indexing.java
final SolrlnputDocument doc = new SolrlnputDocument();
doc.addField("id" , UUID.randomUUID().toString());
doc.addField("name", "Amazon Kindle Paperwhite");
final UpdateResponse updateResponse =
client.add("techproducts" , doc);
//I
ndexed documents must be
committedclient.commit("techproducts");
所谓索引是通过 Update 实现的,往索引文件新加一个,看到新加进去一条它的 id、name 是什么,add一下将新的键值对添加到索引中,返回一个 response,可以对 response 再做处理,比如是成功还是失败,这里默认一定能够成功,所以没有做额外的处理。
4.Java Object Binding
·TechProduct.java
public class TechProduct {
@Field
public String id;
@Field
public String name;
public TechProduct(String id, String name){
this.id = id;
this.name = name;
}
public TechProduct() {}
public String getld() {
return this.id;
}
public String getName(){
return this.name;
}
}
映射成一个
TechProduct
,
Field是solr的@n
otation,不是其他工具
,solr
对
它
进行处理
·
ObjectBinding.java
public class ObjectBinding {
public static void main(String[] args) throws IOException, SolrServerException {
final SolrClient client = getSolrClient();
final TechProduct kindle = new TechProduct(""kindle-id-4" , "Amazon Kindle Paperwhite");
final UpdateResponse response = cient.addBean("techproducts" , kindle);
client.commit("techproducts");
final SolrQuery query = new SolrQuery("*:*");
query.addField("id");
query.addField("name");
query.setSort("id" , SolrQuery.ORDER.asc);
final QueryResponse responseOne = client.query("techproducts" , query);
final List<TechProduct> products = responseOne.getBeans(TechProduct.class)
for (TechProduct product : products){
final String id = product.getld();
final String name = product.getName();
system.out.println("id: " +id + "; name: " + name);
}
}
final List<TechProduct> products = responseOne.getBeans(TechProduct.class);
Solr 将字符串、键值对,装成一个一个 TechProduct 类型的对象,放到 list 中,所有的 TechProduct 都存在 solr 中,在进行操作的时候,眼里其实看不到它和访问一个数据库的差异有多大,在写代码的时候,无论是用 Hibernate还是用 GPA,写代码到上面这一层转成list之后,写法就差不多了,看到 solr 这种索引,一方面可以做常规的用法的一个全文搜索引擎,另外还可以当有一个简单的键值对的存储时,可以把它放里面,利用反向索引支持 JSON 理念,尤其是结构化程度不好,JSON 是 key、value,value 可以是另外的一组 key、value,将 key、value 放到一起去存储时,key、value 对什么内容可以不一样,对结构化程度不好的数据,用 solr 作为一种存储,用 solr 这种所有能力去做搜索,也是一种方案,在讲的过程中没有脱离整个在讨论各种各样的数据如何去存储、去访问。
·ObjectBinding.java
public static SolrClient getSolrClient() {
final String solrUrl = "http://localhost:8983/solr";
return new HttpSolrClient.Builder(solrUrl)
.withConnectionTimeout(10000)
.withSocketTimeout(60000)
.build();
}
}
上图是运行后的实例,运行之后将所有数据排序输出
二、Elasticsearch
·Elasticsearch is a highly scalable open-source full-text searchand analytics engine.
- It allows you to store, search, and analyze big volumes of data quicklyand in near real time.
- It is generally used as the underlying engine/technology that powers applications that have complex search features and requirements.
除了 solr 之外呢,还可能用到另外一个工具 Elasticsearch,关键词也是开源的全文搜索引擎,可以支持集群化的部署,它的运行也很简单,只要进到边缘的目录里头去进行 Elasticsearch 脚本,就可以运行,运行之后没有再写JAVA代码进行处理,大家可以去用后面列的例子处理。JAVA 代码与刚才是类似的,不再重复写。
1.Elasticsearch - Basic Concepts
·Near Real Time
- Elasticsearch is a near real time search platform. What this means is there is a slight latency (normally one second) from the time you indexdocument until the time it becomes searchable.
·Document
- A document is a basic unit of information that can be indexed.
- This document is expressed in JSON (JavaScript Object Notation) whick is a ubiquitous internet data interchange format.
·Index
- An index is a collection of documents that have somewhat similar characteristics.
- An index is identified by a name (that must be all lowercase) and this name is used to refer to the index when performing indexing,search,update, and delete operations against the documents in it.
Elasticsearch 特点,第一个它不是真正实时的它是接近实时的。数据在写入到能够被访问到,它的时间差不多要隔一秒左右,它是一个集群式的服务,当在写入一个索引到在某一个地方出现的时候,肯定要对这个索引重新的处理,处理的结果才能被访问。在处理的时候,可能需要跟其他节点的数据要合到一起去做处理,比如插入的数据它实际上经过索引的排序之后应该存储到这个位置,至于为什么在这个位置,当存入一个数据的时候,它存在什么位置取决于这个数据的内容,而在什么地方被写入,取决于当时是怎样连接的。比如用 ip 哈希的方式,请求到来写入节点上,写入的内容可能在中间的节点,也可能在右边的节点,内容经过索引的排序在哪个位置,大体会有一点延迟,但做的已经可以,大部分操作在一秒之内,先插入东西都可以写入进去,存储东西的类似于 mango,即document,document是以 json 的形式存储,里面带索引,索引为了支持全民搜索也进行反向索引
·Shards & Replicas
- An index can potentially store a large amount of data that can exceed the hardware limits of a single node.
- To solve this problem, Elasticsearch provides the ability to subdivide your index into multiple pieces called shards.
·Sharding is important for two primary reasons:
- It allows you to horizontally split/scale your content volume
- It allows you to distribute and parallelize operations across shards(potentially on multiple nodes) thus increasing
performance/throughput
·Replication is important for two primary reasons:
- It provides high availability in case a shard/node fails. For this reason, it is important to note that a replica shard is never allocated on the samenode as the original/primary shard that it was copied from.
- It allows you to scale out your search volume/throughput since searches can be executed on all replicas in parallel.
它是分布式的,是 Shards & Replicas,Shards 实际上是一个物理的概念,如有三个节点,每个节点都是一个Shards,Replicas 是所有的数据块切小了、存储之后都必须要有两个副本,就会在三个节点当中任取两个不同的节点,各存一个部分,这两个东西是要配套去使用,如果只有一个 Shards,搞多个副本就是只有一个服务器,一个节点,多个副本意义不是很大,如果崩溃的时候东西都没有,只能防止这个节点上硬盘的磁道是不是发生故障,正好一个副本出错,但其他另外一个副本还好。正常情况下副本如果是多个,物理上的节点应该也是多个,Shards一方面允许做水平的切割,一个很大的索引切成四部分之后在四个不同的服务器上运行,另外一个说如果在四个不同的服务器上运行,当用户来访问的时候,可以做一定程度的并行,在搜索索引时,可能在四台机器上同时进行处理,Shards本质上就是一个集群,利用集群的特点提高性能,提高它的可靠性
Replicas 一方面是考虑如果崩了该怎么办,另一方面考虑可以并行,就像数据库里讲的,如果数据有两块一模一样,两个做储存备份,独操作就可以在一个服务器上,在一个副本上执行,写操作在一个副本上执行,把读写分离性能可以
·Index some documents
PUT /customer/_doc/1(http://localhost:9200/customer/_doc/1 )
通过 host 工具启动 Elasticsearch,运行相应的 url,运行方法,带一些方法体的内容就可以知道如何跟Elasticsearch进行交互,没有 java 代码和工程,不详细介绍。
Elasticsearch 做全文搜索,更多的人它当做一种分布式的存储,会把数据存在 Elasticsearch 中,通过分布式的能力去做 key value 存储,通过检索能力实现快速的数据检索,适合 key value 类型数据,这个 v 不应该是图片 value,是全能搜索的引擎,里面应该是文本,是非结构化的文本,是大段大段的文本,没有抽取出主谓宾的信息文本,它针对文本的搜索效率会比较高,Elasticsearch 更多的被拿来当做一种分布式存储来使用,这是很大的一个特点,使用并不复杂,需要交相应的包下载解开使用就可以使用,可以参照示例写,如果有书评,对书评做一个全文的搜索,如果没有,比如写一个聊天室跟这个聊天的记录,如果你记录下来就 Elasticsearch 搜索,去看一下那本书聊天时说了什么
三、Web Services
展示代码如何写,对一些概念加深理解
1.Web Services Overview
·Web Services
- present the opportunity for real interoperability across hardware,operating systems, programming languages, and applications.
· Web
- Access with web protocols
·Services
- Independent of the implementation
web 表示使用 Web 的协议来访问,这就涉及到经常谈论一个问题,Web 和 internet 到底如何区分,Internet它是物理上那张网,Web 是只能在物理上网上跑 Web 协议的这种应用,Web 协议有 http、html 就 CSS 协议或者语言,比如邮件传输协议 smtp、文件传输协议 ft p 等等,只要用这种协议去访问,这些都是 Web 协议,用这种协议去进行访问的,被访问到的这些服务就被称为 Web,这种协议跨网络的能力比较强,比如 HTTP 协议和 RMI 或 JMS,http跨网络的能力更强,RMI 或 JMS 跨网络能力要弱一点,跨网络能力强意味着可以被更多的人访问到,被网络距离更远的人访问到,规模比较大,或者被更多的人被防到,第二个是 Services,Services 很重要的一个特点是要强调要独立于实现,无论HTTP 还是 ftp、smtp,都有一个特点就是在这些协议里面,传输的内容全部都是文本。文本细说可以是json、xml、html 等,但不管怎么说,主要的传输的是文本,不是一个 JAVA 的对象或者不是一个C#的对象,实际上service在被访问时,是传递一个纯文本的数据过来,就能理解如何调用,然后反一个纯文本的东西,就知道如何操作,并不需要产生一个 JAVA 调用或者 C#调用,就可以访问到这个服务,可以做一个易购的系统之间的通信方式,一个java系统可以把它的 java 方法调用,转成纯文本的方式,发送给一个 C#对象,C#对象拿到之后把纯文本的表示转换成对 C#对象的调用,执行调用,一个 JAVA 对象和一个 C#对象实现了一次交互,即独立于具体的实现,这是 web services它的两个主要的特点,即分开看 web 和 services 表达的意思,总结为提供了一个服务,这个服务放到了 web 上,可以用web的协议进行访问,没有要求一定要是什么语言,可以不用任何具体语言,可以用纯文本的方式来调用,web services提供了一个互操作性,互操作性包括跨硬件操作系统、编程语言和应用,只要能够使用 web 协议,只要能够传输纯文本,双方之间就能通信,没有和这里面提到的任何一项绑定,这才是 web 服务真正要考虑的。
了解 web 服务后,好处是可以看到它提供的服务操作性。
2.SOAP
·SOAP is defined by its own XML Schema and relies heavily on the use of XMLNamespaces.
- Here's a SOAP request message that might be sent from a client to a server:
<?xml version='1.0' encoding='UTF-8'?>
<env : Envelope
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env :Header />
<env: Body>
<reservation xmlns="http : // www.titan.com/Reservation">
<customer>
<! -- customer info goes here -->
</customer>
< / reservation>
</env : Body>
</env: Envelope>
Web 服务分两种,一种是 soap,简单对象访问协议,整个协议用 XML 来描述,第二种是 restful。soap 它出现的比较早,soap 传递纯文本就可以,通信,但是传递传文本不代表任何一个文本都能通信,这个文本一定要符合一定的规则,这就是 soap 协议的定义。当调用远方服务时,约定 soap 必须放到一个信封中,信封有信封头和信封体,信封头和信封体分别要放一些属性,其中头要根据你具体情况放,在大多数情况下,没有特别约定可以什么都不放,关键是信封体,信封体中要放跟web service约定好,要调用服务时,要传什么,比如 reservation 就是方法名字,customer 是要传递的参数,
<reservation xmlns="http : // www.titan.com/Reservation">
<customer>
<! -- customer info goes here -->
</customer>
< / reservation>
是服务端和客户端约定的东西,这个东西表示了一个方法调用,要被封装在一个信封的 body 中,整个信封是发送http request 时,作为 http request 的 body 发送,发送的内容是纯文本,soap 是内容是约定请求不能随便发,要按一定格式,只有用 soap 这种方式封装的东西才会被对方认为要发送一个访问对方 web 服务的一个请求,中间部分是根据对方的这个接口映射出来
映射的例子:
内容来自于 WSDL,就是 web service description language,web 服务的描述语言,它约定应该怎么去描述
3.WSDL
·
Imagine that you want to develop a web services componentthat implements the following interface:
public interface TravelAgent {
public String makeReservation(int cruiseID,
int cabinID,int customerId,double price);
}
任何一个 web 服务,都会去生成一个这样的文件,这个文件给客户端拿到之后就可以去做他的事情,可以被调用,假设写了一个接口,写了一个 JAVA 的接口,接口为 TravelAgent,里面有一个方法 makeReservation,要传递四个参数,前三个全是整形的,第四个是 double 类型,开发好之后看到是JAVA,希望把它变成 web service,让C#的客户端去调用,C#客户端要给传希望访问 TravelAgent 的 makeReservation 方法,这个请求需要把四个参数传入,约定使用WSDL 方式来描述,生成的 WSDL 文件,如下:
·A WSDL document that describes the makeReservation( ) method might look like this:
<?xml version="1.0"?>
<definitions name="TravelAgent"
xmlns="http: // schemas.xmlsoap.org/wsd1/"
xmlns:soap="http : / lschemas.xmlsoap.org/wsdl/soap/"
xmlns :xsd="http: // www.w3.org/ 20日1/XMLSchema"
xmlns:titan="http: // www.titan.com/TravelAgent"
targetNamespace="http: // www.titan.com/TravelAgent">
!-- message elements describe the parameters and return values --><message name="RequestMessage" >
<part name="cruiseId" type="xsd:int"/>
<part name="cabinId" type="xsd:int"/>
<part name="customerId" type="xsd :int"/>
<part name="price" type="xsd: double"/ >
</ message>
<message name="ResponseMessage">
<part name="reservationId" type="xsd : string"/>
</message>
首先定义两种消息,一种是 RequestMessage,一种是 ResponseMessage,定义输入时这四个参数应该放到一个消息,返回的 string 如何输出,输入是一个 message,输出是一个 message,输入 message 包含四参数,前三个是整型,最后是 double,里面的内容的名字可以随便命名,可以跟前面参数一样,也可以不一样,主要是按顺序做匹配,通常写成一样的,返回值是一个 string 类型,在 java 只有一个 string 类型没有名字,这里可以随便命名,输入的参数和输出参数应该封装在里面,作为文本消息再进行来回传递,这是定义两个消息
<!-- portType element describes the abstract interface of a web service -->
<portType name="TravelAgent">
<operation name="makeReservation">
<input message="titan : RequestMessage" />
<output message="titan: ResponseMessage" />
</operation>
</ portType>
<!--binding element tells us which protocols and encoding styles are used -->
<binding name="TravelAgentBinding" type="titan:TravelAgent">
<soap:binding style="rpc transport="http:/ /schemas.xmlsoap.org/soap/http"/>
<operation name="makeReservation">
<soap: operation soapAction=""/>
<input>
<soap:body use="literal" namespace="http: / /www.titan.com/TravelAgent" />
</input>
<output>
<soap:body use="literal" namespace="http://www.titan.com/TravelAgent" />
</ output>
</operation>
</binding>
PortType 真正在描述 web service 的这个接口是什么,称为 TravelAgent,PortType 命名为 TravelAgent,里面只有一个方法 makeReservation,PortType 中 operation 命名为 makeReservation,makeReservation 有一个input和output,input 对应 RequestMessage,名字可以随便命名,不一定非要称RequestMessage,不说因为称为RequestMessage才能输入,而是引用消息时说明输入。输出是 ResponseMessage,接口里面会有多个 operation,每个operation的input 和 output 不一样,在这里面会定义很多种不同的 message,
<!-- portType element describes the abstract interface of a web service -->
<portType name="TravelAgent">
<operation name="makeReservation">
<input message="titan : RequestMessage" />
<output message="titan: ResponseMessage" />
</operation>
</ portType>
前面部分我们是用纯文本的方式在描述接口是什么样的,接下来描述如何调用和在什么位置。
第二部分如何绑定,如何调用,定义 binding,用 soap 通过 rpc 方式访问,makeReservation 需要把消息整个信封放到这个消息体中,soap:body 是有四种不同的属性,literal 把信封里的内容就当纯文本发送,output 也是用纯文本的方式再返回,以上说如何用 soap 访问,即上面定义好的必须是 soap 消息,以 rpc 的方式访问,在访问时装到信封体以纯文本的方式,还可以作为附件的方式,还有其他的有两种,其他的方式稍微复杂一点,但是基本的概念是用 soap 访问。要用什么样的协议、什么养的编码方式访问,纯文本或编码一下。
<!-- service element tells us the Internet address of a web service -->
<service name="TravelAgentService">
<port name="TravelAgentPort" binding="titan:TravelAgentBinding">
<soap:address location="http: //www.titan.com/webservices/TravelAgent"/>
</port>
</service>
</definitions>
TravelAgentService 对应的名字为 TravelAgentPort,绑定一个位置 http: //www.titan.com/webservices/TravelAgent,soap 以 rpc 方式绑定时,会告诉是如何去访问,真正在访问时,TravelAgent 和 HPPT 协议 port 绑定,假设 TravelAgent 接口后面会有它的实现类做处理,接口用 HTTP 协议绑定,一个 web service 只要用 web 协议就可以访问,假设还可以用ftp访问,要在底下再写一个 service,
<service name="TravelAgentService">
<port name="TravelAgentPort" binding="titan:TravelAgentBinding">
<soap:address location="http: //www.titan.com/webservices/TravelAgent"/>
</port>
这部分内容要再重复,Location这个位置是 ftp 开头,同样一个接口和它后面的实现类,接口有一个数据endpoint端点,通过它来访问,这个端点可以通过两种不同的协议访问,从用户角度,相当于看到两个 service,后台实际是一个东西,service 和后台 endpoint 之间可以是一对多,可以用不同的 binding 和这个协议,binding 是前面映射的TravelAgentBinding,用不同的 soap 形式或者不同的协议去访问,就是两个不同 service,整个 WSDL文件三部分合起来,第一部分描述接口的样子,第二部分描述用什么样的协议和编码机制可以被访问到,第三部分描述在哪个地方,合起来整个 web service 能被访问到。
4.Accessing Web Service
假设有一个.net 开发 C#的一个服务,是一个信用卡的扣款服务,比如这是中国银行的信用卡扣款服务,它是C#写的,开发完之后应该也有一个接口,后面有一个实现类,实现真正的支付服务,接口的前面会生成一个WSDL文件,客户端访问到服务,第一步将 WSDL 文件拿过来,用工具生成两个东西,第一个生成一个 JAVA 的 endpoint,也就上一个接口是 C#,里面可能有一个 Check 方法,当用 WSDL 描述之后,用工具、用一些框架,针对 WSDL 生成 JAVA 版本的接口,接口是 Java 版本,接口是从 WSDL 来的,WSDL 相当于各种不同的语言之间的世界语,把一种语言的接口描述出来之后,用一个工具就可以根据这个描述生成自己语言针对这个接口的一个接口,所以生成了一个JAVA版本的一个接口,编程针对接口编程就可以,代码得到这个接口,调用接口上面的方法,比如 charge,就可以调用charge方法,调用 charge 方法毕竟这边只有一个接口,用 Axis 工具,生成接口时还会生成一个代理,代理就像我们RMI中的stub 和 skeleton,确实实现接口但不是业务逻辑,它是把 JAVA 的调用怎么转换成 soap 的请求,根据 WSDL 转成 soap 的请求消息,发送到这边,如何把一个 soap 的响应消息翻译成 JAVA 调用的一个结果?第一步调用 web 服务,需要拿到 WSDL 文件,文件部署在某个位置,下载拿到,拿到之后用一些工具框架去生成端点和代理,对着接口编程就可以调用 c harge 方法,每当调用时,代理会把 JAVA 的调用请求转换成一个 soap 请求消息,纯文本的 soap 消息,发送到C#端,C#也有一个 proxy,也可以用框架实现或其他第三方框架都可以,请求也是到达 proxy,proxy 把它转换成对C#接口的编程调用,接口转给具体的实现类处理,处理完结果返回,通过 C#端的 proxy 转 成 soap 请求,将soap请求发送到客户端,客户端的 prox y 将 soap 消息转换成JAVA方法这样的结果发给客户端,客户端需要根据 WSDL 生成这两个东西,剩下就像是使用一个本地的 java 接口一样,中间是经历刚才描述的一系列过程,无论是 JAVA 还是 C#都没有针对纯文本的东西做编程,从客户端代和真正实现charge方法的代码来看,像有一个本地的接口在进行编程是一样的,是中间这一条框架做双向的转化,java 中有 java 的框架,C#有 C#的框架,整个过程是从 JAVA 要转换成 soap,发送给 C#,C#再反一个 soap,soap 再转换成 java,这个效率会有问题,效率不高,多来回转换,总共转换次数为24次,要想实现异构系统的交互,确实实现这种跨编程语言的交互,还有跨平台的,比如这边是 Linux,这边是 Windows,但付出的代价就是性能上会有所下降,所以会认为 soap 的这种方式是不得已而为之,必须要解决这个问题,提高性能,soap里面真正有用的是 WSDL 这一部分,但它必须要放到 soap 信封里,在发送数据时显得比较冗余,两次双向的翻译加上传输数据冗余对性能确实有影响。
5.Producing a SOAP Web Service
·pom.xml
<dependencies> <dependency>
<groupld>org.springframework.boot</groupld>
<artifactld>spring-boot-starter-web</artifactld> </dependency>
<dependency> <groupld>org.springframework.boot</groupld>
<artifactld>spring-boot-starter-web-services</artifactld>
</dependency>
<!-- tag::springws[] -->
<dependency>
<groupld>wsdl4j</groupld>
<artifactld>wsdl4j</artifactld> </dependency>
<!-- end::springws[] -->
</dependencies>
编程可以用 spring-boot 开发一个后端 web service 工程,要用到 wsdl4j,这个库是 springws,如果用 spring boot运行是要依赖一个包
·pom.xml
<profiles>
<profile>
<id>java11</id>
<activation>
<jdk>[11,)</jdk>
</activation>
<dependencies>
<dependency>
<groupld>org.glassfish.jaxb</groupld>
<artifactld>jaxb-runtime</artifactld>
</dependency
</dependencies>
</profile>
</profiles>
运行之后要有一个托管的包,就是 JAVA 基于 xml 调用的一个运行实例包,要依赖进去,在运行时,如用java11运行,依赖包在 pom 文件里面要增加这个依赖
·pom.xml
<!-- tag::xsd[] -->
<plugin>
<groupld>org.codehaus.mojo</groupld>
<artifactld>jaxb2-maven-plugin</artifactld>
<version>2.5.0</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<source>${project.basedir}/src/main/resources/countries.xsd</source>
</sources>
</configuration>
</plugin>
<!-- end::xsd[ -->
代码写出之后还需要一个编译工具生成 WSDL,用 xjc 生成,要去再安装一个插件,这个插件呢其实有很多种,比如xjc、codehaus,里面有一个插件,插件类似于编译工具,设计一些代码,会指定一些编译的目标,目标要生成WSDL文件,下面是 web service 的配置文件,描述 WSDL
·countries.xsd
<xs:element name="getCountryRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
<xs:complexType>
</xs:element>
<xs:element name="getCountryResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="country" type="tns:country" >
</xs:sequence>
</xs:complexType>
</xs:element>
客户端要查询一个国家,需要告诉这个国家在哪里,有 getCountryRequest 和 getCountryResponse 两个用xsd文件定义的类型,类型里面有国家的名字和返回国家的内容
·countries.xsd
<xs:complexType name="country">
<xs:sequence>
<xs:element name="name" type="xs:string" />
<xs:element name="population" type="xs:int" />
<xs:element name="capital" type="xs:string" />
<xs:element name="currency" type="tns:currency" >
</xs:sequence>
</xs:complexType>
<xs:simpleType name="currency">
<xs:restriction base="xs:string">
<xs:enumeration value="GBP"/>
<xs:enumeration value="EUR"/>
<xs:enumeration value="PLN"" />
</xs:restriction</xs:simpleType>
</xs:schema>
国家有名字、人口、首都还有货币,前三个都是基本类型,货币是定义的类型,货币包括 GBP、EUR、PLU,GBP、EUR、PLU 这些值当中的某一个
·CountryRepository.java
import io.spring.guides.gs_producing_web_service.Country;
import io.spring.guides.gs_producing_web_service.Currency;
@Component
public class CountryRepository {
private static final Map<String , Country> countries = new HashMap<>(); @PostConstruct
public void initData() {
Country spain = new Country();
spain.setName("Spain");
spain.setCapital("Madrid");
spain.setCurrency(Currency.EUR);
spain.setPopulation(46704314);
countries.put(spain.getName(), spain);
用 spring 的 web service,在类添加 component 标注,定义 PostConstruct 对象创建之后,对 countries 列表添加内容,国家西班牙以及国家的首都、货币、人口等
·CountryRepository.java
Country poland= new Country();
poland.setName("Poland");
poland.setCapital("Warsaw");
poland.setCurrency(Currency.PLN);
poland.setPopulation(38186860);
countries.put(poland.getName(), poland);
Country uk = new Country();
uk.setName("United Kingdom");
uk.setCapital("London");
uk.setCurrency(Currency.GBP);
uk.setPopulation(63705000);
countries.put(uk.getName(), uk);
}
public Country findCountry(String name) {
Assert.notNull(name, "The country's name must not be null");
return countries.get(name);
}
}
国家有波兰、英国,定义好之后,定义一些操作的方法,定义三 findcountry,返回这个国家的用参数名字所表示的国家的那个信息,要真正要暴露出让别人访问 countrypoint 的一个 endpoint,这个端点与 web service 相关,只有一个 CountryRepository 对象,getCountry 方法会暴露出去,用户会从s oap 消息传递一个请求,new 一个response,把 r esponse 填进去,请求通过 g etName 得到名字,把名字调用 countryRepository 上面的 findCountry 方法就得到一个country,放到了 response 中,把 response 返回,真正接收用户请求的像web service服务
·WebServiceConfig.java
@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, " /ws/*");
}
@Bean(name = "countries")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CountriesPort");
wsdl11Definition.setLocationUri(" /ws");
wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-producing-web-service");
wsdl11Definition.setSchema(countriesSchema);
return wsdl11Definition;
}
@Bean
public XsdSchema countriesSchema() {
return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
}
}
底下是配置文件,如果不用类来描述,用 xml 文件配置也可以,Configuration 是配置,第二个 EnableWs说要enable 加 web service