一、什么是WebService
- WebService直译网络服务,是RPC的一种实现方式。
- RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议
- 客户端要能调用服务端必须遵循TCP协议,而WebService使用的是更高一级的HTTP协议。
- 客户端访问服务器的目的是为了获取数据,那数据必须是与平台、语言无关的。WebService采用的是XML.
二、使用场景
- 由权威机构提供的第三方小功能。
比如Webxml等,发布了一些服务。我们作为客户端来使用。
2.多种客户端的整合。
一些大型应用公司(比如腾讯)为了提高影响力,它会针对不同的平台提供开发不同的客户端。而客户端需要调用获取数据,使用RPC,WS就是一种实现方式。而根据运行环境不同,客户端分为:PC端(web,PC客户端),手机端(web,客户端),微信端。
3.异构系统的整合。
将多个已经建设好的系统,组合成一个系统,统一对外发布服务。
三、开发基于JDK的webService服务
1、开发服务端
开发基于jdk的webService服务,常用的注解都在下面的代码中:
/**
*
* @author lwb 2017年12月28日 下午10:31:31
*
* 用户输入姓名,查看年龄
*
*/
//这里可以改服务的名称,否则为默认的名称,类名+Serive 例子:UserServiceService
@WebService(serviceName="UserService")
//这是默认的格式
//@SOAPBinding(style=Style.DOCUMENT)
@SOAPBinding(style=Style.RPC)
public class UserService {
private static Map<String,Integer> user = new HashMap<>();
static {
user.put("小赖", 26);
user.put("小王", 26);
user.put("小付", 25);
user.put("小钟",24);
}
//这里有没有WebMethod标签,这个方法都是发向外发布出去的
//这个方法的名称也是可以改变的,如果不改变就是默认的方法名
@WebMethod(operationName="getUserAge")
//@WebParam(name = "getAge")可以在发布的wsdl中看见参数的名称
public @WebResult(name="userAge") String getAge(@WebParam(name = "getAge")String name) {
if(name == null) {
return "错误:请输入姓名";
}
if(user.get(name) == null) {
return "你查找的姓名没有信息";
}
return name + ":" + user.get(name);
}
//如果@WebMethod(exclude=true)
//那么这个方法就不会被暴露出去
@WebMethod(exclude=true)
public int getAge(int age) {
return age;
}
}
②发布服务:
import javax.xml.ws.Endpoint;
public class PublicService {
public static void main(String[] args) {
//发布服务的地址
String address = "http://127.0.0.1:8888/user";
//服务的实现类
Object implementor = new UserService();
//发布服务
Endpoint.publish(address, implementor);
System.out.println("服务发布成功.....");
}
}
四、在浏览器中访问发布的服务
在浏览其中访问发布的服务,访问地址就是上面代码中发布的地址:
值得注意的是,在JDK7.0之前,可以直接使用地址访问,如:http://127.0.0.1:8888/user
但是在JDK8.0之后,访问的地址之后需要加上?wsdl,如:http://127.0.0.1:8888/user?wsdl
访问结果截图如下:(图片看不清楚,自己访问一下就知道了)
五、WSDL文件解释
在WSDL文件的最外层definitions标签里有两个非常关键的属性,targetNamespace属性和name属性。targetNamespace属性对应的值来自我们的包结构,是以倒置包名的形式呈现。name属性值是在发布服务在WS中的名称:服务名称=服务类名称+Service。
由于图片太大,看不清楚,所以一步一步记录,将xml文件折叠之后:
①service标签:
②binding标签:
③portType标签:
④message标签:
上图中为啥可以看见可以看见参数的名称?就是因为我们代码中修改了名称,否则就是默认的arg0做为参数名称。
所以,看这个xml文件的时候,是从下向上看,看起来就特别简单了。
六@SOAPBinding(style=Style.DOCUMENT)和@SOAPBinding(style=Style.RPC)的区别
在RPC中,可以看见message标签是这样的,能够看见输入参数和输出参数的类型 和名字:
但是在DOCUMENT中就看不见,但是多了一个<xsd:schema>标签
其中有一个schemalocation属性,访问该地址,就是对输入输出参数的详细介绍了:
为什么会有这样的设计方式,就是为了在参数很多,或者很复杂的情况,可以看到更清楚,更方便,没有别的用意,具体使用哪一种,就视情况而定了。
七、客户端开发
客户端开发是使用jdk自带的工具自动生成的:
wsimport -d . http://127.0.0.1:8888/user?wsdl
-d:将会在指定目录下生成.class文件
wsimport -s . http://127.0.0.1:8888/user?wsdl
-s:将会在指定目录下生成.java文件
比如,我进入我的项目路径下:
当然,这个时候我们的服务端必须是开启的。然后进入我的客户端,就可以看见生成的代码了:
然后编写一个类,穿件调用客户端对象的方法,就可以了:
拓展:
使用@ SOAPBinding标签,需要把他标记在类上:
SOAPBinding.ParameterStyleparameterStyle 确定方法参数是否表示整个消息正文,或者参数是否是包装在以操作命名的顶层元素中的元素。
SOAPBinding.ParameterStyle.WRAPPED,默认,使用对参数进行包装
SOAPBinding.ParameterStyle.BARE,不对参数进行包装
SOAPBinding.Style style 定义发 送到Web Service 的消息 和从Web Service发送的消息的编码样式。
SOAPBinding.Style.RPC:面向RPC
SOAPBinding.Style.DOCUMENT 默认,面向文档
SOAPBinding.Useuse 定义发送到WebService的消息和从WebService发送的消息的格式样式。
SOAPBinding.Use.LITERAL,默认,字面量风格,若服务端和客户端不在一起开发,就应该使用这个
SOAPBinding.Use.ENCODED使用SOAP编码风格,可能导致WS互操作方面失败问题,尽量避免使用。
八、CXF基础
在lib包中选择需要的文件:
2、服务端开发
服务端的开发,和JDK的开发方式一样,因为CXF是遵循wsdl协议的,所以对JDK的标签也是一样的使用:
首先定义一个接口:
@WebService
public interface StudentScoreInterface {
@WebMethod
public int getScore(String studentName);
}
然后实现该接口:
//必须指定对外发布服务的接口
@WebService(endpointInterface="cn.laiwenbo.com.cxf.studentService.StudentScoreInterface")
public class StudentService implements StudentScoreInterface {
private static Map<String,Integer> map = new HashMap<>();
static {
map.put("小赖", 100);
map.put("小王", 90);
map.put("小李", 80);
map.put("小张", 70);
}
@Override
public int getScore(String studentName) {
return map.get(studentName);
}
}
发布服务:
public class PublishStudentService {
public static void main(String[] args) {
//jdk的发布方式
/*String address = "http://127.0.0.1:8888/score";
Object implementor = new StudentService();
Endpoint.publish(address, implementor);
System.out.println("服务发布成功....");*/
//CXF发布方式
JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean();
String address = "http://127.0.0.1:8888/score";
Object implementor = new StudentService();
//设置服务的地址
factoryBean.setAddress(address);
//设置服务的实现类
factoryBean.setServiceBean(implementor);
//设置接口
factoryBean.setServiceClass(StudentScoreInterface.class);
//发布
factoryBean.create();
}
}
3、客户端开发:
开发客户端,同样是使用工具,由于cxf是没有配置环境变量的,所以需要在CXF的工具文件夹下来执行命令:
wsdl2java -d . -p cn.lwb.client http://127.0.0.1:9527/hello?wsdl
-p :可以指定文件生成的位置
. (点):表示当前目录
所以我需要进入到CXF的工具文件夹目录下:
wsdl2java -d G:\laiwenbo\java\eclipse_oxygen\webServicePublish\src(文件生成位置)
cn.itsource.client http://127.0.0.1:9527/hello?wsdl(访问地址)
生成的结果如下:
由于多一个方法,就会多两个文件,比如我的只有一个getScore方法,所以就生成了GetScore.java和
GetScoreResponse.java两个文件。
访问服务:
(1)JDK的访问方式:
//JDK的访问方式,虽然是用CXF生成的,但是JDK的方式依然是可以访问的
@Test
public void jdkClient() throws Exception {
StudentScoreInterfaceService scoreInterfaceService = new StudentScoreInterfaceService();
StudentScoreInterface port = scoreInterfaceService.getStudentScoreInterfacePort();
int score = port.getScore("小李");
System.out.println(port);
System.out.println(score);
}
(2)CXF的访问方式,不需要使用工具生成文件,而是直接调用接口:
/**
* CXF的方式调用服务
* @throws Exception
*/
@Test
public void testCXF() throws Exception {
// 1.创建JaxWsProxyFactoryBean的对象,用于接收服务
JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
// 2.设置服务的发布接口,使用本地的代理接口
factoryBean.setServiceClass(StudentScoreInterface.class);
// 3.设置服务的发布地址,表示去哪里获取服务
String address = "http://127.0.0.1:8888/score";
factoryBean.setAddress(address);
// 4.通过create方法返回接口代理实例
StudentScoreInterface service = (StudentScoreInterface)factoryBean.create();
System.out.println(service.getScore("小赖"));
}
这么看来,CXF反而比JDK复杂了,好像并没有什么特别的地方,但是注意,我们生成的文件可以全部删掉,除了唯一的一个接口之外:
也就是说,删成这样之后,依然没有问题,这就是CXF的优点,注意,删除之后,在剩下的接口中有一个地方会报错:
这个注解没有什么别的作用,就是说可以看另一个地方的东西而已,注释掉就是了:
(3)CXF的另一种调用方式,动态代理:
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
org.apache.cxf.endpoint.Client client = dcf.createClient("http://127.0.0.1:9999/weather?wsdl");
Object[] res = null;
try {
res = client.invoke("getWeather", new String[]{"小赖"});
for (Object obj:res) {
System.out.println(obj);
}
} catch (Exception e) {
e.printStackTrace();
}
getWeather:为服务方法名
new String[]{"小赖"} :为传入的参数素组,按照参数顺序排列
(4)axis2调用
以上就是webService的全部基础了,后面如果学习到新的东西,会补充进来。