JavaSpring Boot整合WebSocket
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaSpring Boot整合WebSocket相关的知识,希望对你有一定的参考价值。
【Java】Spring Boot整合WebSocket
WebSocket简介
WebSocket是一种协议,用于实现客户端和服务器之间的双向通信。它可以在单个TCP连接上提供全双工通信,避免了HTTP协议中的请求-响应模式,从而实现更高效的数据交换。WebSocket协议最初由html5规范提出,现在已成为一种通用的网络协议,被广泛用于Web应用程序中。
WebSocket协议的主要特点包括:
- 建立在TCP上:WebSocket协议使用单个TCP连接进行全双工通信,避免了HTTP协议中的多次连接建立和断开操作,从而减少了网络延迟和带宽消耗。
- 双向通信:WebSocket协议支持双向通信,即客户端和服务器可以同时向对方发送和接收数据,实现更加灵活和高效的数据交换。
- 实时性:WebSocket协议可以实现实时通信,支持消息推送、实时聊天等功能,为Web应用程序带来更好的用户体验。
- 协议标准化:WebSocket协议已经被标准化,并且被广泛支持,几乎所有的现代浏览器都支持WebSocket协议。
WebSocket协议在Web应用程序中广泛使用,例如实现实时通信、在线游戏、即时消息等功能。开发者可以使用javascript编写客户端代码,使用Java、Node.js等语言编写服务器端代码,实现WebSocket协议的双向通信。
Pom
本次Spring Boot版本 2.7.8
,WebSocket 版本 5.3.25
.
parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
dependency
<!--WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
...
配置文件
WebSocketConfig
package cn.com.codingce.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
@Configuration
// 开启 WebSocket 支持
@EnableWebSocket
public class WebSocketConfig
/**
* 必须要有的
*
* @return serverEndpointExporter
*/
@Bean
public ServerEndpointExporter serverEndpointExporter()
return new ServerEndpointExporter();
/**
* WebSocket 配置信息
*
* @return servletServerContainerFactoryBean
*/
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer()
ServletServerContainerFactoryBean bean = new ServletServerContainerFactoryBean();
// 文本缓冲区大小
bean.setMaxTextMessageBufferSize(8192);
// 字节缓冲区大小
bean.setMaxBinaryMessageBufferSize(8192);
return bean;
使用
WebSocket 的注解:
注解 | 作用 | 备注 |
@ServerEndpoint | 用于声明WebSocket响应类,有点像@RequestMapping | @ServerEndpoint(“/websocket”) |
@OnOpen | WebSocket连接时触发 | 参数有:Session session, EndpointConfig config |
@OnMessage | 有消息时触发 | 参数很多,一会再说 |
@OnClose | 连接关闭时触发 | 参数有:Session session, CloseReason closeReason |
@OnError | 有异常时触发 | 参数有:Session session, Throwable throwable |
Controller
BaseWebSocketController
package cn.com.codingce.demo.conrtoller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author ma
*/
@Controller
@ServerEndpoint("/websocket")
public class BaseWebSocketController
Logger logger = LoggerFactory.getLogger(this.getClass());
// ConcurrentHashMap, 保证线程安全, static全局共享 session
// 这里之所以static,是因为这个类不是单例的!!
// 它虽然有@Controller注解,但是不适用Ioc容器中拿对象,每一次请求过来都是一个新的对象
/**
* 存放 session
*/
private final static Map<String, Session> SESSION_MAP = new ConcurrentHashMap<>();
/**
* OnOpen 在连接创建(用户进入聊天室)时触发
*
* @param session session
* @param config config
*/
@OnOpen
public void openSession(Session session, EndpointConfig config)
// 将session存起来, 用于服务器向浏览器发送消息
SESSION_MAP.put(session.getId(), session);
String res = "OnOpen [" + session.getId() + "]进入房间";
sendAll(res);
logger.info(res);
/**
* 响应字符串
*
* @param session session
* @param message message
*/
@OnMessage
public void onMessage(Session session, String message)
String res = "OnMessage [" + session.getId() + "]" + message;
sendAll(res);
logger.info(res);
/**
* 响应字节流
*
* @param session session
* @param message message
*/
@OnMessage
public void onMessage(Session session, byte[] message)
// 这个以后再说
/**
* OnClose 在连接断开(用户离开聊天室)时触发
*
* @param session session
* @param closeReason closeReason
*/
@OnClose
public void closeSession(Session session, CloseReason closeReason)
//记得移除相对应的session
SESSION_MAP.remove(session.getId());
String res = "OnClose [" + session.getId() + "]离开了房间";
sendAll(res);
logger.info(res);
@OnError
public void sessionError(Session session, Throwable throwable)
// 通常有异常会关闭 session
try
session.close();
catch (IOException e)
e.printStackTrace();
/**
* sendAll
*
* @param message message
*/
private void sendAll(String message)
for (Session s : SESSION_MAP.values())
// 获得session发送消息的对象
// Basic是同步, 会阻塞
// Async是异步, 这个会有多线程并发导致异常, 发送消息太快也会有并发异常, 需要有 消息队列 来辅助使用
final RemoteEndpoint.Basic remote = s.getBasicRemote();
try
// 发送消息
remote.sendText(message);
catch (IOException e)
e.printStackTrace();
前端
Conrtoller
package cn.com.codingce.demo.conrtoller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController
@RequestMapping("/index")
public String indexDefault(Model model)
return "index";
web
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>websocket-demo</title>
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css">
</head>
<body>
<div class="container py-3">
<div class="row">
<div class="col-6">
<div>
<label for="messageArea">聊天信息:</label>
</div>
<div>
<textarea id="messageArea" readonly class="w-100" style="height: 75vh;"></textarea>
</div>
</div>
<div class="col">
<div class="my-1">
<label for="messageArea">用 户 名:</label>
</div>
<div class="my-1">
<input type="text" id="username" autocomplete="off">
</div>
<div class="my-1">
<button class="btn-info" id="joinRoomBtn">进入聊天室</button>
<button class="btn-warning" id="leaveRoomBtn">离开聊天室</button>
</div>
<hr/>
<div class="my-1">
<label for="sendMessage">输入消息:</label>
</div>
<div>
<textarea id="sendMessage" rows="5" class="w-100" style="max-height: 50vh"></textarea>
</div>
<div class="my-1">
<button class="btn-primary" id="sendBtn">发送消息</button>
</div>
</div>
</div>
</div>
]
<script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js"></script>
<script>
let webSocket;
// ip和端口号用自己项目的
// websocket: 其实是刚刚那个@ServerEndpoint("/websocket")中定义的
let url = ws://127.0.0.1:8091/websocket;
$(#username).keyup(function (e)
let keycode = e.which;
if (keycode === 13)
$(#joinRoomBtn).click();
);
// 进入聊天室
$(#joinRoomBtn).click(function ()
let username = $(#username).val();
webSocket = new WebSocket(url);
webSocket.onopen = function ()
console.log(webSocket连接创建。。。);
webSocket.onclose = function ()
console.log(webSocket已断开。。。);
$(#messageArea).append(websocket已断开\\n);
webSocket.onmessage = function (event)
$(#messageArea).append(event.data + \\n);
webSocket.onerror = function (event)
console.log(event)
console.log(webSocket连接异常。。。);
);
// 退出聊天室
$(#leaveRoomBtn).click(function ()
if (webSocket)
// 关闭连接
webSocket.close();
);
// 发送消息
$(#sendBtn).click(function ()
var msg = $(#sendMessage).val();
if (msg.trim().length === 0)
alert(请输入内容);
return;
webSocket.send($(#sendMessage).val());
$(#sendMessage).val();
);
</script>
</body>
</html>
测试
页面
用户一:
用户二:
用户一收到的信息:
JavaSpring Boot 日志文件
文章目录
SpringBoot日志文件
1. 日志有什么用
日志是程序的重要组成部分,想象一下,如果程序报错了,不让你打开控制台看日志,那么你能找到报错的原因吗。
日志对于我们来说,最主要的用途就是排除和定位问题。除了发现和定位问题之外,我们还可以通过日志实现以下功能:
- 记录用户登录日志,方便分析用户是正常登录,还是恶意破解用户
- 记录系统的操作日志,方便数据恢复和定位操作人
- 记录程序的执行时间,方便以后优化程序提供数据支持
2. 日志怎么用
Spring Boot项目在启动的时候默认就会有日志的输出,如下图:
通过上述信息我们可以发现:
- Spring Boot内置了日志框架
- 默认情况下,输出的日志并不是开发者定义和打印的,那么开发者怎么在程序中定义打印日志?
- 日志默认是打印在控制台上,而控制台的日志是不能被保存的, 如何将日志永久的保存下来?
3. 自定义日志打印
开发者自定义打印日志的实现步骤:
- 在程序中得到日志
- 使用日志对象的相关语法输出要打印的内容
3.1 在程序中得到日志对象
private static final Logger log = LoggerFactory.getLogger(UserController.class);
日志工厂需要将每个类的类型传进去,这样我们才能知道日志的归属类,才能更方便更直观的定位到问题
注意:logger对象属于org.slf4j包下的,不要导错了
3.2 使用日志对象打印日志
日志对象的打印方法有很多种,我们可以使用info方法来输出日志,
@Controller
@ResponseBody
public class UserController
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@RequestMapping("/sayhi")
public void sayHi()
log.trace("trace");
log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
4. 日志级别
4.1 日志级别有什么用?
- 日志级别可以帮你筛选出重要的信息,比如设置日志级别为error,那么就可以只看到程序的报错日志了,对于普通的调试日志和业务日志就可以忽略了。从而节省开发者筛选的时间
- 日志级别可以控制不同环境下,一个程序是否需要打印日志,如开发环境我们需要很详细的信息,而生产环境为了保持性能和安全性就会输出少量的日志,而通过日志级别可以实现此类需求
4.2 日志级别的分类与使用
日志级别分为:
- trace: 微量,少许的意思,级别最低
- debug:需要调试时候的关键信息打印
- info:普通的打印信息(默认日志级别)
- warn:警告:不影响使用,但需要注意的问题
- error:错误信息,级别较高的错误日志信息
- fatal:致命的,因为代码异常导致程序退出执行的事件
日志级别的顺序:
越往上,接收到的信息就越少,如设置了warn就只能接收到warn及其上面的级别
日志级别设置
logging:
level:
root: error
默认日志输出级别
清除掉配置文件当中的日志设置,观察控制台输出的日志级别
得到结论,日志输出级别默认是info
当存在局部日志级别和全局日志级别设置,那么当访问局部日志时,使用的是局部日志级别。也就是局部日志优先级高于全局日志的优先级
5. 日志持久化
以上的日志都是输出在控制台上,然而生产环境上咱们需要将日志保存下来,以便出现问题之后追溯问题,把日志保存下来的过程叫做持久化
想要将日至持久化,只需要在配置文件中指定日志的存储目录或者是指定日志保存文件名,Spring Boot就会将控制台的日志写到相应的目录或文件下
配置日志文件的保存路径:
logging:
file:
path: D:\\rizhi
保存的路径,当中包含转义字符方面的设置我们可以使用这个/
来作为分割符。
如果坚持使用Windows下的分割符,我们需要使用\\
转义字符来转义一下
配置日志文件的文件名:
logging:
file:
name: D:/rizhi/logger/spring.log
6. 更简单的日志输出–lombok
每次使用LoggerFactory.getLogger很繁琐,且每个类都添加一遍,也很麻烦。这里的lombok是一种更好的日志输出方式
- 添加lombok框架支持
- 使用@slf4j注解输出日志
6.1 添加 lombok 依赖
首先要安装一个插件:
然后再pom.xml页面右键、
最后重新添加依赖就可以了
6.2 输出日志
使用@Slf4j注解,在程序中使用log对象即可输入日志并且只能使用log对象才能输出,这是lombok提供的对象名
6.3 lombok原理解释
lombok 能够打印⽇志的密码就在 target ⽬录⾥⾯,target 为项⽬最终执⾏的代码,查看 target ⽬录我们可以发现:
这里的@Slf4j注解变成了一个对象。
下面是java程序的运行原理:
6.4 lombok更多注解说明
基本注解
注解 | 作用 |
---|---|
@Getter | 自动添加get方法 |
@Setter | 自动添加set方法 |
@ToString | 自动添加toString方法 |
@EqualsAndHashCode | 自动添加equals和hasCode方法 |
@NoArgsConstructor | 自动添加无参构造方法 |
@AllArgsConstructor | 自动添加全属性构造方法,顺序按照属性的定义顺序 |
@NonNull | 属性不能为null |
@RequiredArgsConstructor | 自动添加必须属性的构造方法,final + @NonNull的属性为需 |
组合注解:
注解 | 作用 |
---|---|
@Data | @Getter+@Setter+EqualsAndHashCode+@RequiredArgsConstructor+@NoArgsConstructor |
日志注解
注解 | 作用 |
---|---|
@Slf4j | 添加一个名为log的对象 |
7. 总结
日志是程序员中的重要组成部分,使用日志可以快速的发现和定位问题,Spring Boot提供了日志框架,默认情况下使用的是info日志级别将日志输出到控制台的。我们可以通过lombok提供的@Slf4j注解和log对象快速的打印自定义日志,日志包含了6个级别:
- trace:微量,少许的意思,级别最低
- debug:需要调试的时候的关键信息打印
- info:普通的打印信息
- warn:警告,不影响使用,但需要注意的问题
- error:错误信息,级别较高的错误日志信息
- fatal:致命的,因为代码异常导致程序退出执行的事件
日志级别依次提升,而日志级别越高,收到的日志信息也就越少,我们可以通过配置日志的保存域名或保存目录来将日志永久的保存下来
以上是关于JavaSpring Boot整合WebSocket的主要内容,如果未能解决你的问题,请参考以下文章
六祎- JavaSpring整合Mybatis-配置文件web.xml