SpringBoot扩展——应用Web Service!

应用Web Service

Web Service是一个SOA(面向服务的编程)架构,这种架构不依赖于语言,不依赖于平台,可以在不同的语言之间相互调用,通过Internet实现基于HTTP的网络应用间的交互调用。Web Service是一个可以远程调用的类,即它可以让项目使用其他资源,如在网页上显示天气、地图、微博上的最新动态等,这些都是调用的其他资源。

Web Service简介

在Web Service体系架构中有3个角色:

服务提供者(Service Provider),也称为服务生产者;

服务请求者(Service Requester),也称为服务消费者;

服务注册中心(Service Register),服务提供者在这里发布服务,

服务请求者在这里查找服务,获取服务的绑定信息。

上述3个角色的请求过程如图6.20所示。

Web Service的3个角色间主要有3个操作:

发布(Publish):服务提供者把服务按照规范格式发布到服务注册中心。

查找(Find):服务请求者根据服务注册中心提供的规范接口发出查找请求,获取绑定服务所需的相关信息。

绑定(Bind):服务请求者根据服务绑定信息配置自己的系统,从而可以调用服务提供者提供的服务。

说明:Web Service是通过SOAP方式在Web上提供软件服务,使用WSDL文件说明提供的软件服务的具体信息,并通过UDDI进行注册。

Web Service的主要适用场景是软件的集成和复用,如气象局(服务端系统)、天气查询网站等,具体如下:

当发布一个服务(对内/对外),不考虑客户端类型和性能时,建议使用Web Service。

如果服务端已经确定使用Web Service,则客户端不能再选择其他框架,必须使用Web Service。

在Java项目开发中,Web Service框架主要包括Axis2和CXF,如果需要多语言的支持,建议选择Axis2。如果想和Spring集成或者其他程序集成,建议使用CXF,它们之间的区别如表6.4所示。

表6.4 Axis2和CXF区别

Spring Web Service简介

Spring Web Service(Spring-WS)是Spring团队开发的一个Java框架,其专注于创建文档驱动的Web服务。Spring Web Service的目的是促进契约优先的SOAP服务开发,通过配置XML文件的方式,创建灵活的Web服务,简化WebService的开发。

Spring Web Service有以下几个功能:

XML映射到对象:可以使用Message Payload和SOAP Action Header中存储的信息或使用XPath Expression将基于XML的请求映射给任何对象。

用于解析XML的多API支持:除了用于解析传入XML请求的标准JAXPAPI(DOM、SAX、StAX)之外,还支持其他库,如JDOM、dom4j、XOM。

用于划分多分组XML的多API支持:Spring Web Service使用其Object/XML Mapping模块支持JAXB 1和2、Castor、XMLBeans、JiBX和XStream库。Object/XML Mapping模块也可以用在非Web服务代码中。

基于Spring的配置:在Spring Web Service应用中可以方便、快速地使用Spring配置进行项目的自定义配置。

使用WS-Security模块:可以签名、加密、解密SOAP消息或对其进行身份验证。

支持Acegi安全性:使用Spring Web Service的WS-Security实现,Acegi配置可用于SOAP服务。

Spring Web Service是由5个模块组成的,各模块的功能如下:

Spring-WS Core:是主要模块,提供WebServiceMessage和SoapMessage等中央接口、服务器端框架、强大的消息调度功能及实现Web服务端点的支持类。它还提供Web Service使用者客户端作为WebServiceTemplate。

Spring-WS支持:为JMS和电子邮件等提供支持。

Spring-WS Security:负责提供与核心Web服务模块集成的WSSecurity实现。此模块允许使用现有的Spring SecurityImplementation进行身份验证和授权。Spring XML:为Spring Web Service提供XML支持类。该模块由Spring-WS框架内部使用。

Spring OXM:提供XML与对象映射的支持类。

这5个组件的关系如图6.21所示。

实战:Spring Web Service服务端发布项目

下面新建一个项目,并通过Spring Web Service服务端(功能提供者)发布。

(1)新建一个Web Service的提供者(provider),在pom.xml中添加Spring Web Service依赖如下:

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.2.10.RELEASE</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<groupId>com.example</groupId><artifactId>web-services-provider</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>web-services-provider</name>

<description>Demo project for Spring Boot</description>

<properties>

<java.version>1.8</java.version>

</properties>

<dependencies>

<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.3.5</version>

</dependency>

<dependency>

<groupId>org.apache.cxf</groupId>

<artifactId>cxf-rt-transports-http</artifactId>

<version>3.3.5</version>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

<exclusions>

<exclusion>

<groupId>org.junit.vintage</groupId>

<artifactId>junit-vintage-engine</artifactId>

</exclusion>

</exclusions>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId> </plugin>

</plugins>

</build>

(2)新建Web Service的配置类,在其中配置请求地址信息如下:

package com.example.webservicesprovider.config;

import com.example.webservicesprovider.service.DemoService;

import com.example.webservicesprovider.service.impl.DemoServiceImpl;

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;

@Configuration

public class CxfConfig {

@Bean

public ServletRegistrationBean<CXFServlet> cxfServlet() {

/**

* ServletRegistrationBean是Servlet注册类,

* 参数1为Servlet对象,参数2为请求到Servlet的地址

*/

return new ServletRegistrationBean<>(new CXFServlet(),

"/demo/*");

}

@Bean(name = Bus.DEFAULT_BUS_ID)

public SpringBus springBus() {

return new SpringBus();

} /**

* 类的注册

* @return

*/

@Bean

public DemoService demoService() {

return new DemoServiceImpl();

}

/**

* 发布多个服务时,创建多个接触点,并使用@Qualifier指定不同的名称

* @return

*/

@Bean

public Endpoint endpoint() {

EndpointImpl endpoint = new EndpointImpl(springBus(),

demoService());

endpoint.publish("/api");

return endpoint;

}

}

(3)新建Web Service提供服务的接口:

package com.example.webservicesprovider.service;

import javax.jws.WebService;

/**

* name: Web Service的名称;

* targetNamespace: 指定名称空间,一般使用接口实现类的包名的反缀

*/

@WebService(name = "DemoService", targetNamespace =

"http://impl.service.

server.example.com")

public interface DemoService { String sayHello(String user);

}

(4)新建接口的实现类,对外提供的功能的实现代码如下:

package com.example.webservicesprovider.service.impl;

import com.example.webservicesprovider.service.DemoService;

import javax.jws.WebService;

import java.time.LocalDateTime;

/**

* serviceName: 对外发布的服务名;

* targetNamespace: 指定名称空间,一般使用接口实现类的包名的反缀;

* endpointInterface: 服务接口的全类名;

*/

@WebService(serviceName = "DemoService"

, targetNamespace = "http://impl.service.server.example.com"

, endpointInterface =

"com.example.webservicesprovider.service.DemoService")

public class DemoServiceImpl implements DemoService {

@Override

public String sayHello(String user) {

return user + ",接收到了请求, 现在的时间是: " +

LocalDateTime.now();

}

}

(5)新建Spring Boot的启动类:

package com.example.webservicesprovider;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication

public class WebServicesProviderApplication {

public static void main(String[] args) {

SpringApplication.run(WebServicesProviderApplication.class,

args);

}

}

(6)在application.properties中设置项目端口为8080:

server.port=8080

实战:Spring Web Service客户端调用项目

完成了服务提供者的创建后,新建一个Spring Web Service的消费者(client),在pomx.xml中添加Spring Web Service依赖如下:

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.3.10.RELEASE</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<groupId>com.example</groupId>

<artifactId>web-services-client</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>web-services-client</name>

<description>Demo project for Spring Boot</description>

<properties>

<java.version>1.8</java.version>

</properties> <dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</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.3.5</version>

</dependency>

<dependency>

<groupId>org.apache.cxf</groupId>

<artifactId>cxf-rt-transports-http</artifactId>

<version>3.3.5</version>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

<exclusions>

<exclusion>

<groupId>org.junit.vintage</groupId>

<artifactId>junit-vintage-engine</artifactId>

</exclusion>

</exclusions>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>

(1)在客户端中新建一个测试服务调用的TestController入口,请求

Web Service的提供者对返回的信息进行解析并打印结果。

package com.example.webservicesclient;

import org.apache.cxf.endpoint.Client;

import

org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;

import org.apache.cxf.transport.http.HTTPConduit;

import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RestController;

import org.w3c.dom.Document;

import org.w3c.dom.Node;

import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import java.io.InputStream;

import java.io.OutputStream;

import java.io.OutputStreamWriter;

import java.net.URL;

import java.net.URLConnection;

import java.nio.charset.StandardCharsets;

@RestController

public class TestController {

@GetMapping("/test")

public void test() throws Exception {

//创建动态客户端

JaxWsDynamicClientFactory factory =

JaxWsDynamicClientFactory.newInstance();

//访问自己的服务端

Client client =

factory.createClient("http://localhost:8080/demo/api?wsdl");

// 需要密码时要加上用户名和密码

// client.getOutInterceptors().add(new

ClientLoginInterceptor(USER_NAME,PASS_WORD));

HTTPConduit conduit = (HTTPConduit) client.getConduit(); HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();


httpClientPolicy.setConnectionTimeout(2000); //连接超时


httpClientPolicy.setAllowChunking(false); //取消块编


httpClientPolicy.setReceiveTimeout(120000); //响应超时

conduit.setClient(httpClientPolicy);


//client.getOutInterceptors().addAll(interceptors); //设置拦

截器

try {

Object[] objects;

// 调用方式invoke("方法名",参数1,参数2,参数3....);

objects = client.invoke("sayHello", "cc, i miss you ");

System.out.println("返回数据:" + objects[0]);

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* 测试第三方的Web Service接口,测试天气

*/

@GetMapping("/testWeather")

public void testWeather() {

String weatherInfo = getWeather("北京");

System.out.println(weatherInfo);

}

/**

* 对服务器端返回的XML进行解析

*

* @param city用户输入的城市名称

* @return字符串用#分割

*/

private static String getWeather(String city) {

Document doc;

DocumentBuilderFactory dbf =

DocumentBuilderFactory.newInstance();

dbf.setNamespaceAware(true);

try {

DocumentBuilder db = dbf.newDocumentBuilder();

InputStream is = getSoapInputStream(city); assert is != null;

doc = db.parse(is);

NodeList nl = doc.getElementsByTagName("string");

StringBuffer sb = new StringBuffer();

for (int count = 0; count < nl.getLength(); count++) {

Node n = nl.item(count);

if ("查询结果为

空!".equals(n.getFirstChild().getNodeValue())) {

sb = new StringBuffer(" ");

break;

}

sb.append(n.getFirstChild().getNodeValue()).append("

\n");

}

is.close();

return sb.toString();

} catch (Exception e) {

e.printStackTrace();

return null;

}

}

/**

* 从接口文档中获取SOAP的请求头,并替换其中的标志符号为用户输入的城市

* (方法的接口文档:

* http://ws.webxml.com.cn/WebServices/WeatherWebService.asmx?

op=getWeatherbyCityName)

*

* @param city用户输入的城市名称

* @return客户将要发送给服务器的SOAP请求

*/

private static String getSoapRequest(String city) {

String sb = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +

"<soap:Envelope

xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +

"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +

"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +

"<soap:Body> <getWeatherbyCityName

xmlns=\"http://WebXml.com.cn/\">" +

"<theCityName>" + city +

"</theCityName> </getWeatherbyCityName>" +

"</soap:Body></soap:Envelope>";

return sb;

}

/**

* 通过接口文档的请求头构建SOAP请求,向服务器端发送SOAP请求,并返回流

*

* @param city用户输入的城市名称

* @return服务器端返回的输入流,供客户端读取

* @throws Exception异常

*/

private static InputStream getSoapInputStream(String city) throws

Exception {

try {

String soap = getSoapRequest(city);

// 通过请求的服务地址(即Endpoint)构建URL对象,并使用URL对象开启连接

URL url = new

URL("http://ws.webxml.com.cn/WebServices/WeatherWebService.asmx");

URLConnection conn = url.openConnection();

conn.setUseCaches(false);

conn.setDoInput(true);

conn.setDoOutput(true);

// 为连接设置请求头属性

conn.setRequestProperty("Content-Length",

Integer.toString(soap.length()));

conn.setRequestProperty("Content-Type", "text/xml;

charset=utf-8");

conn.setRequestProperty("SOAPAction",

"http://WebXml.com.cn/getWeatherbyCityName");

// 将请求的XML信息写入连接的输出流

OutputStream os = conn.getOutputStream();

OutputStreamWriter osw = new OutputStreamWriter(os,

StandardCharsets.UTF_8);

osw.write(soap);

osw.flush();

osw.close();

// 获取连接中请求得到的输入流

return conn.getInputStream();

} catch (Exception e) { e.printStackTrace();

return null;

}

}

}

(2)新建Spring Boot启动类:

package com.example.webservicesclient;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class WebServicesClientApplication {

public static void main(String[] args) {

SpringApplication.run(WebServicesClientApplication.class,

args);

}

}

(3)在application.properties中添加当前项目端口为8080:

server.port=8081

(4)启动项目服务端provider和客户端client服务,打开浏览器并且访问网址localhost: 8080/demo/api?wsdl,可以看到服务端提供的WebService的说明,如图6.22所示。

图6.22 服务端提供的Web Service说明详情

(5)访问localhost:8081/test,可以测试Web Service的调用,client完成了provider的功能调用,可以在控制台上看到打印信息,如图6.23所示,表明Web Service调用成功。

图6.23 测试Web Service调用

(6)访问
localhost:8081/testWeather,调用一个公开的Web Service方法可以查询北京市的天气,显示结果如图6.24所示。

图6.24 调用Web Service查询天气

至此完成了Web Service调用的演示。在开发中使用Web Service对外提供接口,能够更好地对外提供数据,实现特定的功能。

### Spring Boot WebSocket 集成 Redis 的实现方案 在实际开发中,Spring Boot 提供了强大的支持来集成 WebSocket 和 Redis。以下是关于如何在一个 Spring Boot 项目中通过 WebSocket 调用 Redis 服务的具体实现方法。 #### 1. 添加依赖项 为了使 Spring Boot 支持 WebSocket 和 Redis 功能,在 `pom.xml` 文件中需引入必要的依赖库: ```xml <dependencies> <!-- Spring Boot Starter Websocket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!-- Spring Data Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Lettuce or Jedis (choose one) --> <dependency> <groupId>io.lettuce.core</groupId> <artifactId>lettuce-core</artifactId> </dependency> </dependencies> ``` 以上配置包含了 WebSocket 和 Redis 所需的核心组件[^1]。 --- #### 2. 配置 Redis 连接 创建一个用于连接 Redis 的配置类,定义 RedisTemplate 并设置序列化方式以便于数据存储和读取操作。 ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public StringRedisSerializer stringRedisSerializer() { return new StringRedisSerializer(); } @Bean ChannelTopic topic() { return new ChannelTopic("chat"); } } ``` 此部分代码实现了对 Redis 数据的操作以及消息监听器的初始化工作[^2]。 --- #### 3. 实现 WebSocket 配置 接下来需要编写一个 WebSocket 配置文件以启用 STOMP 协议并注册端点路径 `/ws` 来处理客户端请求。 ```java import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); // 定义广播前缀 config.setApplicationDestinationPrefixes("/app"); // 设置应用级目标前缀 } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); // 注册 SockJS 端点 } } ``` 上述代码片段展示了如何利用 STOMP 协议扩展标准 WebSocket API,并允许更复杂的通信模式。 --- #### 4. 创建控制器逻辑 最后一步是在 Controller 中完成业务逻辑编码,当收到新消息时将其转发给订阅者或者保存到 Redis 缓存里。 ```java @Controller public class ChatController { private final SimpMessagingTemplate template; @Autowired public ChatController(SimpMessagingTemplate template){ this.template = template; } @MessageMapping("/send/message") public void sendMessage(@Payload Message message, Principal principal) throws Exception{ System.out.println(principal.getName()+" sent "+message.getContent()); // 将消息发送至所有已订阅用户 template.convertAndSend("/topic/messages",new Response(message)); // 同步更新 Redis 数据库中的记录副本 redisTemplate.opsForValue().set("last_message:"+principal.getName(),message); } } ``` 这里不仅完成了向在线用户的实时推送功能,还同步维护了一份最新的聊天历史缓存在内存型数据库——即 Redis 当中[^3]。 --- ### 总结 综上所述,本文介绍了基于 Spring Boot 构建的应用程序怎样借助 WebSocket 技术实现实时通讯的同时结合 Redis 存储机制优化性能表现的方法论。这种方法特别适合构建诸如即时通讯工具之类的高并发场景下的互联网产品原型设计阶段快速验证需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值