WebService就该这么学
一:WebService简介
1:WebService介绍
WebService是一个平台独立的、低耦合的、自包含的、基于可编程的web应用程序,可使用开放的XML来描述、发布、发现、协调和配置这些应用程序,用于开发分布式交互操作的应用程序。
WebService技术,能运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件,就可相互交换数据或集成。依据WebService规范实施的应用之间,无论它们所使用的语言、平台或内部协议是什么,都可以相互交换数据。这么说吧,其实WebService就是一种跨编程语言和跨操作系统平台的远程调用技术(RPC的一种实现方式)。所谓可跨编程语言,就是说服务端程序和客户端程序可以以不同的语言编写也可以利用WebService互相调用;跨操作系统平台则是指服务端程序和客户端程序可以在不同的操作系统上运行。远程调用,就是一台计算机的应用可以调用其他计算机上的应用。例如:我自己编写一个网站,里面想要个天气预报的功能,这个时候我肯定去调用气象局的接口服务而不是我自己发射卫星来监测天气,再引入我网站里。
2:为什么使用WebService
WebService能解决跨平台调用、跨语言调用、远程调用(RPC)
以各个网站显示天气预报功能为例,气象中心的管理系统将收集的天气信息并将数据暴露出来(通过WebService Server),而各大站点的应用就去调用它们得到天气信息并以不同的样式去展示(WebService Client),我们网站虽然提供了天气预报的服务,但其实它们什么也没有做,只是简单的调用了一下气象中心服务器服务接口而已。
3:WebService原理及重要术语
XML、SOAP、WSDL 是构成WebService平台的三大技术
一:基本术语
UDDI:Universal Description, Discovery, and Integration(统一描述、发现和集成)
UDDI是OASIS发起的一个开放项目,它使企业在互联网上可以互相发现并且定义业务之间的交互。
SOAP:simple object access protocal(简单对象访问协议)
①:是一种简单的、基于HTTP和XML的协议, 用于在WEB上交换结构化的数据,结构化数据实际上就是xml的数据
②:SOAP消息:请求消息和响应消息
③:HTTP+XML片断
WSDL:web service definition language(WebService定义语言)
①:对应一种类型的文件.wsdl
②:定义了WebService的服务器端与客户端应用交互传递请求和响应数据的格式和方式
③:一个WebService对应一个唯一的wsdl文档(理解为接口说明书)
SEI:WebService EndPoint Interface(终端,WebService的终端接口)
①:就是WebService服务器端用来处理请求的接口;我们可以理解为是xxxWebService的各个xxxWebServiceImpl实现类
CXF:Celtix + XFire,一个apache的用于开发webservice服务器端和客户端的框架
WebService基本原理
①:WebService是采用HTTP协议在客户端和服务端之间传输数据 ②:WebService传输的数据是使用XML封装后再传输交互,XML的优点在于它的跨平台性(因为市面上的语言基本上都支持xml解析) ③:WebService发送的请求内容和响应内容都采用XML格式封装,并增加了一些特定的HTTP消息头,以说明HTTP消息的内容格式,这些特定
的HTTP消息头和XML内容格式就是SOAP协议规定的 ④:WebService服务器端首先要通过一个WSDL文件来说明自己有什么服务可以对外调用。简单的说,WSDL就像是一个说明书,用于
描述WebService及其方法、参数和返回值。 WSDL文件保存在Web服务器上,通过一个url地址就可以访问到它。客户端要调用
一个WebService服务之前,要知道该服务的WSDL文件的地址。 ⑤:WebService交互的过程就是,WebService遵循SOAP协议通过XML封装数据,然后由Http协议来传输数据。 注:WebService服务提供商可以通过两种方式来暴露它的WSDL文件地址: 1:注册到UDDI服务器,以便被人查找 2:直接告诉给客户端调用者 总结:要想编写WebService接口就一定要以SOAP请求来传输数据(HTTP+XML),编写完成后把服务暴露到UDDI服务器上,这时调用者
可以通过指定的URL来生成WSDL
4:WebService两种开发方式
1:JAX-WS(Java API for XML Web Service):
JAX-WS规范是一组XML web services的JAVA API,JAX-WS允许开发者可以选择RPC-oriented或者message-oriented
来实现自己的web services。
在JAX-WS中,一个远程调用可以转换为一个基于XML的协议例如SOAP,在使用JAX-WS过程中,开发者不需要编写任何生成和处
理SOAP消息的代码。JAX-WS的运行时实现会将这些API的调用转换成为对应的SOAP消息
缺点:我们必须借助WSDL说明书来生成一个java客户端代码,后期就像调用方法一样调用
2:JAX-RS(Java API for RESTful Web Servicecs):
是一个Java编程语言的应用程序接口,支持按照表述性状态转移(REST)架构风格创建Web服务。JAX-RS使用了JavaSE5引入
的Java标注来简化Web服务的客户端和服务端的开发和部署。
优点:不用编写客户端,只需要服务端提供RESTFul的URL调用即可
二:WebService服务端和客户端的开发(了解)
注:本章节案例用的全部注解在后面专门介绍(此章只是入门案例,具体在文章后面集成说明)!!!!!
此章里面的方式了解即可,因为在实际开发中都是以Spring来集成CXF方式开发服务端和客户端;
JAX-WS就是我们俗称的JDK(JDK版本1.6+才可以)开发,因为JDK自带这种方式开发客户端和服务端
Apache CXF = Celtix + XFire,ApacheCXF 的前身叫 Apache CeltiXfire,现在已经正式更名为 Apache CXF 了,以下简称为 CXF。CXF 继承了 Celtix 和XFire 两大开源项目的精华,提供了对 JAX-WS 全面的支持,并且提供了多种 Binding 、DataBinding、Transport 以及各种 Format 的支持,并且可以根据实际项目的需要,采用代码优先(Code First)或者 WSDL 优先(WSDL First)来轻松地实现 Web Services 的发布和使用。
1:使用JAX-WS开发服务端 (熟悉流程)
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:41 * @Description 学生实体类 */ public class Student { private String id; //ID private String name; //姓名 private Integer age; //年龄 private String sex; //性别 private String address; //住址 private Date birthday; //生日 private Dog dog; //学生宠物狗 //下面省略get、set、构造器、toString自己补一下 } ///////////////////////////////////////////// /** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:42 * @Description 狗狗实体类 */ public class Dog { private String name; //名称 private String color; //颜色 private String msg; //介绍 //下面省略get、set、构造器、toString自己补一下 }
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:39 * @Description StudentWebService接口 */ @WebService //告知当前接口为WebService public interface StudentWebService { //根据姓名查询学生 Student findByName(String name); //返回学生总共个数 Integer countAllStudent(); } ////////////////////////////////////////////////////////////////////// /** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:52 * @Description StudentWebService接口实现类 */ @WebService //告知当前的接口实现类为WebService服务 public class StudentWebServiceImpl implements StudentWebService { /*** * 根据姓名查询学生 * @param name * @return */ @Override @WebMethod public Student findByName(String name) { System.out.println("》》》调用findByName方法 获取参数为:" + name); return new Student(UUID.randomUUID().toString().replace("-", ""), name, 23, "男", "安徽六安", new Date(), new Dog("大黄", "#ff0", "小狗真漂亮")); } /*** * 返回学生总共个数 * @return */ @Override @WebMethod public Integer countAllStudent() { System.out.println("》》》调用countAllStudent方法"); return 3234567; } }
public class TestMain { public static void main(String[] args) { //接口发布 //Endpoint类发布服务,publish方法,两个参数:参数1.服务地址,参数2.服务实现类。 Endpoint.publish("http://localhost:8888/WebService/student", new StudentWebServiceImpl()); System.out.println("接口发布完成"); } }
完成到这一步后我们的服务端就编写好了,接下来我们就要测试我们当前发布的服务是否可以访问,这时候我们就要访问我们当前编写服务的WSDL说明书,WSDL服务访问必须是我们发布的服务地址后加上?wsdl才可访问;我们通过阅读wsdl可以确定客户端要怎么调用该服务端,wsdl定义了接口、方法、参数、返回值,告知我们以指定规范来调用服务端
说明:使用JDK自带的WebService是没有拦截器的,在这里大家知道它没有拦截器功能即可!!!!!!!
2:使用JAX-WS开发客户端(熟悉流程)
其实客户端调用服务端的方法有很多,但在大多情况下我们都是用工具生成客户端代码,这里我们就用到wsimport.exe工具(JDK的bin目录下自带此工具,因为JAX-WS写的服务本身就是JDK拥有的,所以也有生成客户端工具)是jdk自带的webservice客户端工具可以根据wsdl文档生成客户端调用代码(java代码)。当然,无论服务器端的WebService是用什么语言写的,都可以生成调用webservice的客户端代码
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 14:10 * @Description 客户端调用 TestClient类 */ public class TestClient { public static void main(String[] args) { standard(); simple(); } //标准方式调用 推荐使用此方式 private static void standard() { try { //创建QName 参数说明:QName(final String namespaceURI, final String localPart) //第一个参数namespaceURI:我们WSDL里面定义的命名空间 //第二个参数localPart:服务视图名 QName qName = new QName("http://impl.webservice.xw.cn/", "StudentWebServiceImplService"); //第一个参数:wsdl地址 //第二个参数:wsdl命名空间对象 Service service = StudentWebServiceImplService.create(new URL("http://localhost:8888/WebService/student?wsdl"), qName); //通过服务对象获取服务的核心类对象 StudentWebServiceImpl servicePort = service.getPort(StudentWebServiceImpl.class); //调用方法 Student student = servicePort.findByName("蚂蚁小哥"); System.out.println(student.toString()); } catch (MalformedURLException e) { e.printStackTrace(); } } //简单方式调用 private static void simple() { //创建服务视图对象 StudentWebServiceImplService factory = new StudentWebServiceImplService(); //根据服务视图对象直接找到服务核心对象 通过wsdl里的binding---portType 直接获取到portType //实现类从wsdl文件的portType的name属性获取 StudentWebServiceImpl studentWebServiceImplPort = factory.getStudentWebServiceImplPort(); //调用服务中方法 Student student = studentWebServiceImplPort.findByName("蚂蚁小哥"); System.out.println(student.toString()); } }
3:使用CXF_WS开发服务端(WS方式)
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:41 * @Description 学生实体类 */ public class Student { private String id; //ID private String name; //姓名 private Integer age; //年龄 private String sex; //性别 private String address; //住址 private Date birthday; //生日 private Dog dog; //学生宠物狗 //下面省略get、set、构造器、toString自己补一下 } ///////////////////////////////////////////// /** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:42 * @Description 狗狗实体类 */ public class Dog { private String name; //名称 private String color; //颜色 private String msg; //介绍 //下面省略get、set、构造器、toString自己补一下 }
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:39 * @Description StudentWebService接口 */ //这里不要加@WebService哟 public interface StudentWebService { //根据姓名查询学生 Student findByName(String name); //返回学生总共个数 Integer countAllStudent(); } ////////////////////////////////////////////////////////////////////// /** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:52 * @Description StudentWebService接口实现类 */ @WebService //告知当前的接口实现类为WebService服务 public class StudentWebServiceImpl implements StudentWebService { /*** * 根据姓名查询学生 * @param name * @return */ @Override @WebMethod public Student findByName(String name) { System.out.println("》》》调用findByName方法 获取参数为:" + name); return new Student(UUID.randomUUID().toString().replace("-", ""), name, 23, "男", "安徽六安", new Date(), new Dog("大黄", "#ff0", "小狗真漂亮")); } /*** * 返回学生总共个数 * @return */ @Override @WebMethod public Integer countAllStudent() { System.out.println("》》》调用countAllStudent方法"); return 3234567; } }
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>webservice_cxf_service</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--CXF核心坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.4.3</version> </dependency> <!--CXF关于http请求坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.4.3</version> </dependency> <!--CXF关于SOAP规范坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.4.3</version> </dependency> <!-- 内置jetty web服务器 这里必须添加,因为运行在服务器上--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>3.4.3</version> </dependency> <!--日志坐标--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.30</version> </dependency> </dependencies> <build> <plugins> <!--配置maven编译版本--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source><!--源代码使用的JDK--> <target>1.8</target><!--target需要生成的目标class文件的编译版本--> <encoding>UTF-8</encoding><!--字符集编码,防止中文乱码--> <failOnError>true</failOnError><!--指示即使存在编译错误,构建是否仍将继续--> <failOnWarning>false</failOnWarning><!--指示即使存在编译警告,构建是否仍将继续--> <showDeprecation>false</showDeprecation><!--设置是否显示使用不推荐API的源位置--> <showWarnings>false</showWarnings><!--设为true若要显示编译警告,请执行以下操作--> <meminitial>128M</meminitial><!--编译器使用的初始化内存--> <maxmem>512M</maxmem><!--编译器使用的最大内存--> </configuration> </plugin> </plugins> </build> </project>
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/31 15:21 * @Description */ public class TestMain { public static void main(String[] args) { //我注释一个方法,因为这两个方法jar包有冲突 releaseServiceA(); //releaseServiceB(); } //第一种发布方式(推荐) public static void releaseServiceA() { //import cn.xw.webservice.impl.StudentWebServiceImpl; //import org.apache.cxf.interceptor.Interceptor; //import org.apache.cxf.interceptor.LoggingInInterceptor; //import org.apache.cxf.interceptor.LoggingOutInterceptor; //import org.apache.cxf.jaxws.JaxWsServerFactoryBean; //import org.apache.cxf.message.Message; //创建发布服务工厂 JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean(); //设置服务地址 factoryBean.setAddress("http://127.0.0.1:9999/WebService/student"); //设置服务类 factoryBean.setServiceBean(new StudentWebServiceImpl()); //##添加系统拦截器 //服务端入拦截器 List<Interceptor<? extends Message>> inInterceptors = factoryBean.getInInterceptors(); inInterceptors.add(new LoggingInInterceptor());//设置请求拦截 //服务端出拦截器 List<Interceptor<? extends Message>> outInterceptors = factoryBean.getOutInterceptors(); outInterceptors.add(new LoggingOutInterceptor());//设置响应拦截 //发布服务 factoryBean.create(); System.out.println("发布完成"); } //第二种发布方式 public static void releaseServiceB() { //使用到的包 //import cn.xw.webservice.impl.StudentWebServiceImpl; //import org.apache.cxf.interceptor.Interceptor; //import org.apache.cxf.interceptor.LoggingInInterceptor; //import org.apache.cxf.interceptor.LoggingOutInterceptor; //import org.apache.cxf.jaxws.EndpointImpl; //import org.apache.cxf.message.Message; //import javax.xml.ws.Endpoint; //import java.util.List; //创建发布端点 // Endpoint endpoint = Endpoint.publish("http://127.0.0.1:9999/WebService/student", new StudentWebServiceImpl()); // //后面要加一些处理,所以Endpoint接口是没有那么多方法,我们需要强转 // //EndpointImpl是 org.apache.cxf.jaxws.EndpointImpl里面的别导入错误 // EndpointImpl endpointImpl = (EndpointImpl) endpoint; // //##添加系统拦截器 // //服务端入拦截器 // List<Interceptor<? extends Message>> inInterceptors = endpointImpl.getInInterceptors(); // inInterceptors.add(new LoggingInInterceptor());//设置请求拦截 // //服务端出拦截器 // List<Interceptor<? extends Message>> outInterceptors = endpointImpl.getOutInterceptors(); // outInterceptors.add(new LoggingOutInterceptor());//设置响应拦截 // System.out.println("发布成功"); } }
# Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategory=INFO, CONSOLE debug info warn error fatal log4j.rootCategory=info, CONSOLE, LOGFILE # Set the enterprise logger category to FATAL and its only appender to CONSOLE. log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n # LOGFILE is set to be a File appender using a PatternLayout. log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=d:\axis.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
完成到这一步后我们访问 http://127.0.0.1:9999/WebService/student?wsdl 来查看我们的wsdl说明书(注SEI接口不要添加@WebService),查询到wsdl我们就可以开发客户端了
4:使用CXF_WS开发客户端(WS方式)
在上面我们介绍了JDK自带的wsimport.exe执行文件,其实用此工具也是可以生成CXF_WS的客户端代码,但是我们既然都知道是CXF写的了,何必不用CXF自家的客户端生成工具呢?所以我就介绍一些CXF特有的生成客户端工具 wsdl2java
cxf是Apache的,所以我们要去下载一个 apache-cxf-x.x 按照自己合适的版本来,我下载的是apache-cxf-3.4.4.zip ,解压后在bin目录下就有wsdl2java工具,此时如果想随时随地cmd使用我们就得去配置环境变量,大家自己像maven的配置方式配置,保证可以找到cxf的bin目录里面(我就不配置了,直接访问到此文件夹里)
基本使用wsdl2java 基本语法:wsdl2java -d '生成客户端的位置' -client '?wsdl地址或者wsdl文件' -p: 指定其wsdl的命名空间,也就是要生成代码的包名 -d: 指定要产生代码所在目录 -client: 生成客户端测试web service的代码 -server: 生成服务器启动web service的代码 -impl: 生成web service的实现代码 -ant: 生成build.xml文件 -all: 生成所有开始端点代码:types,service proxy,,service interface, server mainline, client mainline, implementation object, and an Ant build.xml file. 如我当前使用: wsdl2java -d C:\Users\xiaof\Desktop\client -client http://127.0.0.1:9999/WebService/student?wsdl wsdl2java -d C:\Users\xiaof\Desktop\client -client C:\Users\xiaof\Desktop\student.wsdl
说到这,我们就可以生成客户端代码在我们的项目中了,关于cxf_ws的项目我们要引入一些指定的坐标
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>webservice_cxf_client</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--CXF关于SOAP规范坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.4.3</version> </dependency> <!--CXF关于安全的坐标,里面可以对获取的请求拦截--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-security</artifactId> <version>3.4.3</version> </dependency> <!-- 内置jetty web服务器 这里必须添加,因为运行在服务器上--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>3.4.3</version> </dependency> </dependencies> <build> <plugins> <!--配置maven编译版本--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source><!--源代码使用的JDK--> <target>1.8</target><!--target需要生成的目标class文件的编译版本--> <encoding>UTF-8</encoding><!--字符集编码,防止中文乱码--> <failOnError>true</failOnError><!--指示即使存在编译错误,构建是否仍将继续--> <failOnWarning>false</failOnWarning><!--指示即使存在编译警告,构建是否仍将继续--> <showDeprecation>false</showDeprecation><!--设置是否显示使用不推荐API的源位置--> <showWarnings>false</showWarnings><!--设为true若要显示编译警告,请执行以下操作--> <meminitial>128M</meminitial><!--编译器使用的初始化内存--> <maxmem>512M</maxmem><!--编译器使用的最大内存--> </configuration> </plugin> </plugins> </build> </project>
public class TestClient { public static void main(String[] args) throws MalformedURLException { //测试时大家可以加上一个log4j.properties和日志坐标log4j //创建服务视图对象 StudentWebServiceImplService serviceImplService = new StudentWebServiceImplService(); //根据服务视图对象直接找到服务核心对象 通过wsdl里的binding---portType 直接获取到portType //实现类从wsdl文件的portType的name属性获取 StudentWebServiceImpl serviceImplPort = serviceImplService.getStudentWebServiceImplPort(); //创建客户端代理 Client client = ClientProxy.getClient(serviceImplPort); //##添加系统拦截器 //客户端入拦截器 List<Interceptor<? extends Message>> inInterceptors = client.getInInterceptors(); inInterceptors.add(new LoggingInInterceptor());//设置请求拦截 //客户端出拦截器 List<Interceptor<? extends Message>> outInterceptors = client.getOutInterceptors(); outInterceptors.add(new LoggingOutInterceptor());//设置响应拦截 //调用方法 Student student = serviceImplPort.findByName("安徽六安"); //打印 System.out.println(student.getAddress()); } }
5:使用CXF_RS开发服务端(restful方式)
此结构与CXF-WS目录是一样的定义,只是代码改变,此方式是没有wsdl说明书的,客户端无需使用WSDL来生成客户端代码可以直接调用
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:41 * @Description 学生实体类 */ @XmlRootElement(name = "Student") //在映射XML类型是必须编写@XmlRootElement public class Student { private String id; //ID private String name; //姓名 private Integer age; //年龄 private String sex; //性别 private String address; //住址 private Date birthday; //生日 private Dog dog; //宠物狗 //下面省略get、set、构造器、toString自己补一下 } ///////////////////////////////////////////////////////// @XmlRootElement(name="Dog") public class Dog { private String name; //名称 private String color; //颜色 private String msg; //介绍 //下面省略get、set、构造器、toString自己补一下 }
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:39 * @Description StudentWebService接口 */ //在开发JAX-RS方式时是不用编写@WebService @Path("/student") //一层路径 @Produces("*/*") public interface StudentWebService { //增 @POST @Path("/add") @Consumes(value = {MediaType.APPLICATION_JSON}) //请求类型 @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) //响应类型 void addStudent(Student student); //删 @DELETE @Path("/delete/{id}") @Produces(value = {"application/json;charset=utf-8"}) void deleteById(@PathParam(value = "id") String id); //改 @PUT @Path("/update") @Consumes(value = {"application/json;charset=utf-8"}) //请求类型 @Produces(value = {"application/json;charset=utf-8"}) //响应参数类型 void updateById(Student student); //查 @GET @Path("/findAll/{age}") @Produces(value = {"application/json;charset=utf-8"}) List<Student> findByAge(@PathParam("age") Integer age); } ////////////////////////////////////////////////////////// /** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:52 * @Description StudentWebService接口实现类 */ //在开发JAX-RS方式时是不用编写@WebService注解 public class StudentWebServiceImpl implements StudentWebService { //添加学生 @Override public void addStudent(Student student) { System.out.println("》》》》》执行添加操作:addStudent"); System.out.println("准备插入数据:" + student); } //删除学生 @Override public void deleteById(String id) { System.out.println("》》》》》执行删除操作:deleteById"); System.out.println("id为:" + id + " 的学生删除完毕"); } //更新学生 @Override public void updateById(Student student) { System.out.println("》》》》》执行更新操作:updateById"); System.out.println("学生更新完成:" + student); } //查询学生 @Override public List<Student> findByAge(Integer age) { System.out.println("》》》》》执行查询操作:findByAge"); List<Student> list = new ArrayList<>(); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "蚂蚁小哥", age, "男", "安徽六安", new Date(), new Dog("大黄", "#ff0", "小狗真漂亮"))); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "许龄月", age, "女", "安徽合肥", new Date(), new Dog("大白", "#fff", "小狗真白"))); return list; } }
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:59 * @Description */ public class TestMain { public static void main(String[] args) { //创建发布工厂类 JAXRSServerFactoryBean factoryBean = new JAXRSServerFactoryBean(); //设置服务发布地址 factoryBean.setAddress("http://localhost:7777/WebService/"); //设置服务类 factoryBean.setServiceBean(new StudentWebServiceImpl()); //设置提供者方式 List<Object> providers = new ArrayList<>(); //此包com.colobu.fastjson.FastJsonProvider,这个类用来转换json数据返回 providers.add(new FastJsonProvider()); factoryBean.setProviders(providers); //创建日志输入输出打印 factoryBean.getInInterceptors().add(new LoggingInInterceptor()); factoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); //发布服务 factoryBean.create(); System.out.println("发布服务成功,端口7777"); } }
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>webservice_cxf_rs_service</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--CXF核心坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.4.3</version> </dependency> <!--CXF关于http请求坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.4.3</version> </dependency> <!--CXF关于SOAP规范坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxrs</artifactId> <version>3.4.3</version> </dependency> <!-- 内置jetty web服务器 这里必须添加,因为运行在服务器上--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>3.4.3</version> </dependency> <!--日志坐标--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.30</version> </dependency> <!--json工具包--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.67</version> </dependency> <!--fastJSON整合jaxrs坐标--> <dependency> <groupId>com.colobu</groupId> <artifactId>fastjson-jaxrs-json-provider</artifactId> <version>0.3.1</version> </dependency> </dependencies> <build> <plugins> <!--配置maven编译版本--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source><!--源代码使用的JDK--> <target>1.8</target><!--target需要生成的目标class文件的编译版本--> <encoding>UTF-8</encoding><!--字符集编码,防止中文乱码--> <failOnError>true</failOnError><!--指示即使存在编译错误,构建是否仍将继续--> <failOnWarning>false</failOnWarning><!--指示即使存在编译警告,构建是否仍将继续--> <showDeprecation>false</showDeprecation><!--设置是否显示使用不推荐API的源位置--> <showWarnings>false</showWarnings><!--设为true若要显示编译警告,请执行以下操作--> <meminitial>128M</meminitial><!--编译器使用的初始化内存--> <maxmem>512M</maxmem><!--编译器使用的最大内存--> </configuration> </plugin> </plugins> </build> </project>
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=info, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
注意:
①:我们在访问时界面出现这种错误提示
提示内容:此页面包含以下错误:第1列第1行上的错误:文档为空。下面是直到第一个错误的页面呈现
此原因是因为我们没有在对应的实体类上添加@XmlRootElement注解导致无法映射为XML,从而导致文档为空
# 因为CXF_RS开发的服务端,在客户端是可以直接调用的,比如在浏览器中调用Get请求 http://localhost:7777/WebService/student/findAll/22响应JSON数据在浏览器界面
6:使用CXF_RS开发客户端(restful方式)
我们使用CXF_RS开发客户端会使用到一个叫CXF的WebClient的工具类(后面章节介绍),可以发送不同类型的请求
在编写客户端之前,我们需要把服务端的实例对象复制到客户端里
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>webservice_cxf_rs_client</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--导入CXF的WebClient工具类--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-client</artifactId> <version>3.4.3</version> </dependency> <!--单元测试坐标--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> </dependency> </dependencies> <build> <plugins> <!--配置maven编译版本--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source><!--源代码使用的JDK--> <target>1.8</target><!--target需要生成的目标class文件的编译版本--> <encoding>UTF-8</encoding><!--字符集编码,防止中文乱码--> <failOnError>true</failOnError><!--指示即使存在编译错误,构建是否仍将继续--> <failOnWarning>false</failOnWarning><!--指示即使存在编译警告,构建是否仍将继续--> <showDeprecation>false</showDeprecation><!--设置是否显示使用不推荐API的源位置--> <showWarnings>false</showWarnings><!--设为true若要显示编译警告,请执行以下操作--> <meminitial>128M</meminitial><!--编译器使用的初始化内存--> <maxmem>512M</maxmem><!--编译器使用的最大内存--> </configuration> </plugin> </plugins> </build> </project>
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/4 11:59 * @Description 客户端 */ public class TestMain { public static void main(String[] args) { } //添加操作 @Test public void add() { //创建实体类 Student student = new Student(UUID.randomUUID().toString().replace("-", ""), "蚂蚁小哥", 22, "男", "安徽六安", new Date(), new Dog("大黄", "#ff0", "小狗真漂亮")); //发送POST请求并携带json数据 WebClient .create("http://localhost:7777/WebService/student/add/") .type("application/json;charset=utf-8") .post(student); } //删除操作 @Test public void delete() { WebClient.create("http://localhost:7777/WebService/student/delete/55") .type(MediaType.APPLICATION_JSON) .delete(); } //更新操作 @Test public void update() { //创建实体类 Student student = new Student(UUID.randomUUID().toString().replace("-", ""), "蚂蚁小哥", 22, "男", "安徽六安", new Date(), new Dog("大黄", "#ff0", "小狗真漂亮")); //发送PUT请求并携带json数据 WebClient.create("http://localhost:7777/WebService/student/update") .type(MediaType.APPLICATION_JSON) .put(student); } //查询 @Test public void findAllByAge() { //发送GET请求并根据年龄查询数据 Collection<? extends Student> students = WebClient .create("http://localhost:7777/WebService/student/findAll/22") .accept(MediaType.APPLICATION_JSON) .getCollection(Student.class); //获取集合的迭代器遍历数据 Iterator<? extends Student> iterator = students.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
问题:
<!--客户端加上此坐标可以转换为json数据--> <!--服务端加上此坐标可以解析json数据--> <!--下面这两个坐标不设置就会导致 是发送端无法转换json发送,是接收端无法把json转换为对象接收--> <!--设置CXF提供者坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-extension-providers</artifactId> <version>3.4.3</version> </dependency> <!--JSON转换坐标,会集成到CXF的providers里--> <dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>1.4.1</version> </dependency>
三:使用SoapUI工具监控请求及响应
file --> New SOAP Project 创建SOAP请求 file --> New REST Project 创建Rest请求(后面说)
四:WSDL说明书详细说明(重要)
如果你对 XML 不熟悉的请先研究一下 XML ==> 直达XML学习地址 学习一下 Schema 约束那一章
下面我就以如下的文件来详细分析一下 WSDL 说明书,这个文件是由我上面的 JAX-WS 案例生成的
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://impl.webservice.xw.cn/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://impl.webservice.xw.cn/" name="StudentWebServiceImplService"> <types> <xsd:schema> <xsd:import namespace="http://impl.webservice.xw.cn/" schemaLocation="http://localhost:8888/WebService/student?xsd=1"/> </xsd:schema> </types> <message name="findByName"> <part name="parameters" element="tns:findByName"/> </message> <message name="findByNameResponse"> <part name="parameters" element="tns:findByNameResponse"/> </message> <message name="countAllStudent"> <part name="parameters" element="tns:countAllStudent"/> </message> <message name="countAllStudentResponse"> <part name="parameters" element="tns:countAllStudentResponse"/> </message> <portType name="StudentWebServiceImpl"> <operation name="findByName"> <input wsam:Action="http://impl.webservice.xw.cn/StudentWebServiceImpl/findByNameRequest" message="tns:findByName"/> <output wsam:Action="http://impl.webservice.xw.cn/StudentWebServiceImpl/findByNameResponse" message="tns:findByNameResponse"/> </operation> <operation name="countAllStudent"> <input wsam:Action="http://impl.webservice.xw.cn/StudentWebServiceImpl/countAllStudentRequest" message="tns:countAllStudent"/> <output wsam:Action="http://impl.webservice.xw.cn/StudentWebServiceImpl/countAllStudentResponse" message="tns:countAllStudentResponse"/> </operation> </portType> <binding name="StudentWebServiceImplPortBinding" type="tns:StudentWebServiceImpl"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/> <operation name="findByName"> <soap:operation soapAction=""/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> <operation name="countAllStudent"> <soap:operation soapAction=""/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> </binding> <service name="StudentWebServiceImplService"> <port name="StudentWebServiceImplPort" binding="tns:StudentWebServiceImplPortBinding"> <soap:address location="http://localhost:8888/WebService/student"/> </port> </service> </definitions>
<xs:schema xmlns:tns="http://impl.webservice.xw.cn/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0" targetNamespace="http://impl.webservice.xw.cn/"> <xs:element name="countAllStudent" type="tns:countAllStudent"/> <xs:element name="countAllStudentResponse" type="tns:countAllStudentResponse"/> <xs:element name="findByName" type="tns:findByName"/> <xs:element name="findByNameResponse" type="tns:findByNameResponse"/> <xs:complexType name="countAllStudent"> <xs:sequence/> </xs:complexType> <xs:complexType name="countAllStudentResponse"> <xs:sequence> <xs:element name="return" type="xs:int" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="findByName"> <xs:sequence> <xs:element name="arg0" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="findByNameResponse"> <xs:sequence> <xs:element name="return" type="tns:student" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="student"> <xs:sequence> <xs:element name="address" type="xs:string" minOccurs="0"/> <xs:element name="age" type="xs:int" minOccurs="0"/> <xs:element name="birthday" type="xs:dateTime" minOccurs="0"/> <xs:element name="dog" type="tns:dog" minOccurs="0"/> <xs:element name="id" type="xs:string" minOccurs="0"/> <xs:element name="name" type="xs:string" minOccurs="0"/> <xs:element name="sex" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="dog"> <xs:sequence> <xs:element name="color" type="xs:string" minOccurs="0"/> <xs:element name="msg" type="xs:string" minOccurs="0"/> <xs:element name="name" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType> </xs:schema>
wsdl文档说明书是由definitions标签来包裹,内部有 五大标签各司其职 分别为
service、binding、portType、message、types
这五大标签我将在下面一一介绍
1:WSDL报文之 definitions
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://impl.webservice.xw.cn/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://impl.webservice.xw.cn/" name="StudentWebServiceImplService"> <!--内部标签省略.....`--> </definitions> 标签: definitions:根元素 每个wsdl文件包含一个根元素,内部定义一些属性 属性: xmlns:tns:引用 相当于Java里面的import包的反转路径 name:SEI定义名称 我们Java程序中的接口实现类,SEI定义规则是:服务接口类+Service后缀,Service系统自动追加 targetNamespace:命名空间 相当于Java里面的package,它刚好是和我们定义的的包名相反 其它属性:出现的其它属性不能改动,必须那样写,因为是整篇wsdl说明书的约束条件
2:WSDL报文之 types
定义Web服务里用到的,XMLSchema定义的数据类型以外的自定义数据类型,对于我们自定义的类(Student),会对应到一个<complexType>(复杂类型标签),其中用<element>元素指定每个参数的类型。
<types> <xsd:schema> <xsd:import namespace="http://impl.webservice.xw.cn/" schemaLocation="http://localhost:8888/WebService/student?xsd=1"/> </xsd:schema> </types> 标签:types 定义Web服务中用到的请求与响应的数据类型 标签:xsd:schema 约束标签,用来指定内部类型按照指定规范编写 标签:xsd:import 导入外部文件 属性: namespace:命名空间 schemaLocation:约束文件地址 如下是http://localhost:8888/WebService/student?xsd=1约束文件 <xs:schema xmlns:tns="http://impl.webservice.xw.cn/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0" targetNamespace="http://impl.webservice.xw.cn/"> <!--定义countAllStudent方法的请求类型 指定的类型是countAllStudent--> <xs:element name="countAllStudent" type="tns:countAllStudent"/> <!--定义countAllStudentResponse方法的响应类型 指定的类型是countAllStudentResponse--> <xs:element name="countAllStudentResponse" type="tns:countAllStudentResponse"/> <!--定义findByName方法的请求类型 指定的类型是findByName--> <xs:element name="findByName" type="tns:findByName"/> <!--定义findByNameResponse方法的响应类型 指定的类型是findByNameResponse--> <xs:element name="findByNameResponse" type="tns:findByNameResponse"/> <!--定义一个复杂类型,但此类型为空(就是说countAllStudent方法为空参数)--> <xs:complexType name="countAllStudent"> <xs:sequence/> </xs:complexType> <!--定义一个复杂类型,返回的是一个序列(参数按照要求位置)--> <xs:complexType name="countAllStudentResponse"> <xs:sequence> <!--此处告诉我们是一个响应的return参数,此时的参数类型为int 并且至少出现0次--> <xs:element name="return" type="xs:int" minOccurs="0"/> </xs:sequence> </xs:complexType> <!--后面不会的请参考具体的xml学习--> <!--后面的省略........--> </xs:schema>
3:WSDL报文之 message
具体定义了在通信中使用的消息的数据结构,Message元素包含了一组Part元素,每个Part元素都是最终消息的一个组成部分,每个Part都会引用一个DataType(就是part里的element属性)来表示它的结构。
<message name="findByName"> <part name="parameters" element="tns:findByName"/> </message> <message name="findByNameResponse"> <part name="parameters" element="tns:findByNameResponse"/> </message> <message name="countAllStudent"> <part name="parameters" element="tns:countAllStudent"/> </message> <message name="countAllStudentResponse"> <part name="parameters" element="tns:countAllStudentResponse"/> </message> 标签:message 用来定义请求/响应消息的结构 属性: name:方法名称 标签:part 指定引用types中定义的标签片段 属性: name:此时说明都是参数 element:引用指定的类型,类型在types中定义 大白话: 我们编写的SEI实现类的某一个方法会对于的两个message,分别对象请求message和响应message 而不管请求还是响应message都会有参数的传输,比如请求的message是有个请求参数的,这个请求参数 的类型就是在此处说明,并引用types定义的具体复杂类型
4:WSDL报文之 portType
Operation是对服务中所支持的操作的抽象描述,一般单个Operation描述了一个访问入口的请求/响应消息对。
PortType具体定义了一种服务访问入口的类型,就是输入/输出消息的模式及其格式。一个PortType可以包含若干个Operation,而一个Operation则是指访问入口支持的一种类型的调用。在WSDL里面支持四种访问入口调用的模式如单请求、单响应、请求/响应、响应/请求。
在这里请求指的是从客户端到Web服务端,而响应指的是从Web服务端到客户端。PortType的定义中会引用消息定义部分的一个到两个消息,作为请求或响应消息的格式。
<portType name="StudentWebServiceImpl"> <operation name="findByName"> <input wsam:Action="http://impl.webservice.xw.cn/StudentWebServiceImpl/findByNameRequest" message="tns:findByName"/> <output wsam:Action="http://impl.webservice.xw.cn/StudentWebServiceImpl/findByNameResponse" message="tns:findByNameResponse"/> </operation> <operation name="countAllStudent"> <input wsam:Action="http://impl.webservice.xw.cn/StudentWebServiceImpl/countAllStudentRequest" message="tns:countAllStudent"/> <output wsam:Action="http://impl.webservice.xw.cn/StudentWebServiceImpl/countAllStudentResponse" message="tns:countAllStudentResponse"/> </operation> </portType> 标签:portType 用来定义服务器端的SEI接口 属性: name:定义我们的实现类名称 标签:operation 用来指定SEI中的处理请求的方法,一般分为请求和响应 属性: name:SEI实现类里的具体实现方法 标签:input 指定客户端请求过来的数据,它会引用我们定义的message标签来强行约束类型 属性: message:引用请求消息的约束 标签:output 指定服务端响应回去的数据,它会引用我们定义的message标签来强行约束类型 属性: message:引用响应消息的约束
5:WSDL报文之 binding
此标签包含了如何将抽象接口的元素(portType)转变为具体表示的细节,具体表示也就是指特定的数据格式和协议的结合;特定端口类型的具体协议和数据格式规范的绑定。JAX-RPC规范规定,SOAP绑定可以有rpc和document两种类型,分别表示远程过程调用和基于消息的方式。use属性可以是encoded或literal,对于前者要支持rpc的方式,对于后者要支持rpc和document的方式。
<binding name="StudentWebServiceImplPortBinding" type="tns:StudentWebServiceImpl"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/> <operation name="findByName"> <soap:operation soapAction=""/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> <operation name="countAllStudent"> <soap:operation soapAction=""/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> </binding> 标签:binding 用于定义服务器端的SEI接口实现类 属性: name:实现类名称 type:此时这里是引用portType指定标签 标签:soap:binding 绑定什么类型的数据用来请求响应 属性: type:绑定的数据是一个document(就是xml格式类型的请求响应) 标签:operation 用来指定SEI中的处理请求的方法,一般分为请求和响应 属性: name:SEI实现类里的具体实现方法 标签:input和output 指定客户端请求和服务端响应过来的数据类型 属性: use:定义类型literal(文本) 结合上面可以看出当前的SEI实现类只可以用document(xml)类型来交互,并且里面的方法是使用xml的文本方式交互
6:WSDL报文之 service
描述的是一个具体被部署的Web服务所提供的所有访问入口的部署细节,一个Service往往会包含多个服务访问入口,而每个访问入口都会使用一个Port元素来描述
<service name="StudentWebServiceImplService"> <port name="StudentWebServiceImplPort" binding="tns:StudentWebServiceImplPortBinding"> <soap:address location="http://localhost:8888/WebService/student"/> </port> </service> 标签:service 一个WebService的容器 属性: name:它用来指定客户端容器类 标签:port 用来指定一个服务器处理的请求入口(就是SEI的实现,理解为实现类) 属性: binding:引用具体的<binding/>标签 name:具体的实现类对象,正如 factory.getStudentWebServiceImplPort()获取实现类对象 标签:soap:address 用来说明当前WebService的请求地址 属性: soap:address.location:标明具体的WebService请求地址
五:WebService开发常用注解介绍
1:@WebService
此注解主要是标注当前是个WebService服务
@WebService
serviceName:
对外发布的服务名,指定 WebService 的服务名称;缺省值为Java类的非限定名称+"Service"
修改:wsdl:service["name"]
修改:wsdl:definitions["name"]
name:
设置端口类型名称,缺省值为Java接口或者类的非限定名称
修改:wsdl:portType["name"]
portName:
设置端口类型名称,缺省值为端口类型名称(就是我们设置的name或者缺省)+"Port"
修改:wsdl:port["name"]
targetNamespace:
指定WebService生成的WSDL和XML元素命名空间,缺省为 "http://"+当前接口的包名倒排
修改:wsdl:definitions["xmlns:tns"]
修改:wsdl:definitions["targetNamespace"]
endpointInterface:
用于定义服务的抽象WebService的服务端点的限定名,如果指定了限定名,则会使用改服务端接口来确定抽象WSDL约束
wsdlLocation:
指定用于定义WebService的WSDL文档的Web地址。Web地址可以是相对路径或绝对路径
注:
①:实现类上可以不添加 @WebService
②:如果@WebService在SEI实现类上添加后可以指定endpointInterface属性来告知SEI接口限定类名
2:@WebMethod
此注解主要是标注WebService方法的具体名称
@WebMethod
operationName:
指定SEI方法的具体名称,缺省值为Java方法名称
修改:wsdl:operation["name"]
action:
定义操作的行为,缺省值为""
修改:soap:operation["soapAction"]
exclude:
指定是否从WebService中排除某一方法,缺省值为false
注:
①:此注解添仅支持添加在方法上,且当前方法的类上必须存在@WebService注解才可使用
②:修改operationName会导致客户端调用的方法名称改变
4:@Oneway
此注解必须写在带有@WebService的类方法上,它是声明此方法只有输入消息而没有输出消息的WebService单向操作
5:@WebParam
此注解主要标注参数至WebService消息部件和XML元素的映射,不指定WebService参数名称则默认xs:element["name"] = "args[0~*]"
@WebParam name: 交互方法的参数名称,如果操作是远程过程调用(RPC)类型并且未指定partName属性,那么这是用于表示参数的wsdl:part属性的名称 如果操作是文档类型或者参数映射至某个头,那么name是用于表示该参数的XML元素的局部名称。 如果操作是文档类型、参数类型为BARE并且方式为OUT或INOUT,那么必须指定此属性 targetNamespace: 指定返回值的XML名称空间。仅当操作类型为RPC或者操作是文档类型并且参数类型为BARE时才使用此参数 header: 指定头中是否附带结果。缺省值为false partName: 指定RPC或DOCUMENT/BARE操作的结果的部件名称。缺省值为@WebResult.name
6:@WebResult
此注解主要标注从返回值至WSDL部件或XML元素的映射
@WebResult
name:
当返回值列是在WSDL文件中并且在连接上的消息中找到该返回值时,指定该返回值的名称。
对于RPC绑定,这是用于表示返回值的wsdl:part属性的名称。对于文档绑定,name参数是用于表示返回值的XML元素的局部名。
对于RPC和DOCUMENT/WRAPPED绑定,缺省值为return。对于DOCUMENT/BARE绑定,缺省值为方法名 + Response。
targetNamespace:
指定返回值的XML名称空间。仅当操作类型为RPC或者操作是文档类型并且参数类型为BARE时才使用此参数。
header:
指定头中是否附带结果。缺省值为false
partName:
指定RPC或DOCUMENT/BARE操作的结果的部件名称。缺省值为@WebResult.name
7:@HandlerChain
注释用于使 Web Service 与外部定义的处理程序链相关联。只能通过对 SEI 或实现类使用 @HandlerChain 注释来配置服务器端的处理程序。
但是可以使用多种方法来配置客户端的处理程序。可以通过对生成的服务类或者 SEI 使用 @HandlerChain 注释来配置客户端的处理程序。此外,可以按程序在服务上注册您自己的 HandlerResolver 接口实现,或者按程序在绑定对象上设置处理程序链。
@HandlerChain
file:
指定处理程序链文件所在的位置。文件位置可以是采用外部格式的绝对 java.net.URL,也可以是类文件中的相对路径
name:
指定配置文件中处理程序链的名称
8:@XmlRootElement
类级别的注解。将类映射为xml全局元素,也就是根元素
@XmlRootElement
name:
类级别的注解。将类映射为xml全局元素,也就是根元素
namespace:
namespace属性用于指定生成的元素所属的命名空间
9:@XmlSeeAlso
指示JAXB在绑定此类时也绑定其他类;
@XmlSeeAlso
value:
类级别的注解。映射属性内部类型,value是class[]类型
@XmlRootElement(name = "Result") @XmlSeeAlso({Student.class}) public class Result<T> { private Integer code; //响应码 private String msg; //响应信息 //示例这个T可能是Student ,但也有可能会是别的类型,把可能出现的类型列举在@XmlSeeAlso private List<T> data; //响应数据 }
六:WebService开发RS方式类型特有注解
具体API官方文档 进入文档使用全文匹配搜索Ctrl+f 搜索 ' javax.ws.rs '
1:@Path
标识资源类或类方法将为其请求提供服务的URI路径。
@Path
value:
设置请求路径名称,(和SpringMVC的@RequestMapping一个道理)
注意:资源路径是以RSETful风格的路径
GET方式:
http://localhost/student/findById/23
@Path("/student/findById/{id}")
POST方式:
http://localhost/student/save
@Path("/student/save")
2:@GET、@PUT、@POST、@DELETE
标注方法是用什么请求的HTTP才可以匹配到此方法,无属性
3:@Produces
定义资源类或MessageBodyWriter的方法可以生成的媒体类型;说白了就是告诉当前方法的响应MIME媒体类型
@Produces
value:
MIME类型名称
示例
@Produces(value = {"application/json;charset=utf-8"})
返回JSON类型并且是UTF-8格式
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
可以返回XML类型以及JSON类型
MediaType枚举类定义了各种类型
类型举例:
text/plain 文本类型
text/html HTML类型
application/json JSON类型
application/xml XML类型
image/png PNG类型
4:@Consumes
定义资源类或MessageBodyReader的方法可以接受的媒体类型;说白了就是告诉当前方法的请求MIME媒体类型
@Consumes
value:
MIME类型名称
使用上和@Produces一样,是个对应关系,一个请求和一个响应
5:@PathParam
将URI模板参数或包含模板参数的路径段的值绑定到资源方法参数、资源类字段或资源类Bean属性;说白了就是绑定RESTful风格的URL后面的参数到方法参数上
@PathParam
value:
绑定的名称,注意要和URL里被{}括起来的名称一一对应
示例:
@DELETE
@Path("/delete/{id}")
@Produces(value = {"application/json;charset=utf-8"})
void deleteById(@PathParam(value = "id") String id);
6:@MatrixParam
将URI的Matrix参数的值绑定到资源方法参数、资源类字段或资源类Bean属性。
@MatrixParam
value:
获取Matrix参数,名称要和URL里的名称对应
URI的哪一部分属于Matrix 如:http://localhost/student/22;JSESSIONID=a1234123-113a123;address=Shanghai 答:以URL里的分号向后 ';' 可以有多个Matrix参数,可以对Restful参数的扩展 ①:我们平常写的URL解析: http://localhost:7777/WebService/testA/st001/tom 编写接口: @GET @Path("testA/{id}/{name}") @Produces({"text/plain;charset=utf-8"}) String testA(@PathParam("id") String id, @PathParam("name") String name); 编写实现: @Override public String testA(String id, String name) { System.out.println("打印id:" + id); System.out.println("打印name:" + name); return "成功执行此方法!!"; } ②:携带Matrix参数的URL解析: http://localhost:7777/WebService/testA/st001/tom;address=Shanghai 编写接口: @GET @Path("testA/{id}/{name}") @Produces({"text/plain;charset=utf-8"}) String testA(@PathParam("id") String id, @PathParam("name") String name, @MatrixParam("address") String address); 编写实现类: @Override public String testA(String id, String name, String address) { System.out.println("打印id:" + id); System.out.println("打印name:" + name); System.out.println("打印Matrix参数address:" + address); return "成功执行此方法!!"; } ③:携带多个Matrix参数的URL解析 http://localhost:7777/WebService/testA/st001/tom;address=Shanghai;school=安徽大学 编写接口: @GET @Path("testA/{id}/{name}") @Produces({"text/plain;charset=utf-8"}) String testA(@PathParam("id") String id, @PathParam("name") String name, @MatrixParam("address") String address, @MatrixParam("school") String school); 编写接口实现类: @Override public String testA(String id, String name, String address, String school) { System.out.println("打印id:" + id); System.out.println("打印name:" + name); System.out.println("打印Matrix参数address:" + address); System.out.println("打印Matrix参数school:" + school); return "成功执行此方法!!"; } ④:携带Matrix参数的URL解析:不使用@Matrix注解 http://localhost:7777/WebService/testA/st001/tom;address=Shanghai 编写接口: @GET @Path("testA/{id}/{name};{address}") @Produces({"text/plain;charset=utf-8"}) String testA(@PathParam("id") String id, @PathParam("name") String name, @PathParam("address") PathSegment address); 编写接口实现类: @Override public String testA(String id, String name, PathSegment address) { System.out.println("打印id:" + id); System.out.println("打印name:" + name); System.out.println("获取Matrix的Path:" + address.getPath());//获取Matrix的Path:address=Shanghai System.out.println(address.getMatrixParameters());//{address=[Beijing]} return "成功执行此方法!!"; }
7:@QueryParam
将HTTP查询参数的值绑定到资源方法参数、资源类字段或资源类Bean属性。说白了就是绑定问号后的参数 ?x=x&z=z
@QueryParam value: 从URL中提取指定名称的参数;适用于?xxx=xxx&zzz=zzz
http://localhost:7777/WebService/testB/st001?address=shanghai&school=安徽大学 接口: GET @Path("testB/{id}") @Produces({"text/plain;charset=utf-8"}) String testB(@PathParam("id") String id, @QueryParam("address") String address, @QueryParam("school") String school); 实现: @Override public String testB(String id, String address, String school) { System.out.println("打印id:" + id); System.out.println("打印address:" + address); System.out.println("打印school:" + school); return "成功执行此方法!!"; }
8:@FormParam
将URI模板参数或包含模板参数的路径段的值绑定到资源方法参数、资源类字段或资源类Bean属性。说白了就是绑定POST提交的Form参数,其中Content-Type被假设为application/x-www-formurlencoded。
@FormParam
value:
表单提交的键名称
具体操作和@QueryParam差不多,只是@QueryParam是直接绑定URL上,而@FormParam是绑定POST提交的表单上
9:@HeaderParam
将HTTP标头的值绑定到资源方法参数、资源类字段或资源类Bean属性。说白了就是获取请求的头信息
@HeaderParam value: 获取请求头信息,value填写获取的头名称(键)
10:@CookieParam
将HTTP Cookie的值绑定到资源方法参数、资源类字段或资源类Bean属性。说白了就是获取指定名称Cooke值
@CookieParam
value:
获取Cookie信息,value填写获取的指定Cookie名称(键)
11:@DefaultValue
定义使用以下注释之一绑定的请求元数据的默认值:PathParam
, QueryParam
, MatrixParam
,CookieParam
, FormParam
, HeaderParam
@DefaultValue
value:
设置指定参数默认值
http://localhost:7777/WebService/testB/st001 接口: @GET @Path("testC/{id}") @Produces({"text/plain;charset=utf-8"}) String testC(@PathParam("id") String id, @DefaultValue("安徽大学") @QueryParam("address") String address); 实现类: @Override public String testC(String id,String address) { System.out.println("打印id:" + id); System.out.println("打印address:" + address); return "成功执行此方法!!"; }
七:Spring整合CXF_WS(SOAP 重要)
1:CXF_WS开发服务端
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>spring_cxf_ws_service</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!--Spring核心上下文坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.8</version> </dependency> <!--Spring测试坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.8</version> </dependency> <!--SpringMVC坐标,内部依赖了web坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.8</version> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <!--CXF核心坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.4.3</version> </dependency> <!--CXF对WebService SOAP支持--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.4.3</version> </dependency> <!--CXF封装发送Http消息坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.4.3</version> </dependency> </dependencies> <build> <plugins> <!--配置maven编译版本--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source><!--源代码使用的JDK--> <target>1.8</target><!--target需要生成的目标class文件的编译版本--> <encoding>UTF-8</encoding><!--字符集编码,防止中文乱码--> <failOnError>true</failOnError><!--指示即使存在编译错误,构建是否仍将继续--> <failOnWarning>false</failOnWarning><!--指示即使存在编译警告,构建是否仍将继续--> <showDeprecation>false</showDeprecation><!--设置是否显示使用不推荐API的源位置--> <showWarnings>false</showWarnings><!--设为true若要显示编译警告,请执行以下操作--> <meminitial>128M</meminitial><!--编译器使用的初始化内存--> <maxmem>512M</maxmem><!--编译器使用的最大内存--> </configuration> </plugin> </plugins> </build> </project>
@XmlRootElement(name="Student") public class Student { private String id; //ID private String name; //姓名 private Integer age; //年龄 private String sex; //性别 private String address; //住址 private Date birthday; //生日 private Dog dog; //省略get、set、构造器、toString.... } ///////////////////////////////////////////////////////// @XmlRootElement(name="Dog") public class Dog { private String name; //名称 private String color; //颜色 private String msg; //介绍 //下面省略get、set、构造器、toString自己补一下 }
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/5 18:51 * @Description 学生WebServiceSEI接口 */ @WebService( serviceName = "StudentWebServiceService", name = "StudentWebService", portName = "StudentWebServicePort", targetNamespace = "http://webservice.xw.cn" ) public interface StudentWebService { /*** * 根据地址查询 * @param address 条件 地址 * @param Accept 此参数我设置了header = true,代表此请求信息为头信息 * @return */ @WebMethod(operationName = "fun_findByAddress") @WebResult(name = "Students") List<Student> findByAddress( @WebParam(name = "address") String address, @WebParam(name = "Accept", header = true) String Accept ); //添加学生 @WebMethod(operationName = "fun_add") @Oneway //没有响应数据 void add(@WebParam(name = "student") Student student); //更新学生 @WebMethod(operationName = "fun_update") @Oneway void update(@WebParam(name = "student") Student student); //删除学生 @WebMethod(operationName = "fun_deleteById") @Oneway void deleteById(@WebParam(name = "id") String id); } //////////////////////////////////////////////////////////// /** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/5 19:04 * @Description 学生WebServiceSEI接口实现类 */ @WebService( endpointInterface = "cn.xw.webservice.StudentWebService" ) public class StudentWebServiceImpl implements StudentWebService { //根据地址查询全部学生数据 @Override public List<Student> findByAddress(String address, String Accept) { System.out.println("》》》》》执行查询操作:findByAddress"); System.out.println("获取请求头信息::" + Accept); List<Student> list = new ArrayList<>(); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "蚂蚁小哥", 23, "男", address, new Date(), new Dog("大黄", "#ff0", "小狗真漂亮"))); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "许龄月", 25, "女", address, new Date(), new Dog("大白", "#fff", "小狗真白"))); return list; } //保存操作 //注:日期类型必须以 yyyy-MM-dd @Override public void add(Student student) { System.out.println("》》》》》执行查询操作:add"); System.out.println("获取到数据并保存:" + student); } //更新操作 @Override public void update(Student student) { System.out.println("》》》》》执行查询操作:update"); System.out.println("获取到数据并保存:" + student); } //删除操作 @Override public void deleteById(String id) { System.out.println("》》》》》执行查询操作:add"); System.out.println("已成功删除数据 ID为:" + id); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrw="http://cxf.apache.org/jaxws" 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"> <!--设置Student的SEI接口实现类对象bean--> <bean id="studentWebService" class="cn.xw.webservice.impl.StudentWebServiceImpl"/> <jaxrw:server address="/student" serviceClass="cn.xw.webservice.StudentWebService"> <jaxrw:serviceBean> <!--这里可以使用直接引用上面的bean,或者你添加了注解扫描,类上有@Component注解的化也可以直接写注入到容器的名称--> <!--<ref bean="studentWebService"/>--> <!--或者单独指定类的限定名称--> <bean class="cn.xw.webservice.impl.StudentWebServiceImpl"/> </jaxrw:serviceBean> <!--配置请求和响应的日志打印--> <!--请求的请求头和请求体 响应的响应头和响应体--> <jaxrw:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/> </jaxrw:inInterceptors> <jaxrw:outInterceptors> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/> </jaxrw:outInterceptors> </jaxrw:server> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <!--引入加载其它配置文件--> <import resource="classpath:spring-cxf.xml"/> </beans>
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--创建监听器 监听Spring容器创建并加载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> <!--配置CXF控制器--> <servlet> <servlet-name>cxfServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>cxfServlet</servlet-name> <url-pattern>/WebService/*</url-pattern> </servlet-mapping> </web-app>
2:CXF_WS开发客户端
开发前根据wsdl说明书来使用wsimport.exe生成具体的客户端代码,这里不懂的请看第二章入门案例
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>spring_cxf_ws_client</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!--Spring核心上下文坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.8</version> </dependency> <!--Spring测试坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.8</version> </dependency> <!--SpringMVC坐标,内部依赖了web坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.8</version> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <!--CXF核心坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.4.3</version> </dependency> <!--CXF对WebService SOAP支持--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.4.3</version> </dependency> <!--CXF封装发送Http消息坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.4.3</version> </dependency> </dependencies> <build> <plugins> <!--配置maven编译版本--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source><!--源代码使用的JDK--> <target>1.8</target><!--target需要生成的目标class文件的编译版本--> <encoding>UTF-8</encoding><!--字符集编码,防止中文乱码--> <failOnError>true</failOnError><!--指示即使存在编译错误,构建是否仍将继续--> <failOnWarning>false</failOnWarning><!--指示即使存在编译警告,构建是否仍将继续--> <showDeprecation>false</showDeprecation><!--设置是否显示使用不推荐API的源位置--> <showWarnings>false</showWarnings><!--设为true若要显示编译警告,请执行以下操作--> <meminitial>128M</meminitial><!--编译器使用的初始化内存--> <maxmem>512M</maxmem><!--编译器使用的最大内存--> </configuration> </plugin> </plugins> </build> </project>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" 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--> <!--id为注入容器的名称--> <!--设置SEI接口的位置--> <!--address设置具体的WebService服务端位置地址--> <jaxws:client id="studentWebService" serviceClass="cn.xw.webservice.StudentWebService" address="http://localhost:8080/spring_cxf_ws_service/WebService/student?wsdl"> </jaxws:client> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <!--导入关于CXF的配置--> <import resource="classpath:spring-cxf.xml"/> </beans>
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/8 12:34 * @Description */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class TestClient { @Autowired @Qualifier(value = "studentWebService") private StudentWebService studentWebService; //查询测试 @Test public void findByAddressTest() { //创建指定类型数据 就对象就是自动生成的 FunFindByAddress address = new FunFindByAddress(); address.setAddress("安琥六安"); FunFindByAddressResponse students = studentWebService.funFindByAddress(address, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*"); //看打印效果,去Student和Dog加上toString System.out.println(students.getStudents()); } //更新操作 @Test public void funUpdateTest() throws DatatypeConfigurationException { //创建公历对象 GregorianCalendar gregorianCalendar = new GregorianCalendar(); //传入日期 转换为公历 gregorianCalendar.setTime(new Date()); //转换为XMLGregorianCalendar对象 XMLGregorianCalendar date2 = DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar); Student student = new Student(); student.setId(UUID.randomUUID().toString().replace("-", "")); student.setName("蚂蚁小哥"); student.setAge(23); student.setSex("男"); student.setBirthday(date2); student.setAddress("安徽合肥"); student.setDog(new Dog()); studentWebService.funUpdate(student); } //添加操作 @Test public void funAddTest() throws DatatypeConfigurationException { //创建公历对象 GregorianCalendar gregorianCalendar = new GregorianCalendar(); //传入日期 转换为公历 gregorianCalendar.setTime(new Date()); //转换为XMLGregorianCalendar对象 XMLGregorianCalendar date2 = DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar); Student student = new Student(); student.setId(UUID.randomUUID().toString().replace("-", "")); student.setName("蚂蚁小哥"); student.setAge(23); student.setSex("男"); student.setBirthday(date2); student.setAddress("安徽合肥"); student.setDog(new Dog()); studentWebService.funAdd(student); } //删除操作 @Test public void funDeleteByIdTest() { studentWebService.funDeleteById("st2545"); } //演示此代码 不可以在Test文件下运行,可以把main方法复制到src下随便一个类运行 public static void main(String[] args) throws DatatypeConfigurationException { ////创建日历对象 //Calendar createDate = Calendar.getInstance(); ////获取当前日历的时间 //Date cDate = createDate.getTime(); //System.out.println(cDate); ////创建公历对象 (日历的详细对象,超详细) //GregorianCalendar gregorianCalendar = new GregorianCalendar(); ////传入日期 转换为公历 //gregorianCalendar.setTime(cDate); //System.out.println(gregorianCalendar.getTime()); ////设置XML公历 } }
八:Spring整合CXF_RS(RestFul 重要)
Rest是一种风格,而RestFul是一种符合Rest风格的一种架构,RestFul风格的URL是一个资源定位,通过GET、POST、PUT、DELETE来区分查增改删方式,具体的规范请参考其它具体文章
我们在使用CXF_RS时我们是不用像开发WS一样编写完服务端还有根据WSDL说明书生成客户端代码,使用RS方式我们只要编写服务端代码,而客户端代码我们是不用自己编写的,我们客户端调用服务端方法只需要一个URL请求即可完成调用
1:CXF_RS开发服务端
在开发CXF_RS服务端时不理解的可以参考第二章节的入门开发方式,这里只是一个整合
@XmlRootElement(name = "Student") public class Student { private String id; //ID private String name; //姓名 private Integer age; //年龄 private String sex; //性别 private String address; //住址 private Date birthday; //生日 private Dog dog; //宠物狗 //省略get、set、构造器、toString.... } ////////////////////////////////////////////////// @XmlRootElement(name = "Dog") public class Dog { private String name; //名称 private String color; //颜色 private String msg; //介绍 //下面省略get、set、构造器、toString自己补一下 } ////////////////////////////////////////////////// @XmlRootElement(name = "Result") @XmlSeeAlso({Student.class}) public class Result<T> { private Integer code; //响应码 private String msg; //响应信息 private List<T> data; //响应数据 //省略get、set、构造器、toString.... }
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/10 19:55 * @Description 学生的服务接口 */ @Path("/student") @Produces("*/*") public interface StudentWebService { //根据地址查询学生 @GET @Path(value = "/find/{address}") @Produces({MediaType.APPLICATION_JSON}) //响应类型 Result<Student> findByAddress(@PathParam("address") String address); //根据ID删除学生 @DELETE @Path(value = "/delete/{id}") @Produces({MediaType.APPLICATION_ATOM_XML}) Result<Student> deleteById(@PathParam("id") String id); //根据ID更新学生 @PUT @Path("/update") @Produces({"application/json;charset=utf-8"}) //响应数据为JSON格式 @Consumes({"application/json;charset=utf-8"}) //请求数据为JSON格式 Result<Student> updateById(Student student); //添加学生 @POST @Path("/save") @Produces({"application/json;charset=utf-8"}) //响应数据为JSON格式 @Consumes({"application/json;charset=utf-8"}) //请求数据为JSON格式 Result<Student> save(Student student); } ///////////////////////////////////////////////////////// /** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/10 20:30 * @Description 编写Student服务实现类 */ public class StudentWebServiceImpl implements StudentWebService { @Override public Result<Student> findByAddress(String address) { System.out.println("》》》》》执行查询操作:findByAddress"); List<Student> list = new ArrayList<>(); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "蚂蚁小哥", 23, "男", address, new Date(), new Dog("大黄", "#ff0", "小狗真漂亮"))); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "许龄月", 25, "女", address, new Date(), new Dog("大白", "#fff", "小狗真白"))); return new Result<>(200, "查询成功", list); } @Override public Result<Student> deleteById(String id) { System.out.println("》》》》》执行删除操作:deleteById"); System.out.println("已成功删除数据 ID为:" + id); return new Result<>(200, "删除成功", null); } @Override public Result<Student> updateById(Student student) { System.out.println("》》》》》执行更新操作:updateById"); System.out.println("获取到数据并更新:" + student); return new Result<>(200, "更新成功", null); } @Override public Result<Student> save(Student student) { System.out.println("》》》》》执行保存操作:save"); System.out.println("获取到数据并保存:" + student); return new Result<>(200, "保存成功", null); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrs="http://cxf.apache.org/jaxrs" 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"> <!--服务实现类bean对象--> <bean id="studentWebService" class="cn.xw.webservice.impl.StudentWebServiceImpl"/> <!--address:设置服务的路径--> <!--注:RS方式编写服务端不要设置 serviceClass--> <jaxrs:server address="/"> <!--设置具体的实现服务类--> <jaxrs:serviceBeans> <ref bean="studentWebService"/> </jaxrs:serviceBeans> <!--配置请求和响应的日志打印--> <!--请求的请求头和请求体 响应的响应头和响应体--> <jaxrs:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/> </jaxrs:inInterceptors> <jaxrs:outInterceptors> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/> </jaxrs:outInterceptors> <!--配置提供响应转换JSON转换类--> <!--说明:cxf-rt-rs-extension-providers 和 jettison 坐标导入后我们可以不指定下面的提供者指出--> <jaxrs:providers> <bean class="com.colobu.fastjson.FastJsonProvider"/> </jaxrs:providers> </jaxrs:server> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <!--加载CXF配置文件--> <import resource="classpath:spring-cxf.xml"/> </beans>
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=info, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--创建监听器 监听Spring容器创建并加载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> <!--配置CXF控制器--> <servlet> <servlet-name>cxfServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>cxfServlet</servlet-name> <url-pattern>/WebService/*</url-pattern> </servlet-mapping> </web-app>
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>spring_cxf_rs_service</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!--Spring核心上下文坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.8</version> </dependency> <!--Spring测试坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.8</version> </dependency> <!--SpringMVC坐标,内部依赖了web坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.8</version> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <!--CXF核心坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.4.3</version> </dependency> <!--CXF关于http请求坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.4.3</version> </dependency> <!--CXF对RestFul Service的支持坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxrs</artifactId> <version>3.4.3</version> </dependency> <!-- 内置jetty web服务器 这里必须添加,因为运行在服务器上--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>3.4.3</version> </dependency> <!--fastJSON整合jaxrs坐标--> <dependency> <groupId>com.colobu</groupId> <artifactId>fastjson-jaxrs-json-provider</artifactId> <version>0.3.1</version> </dependency> <!--下面这两个坐标不设置就会导致 是请求端无法转换json发送,是接收端无法把json转换为对象接收--> <!--设置CXF提供者坐标--> <!--设置此下面这2个坐标我们可以不要在额外自己配置CXF提供者对JSON支持 --> <!--fastjson-jaxrs-json-provider和 fastjson就不用来回设置了--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-extension-providers</artifactId> <version>3.4.3</version> </dependency> <!--JSON转换坐标,会集成到CXF的providers里--> <dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>1.4.1</version> </dependency> <!--json工具包--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.67</version> </dependency> <!--日志坐标--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> <build> <plugins> <!--配置maven编译版本--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source><!--源代码使用的JDK--> <target>1.8</target><!--target需要生成的目标class文件的编译版本--> <encoding>UTF-8</encoding><!--字符集编码,防止中文乱码--> <failOnError>true</failOnError><!--指示即使存在编译错误,构建是否仍将继续--> <failOnWarning>false</failOnWarning><!--指示即使存在编译警告,构建是否仍将继续--> <showDeprecation>false</showDeprecation><!--设置是否显示使用不推荐API的源位置--> <showWarnings>false</showWarnings><!--设为true若要显示编译警告,请执行以下操作--> <meminitial>128M</meminitial><!--编译器使用的初始化内存--> <maxmem>512M</maxmem><!--编译器使用的最大内存--> </configuration> </plugin> </plugins> </build> </project>
2:CXF_RS开发客户端
其实CXF_RS开发客户端容易,我们其实不需要生成任何的代码来协助客户端的编写,我们只要借助WebClient就可以在任意的框架下使用,使用CXF_RS开发客户端我们得保证我们的实体类与服务端的一样
public class TestClient { public static void main(String[] args) { } //添加操作 @Test public void add() { //创建实体类 Student student = new Student(UUID.randomUUID().toString().replace("-", ""), "蚂蚁小哥", 22, "男", "安徽六安", new Date(), new Dog("大黄", "#ff0", "小狗真漂亮")); //发送POST请求并携带json数据 Result result = WebClient .create("http://localhost:8080/spring_cxf_rs_service/WebService/student/save/") .type(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .post(student).readEntity(Result.class); //打印数据 System.out.println(result); } //删除操作 @Test public void delete() { Result result = WebClient .create("http://localhost:8080/spring_cxf_rs_service/WebService/student/delete/st223") .accept(MediaType.APPLICATION_ATOM_XML) .delete().readEntity(Result.class); System.out.println(result); } //更新操作 @Test public void update() { //创建实体类 Student student = new Student(UUID.randomUUID().toString().replace("-", ""), "蚂蚁小哥", 22, "男", "安徽六安", new Date(), new Dog("大黄", "#ff0", "小狗真漂亮")); //发送PUT请求并携带json数据 Result result = WebClient.create("http://localhost:8080/spring_cxf_rs_service/WebService/student/update") .type(MediaType.APPLICATION_JSON) .accept("application/json;charset=utf-8") .put(student).readEntity(Result.class); System.out.println(result); } //查询 @Test public void findAllByAge() { //发送GET请求并根据年龄查询数据 Result result = WebClient .create("http://localhost:8080/spring_cxf_rs_service/WebService/student/find/安徽六安") .accept(MediaType.APPLICATION_JSON) .get(Result.class); System.out.println(result); List data = result.getData(); data.forEach(System.out::println); } }
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>spring_cxf_rs_client</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> </dependency> <!--CXF-RS客户端坐标支持--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-client</artifactId> <version>3.4.3</version> </dependency> <!--下面这两个坐标不设置就会导致 是发送端无法转换json发送,是接收端无法把json转换为对象接收--> <!--设置CXF提供者坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-extension-providers</artifactId> <version>3.4.3</version> </dependency> <!--JSON转换坐标,会集成到CXF的providers里--> <dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>1.4.1</version> </dependency> </dependencies> <build> <plugins> <!--配置maven编译版本--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source><!--源代码使用的JDK--> <target>1.8</target><!--target需要生成的目标class文件的编译版本--> <encoding>UTF-8</encoding><!--字符集编码,防止中文乱码--> <failOnError>true</failOnError><!--指示即使存在编译错误,构建是否仍将继续--> <failOnWarning>false</failOnWarning><!--指示即使存在编译警告,构建是否仍将继续--> <showDeprecation>false</showDeprecation><!--设置是否显示使用不推荐API的源位置--> <showWarnings>false</showWarnings><!--设为true若要显示编译警告,请执行以下操作--> <meminitial>128M</meminitial><!--编译器使用的初始化内存--> <maxmem>512M</maxmem><!--编译器使用的最大内存--> </configuration> </plugin> </plugins> </build> </project>
九:SpringBoot整合CXF_WS和CXF_RS
1:SpringBoot整合CXF_WS开发服务端
注:如果是我们创建BUS方法的话,那么注入到方法是必须写方法名如springBus(),如果是直接注入就写属性名称(参考配置类)
@XmlRootElement(name="Student") public class Student { private String id; //ID private String name; //姓名 private Integer age; //年龄 private String sex; //性别 private String address; //住址 private Date birthday; //生日 private Dog dog; //省略get、set、构造器、toString.... } /////////////////////////////////////////////////////////////////// @XmlRootElement(name="Dog") public class Dog { private String name; //名称 private String color; //颜色 private String msg; //介绍 //下面省略get、set、构造器、toString自己补一下 }
//Attributes portName, serviceName and endpointInterface are not allowed in the @WebService annotation of an SEI. //注:属性 portName、serviceName 和 endpointInterface 不允许出现在 SEI 的 @WebService 注释中。 @WebService( name = "StudentWebService", //设置端口类型名称 targetNamespace = "http://webservice.xw.cn") //设置命名空间 public interface StudentWebService { /*** * 根据地址查询 * @param address 条件 地址 * @param Accept 此参数我设置了header = true,代表此请求信息为头信息 * @return */ @WebMethod(operationName = "fun_findByAddress") @WebResult(name = "Students") List<Student> findByAddress( @WebParam(name = "address") String address, @WebParam(name = "Accept", header = true) String Accept ); //添加学生 @WebMethod(operationName = "fun_add") @Oneway //没有响应数据 void add(@WebParam(name = "student") Student student); //更新学生 @WebMethod(operationName = "fun_update") @Oneway void update(@WebParam(name = "student") Student student); //删除学生 @WebMethod(operationName = "fun_deleteById") @Oneway void deleteById(@WebParam(name = "id") String id); } /////////////////////////////////////////////////////////////////// @WebService( serviceName = "StudentWebServiceService", // 该webservice服务的名称,默认是SEI接口设置的name+Service targetNamespace = "http://webservice.xw.cn", //名称空间,通常使用接口包名反转 endpointInterface = "cn.xw.webservice.StudentWebService") //SEI服务接口全路径 @Component(value = "studentWebService") public class StudentWebServiceImpl implements StudentWebService { //根据地址查询全部学生数据 @Override public List<Student> findByAddress(String address, String Accept) { System.out.println("》》》》》执行查询操作:findByAddress"); System.out.println("获取请求头信息::" + Accept); List<Student> list = new ArrayList<>(); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "蚂蚁小哥", 23, "男", address, new Date(), new Dog("大黄", "#ff0", "小狗真漂亮"))); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "许龄月", 25, "女", address, new Date(), new Dog("大白", "#fff", "小狗真白"))); return list; } //保存操作 //注:日期类型必须以 yyyy-MM-dd @Override public void add(Student student) { System.out.println("》》》》》执行查询操作:add"); System.out.println("获取到数据并保存:" + student); } //更新操作 @Override public void update(Student student) { System.out.println("》》》》》执行查询操作:update"); System.out.println("获取到数据并保存:" + student); } //删除操作 @Override public void deleteById(String id) { System.out.println("》》》》》执行查询操作:add"); System.out.println("已成功删除数据 ID为:" + id); } }
import cn.xw.webservice.StudentWebService; import org.apache.cxf.Bus; import org.apache.cxf.bus.spring.SpringBus; import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; import org.apache.cxf.jaxws.EndpointImpl; import org.apache.cxf.transport.servlet.CXFServlet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import javax.xml.ws.Endpoint; /** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/11 13:44 * @Description 配置JaxWs */ @SpringBootConfiguration //告知是个配置类 public class JaxWsConfig { //注入SEI服务实现类 @Autowired @Qualifier(value = "studentWebService") private StudentWebService studentWebService; @Autowired private Bus bus; //注入Bus对象,它是用来发布SEI接口 //如果使用@Autowired的话不用在自己创建个Bus对象 //@Bean(name = Bus.DEFAULT_BUS_ID) //public SpringBus springBus() { // return new SpringBus(); //} //注册CXFServlet并设置映射地址;这一步就是我们以前在Spring集成CXF-WS时的web.xml里面配置CXFServlet @Bean public ServletRegistrationBean<CXFServlet> cxfServlet() { return new ServletRegistrationBean<>(new CXFServlet(), "/WebServices/*"); } //发布端点studentWebService;注如果有多个端点服务,可以再写个此方法注入端点发布 @Bean public Endpoint endpoint() { //创建端点并初始化端点SEI服务 EndpointImpl endpoint = new EndpointImpl(bus, studentWebService); //日志打印,监测请求与响应的数据 endpoint.getInInterceptors().add(new LoggingInInterceptor()); endpoint.getOutInterceptors().add(new LoggingOutInterceptor()); //发布端点SEI endpoint.publish("/student"); return endpoint; } }
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.w</groupId> <artifactId>springboot_cxf_ws_service</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot_cxf_ws_service</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--SpringBoot启动基本坐标--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--SpringBoot集成CXF-WS启动器坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.4.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
我们完成上面几步后,我们启动SpringBoot项目后可以通过访问?WSDL来查看我们发布的服务说明书,这时候我们客户端就可生成客户端代码了
关于客户端使用方式可以去参考我第二章我JAX_WS开发
2:SpringBoot整合CXF_RS开发服务端
Springboot整合CXF_RS开发服务端和整合CXF_WS的目录是一样的
@XmlRootElement(name = "Student") public class Student { private String id; //ID private String name; //姓名 private Integer age; //年龄 private String sex; //性别 private String address; //住址 private Date birthday; //生日 private Dog dog; //宠物狗 //省略get、set、构造器、toString.... } ///////////////////////////////////////////////////////// @XmlRootElement(name = "Dog") public class Dog { private String name; //名称 private String color; //颜色 private String msg; //介绍 //下面省略get、set、构造器、toString自己补一下 } ///////////////////////////////////////////////////////// @XmlRootElement(name = "Result") @XmlSeeAlso({Student.class}) public class Result<T> { private Integer code; //响应码 private String msg; //响应信息 private List<T> data; //响应数据 //省略get、set、构造器、toString.... }
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/10 19:55 * @Description 学生的服务接口 */ @Path("/student") @Produces("*/*") public interface StudentWebService { //根据地址查询学生 @GET @Path(value = "/find/{address}") @Produces({MediaType.APPLICATION_JSON}) //响应类型 Result<Student> findByAddress(@PathParam("address") String address); //根据ID删除学生 @DELETE @Path(value = "/delete/{id}") @Produces({MediaType.APPLICATION_ATOM_XML}) Result<Student> deleteById(@PathParam("id") String id); //根据ID更新学生 @PUT @Path("/update") @Produces({"application/json;charset=utf-8"}) //响应数据为JSON格式 @Consumes({"application/json;charset=utf-8"}) //请求数据为JSON格式 Result<Student> updateById(Student student); //添加学生 @POST @Path("/save") @Produces({"application/json;charset=utf-8"}) //响应数据为JSON格式 @Consumes({"application/json;charset=utf-8"}) //请求数据为JSON格式 Result<Student> save(Student student); } /////////////////////////////////////////////////////////////// @Component(value = "studentWebService") //注入Spring容器 public class StudentWebServiceImpl implements StudentWebService { @Override public Result<Student> findByAddress(String address) { System.out.println("》》》》》执行查询操作:findByAddress"); List<Student> list = new ArrayList<>(); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "蚂蚁小哥", 23, "男", address, new Date(), new Dog("大黄", "#ff0", "小狗真漂亮"))); list.add(new Student(UUID.randomUUID().toString().replace("-", ""), "许龄月", 25, "女", address, new Date(), new Dog("大白", "#fff", "小狗真白"))); return new Result<>(200, "查询成功", list); } @Override public Result<Student> deleteById(String id) { System.out.println("》》》》》执行删除操作:deleteById"); System.out.println("已成功删除数据 ID为:" + id); return new Result<>(200, "删除成功", null); } @Override public Result<Student> updateById(Student student) { System.out.println("》》》》》执行更新操作:updateById"); System.out.println("获取到数据并更新:" + student); return new Result<>(200, "更新成功", null); } @Override public Result<Student> save(Student student) { System.out.println("》》》》》执行保存操作:save"); System.out.println("获取到数据并保存:" + student); return new Result<>(200, "保存成功", null); } }
@SpringBootConfiguration //声明为配置文件 public class JaxRsConfig { //注入StudentWebService的服务实现类 @Autowired @Qualifier(value = "studentWebService") private StudentWebService studentWebService; @Autowired private Bus bus; //注入Bus对象,它是用来发布SEI接口 //如果使用@Autowired的话不用在自己创建个Bus对象 //@Bean(name = Bus.DEFAULT_BUS_ID) //public SpringBus springBus() { return new SpringBus(); } //注册CXFServlet并设置映射地址;这一步就是我们以前在Spring集成CXF-WS时的web.xml里面配置CXFServlet @Bean public ServletRegistrationBean<CXFServlet> cxfServlet() { return new ServletRegistrationBean<>(new CXFServlet(), "/WebServices/*"); } //发布服务 @Bean public Server createServer(){ //创建JAX-RS服务工厂 JAXRSServerFactoryBean factoryBean =new JAXRSServerFactoryBean(); //设置访问地址 factoryBean.setAddress("/student"); //设置Bus对象,用来发布服务 factoryBean.setBus(bus); //设置实体类对象 factoryBean.setServiceBean(studentWebService); //日志打印,监测请求与响应的数据 factoryBean.getInInterceptors().add(new LoggingInInterceptor()); factoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); return factoryBean.create(); } }
<!--SpringBoot集成CXF-RS启动器坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxrs</artifactId> <version>3.4.3</version> </dependency> <!--下面这两个坐标不设置就会导致 是请求端无法转换json发送,是接收端无法把json转换为对象接收--> <!--设置CXF提供者坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-extension-providers</artifactId> <version>3.4.3</version> </dependency> <!--JSON转换坐标,会集成到CXF的providers里--> <dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>1.4.1</version> </dependency>
十:WebService拦截器
1:WebService拦截器介绍
在前面的整合和入门案例中,关于CXF_WS/CXF_RS这两个技术中我都添加了系统的拦截器用来拦截日志的请求和响应并输出,其实拦截器就是用来拦截我们的请求和响应,用来拒绝非法访问,如访问前验证token啦,密码验证等等
系统拦截器: LoggingInInterceptor:系统请求入日志打印拦截器 LoggingOutInterceptor:系统响应出日志打印拦截器 自定义拦截器: 我们创建的每一个自定义拦截器都得继承AbstractPhaseInterceptor类 要注意的是,不管是系统的拦截器还是自定义拦截器,我们都要最终添加到端点发布的 如: //服务端入拦截器 List<Interceptor<? extends Message>> inInterceptors = endpoint.getInInterceptors(); inInterceptors.add(new LoggingInInterceptor());//设置请求拦截日志 //服务端出拦截器 List<Interceptor<? extends Message>> outInterceptors = endpoint.getOutInterceptors(); outInterceptors.add(new LoggingOutInterceptor());//设置响应拦截日志
2:SpringBoot整合CXF_WS并设置自定义拦截器(服务端)
本章我们说说SpringBoot整合CXF_WS,并设置了拦截器,请求头设置账号密码都正确才可以访问具体方法,我们复用上面的springboot整合CXF_WS的案例进一步添加功能
第一步我们就要创建一个自定义拦截器(入)加入容器了,用来校验请求来的账号密码是否匹配
第二步我们再创建一个自定义的拦截器(出)加入容器了,用来都操作成功后响应拦截器写回一些标签属性
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/31 19:38 * @Description 入拦截器,拦截请求 */ @Component //注入容器,要在发布服务配置里指定add添加 public class InJaxWsInterceptor extends AbstractPhaseInterceptor<SoapMessage> { //创建构造器,并传入拦截器触发阶段,Phase.PRE_PROTOCOL在预协议前执行(就是在请求响应前拦截) public InJaxWsInterceptor() { super(Phase.PRE_PROTOCOL); } //拦截的方法 @Override public void handleMessage(SoapMessage soapMessage) throws Fault { System.out.println("执行入拦截器拦截~~~~~~"); //获取到具体的请求头,当然你一下子获取全部也行 第一个参数是我们服务端的命名空间 第二个是要获取的头标签 //new QName("http://webservice.xw.cn", "safety") 如果请求头有命名空间得这么设置 Header header = soapMessage.getHeader(new QName("safety")); //List<Header> headers = soapMessage.getHeaders(); //是否获取到指定头信息 if (header == null) { //未获取到则需要 拦截 ,拦截则需要抛出方法指定的异常 throw new Fault(new IllegalArgumentException("请求头验证安全参数不可未空")); } //获取具的头标签内容,注:这里的Element是 org.w3c.dom.Element; 包里的 Element el = (Element) header.getObject(); //获取当前请求头safety标签里的全部指定标签 NodeList nameTag = el.getElementsByTagName("name"); NodeList passwordTag = el.getElementsByTagName("password"); if (nameTag.getLength() < 0 || passwordTag.getLength() < 0) { throw new Fault(new IllegalArgumentException("请求头参数不合法")); } //获取具体的账号密码 String name = nameTag.item(0).getTextContent(); String password = passwordTag.item(0).getTextContent(); //验证 if (!"tom".equals(name) || !"123456".equals(password)) { throw new Fault(new IllegalArgumentException("请求头参数账号密码不正确")); } //不抛异常就代表放行 System.out.println("密码正确,访问成功~~~"); } }
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/31 22:55 * @Description 服务端出拦截器 */ @Component public class OutJaxWsInterceptor extends AbstractPhaseInterceptor<SoapMessage> { //创建构造函数并设置 Phase.PRE_PROTOCOL 协议前调用此拦截器 public OutJaxWsInterceptor() { super(Phase.PRE_PROTOCOL); } //这个拦截器就给客户端响应一些数据 @Override public void handleMessage(SoapMessage message) throws Fault { System.out.println("执行出拦截器拦截~~~~~~"); //获取全部的请求头信息,后面要追加请求头信息 List<Header> headers = message.getHeaders(); //创建Document对象 Document document = DOMHelper.createDocument(); //要在响应前在请求头加上此响应头信息 // <encryptionKey> // <key msg="@#b%)a&S+2er">4623347787432355</key> // </encryptionKey> //创建2个元素,其中encryptionKey为根元素,添加了key Element encryptionKey = document.createElement("encryptionKey"); Element key = document.createElement("key"); //设置属性和数据并添加到根元素中 key.setAttribute("msg", "@#b%)a&S+2er"); key.setTextContent(UUID.randomUUID().toString().replace("-", "")); encryptionKey.appendChild(key); //把创建好的请求头添加到请求头集合中 headers.add(new Header(new QName("http://webservice.xw.cn", "encryptionKey"), encryptionKey)); } }
第三步光定义了自定义拦截器还不够,我们要像系统拦截器一样添加到服务发布端点里
//发布端点studentWebService;注如果有多个端点服务,可以再写个此方法注入端点发布 @Bean public Endpoint endpoint() { //创建端点并初始化端点SEI服务 EndpointImpl endpoint = new EndpointImpl(bus, studentWebService); //##添加系统拦截器 //服务端入拦截器 List<Interceptor<? extends Message>> inInterceptors = endpoint.getInInterceptors(); inInterceptors.add(new LoggingInInterceptor());//设置请求拦截日志 //注::这里我是以注入的方式 @Autowired private JaxWsInterceptor jaxWsInterceptor; inInterceptors.add(inJaxWsInterceptor); //设置请求头验证拦截 //服务端出拦截器 List<Interceptor<? extends Message>> outInterceptors = endpoint.getOutInterceptors(); outInterceptors.add(new LoggingOutInterceptor());//设置响应拦截日志 //注::这里我是以注入的方式 @Autowired private OutJaxWsInterceptor outJaxWsInterceptor; outInterceptors.add(outJaxWsInterceptor); //设置响应出拦截 //发布端点SEI endpoint.publish("/student"); return endpoint; }
Phase类里面定义了特别多静态常量,常用的就是 Phase.PRE_REOTOCOL 代表请求协议和内容过来前拦截
说明:自定义拦截器抛出Fault代表拦截,什么都不抛出,正常把方法执行完代表允许放行
2:SpringBoot整合CXF_WS并设置自定义拦截器(客户端)
客户端代码大家只需要通过wsdl2java工具生成即可,下面直接上重点调用
<dependencies> <!--CXF关于SOAP规范坐标--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.4.3</version> </dependency> <!--CXF关于安全的坐标,里面可以对获取的请求拦截--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-security</artifactId> <version>3.4.3</version> </dependency> <!-- 内置jetty web服务器 这里必须添加,因为运行在服务器上--> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> </dependencies>
我们客户端每次发送请求都要携带安全验证账号密码头,所以我直接在客户端中设置出拦截器,用来每次创建指定的头
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/31 22:36 * @Description */ public class JaxWsInterceptor extends AbstractPhaseInterceptor<SoapMessage> { private String name; private String password; public JaxWsInterceptor(String name, String password) { super(Phase.PRE_PROTOCOL); this.name = name; this.password = password; } // <web:safety> // <name>tom</name> // <password>123456</password> // </web:safety> @Override public void handleMessage(SoapMessage message) throws Fault { List<Header> headers = message.getHeaders(); Document document = DOMHelper.createDocument(); Element safety = document.createElement("safety"); Element name = document.createElement("name"); Element password = document.createElement("password"); name.setTextContent("tom"); password.setTextContent("123456"); safety.appendChild(name); safety.appendChild(password); headers.add(new Header(new QName("http://webservice.xw.cn", "safetyRequest"), safety)); } }
/** * @Author AnHui_XiaoYang * @Email 939209948@qq.com * @Date 2021/7/31 22:12 * @Description */ public class TestClient { public static void main(String[] args) { //创建服务视图对象 StudentWebServiceService service = new StudentWebServiceService(); //根据服务视图对象直接找到服务核心对象 通过wsdl里的binding---portType 直接获取到portType //实现类从wsdl文件的portType的name属性获取 StudentWebService studentWebServiceImplPort = service.getStudentWebServiceImplPort(); //创建客户端代理 Client client = ClientProxy.getClient(studentWebServiceImplPort); //##添加系统拦截器 //客户端入拦截器 List<Interceptor<? extends Message>> inInterceptors = client.getInInterceptors(); inInterceptors.add(new LoggingInInterceptor());//设置请求拦截 //客户端出拦截器 List<Interceptor<? extends Message>> outInterceptors = client.getOutInterceptors(); outInterceptors.add(new LoggingOutInterceptor());//设置响应拦截 outInterceptors.add(new JaxWsInterceptor("tom","123456")); //添加自定义拦截器,每次往外界访问都携带账号密码 //设置数据并访问打印 FunFindByAddress funFindByAddress = new FunFindByAddress(); funFindByAddress.setAddress("安徽六安"); FunFindByAddressResponse funFindByAddressResponse = studentWebServiceImplPort.funFindByAddress(funFindByAddress, "*/*,zh_CN"); List<Student> students = funFindByAddressResponse.getStudents(); students.forEach(System.out::println); } }
补充:WebService的热部署使用JRebel插件,在IDEA就可以搜索;