目录
webService 服务端准备
浏览器访问
WSDL文件剖析
数据类型说明
@WebResult 与 @WebParam
学生实体验证示例
服务端
客户端
1、web Service 服务端准备如下:
1、CalculatorService:web Service 服务端对外提供的服务接口
2、CalculatorServiceImpl:web Service 服务端对外提供的服务接口的实现类
3、Web_Service:用于启动 web Service 服务
2、提供一个简单的服务接口,用于计算 "加法" 与 "乘法"。
web Service 服务端 3 个类的源码分别如下:
import javax.jws.*;
/**
* Created by Administrator on 2019/1/25 0025.
* 计算器接口
* SEI(Service Endpoint Interface):服务终端接口,即webService提供的服务接口
* SIB(Service Implemention Bean):服务实现类,即webService提供的服务接口的实现类
*
* @javax.jws.WebService : 将 Java 类标记为实现 Web Service,或者将 Java 接口标记为定义 Web Service 接口
* 不过是服务端还是客户端,接口上都必须写上此注解
*/
@WebService
public interface CalculatorService {
//加法
public float addition(float a, float b);
//乘法
public float multiplication(float a, float b);
}
import javax.jws.WebService;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/1/25 0025.
* SEI(Service Endpoint Interface):服务终端接口,即webService提供的服务接口
* SIB(Service Implemention Bean):服务实现类,即webService提供的服务接口的实现类
*
* @javax.jws.WebService : 将 Java 类标记为实现 Web Service,或者将 Java 接口标记为定义 Web Service 接口
* endpointInterface:终端接口,定义服务抽象 Web Service 协定的服务端点接口的完整名称,通常设置为服务实现类的全路径
* webService 服务端中的接口与实现类都必须加 @WebService 注解
* @WebService 注解接口一共提供了以下属性,而且都有默认值,它们将来都会出现在 wsdl 的 xml 文件中
* String name() default "";
* String targetNamespace() default "";
* String serviceName() default "";
* String portName() default "";
* String wsdlLocation() default "";
* String endpointInterface() default "";
*/
@WebService(endpointInterface = "com.lct.web_service.CalculatorService")
public class CalculatorServiceImpl implements CalculatorService {
//日志记录器
public static final Logger logger = Logger.getGlobal();
@Override
public float addition(float a, float b) {
logger.info("加法计算:" + a + " + " + b + " = " + (a + b));
return a + b;
}
@Override
public float multiplication(float a, float b) {
logger.info("乘法计算:" + a + " x " + b + " = " + (a * b));
return a * b;
}
}
import javax.xml.ws.Endpoint;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/1/25 0025.
* webServer 启动类
*/
public class Web_Service {
//日志记录器
public static final Logger logger = Logger.getGlobal();
public static void main(String[] args) {
/**webService服务器提供给客户端访问的地址
* 192.168.1.20 为服务器 ip、3333为指定的端口号、web_server 为应用名称、calculator 为标识
* 这个地址符合 http 地址即可,为了看起来更像是 web访问,所以才写了应用名,即使是 http://192.168.1.20:3333/xxx 也是可以的
*/
String wsUrl = "http://192.168.1.20:3333/web_server/calculator";
/**
* javax.xml.ws.Endpoint 表示一个 web service 终端,这是一个抽象类,其中提供了静态方法可以直接调用
* Endpoint publish(String address, Object implementor)
* address:传输协议的 url 地址;
* implementor(实现者):web service 终端的实现类,因为一个 ws 接口可能会有多个实现类
*/
Endpoint.publish(wsUrl, new CalculatorServiceImpl());
logger.info("webService 服务启动...");
}
}
1、启动 webService 服务端,然后直接通过浏览器进行访问;
2、访问时结尾必须加 "?wsdl" 参数;
为了后面分析 wsdl 文件更加清晰,这里贴出 wsdl 文件的全部内容如下:
This XML file does not appear to have any style information associated with it. The document tree is shown below.
WSDL(Web Services Description Language) 是基于 XML web服务描述语言,用于描述 Web Service 及其函数、参数和返回值等,这样客户端才能方便调用。一些开发工具既能根据 Web service 生成 WSDL 文档,又能导入 WSDL 文档,生成调用相应 WebService 的代理类代码。
WSDL 文件保存在Web服务器上,通过一个 url 地址即可访问。客户端要调用一个WebService服务之前,要知道该服务的WSDL文件的地址,WebService 服务提供商通常通过两种方式来暴露它的 WSDL 文件地址:1、注册到UDDI服务器,以便被人查找;2、直接告诉给客户端调用者。
1、WSDL 文件作为一个 XML 文件,顶部必须用 声明。
2、WSDL 文档的根元素是 definitions 元素,WSDL 文档包含5部分:types, message, portType, binding,service 。
1)definitions 元素
A、WSDL 文档的根元素都是 definition 元素,其中主要的是最后的两个属性,targetNameSpace 与 name。
B、targetNameSpace 属性 对应 @WebService 注解的 targetNameSpace属性,name 属性对应 @WebService 注解的 serviceName 属性。
C、可以在 webService 服务端的接口实现类中的 @WebService 注解中通过修改属性,从而修改 wsdl 文件的definition 元素的属性值,否则 targetNameSpace 为 "http://" 加 "包名的反写",name 为 "实现类名称"+"Service"。
2)types 元素:定义访问的类型,如请求的方法名,参数,以及返回值等。
可以浏览器访问 schemaLocation 属性的值,看到详细信息。
3)message:SOAP 协议消息传递的参数,同样包括请求与返回两部分。
message 的元素会和 types 中的元素进行对应。
4)portType:指明服务器的接口,并且通过 operation 绑定相应的 input、ouput 消息。
5)binding:指定消息传到所使用的格式
6)service:指定服务所发布的名称
1、Java 默认提供的 JWS API 开发 webService 时,支持自己的 8 种基本类型: boolean、byte、short、int、long、float、double 和 char、也支持引用类型 List、Set、Array,以及自定义的 POJO 对象 等,但唯独不支持 Map 类型。
2、如果服务接口方法的参数或者返回值是 Map 类型,则 Endpoint.publish 发布时直接报错失败,如下所示为方法的返回值使用 Map 类型时,启动服务时报错:
Exception in thread "main" javax.xml.ws.WebServiceException:
class com.lct.web_service.jaxws.GetStudentMapResponse do not have a property of the name return
3、所以要么就服务接口中的方法参数以及返回值尽量不要使用 Map 类型,换成其它类型,如 List 等,如果一定要用 Map 类型,则只能导入 CXF 等第三方框架的 Jar 包,不用改变任何代码,即可正常发布成功。客户端仍然使用 JWS 的 API 即可,可以不用导入 CXF 的 包。
上面 types 元素定义访问的类型,如请求的方法名,参数,以及返回值等,使用的都是默认值,如果想要进行修改,则可以使用 这两个注解。
@WebResult :定义 wsdl 文件中,服务端方法返回值名称
@WebParam :定义 wsdl 文件中,客户端请求的方法参数名称定义在接口中的方法上即可,如下所示进行修改,其余内容不变。
import javax.jws.*;
/**
* Created by Administrator on 2019/1/25 0025.
* 计算器接口
* SEI(Service Endpoint Interface):服务终端接口,即webService提供的服务接口
* SIB(Service Implemention Bean):服务实现类,即webService提供的服务接口的实现类
*
* @javax.jws.WebService : 将 Java 类标记为实现 Web Service,或者将 Java 接口标记为定义 Web Service 接口
* 不过是服务端还是客户端,接口上都必须写上此注解
*/
@WebService
public interface CalculatorService {
/**
* @WebResult :定义 wsdl 文件中,服务端方法返回值名称
* @WebParam :定义 wsdl 文件中,客户端请求的方法参数名称
*/
//加法
@WebResult(name = "additionResult")
public float addition(@WebParam(name = "a") float a, @WebParam(name = "b") float b);
//乘法
@WebResult(name = "multiplicationResult")
public float multiplication(@WebParam(name = "a") float a, @WebParam(name = "b") float b);
}
这里提供两个服务,一是查询学生在校是否有惩罚的服务(如警告、记过等),二是查询学生的缴费状态。
服务端内容结构如上。
Student:学生实体类、StudentService:提供的学生服务接口、StudentServiceImpl:服务接口的实现
Web_Service:用于启动 webService 服务
import java.util.Date;
/**
* Created by Administrator on 2019/2/13 0013.
* Student 实体
*/
public class Student {
/**
* sID:学生学号
* sName:学生姓名
* sex:性別,true 表示男生、false 表示女生
* birthday:学生生日
* punishStatus:在校是否被惩罚过,未惩罚(0)、警告(1)、记过(2)、记大过(3)、留校察看(4)、开除学籍(5)
*/
private Integer sID;
private String sName;
private Boolean sex;
private Date birthday;
private Integer punishStatus;
public Boolean getSex() {
return sex;
}
public void setSex(Boolean sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Integer getPunishStatus() {
return punishStatus;
}
public void setPunishStatus(Integer punishStatus) {
this.punishStatus = punishStatus;
}
public Integer getsID() {
return sID;
}
public void setsID(Integer sID) {
this.sID = sID;
}
public String getsName() {
return sName;
}
public void setsName(String sName) {
this.sName = sName;
}
@Override
public String toString() {
return "Student{" +
"birthday=" + birthday +
", sID=" + sID +
", sName='" + sName + '\'' +
", sex=" + sex +
", punishStatus=" + punishStatus +
'}';
}
}
import com.lct.domain.Student;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
/**
* Created by Administrator on 2019/2/13 0013.
* 用户登录校验服务接口
*
* @javax.jws.WebService : 将 Java 类/接口标记为 Web Service(web服务)
* 服务端的 web service 接口与其实现类都必须写 @WebService 注解
*/
@WebService
public interface StudentService {
/**
* 根据学生学号获取学生 "奖惩信息"
*
* @param sId :学生学号
* @return 返回整个学生的简要实体数据, 主要是演示返回值为实体
*/
@WebResult(name = "getStudentById")
public Student getStudentById(@WebParam(name = "sId") Integer sId);
/**
* 学生是否支付全部学费
*
* @param student
* @return
* @WebParam 的作用是 java jdk wsimport 工具生成的代理类看起来可以更直观,否则会是 arg0、arg1 ...
* 单纯从调用上来说,写没写 @WebParam 调用都是一样的
* @WebResult 也是同理,作用是使生成的 wsdl 文件看起来更直观,从调用上来说,写不写都是一样的
*/
@WebResult(name = "isPayTuition")
public boolean isPayTuition(@WebParam(name = "Student") Student student);
}
import com.lct.domain.Student;
import javax.jws.WebService;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/13 0013.
*
* @javax.jws.WebService : 将 Java 类/接口标记为 Web Service(web服务),服务端的 web service 接口与其实现类都必须写 @WebService 注解
* endpointInterface:终端接口,定义服务抽象 Web Service 协定的服务端点接口的完整名称,通常设置为服务实现类的全路径
* 写了endpointInterface,@WebResult、@WebParam才会有效
* serviceName:对应生成的 WSDL 文件中的 definitions 元素中的 name 属性值,默认以服务类名称+"Service"为值
* 客户端连接时使用QName创建实例时需要传入此 name 属性值,以及targetNamespace属性值,都可以在 @WebService 中指定
*/
@WebService(endpointInterface = "com.lct.web_service.StudentService", serviceName = "StudentService")
public class StudentServiceImpl implements StudentService {
/**
* 日志记录器
*/
public static final Logger logger = Logger.getGlobal();
@Override
public Student getStudentById(Integer sId) {
/**随机生成一个学生,只做演示*/
Student student = new Student();
student.setsID(sId);
student.setBirthday(new Date());
student.setPunishStatus(new Random().nextInt(6));
student.setsName(UUID.randomUUID().toString());
logger.info("获取学号 " + sId + " 的学生信息结果为:" + student.toString());
return student;
}
@Override
public boolean isPayTuition(Student student) {
/**随机生成是否已经交费,nextInt(a)生成 [0,a)直接的随机整数*/
boolean flag = new Random().nextInt(2) == 0 ? true : false;
logger.info(student.toString() + " 是否已经全部缴费?" + flag);
return flag;
}
}
import javax.xml.ws.Endpoint;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/1/25 0025.
* webServer 启动类
*/
public class Web_Service {
//日志记录器
public static final Logger logger = Logger.getGlobal();
public static void main(String[] args) {
/**webService服务器提供给客户端访问的地址
* 192.168.1.20 为服务器 ip、3333为指定的端口号、web_server 为应用名称、student 为标识
* 这个地址符合 http 地址即可,为了看起来更像是 web访问,所以才写了应用名,即使是 http://192.168.1.20:3333/xxx 也是可以的
*/
String wsUrl = "http://192.168.1.20:3333/web_server/student";
/**
* javax.xml.ws.Endpoint 表示一个 web service 终端,这是一个抽象类,其中提供了静态方法可以直接调用
* Endpoint publish(String address, Object implementor)
* address:传输协议的 url 地址;
* implementor(实现者):web service 终端的实现类,因为一个 ws 接口可能会有多个实现类
*/
Endpoint.publish(wsUrl, new StudentServiceImpl());
logger.info("webService 服务启动,等待客户端请求...");
}
}
启动 webService 后,浏览器便可以进行访问,整个 wsdl 文件:http://192.168.1.20:3333/web_server/student?wsdl、数据类型地址:http://192.168.1.20:3333/web_server/student?xsd=1 (可在 types 的 schemaLocation 属性中找到)。
新建客户端项目,客户端使用 Java JDK wsimport.exe 工具根据服务端提供的 wsdl 文件地址生成代理类,在 cmd 窗口中操作命令如下:
wsimport -d E:/wmx/webservice -keep -verbose http://192.168.1.20:3333/web_server/student?wsdl
其中 -d 表示输出内容存放的目录,此目录必须事先存在;最后是 wsdl 地址,结尾必须加上 ?wsdl 参数。
如上所需要的一共7个,2个方法输入输出一共生成4个类,加上 ObjectFactory 对象工厂类、以及 Student 实体类、StudentService 服务接口。导入到客户端项目中:
其中的 Web_service 用于启动应用,调用服务端方法,其内容如下:
import com.lct.web_service.Student;
import com.lct.web_service.StudentService;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/1/25 0025.
*/
public class Web_service {
//日志记录器
public static final Logger logger = Logger.getGlobal();
public static void main(String[] args) {
try {
/** url:webservice 服务端提供的服务地址,结尾必须加 "?wsdl"*/
URL url = new URL("http://192.168.1.20:3333/web_server/student?wsdl");
/** QName 表示 XML 规范中定义的限定名称,QName 的值包含名称空间 URI、本地部分和前缀。
* QName(final String namespaceURI, final String localPart):指定名称空间 URI 和本地部分的 QName 构造方法。
* 如下所示的两个数据都可以浏览器访问服务端时返回 xml 中第一行找到,如:
*
* namespaceURI 可以通过服务端的 @WebService 的 targetNamespace 属性指定
* localPart 可以通过服务端的 @WebService 的 serviceName 属性指定
*/
QName qName = new QName("http://web_service.lct.com/", "StudentService");
/**
* Service 对象提供 Web 服务的客户端视图
* Service 作为以下内容的工厂:1、目标服务端点的代理,2、用于远程操作的动态面向消息调用的 javax.xml.ws.Dispatch 实例。
* create(java.net.URL wsdlDocumentLocation,QName serviceName):创建 Service 实例。
* wsdlDocumentLocation : 服务 WSDL 文档位置的 URL
* serviceName : 服务的 QName
*/
Service service = Service.create(url, qName);
/**
* 使用 getPorts 方法可以对服务上可用的端口/接口进行枚举
* getPort(Class serviceEndpointInterface):获取支持指定服务端点接口的对象实例
* serviceEndpointInterface:指定返回的代理所支持的服务端点接口
*/
StudentService studentService = service.getPort(StudentService.class);
Student student = studentService.getStudentById(9527);
boolean flag = studentService.isPayTuition(student);
logger.info("获取学生信息:姓名:" + student.getSName() + ",惩罚状态:" + student.getPunishStatus() + ",生日:" + student.getBirthday());
logger.info("此学生是否已经交完学费:" + flag);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
先启动服务端,然后运行客户端,结果如下:
webService 调用服务端完全没有问题。