Web Service
1. 概述
Web Service是一个平台独立的,低耦合的,基于可编程的web的应用程序,可使用开放的XML标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。
Web Service技术, 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件, 就可相互交换数据或集成。
依据Web Service规范实施的应用之间, 无论它们所使用的语言、 平台或内部协议是什么, 都可以相互交换数据。
Web Service为整个企业甚至多个组织之间的业务流程的集成提供了一个通用机制。
两个公司的产品要战略合作,即相互之间的业务要对接,在彼此的业务中都会融入对方的业务,彼此需要对方的业务数据。
电商系统中,比如订单子系统,和,递送子系统。两个系统是独立的两个产品,不在同一个服务器,有不同的运行环境,不同的语言环境。但订单系统中需要递送子系统的业务数据。
我的应用中想要在首页显示天气,则我需要气象局的系统业务信息。
………
每当要在不同的远程的应用、系统之间通信、集成、协作时,该如何?
调用本地类、jar中的功能很简单,但当要调用的业务功能在远方,该如何进行远程调用?
如果可以实现,将实现互联网中的软件和数据的极大重用。
Web Service技术使得我们可以将应用中的业务处理暴露出来,发布到互联网中,
但注意,这并不意味着将所有源码暴露,只是在互联网中暴露了一个入口,需要此业
务的一方,可以发现并通过此入口远程调用到入口所连接着的业务。
业务的提供方将业务发布成Web Service.业务的调用方发现此入口并完成业务的远程调用。
如此则以上的设想都可实现。
2. 架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G9ymPMTL-1571626984101)(mdpic/archetype.jpg)]
soap协议
如上图所示,soap协议时web service的通信协议。
soap=http+xml 即soap协议在通信数据时依然采用http+协议,不过通信的数据是具有特定规格的xml。
wsdl
web service description language 描述语言。
web service的描述者,web服务会有自己的wsdl,要发现某个web服务就需要知道其对应的wsdl,之后才可以远程调用到互联网中的web服务。
3. 开发流程
3.1 定义Web-Service
public interface WeatherInterface {
public String queryWeather(String cityName);
}
@WebService //注解标识 此为一个webservice组件
public class WeatherImpl implements WeatherInterface{
@Override
public String queryWeather(String cityName) {
System.out.println("city:"+cityName);
return "晴天~微风~";
}
}
3.2 发布 Web-Service
public static void main( String[] args ){
// 服务地址 服务实现
Endpoint.publish("http://localhost:8989/weather",new WeatherImpl());
}
3.3 WSDL
访问:http://localhost:8989/weather?wsdl 【服务地址?wsdl】,查看服务的说明,亦验证服务是否发布成功
4. 调用流程
4.1 创建服务客户端
`wsimport -s . http://localhost:8989/weather?wsdl
会在当前目录下,生成客户端代码
4.2 调用
//将客户端代码,复制到项目中,并调用即可
public static void main( String[] args ){
//创建web-service并获得Port对象
WeatherImpl port = new WeatherImplService().getWeatherImplPort();
//通过Port对象调用远程过程 (RPC)
System.out.println(port.queryWeather("保定"));
}
4.3 查询手机号归属地查询
wsimport -s . http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?WSDL
public static void main( String[] args ){
// 查询手机号归属地
System.out.println(new MobileCodeWS().getMobileCodeWSSoap().getMobileCodeInfo("18301161911", null));
}
4.4 查询天气
.....
4.5 细节
serviceName=修改默认服务名;
name=修改默认Port类型
…
@WebService(serviceName = "myservice", 服务名默认 “类名+service”
name="WeatherImpl", portType 默认"类名"
portName = "WeatherPort", Port名 默认“类名+Port”
targetNamespace = "http://ws.zhj.com") 默认“http://包路径倒写/”
public class WeatherImpl implements WeatherInterface{
@WebMethod(operationName = "queryWeather99")
public String queryWeather(@WebParam(name = "city") String cityName) {
System.out.println("city:"+cityName);
return "晴天~微风~";
}
}
5. CXF
Web-Service框架
通过CXF,将web-service更好的和web项目集成
在现有的spring工厂中,将一个web-service,作为一个组件,纳入工厂,完成web-service 和现有项目的整合
<!-- CXF 依赖 -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.1.10</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.1.10</version>
</dependency>
<!-- spring 照旧 ... -->
5.1 服务端集成
5.1.1 定义Web-Service
@WebService
public class WeatherImpl implements WeatherInterface{
@Override
@WebMethod(operationName = "queryWeather")
public String queryWeather(@WebParam(name = "city") String cityName) {
System.out.println("city:"+cityName);
return "晴天~微风~";
}
}
####5.1.2 将Web-Service纳入工厂
<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:ws="http://cxf.apache.org/jaxws"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<!-- 将服务作为组件,纳入工厂 -->
<ws:endpoint address="/hello" implementor="ws.WeatherImpl"></ws:endpoint>
</beans>
5.1.3 CXFServlet
<!-- web.xml -->
<!-- 接收调用服务的请求,找到工厂,调用工厂中的服务组件
http://xxxx/ws/hello 请求被该Servlet捕获,请求路径为 "/ws/hello",其中 “/*”所匹配的内容是“/hello”
则此servlet会用“/hello”去工厂中查找是否有此路径的服务组件。
-->
<servlet>
<servlet-name>cxf</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<!-- 启动工厂,此处照常在项目启动时启动工厂,在工厂启动时,工厂中的 服务组件 也就启动啦,
后续可以由 CXFServlet 转发接收外界调用 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
5.1.4 管理模式
如上,
服务器启动,工厂启动,服务发布
服务器关闭,工厂关闭,服务关闭
5.2 客户端集成
5.2.1 生成客户端代码
同样需要生成客户端代码
5.2.2 将客户端纳入工厂
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:ws="http://cxf.apache.org/jaxws"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<!-- webservice( Interface ) 客户端纳入工厂 -->
<ws:client id="ws"
serviceClass="com.zhj.ws.WeatherImpl"
address="http://xxxx?wsdl"></ws:client>
<!-- 将webservice 的客户端任意注入到需要rpc的本地业务中 -->
<bean class="com.zhj.service.MyLocalService" id="localService">
<property name="xxx" ref="ws"/>
</bean>
</beans>
5.2.3 测试
// 启动项目,启动工厂,在Controller中调用本地业务,即可。
// 或可直接在main函数中,启动工厂,获取本地业务,测试即可。
6. CXF-Rest
以上是Soap协议的Web-Service.
还可以定义 Rest风格的Web-Service, Rest-WebService会直接使用HTTP协议,效率更高,也更加流行。
<!-- cxf -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
<version>3.1.10</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.1.10</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-service-description</artifactId>
<version>3.1.10</version>
</dependency>
<!-- 跨域 -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-security-cors</artifactId>
<version>3.1.10</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.9.8</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<!-- spring照旧 -->
6.1 服务端集成
6.1.1 定义Web-Service
@Path("/user")
@Produces("application/json;charset=utf-8") //每个方法都返回json格式数据
public class MyRestService {
@Path("/query/{id}")
@GET
public User queryUser(@PathParam("id") Integer id){// @PathParam接收路径中的数据
return new User(id,"注解",true,new Date());
}
@Path("/insert")
@POST
public Status insertUser(User user){
System.out.println("insert:"+user);
return new Status("ok");
}
@Path("/update")
@PUT
public Status updateUser(User user){
System.out.println("update:"+user);
return new Status("ok");
}
@Path("/delete/{id}")
@DELETE
public Status deleteUser(@PathParam("id") Integer id){// @PathParam接收路径中的数据
System.out.println("delete id:"+id);
return new Status("ok");
}
}
public class User {
private Integer id;
private String name;
private Boolean gender;
@JsonFormat(pattern = "yyyy+MM+dd") //Jackson日期格式化( 序列化+反序列化 )
// @JsonFormat(pattern = "yyyy-MM-dd")
//@JSONField(format = "yyyy+MM+dd") //FastJson日期格式化( 序列化+反序列化 )
//@JSONField(format = "yyyy/MM/dd")
private Date birth;
...
}
public class Status {
private String status;
...
}
6.1.2 将Web-Service纳入工厂
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:rs="http://cxf.apache.org/jaxrs"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd">
<!-- web服务,进入工厂,成一个单独的组件
随工厂的启动而启动,随工厂的关闭而关闭
-->
<rs:server address="/ws9">
<rs:serviceBeans>
<bean class="com.zhj.ws.MyRestService"/>
<bean class="com.zhj.ws.MyRestService2"/>
...
</rs:serviceBeans>
<rs:providers>
<!-- json解决方案,Jackson -->
<bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
<!-- json解决方案,FastJson
<bean class="com.alibaba.fastjson.support.jaxrs.FastJsonProvider"/>-->
<bean id="sf" class="org.apache.cxf.rs.security.cors.CrossOriginResourceSharingFilter">
<!-- 指定允许哪些域访问,默认:"*",即所有域都可以访问 -->
<property name="allowOrigins">
<list>
<value>http://localhost:8990</value>
</list>
</property>
<!-- 允许其他域 携带本域cookie -->
<property name="allowCredentials" value="true"/>
</bean>
</rs:providers>
</rs:server>
</beans>
6.1.3 CXFServlet
<!-- CXFServlet -->
<servlet>
<servlet-name>cxf</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/cxf/*</url-pattern>
</servlet-mapping>
<!-- spring 启动工厂 照旧 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
6.1.4 管理模式
依然,
服务器启动,工厂启动,服务发布
服务器关闭,工厂关闭,服务关闭
细节:访问CXFServlet,http://xxxx/cxf 有惊喜!
6.2 客户端调用
由于直接使用http协议,
则Get方式的方法可以直接通过浏览器测试
。而且,不存在客户端代码的概念。
6.2.1 Ajax调用
var xhr = new XMLHttpRequest();
xhr.open("get","http://localhost:8989/cxf/ws9/user/query/1");
// 申请携带目标域cookie (可选)
xhr.withCredentials=true;
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
var ret = xhr.responseText;
console.log(ret);
}
}
xhr.send();
/
var xhr2 = new XMLHttpRequest();
xhr2.open("put","http://localhost:8989/cxf/ws9/user/update");
// 申请携带目标域cookie (可选)
xhr2.withCredentials=true;
xhr2.setRequestHeader("content-type","application/json;charset=utf-8")// 声明请求参数数据格式为json
xhr2.onreadystatechange=function(){
if(xhr2.readyState==4){
var ret = xhr2.responseText;
console.log(ret);
}
}
xhr2.send('{"id":1,"name":"zhj","gender":true,"birth":"2019+12+13"}');//发送json数据
//
var xhr3 = new XMLHttpRequest();
xhr3.open("delete","http://localhost:8989/cxf/ws9/user/delete/99");
// 申请携带目标域cookie (可选)
xhr3.withCredentials=true;
xhr3.onreadystatechange=function(){
if(xhr3.readyState==4){
var ret = xhr3.responseText;
console.log(ret);
}
}
xhr3.send();
6.2.2 Java调用
<!-- 导入 HttpClient-Fluent包 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
@Test
public void teHTTP() throws ClientProtocolException, IOException{
String user = Request.Get("http://localhost:8989/cxf/ws9/user/query/1") //get请求
.execute() //发送请求
.returnContent() //接收响应
.asString(Charset.forName("utf-8")); //响应转换为字符串格式
System.out.println(JSON.parseObject(user,User.class));//解析
}
@Test
public void testHTTP2() throws ClientProtocolException, IOException {
//生成json
String userJson = JSON.toJSONString(new User(null, "zhj", true, new Date()));
Request.Post("http://localhost:8989/cxf/ws9/user/insert")
.bodyByteArray(userJson.getBytes(Charset.forName("utf-8")))//json转为字节作为请求体
.setHeader("content-type", "application/json;charset=utf-8")//请求头,声明请求体中数据格式
.execute();//发送请求,此处如果没有响应内容,就不用调用returnContrent()方法。
}
@Test
public void testHTTP3() throws ClientProtocolException, IOException{
String userJson = JSON.toJSONString(new User(1, "zhj", true, new Date()));
Request.Put("http://localhost:8989/cxf/ws9/user/update")
.bodyByteArray(userJson.getBytes(Charset.forName("utf-8")))
.setHeader("content-type", "application/json;charset=utf-8")
.execute();
}
@Test
public void testHTTP4() throws ClientProtocolException, IOException{
String ret = Request.Delete("http://localhost:8989/cxf/ws9/user/delete/11")
.execute()
.returnContent()
.asString();
System.out.println(ret);
}