前言
在上一章节中,带大家学习了Web Service的理论知识。那么在接下来的章节中,会带大家在SpringBoot中整合Web Service,实现远程接口的调用。
一. 创建通用模块
在本项目中,我们需要创建远程调用的接口,也就是需要有一个可以在A项目里来调用B项目的接口。但A、B两个项目需要依赖一些共同的内容,所以我们应该首先创建这个被共同依赖的通用模块C。
1. 创建实体类User
我们先创建一个封装用户信息的实体类User。
package com.yyg.boot.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/9
* @Description Description
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private Long id;
private String username;
private String address;
}
2. 定义通用的MyService接口
这里壹哥先给大家讲解这个MyService接口的作用及工作原理。
接口类上添加@WebService注解,代表发布一个WebService服务;
接口类添加上@WebService注解后,类中所有的非静态方法都将会对外公布;
如果希望某个方法不对外公开,可以在方法上添加@WebMethod(exclude=true),阻止对外公开;
如果一个类上被添加了@WebService注解,则此类必须至少有一个可以公开的方法,否则将会启动失败。
protected、private、final、static方法不能对外公开。
package com.yyg.boot.service;
import com.yyg.boot.domain.User;
import javax.jws.WebService;
import java.util.List;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/9
* @Description Description
*/
@WebService(name = "MyService", // 暴露服务名称
targetNamespace = "http://service.boot.yyg.com"// 命名空间,一般是接口的包名倒序
)
public interface MyService {
String sayHello(String msg);
List<User> getUsers();
}
二. 创建Web Service中的Server端项目
接下来我们先创建服务端项目,该项目可以被其他的客户端项目进行远程调用。
1. 添加依赖包
我们在pom.xml文件中添加核心依赖包,如下所示:
<dependency>
<groupId>com.yyg.boot</groupId>
<artifactId>demo46_commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.1.12</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.1.12</version>
</dependency>
注意:网上有网友用cxf-spring-boot-starter-jaxws这个依赖包,但是我在Spring Boot2.2.5的环境中,使用该依赖会导致如下异常:
java.lang.ClassNotFoundException: org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer
所以我这里不适用该依赖,而是使用cxf-rt-frontend-jaxws与cxf-rt-transports-http依赖包。
2. 定义CXFServlet的配置类
然后我们还需要创建一个配置类,该类的功能作用如下。
通过EndPoint(端点服务)发布一个webService。Endpoint也是jdk提供的一个专门用于发布服务的类,它的publish方法接收两个参数,一个是本地的服务地址,二是提供服务的类。它位于javax.xml.ws.*包中。
Endpoint.publish(String address, Object implementor) 静态方法在给定地址处针对指定的实现者对象创建并发布端点。
package com.yyg.boot.config;
import com.yyg.boot.service.MyService;
import com.yyg.boot.service.impl.MyServiceImpl;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.xml.ws.Endpoint;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/9
* @Description Description
*/
@Configuration
public class CxfConfig {
@Bean
public ServletRegistrationBean createServletRegistrationBean() {
return new ServletRegistrationBean(new CXFServlet(), "/myService/*");
}
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
@Bean
public MyService myService() {
return new MyServiceImpl();
}
@Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), myService());
endpoint.publish("/api");
return endpoint;
}
}
注意:在该配置类中,网上有不少文章,在注册CXFServlet的时候,定义的方法名称都是dispatcherServlet,注意不要这么定义!!!否则会导致ErrorMvcAutoConfiguration错误。
//注意:该方法的名称不能使用dispatcherServlet(),否则会导致ErrorMvcAutoConfiguration错误.
//org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
//required a bean of type 'org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath'
// that could not be found.
// @Bean
// public ServletRegistrationBean dispatcherServlet() {
// return new ServletRegistrationBean(new CXFServlet(), "/myService/*");
// }
3. 定义MyServiceImpl接口实现类
在实现类中也要添加@WebService注解,指定serviceName,targetNamespace,endpointInterface属性。
package com.yyg.boot.service.impl;
import com.yyg.boot.domain.User;
import com.yyg.boot.service.MyService;
import javax.jws.WebService;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/9
* @Description Description
*/
@WebService(serviceName = "MyService", // 与接口中指定的服务name一致
targetNamespace = "http://service.boot.yyg.com", // 与接口中的命名空间一致,一般是接口的包名倒
endpointInterface = "com.yyg.boot.service.MyService"// 接口地址
)
public class MyServiceImpl implements MyService {
@Override
public String sayHello(String msg) {
return "收到的信息是:--->" + msg + ",现在时间是:--->" + new Date();
}
@Override
public List<User> getUsers() {
List<User> users = new ArrayList<>();
users.add(new User(1L, "一一哥", "北京"));
users.add(new User(2L, "一一哥", "上海"));
return users;
}
}
想看视频系统学习了找小助理领取资料哈,内附源码笔记!
4. 创建application.yml配置文件
我们还有创建一个application.yml配置文件,对服务端进行必要的配置。
server:
port: 8080
spring:
application:
name: web-service-server
5. 创建入口类
最后创建一个项目入口类,用于启动服务端。
package com.yyg.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/9
* @Description Description
*/
@SpringBootApplication
public class WebServiceApplication {
public static void main(String[] args) {
SpringApplication.run(WebServiceApplication.class, args);
}
}
6. 启动Server端项目进行测试
我们可以把项目启动起来,然后可以在控制台中打印出如下启动信息。
接着我们在浏览器中输入如下地址:http:/ /localhost:8080/myService/api?wsdl
就会看到如下的xml信息:
在这份xml文件中,指明了我们在service中定义的接口等信息。
7. 完整项目结构
完整的服务端项目结构如下图所示,各位可以参考创建。
三. 创建Web Service的client项目
接着我们再创建一个客户端项目,去调用服务端里的接口。
1. 添加依赖包
客户端项目也需要添加必要的核心依赖包。
<dependency>
<groupId>com.yyg.boot</groupId>
<artifactId>demo46_commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.1.12</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.1.12</version>
</dependency>
2. 创建application.yml配置文件
这里我们把客户端的端口号定义为8081。
server:
port: 8081
spring:
application:
name: web-service-client
3. 定义一个Controller进行测试
我们创建一个Controller,编写接口进行测试。
package com.yyg.boot.web;
import com.yyg.boot.domain.User;
import com.yyg.boot.service.MyService;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/9
* @Description Description
*/
@RestController
public class ClientController {
/**
* webservice接口地址
*/
private static String address = "http://localhost:8080/myService/api?wsdl";
@GetMapping("/show")
public String showMsg() {
try {
// 代理工厂
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
// 设置代理地址
jaxWsProxyFactoryBean.setAddress(address);
//添加用户名密码拦截器
//jaxWsProxyFactoryBean.getOutInterceptors().add(new LoginInterceptor("root","admin"));;
// 设置接口类型
jaxWsProxyFactoryBean.setServiceClass(MyService.class);
// 创建一个代理接口实现
MyService service = (MyService) jaxWsProxyFactoryBean.create();
// 调用代理接口的方法调用并返回结果
return service.sayHello("hello");
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}
@GetMapping("/users")
public List<User> showUsers() {
try {
// 代理工厂
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
// 设置代理地址
jaxWsProxyFactoryBean.setAddress(address);
// 设置接口类型
jaxWsProxyFactoryBean.setServiceClass(MyService.class);
// 创建一个代理接口实现
MyService service = (MyService) jaxWsProxyFactoryBean.create();
// 调用代理接口的方法调用并返回结果
return service.getUsers();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
4. 创建应用程序入口类
最后也要创建一个项目入口类,用于启动项目。
package com.yyg.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/9
* @Description Description
*/
@SpringBootApplication
public class CxfClientApplication {
public static void main(String[] args){
SpringApplication.run(CxfClientApplication.class,args);
}
}
想看视频系统学习了找小助理领取资料哈,内附源码笔记!
5. 启动项目进行测试
5.1 测试show接口
项目启动后,我们访问show接口,浏览器中可以展示如下信息。这就说明我们的客户端已经成功的调用了服务端的接口,实现了RPC跨进程通信。
5.2 测试users接口
然后我们再访问users接口,浏览器中可以展示如下信息,这说明我们的客户端也已经成功的调用了服务端的接口,也实现了RPC跨进程通信。
四. WSDL文件解析
现在我们的功能已经实现了,但Web Service的实现原理是什么?上面的那些XML文件都是什么含义?接下来 壹哥 带各位看看这些xml中的节点含义。
1. WSDL报文概述
我们先看看wsdl报文是什么含义。
<definitions>
<types>
定义 web service 使用的数据类型
</types>
<message>
每个消息均由一个或多个部件组成。可以把它当做java中一个函数调用的参数。
</message>
<portType>
它类似Java中的一个函数库(或一个模块、或一个类)
</portType>
<binding>
为每个端口定义消息格式和协议细节。
</binding>
</definitions>
2. WSDL报文之wsdl:definitions
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://service.boot.yyg.com" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="MyService" targetNamespace="http://service.boot.yyg.com">
</wsdl:definitions>
3. WSDL报文之wsdl:types
我们java中定义的服务接口中某个方法的输入参数和返回值。
4. WSDL报文之wsdl:message
通信消息的数据结构的抽象类型化定义,使用Types所定义的类型来定义整个消息的数据结构。
WebService中每个方法包含两部分:
一个是方法的输入参数,另一个是方法的输出参数。
其实质都是基于SOAP协议将其封装为消息,所以每一个方法对应有两个消息,一个输入一个输出回应。简单而言,就是方法和Message的关系是N:2N的关系,一对二。
Message中的具体内容是part,结合前面可知,message中的part内容请到前面定义过的types中去看,它会引用之前的type相关内容。
5. WSDL报文之wsdl:portType
portType = 接口
operation = 接口中定义的方法
6. WSDL报文之wsdl:binding
特定端口类型的具体协议和数据格式规范的绑定。
7. WSDL报文之wsdl:service
负责将网络通信地址赋给一个具体的绑定。
结语
至此,今天就带各位利用Web Service实现了两个不同项目之间的远程调用,现在你学会了吗?有什么问题,可以在评论区留言哦。
想看视频系统学习了找小助理领取资料哈,内附源码笔记!