SpringBoot整合传统WebService
什么是传统WebService
传统的WebService是指使用SOAP协议(Simple Object Access Protocol)来进行通信和数据交换的Web服务。
-
使用SOAP协议来封装和传输数据,这些数据通常是以XML格式表示
SOAP(Simple Object Access Protocol)是一种基于XML的协议,用于在网络上进行信息交换。它定义了一种标准的消息格式和通信模式,使得不同的应用程序能够通过网络进行通信和数据交换。
SOAP协议使用XML格式来描述消息和数据,通过HTTP协议进行传输。它定义了一种通用的消息结构,包括消息头和消息体,可以在消息中传递不同类型的数据。
SOAP协议还定义了一套规范的操作和错误处理机制,使得应用程序能够进行远程调用和错误处理。
SOAP协议具有以下特点:
- 独立于平台和语言:SOAP协议是独立于平台和语言的,可以在不同的操作系统和编程语言之间进行通信和数据交换。
- 灵活的消息格式:SOAP协议使用XML格式来描述消息和数据,可以传递复杂的数据结构和对象。
- 强大的功能和扩展性:SOAP协议提供了一套强大的功能和扩展机制,可以支持复杂的消息传输和处理,适用于企业级应用和数据交换。
- 可靠和安全:SOAP协议提供了可靠的消息传输和安全机制,可以确保通信和数据的安全性。
SOAP协议广泛应用于Web服务和分布式系统中,通过定义接口和消息格式,实现不同系统之间的集成和交互。
-
使用WSDL(Web Services Description Language)来描述服务的接口和操作
WSDL(Web Services Description Language)是一种用于描述Web服务接口的XML格式标记语言。它定义了Web服务的接口、操作、消息和网络地址等信息,可以帮助客户端生成代码和调用服务。
WSDL文件通常由服务提供者创建,并在服务发布时提供给客户端。它包含了以下主要部分:
- 服务接口(PortType):描述了服务所支持的操作和消息,以及它们之间的关系。每个操作定义了输入和输出消息的结构。
- 消息(Message):定义了操作的输入和输出参数的数据类型和结构。消息可以包含多个部分,每个部分可以是简单类型(如字符串)或复杂类型(如自定义对象)。
- 绑定(Binding):将服务接口与具体的协议和消息格式绑定在一起。它定义了服务的网络地址、协议(如SOAP)和消息的编码方式。
- 服务(Service):描述了服务的名称和网络地址。一个WSDL文件可以包含多个服务,每个服务可以有多个端口。
WSDL文件可以通过HTTP或其他适当的方式公开,使得客户端可以通过解析WSDL文件来了解服务的接口和操作。客户端可以使用WSDL文件生成代码,以便调用服务的方法和传递参数。
WSDL是一种标准化的描述语言,它提供了一种统一的方式来描述Web服务的接口和操作。它使得不同的应用程序可以通过解析WSDL文件来理解和调用服务,实现了跨平台和松耦合的应用集成。
传统WebService与Restful
WebService和RESTful接口都是用于实现不同系统和应用之间的通信和数据交换的技术。
WebService是一种基于SOAP协议的技术,它使用XML格式来描述消息和数据,并通过HTTP协议进行传输。WebService提供了一套标准的协议和规范,包括WSDL(Web Services Description Language)和UDDI(Universal Description, Discovery, and Integration),用于描述接口和服务的结构和功能。WebService适用于复杂的企业级应用和数据交换,提供了强大的功能和灵活的扩展性,但复杂性和性能较低。
RESTful接口是一种基于REST(Representational State Transfer)原则的技术,它使用简洁的URL和HTTP方法来定义资源和操作,并使用JSON或XML等格式进行数据交换。RESTful接口遵循无状态和统一接口的原则,具有简洁和高性能的特点。RESTful接口适用于简单的Web服务和移动应用程序,具有良好的可扩展性和互操作性。
WebService的优点:
- 支持多种协议和数据格式:WebService可以使用不同的协议(如HTTP、SMTP、FTP等)和数据格式(如XML)进行通信和数据交换,具有较高的灵活性和可扩展性。
- 强大的功能和复杂的消息传输:WebService使用SOAP协议和WSDL描述服务接口,可以支持复杂的消息传输和处理,适用于复杂的企业级应用。
- 高度可靠和安全:WebService提供了强大的安全机制,如身份验证、加密传输等,可以确保通信和数据的安全性。
- 跨平台和跨语言的互操作性:WebService使用标准的Internet协议和格式,可以实现不同平台和编程语言之间的互操作性,方便不同系统之间的集成和交互。
WebService的缺点:
- 复杂性:WebService使用SOAP协议和XML数据格式,消息格式复杂,需要进行XML解析,增加了系统的复杂性和开发难度。
- 性能较低:由于SOAP消息的复杂性和XML解析的开销,WebService在性能方面相对较低,对于大规模数据传输和高并发请求可能存在性能瓶颈。
- 通信开销大:由于SOAP消息的冗余性和复杂性,WebService的通信开销相对较大,占用网络带宽和资源。
- 缺乏简洁性:由于SOAP消息的复杂性,WebService的接口设计相对较复杂,不够简洁和直观。
RESTful接口的优点:
- 简洁和直观:RESTful接口使用简洁的URL和HTTP方法,接口设计直观易懂,提供了良好的可读性和可维护性。
- 轻量级和高性能:RESTful接口使用简单的数据格式(如JSON),消息传输和解析开销较小,具有较高的性能和响应速度。
- 无状态和可缓存:RESTful接口是无状态的,每个请求都是独立的,不需要维护会话和状态信息,方便实现负载均衡和缓存机制,提高系统的性能和可扩展性。
- 易于集成和扩展:RESTful接口使用标准的HTTP协议和数据格式,可以方便地与其他系统集成,支持跨平台和跨语言的互操作性,同时也具有较好的可扩展性。
RESTful接口的缺点:
- 功能相对简单:相比于WebService,RESTful接口的功能相对简单,不适用于复杂的企业级应用和数据交换。
- 可扩展性较弱:RESTful接口使用简洁的URL和HTTP方法,扩展性相对较弱,对于复杂的消息传输和处理可能不够灵活。
- 缺乏标准化:RESTful接口没有像WebService那样的标准化协议和描述语言,接口设计和实现可能存在差异,不够统一和规范。
使用org.apache.cxf整合
Gradle项目增加依赖
api "org.apache.cxf:cxf-spring-boot-starter-jaxws:3.5.6"
Maven项目增加依赖
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.2.4</version>
</dependency>
服务端整合
增加webService服务配置
@Configuration
public class WebServiceConfig {
@Autowired
private MyService myService;
@Autowired
private BasicAuthInterceptor basicAuthInterceptor;
/**
* Apache CXF 核心架构是以BUS为核心,整合其他组件。
* Bus是CXF的主干, 为共享资源提供一个可配置的场所,作用类似于Spring的ApplicationContext,这些共享资源包括
* WSDl管理器、绑定工厂等。通过对BUS进行扩展,可以方便地容纳自己的资源,或者替换现有的资源。默认Bus实现基于Spring架构,
* 通过依赖注入,在运行时将组件串联起来。BusFactory负责Bus的创建。默认的BusFactory是SpringBusFactory,对应于默认
* 的Bus实现。在构造过程中,SpringBusFactory会搜索META-INF/cxf(包含在 CXF 的jar中)下的所有bean配置文件。
* 根据这些配置文件构建一个ApplicationContext。开发者也可以提供自己的配置文件来定制Bus。
*/
@Autowired
@Qualifier(Bus.DEFAULT_BUS_ID)
private SpringBus bus;
@Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(bus, myService);
endpoint.publish("/myService");
//拦截器增加basic认证时校验用户名密码
endpoint.getInInterceptors().add(basicAuthInterceptor);
return endpoint;
}
/**
* 此方法作用是改变项目中服务名的前缀名,此处127.0.0.1或者localhost不能访问时,
* 请使用ipconfig查看本机ip来访问
* 此方法被注释后:wsdl访问地址为http://127.0.0.1:8080/services/user?wsdl
* 去掉注释后:wsdl访问地址为:http://127.0.0.1:8080/soap/user?wsdl
*
* @return
*/
@Bean
public ServletRegistrationBean<CXFServlet> disServlet() {
return new ServletRegistrationBean<>(new CXFServlet(), "/webService/myservice/*");
}
}
增加service
@WebService(targetNamespace = "http://com.demo.myservice/")
public interface MyService {
@WebMethod
void sayHello();
@WebMethod
void sayHelloToParam(final @WebParam(name = "myParam") String myParam);
}
@Service
@WebService(serviceName = "myService", targetNamespace = "http://com.demo.myservice/",
endpointInterface = "com.ddys.gemsell.webservice.MyService")
public class MyServiceImpl implements MyService {
@Override
public void sayHello() {
System.out.println("Hello");
}
@Override
public void sayHelloToParam(String myParam) {
System.out.println("Hello" + myParam);
}
}
项目启动后访问ip:port/url url为在配置文件中配置的服务地址 即可访问webService服务
可以看到对应生成的可用的SOAP服务,点击服务可看到对应生成的WSDL文件信息
增加Http Basic验证
在某些场景下当在访问WebService时需要携带鉴权信息,此处使用Spring Security实现Http Basic认证
增加依赖
implementation 'org.springframework.boot:spring-boot-starter-security:2.3.3.RELEASE'
Spring Security配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用跨域请求伪造
http.csrf().disable();
//对webService服务接口进行拦截
http.authorizeRequests().antMatchers("/webService/myservice/**").hasRole("ADMIN")
.anyRequest().permitAll().and().httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());;
}
}
@Service
public class CustomUserDetailServiceImpl implements UserDetailsService {
public static final String DEFAULT_USER = "ADMIN";
public static final String DEFAULT_PASSWORD = "ADMIN";
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//配置角色
List<GrantedAuthority> roles = new ArrayList<>();
roles.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
//用户名匹配返回对应用户
if (DEFAULT_USER.equals(username)) {
return new User(username, new BCryptPasswordEncoder().encode(DEFAULT_PASSWORD), roles);
}
throw new UsernameNotFoundException("User not found");
}
}
配置拦截器
@Component
public class BasicAuthInterceptor extends AbstractSoapInterceptor {
public BasicAuthInterceptor() {
//调用之前拦截
super(Phase.PRE_INVOKE);
}
//可以做身份鉴权
@Override
public void handleMessage(SoapMessage message) throws Fault {
HttpServletRequest request = (HttpServletRequest)message.get(AbstractHTTPDestination.HTTP_REQUEST);
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Basic ")) {
String[] tokens = new String(Base64.getDecoder().decode(authHeader.substring(6))).split(":");
String username = tokens[0];
String password = tokens[1];
String localusername = "ADMIN";
String localpassword = "ADMIN";
if (localusername.equals(username) && localpassword.equals(password)) {
return;
}
}
throw new SecurityException("Unauthorized access");
}
}
再次访问接口
需要进行验证登录,输入配置的账号密码
可以重新访问。
客户端访问webService
动态调用
根据地址可直接访问调用
//创建动态客户端
JaxWsDynamicClientFactory factory = JaxWsDynamicClientFactory.newInstance();
Client client = factory.createClient("http://localhost:8088/gemsell-api/webService/myservice/myService?wsdl");
HTTPConduit conduit = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
//连接超时
httpClientPolicy.setConnectionTimeout(2000);
//取消块编码
httpClientPolicy.setAllowChunking(false);
//响应超时
httpClientPolicy.setReceiveTimeout(120000);
conduit.setClient(httpClientPolicy);
try{
Object[] objects = new Object[0];
//第一个参数为方法名,第二个为参数
objects = client.invoke("sayHelloToParam","123");
System.out.println("返回数据:" + objects[0]);
}catch (Exception e){
e.printStackTrace();
}
根据wsdl文件生成java代码调用
可使用maven插件根据WSDL文件生成Java代码,在使用生成的客户端代码进行服务调用
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>3.4.6</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.basedir}/src/main/java/mywebservice/</sourceRoot>
<wsdlOptions>
<wsdlOption>
<!-- wsdl文件地址,可以使用绝对地址和类路径,-->
<!-- 在使用类路径时,需要先把文件编译到classes中在进行生成-->
<wsdl>classpath:mywebservice.wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
执行Maven Install 生成对应的java代码
使用生成的代码调用服务端
MyService_Service service = new MyService_Service();
MyService myServiceImplPort = service.getMyServiceImplPort();
//方法参数
SayHelloToParam sayHelloToParam = new SayHelloToParam();
sayHelloToParam.setMyParam("213");
//调用方法
myServiceImplPort.sayHello();
myServiceImplPort.sayHelloToParam(sayHelloToParam.myParam);
设置权限信息
MyService_Service service = new MyService_Service();
MyService myServiceImplPort = service.getMyServiceImplPort();
// 设置权限信息
Map<String, Object> requestContext = ((BindingProvider) myServiceImplPort).getRequestContext();
requestContext.put(BindingProvider.USERNAME_PROPERTY, "ADMIN");
requestContext.put(BindingProvider.PASSWORD_PROPERTY, "ADMIN");
SayHelloToParam sayHelloToParam = new SayHelloToParam();
sayHelloToParam.setMyParam("213");
myServiceImplPort.sayHello();
myServiceImplPort.sayHelloToParam(sayHelloToParam.myParam);
问题及其他
问题
-
Spring Security默认启用了CSRF防御会导致POST请求出现403问题,所以增加
http.csrf().disable();
-
在使用maven插件使用classpath路径将WSDL文件生成Java代码过程中报错
-
classpath protocal unknow
先将wsdl文件编译到classes中
-
使用IDEA生成可能重复执行不会生成,可以重启IDEA尝试
-
-
在客户端调用工程中报错请使用 @XmlType.name 和 @XmlType.namespace 为类分配不同的名称。
我的客户端和服务端在同一项目中出现报错,分开后调用正常
其他
可以使用SoapUI进行webService的测试和调试