今天介绍另外一种websocket实现方式,结合了spring MVC,并完善了第二节所提到做一个简单的登录认证用来识别用户的名称。界面继续沿用第二节的布局样式,同时增加上线和下线功能。

参考了 https://blog.csdn.net/mybook201314/article/details/70173674 这篇文章,但是同时做了部分改进。更多内容请看https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket  这是spring 官方文档,相当详细,我做的过程中更多的是看官方介绍。

1.环境

Eclisp + jdk1.7 +tomcat 8,低版本的Tomcat7可能跑不起来,项目采用maven构建。

2.jar 包(只写websocket部分,spring 核心包就不写了)

        <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.0.2.RELEASE</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>4.0.2.RELEASE</version>
</dependency> <dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.23</version>
</dependency>

这里添加了阿里巴巴的fastjson来处理json字符串。当然不用这个也可以。

web.xml 配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<absolute-ordering />
<display-name>websocket2</display-name>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list> <!-- Spring配置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-content.xml</param-value>
</context-param> <listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <!-- Spring MVC配置 -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 可以自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<async-supported>true</async-supported>
<load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping> <!-- 中文过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> <servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping> </web-app>

web.xml 里面有三点需要注意:命名空间要3.0及以上,添加配置文件开头添加<absolute-ordering />,具体可参看官方文档 Deployment 这个章节。

构建完成的项目如下图所示

没有用的文件请自行忽略

3.实现

首先新建一个自己的websocket处理类,这个类需要实现WebSocketHandler 这个接口,当然根据官方文档 里面的这句话Creating a WebSocket server is as simple as implementing WebSocketHandler or more likely extending either TextWebSocketHandler or BinaryWebSocketHandler 也可以实现TextWebSocketHandler或者BinaryWebSocketHandler。这个接口里面有几个方法分别对应连接前,连接成功后,和消息处理,关闭连接等操作。

package com.lzl.ws;

import java.util.ArrayList;
import java.util.Map; import org.apache.log4j.Logger;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession; import com.alibaba.fastjson.JSON; //websocket处理类
public class MyWebSocketHandler implements WebSocketHandler{
private static final Logger log = Logger.getLogger(MyWebSocketHandler.class); // 保存所有的用户session
private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>(); //连接关闭后
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status)
throws Exception {
// TODO Auto-generated method stub
log.info("已经关闭连接。。。。");
users.remove(session);
} //连接就绪后
@Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
log.info("已经成功连接。。。sessionID是"+session.getId());
users.add(session); } //处理信息
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
throws Exception {
log.info("收到消息。。。sessionID:"+session.getId());
log.info("消息内容:"+message.getPayload().toString());
Map<String,Object > map = JSON.parseObject(message.getPayload().toString(),Map.class);
TextMessage textMessage = new TextMessage(map.get("userID").toString()+":"+map.get("msgContent").toString(),true);
log.info("转发:"+textMessage.getPayload().toString());
for(WebSocketSession user:users){
user.sendMessage(textMessage);
}
log.info("发送完成。。。。");
} //发生错误
@Override
public void handleTransportError(WebSocketSession session, Throwable throwable)
throws Exception {
// TODO Auto-generated method stub
log.info("发生错误。。。。"+throwable.getMessage()+"*****SessionID"+session.getId());
} @Override
public boolean supportsPartialMessages() {
// TODO Auto-generated method stub
return false;
} }

我这里是把消息发送给所有已经连接的客户了

接下来新建websocket注册类

package com.lzl.ws;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; @Configuration
@EnableWebMvc
@EnableWebSocket
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{ @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// TODO Auto-generated method stub
registry.addHandler(new MyWebSocketHandler(), "/websocket").addInterceptors(new HttpSessionHandshakeInterceptor()); } }

这个类一定 要在spring MVC 自动扫描的包下面。其中 HttpSessionHandshakeInterceptor 类称为握手操作类,可以自己新建一个类去继承他,这里面可以根据需要写一些建立连接前,和建立连接后的动作,我这里暂时没有用到,所有就直接调用父类了。

最后一步,在spring 配置文件中配置websocket

<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd"> <!-- 引入jdbc配置文件 -->
<!-- <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:application.properties</value>
</list>
</property>
</bean> -->
<context:property-placeholder location="classpath:application.properties"/>
<!-- 自动扫描注解的bean -->
<context:component-scan base-package="com.lzl" /> <!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean> <!-- 配置Mybatis的文件 ,mapperLocations配置**Mapper.xml文件位置,configLocation配置mybatis-config文件位置-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<property name="configLocation" value="classpath:mybatis-config.xml" />
</bean> <!-- 自动扫描了所有的XxxxMapper.xml对应的mapper接口文件,这样就不用一个一个手动配置Mpper的映射了,只要Mapper接口类和Mapper映射文件对应起来就可以了。 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.lzl.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean> <bean id = "Handler" class = "com.lzl.ws.MyWebSocketHandler"></bean>
<websocket:handlers>
<websocket:mapping path="/websocket" handler="Handler"/>
<websocket:handshake-interceptors>
<bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
</websocket:handshake-interceptors>
</websocket:handlers> </beans>

这里需要注意的是在在头文件添加webcoket支持。对于不支持的websocket的浏览器可以参照官方文档的  SockJS Fallback 这个章节处理,或者参考https://blog.csdn.net/mybook201314/article/details/70173674

至此,websocket 相关内容完成,接着做登录操作。登录这里很简单,直接将用户名称和聊天页面的地址返回。

package com.lzl.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView; import com.lzl.dao.User; @Controller
@RequestMapping("/login")
public class LoginController {
@RequestMapping(value = "/setUser.do",method=RequestMethod.POST)
public ModelAndView login(User user ,HttpServletRequest request,HttpServletResponse response ){
HttpSession session = request.getSession();
session.setAttribute("user", user);
ModelAndView mv = new ModelAndView();
mv.addObject("user", user);
mv.setViewName("chat");
return mv;
} @RequestMapping(value = "/getLogin.do",method=RequestMethod.GET)
public ModelAndView getlogin(){
System.out.println("-----------------------");
ModelAndView mv = new ModelAndView();
mv.setViewName("login");
return mv;
}
}

前端界面有两个,分别是登录界面和聊天界面,都是用bootstrap做的

login.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>用户登录</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit"> <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<style type="text/css">
body {
background-color:#f8f6e9;
}
.mycenter{
margin-top: 100px;
margin-left: auto;
margin-right: auto;
height: 350px;
width:500px;
padding: 5%;
padding-left: 5%;
padding-right: 5%;
}
.mycenter mysign{
width: 440px;
}
.mycenter input,checkbox,button{
margin-top:2%;
margin-left: 10%;
margin-right: 10%;
}
.mycheckbox{
margin-top:10px;
margin-left: 40px;
margin-bottom: 10px;
height: 10px;
}
</style>
<script type="text/javascript">
/* $(function(){
$("#submit").click(function(){
var data = $("#loginform").serialize();
$.post("${pageContext.request.contextPath}/login/setUser.do",data,function(result){
$("span").html(result);
}); }); }); */ </script> </head>
<body>
<form id="loginform" method = "post" action="${pageContext.request.contextPath}/login/setUser.do">
<div class="mycenter">
<div class="mysign">
<div class="col-lg-11 text-center text-info">
<h2>请登录</h2>
</div>
<div class="col-lg-10">
<input type="text" class="form-control" id = "username" name="username" placeholder="请输入账户名" required autofocus/>
</div>
<div class="col-lg-10"></div>
<div class="col-lg-10">
<input type="password" class="form-control" id = "password" name="password" placeholder="请输入密码" required autofocus/>
</div>
<div class="col-lg-10"></div>
<div class="col-lg-10 mycheckbox checkbox">
<input type="checkbox" class="col-lg-1">记住密码</input>
</div>
<div class="col-lg-10"></div>
<div class="col-lg-10">
<button type="submit" id = "submit" class="btn btn-success col-lg-12">登录</button>
</div>
</div>
</div>
</form>
</body>
</html>

chat.jsp

<%@page import="com.lzl.dao.User"%>
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>聊天</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit"> <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script type="text/javascript">
var websocket;
$(function() {
// 首先判断是否 支持 WebSocket
if('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/websocket2/websocket");
} else if('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://localhost:8080/websocket2/websocket");
} else {
websocket = new SockJS("http://localhost:8080/websocket2/sockjs/websocket");
}
// 打开时
websocket.onopen = function(evnt) {
$("#tou").html("链接服务器成功!")
};
// 处理消息时
websocket.onmessage = function(evnt) {
console.log(evnt.data);
$("#msg").html($("#msg").html() + "<br/>" + evnt.data);
};
websocket.onerror = function(evnt) {
console.log(" websocket.onerror ");
};
websocket.onclose = function(evnt) {
$("#tou").html("与服务器断开了链接!")
};
// 点击了发送消息按钮的响应事件
$("#send").click(function(){
// 获取消息内容
var text = $("#message").val();
// 判断
if(text == null || text == ""){
alert(" content can not empty!!");
return false;
}
var username = $("#username").val();
console.log("username="+username);
var msg = {
msgContent: text,
userID:username
};
// 发送消息
websocket.send(JSON.stringify(msg));
$("#message").val("");
}); $("#offline").click(function(){
console.log(websocket.readyState);
var state = websocket.readyState;
if(1==state){
websocket.close();
$("#offline").html("上线");
}else{
connect();
$("#offline").html("下线");
}
}); }); function connect (){
// 首先判断是否 支持 WebSocket
if('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/websocket2/websocket");
} else if('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://localhost:8080/websocket2/websocket");
} else {
websocket = new SockJS("http://localhost:8080/websocket2/sockjs/websocket");
}
// 打开时
websocket.onopen = function(evnt) {
$("#tou").html("链接服务器成功!")
};
// 处理消息时
websocket.onmessage = function(evnt) {
$("#msg").html($("#msg").html() + "<br/>" + evnt.data);
};
websocket.onerror = function(evnt) {
console.log(" websocket.onerror ");
};
websocket.onclose = function(evnt) {
$("#tou").html("与服务器断开了链接!")
};
} //给发送按钮绑定回车键事件
$(document).keydown(function(event){
if(event.keyCode == 13){ //绑定回车
$('#send').click();
}
});
</script>
</head>
<body>
<input type="hidden" value="${user.username}" id="username">
<div>
<div class="page-header" id="tou" style = "text-align:center">
webSocket多终端聊天测试
</div>
<div class="well" id="msg" style = "width:800px;margin:0 auto"></div>
&nbsp;
<div class="col-lg">
<div class="input-group" style = "width:800px;margin:0 auto">
<input type="text" class="form-control" placeholder="发送信息..." id="message">
<span class="input-group-btn">
<button class="btn btn-default" type="button" id="send" >发送</button>
</span>
</div>
</div>
</div>
&nbsp;
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<button class="btn btn-warning btn-block" type="button" id= "offline" style = "width:800px;margin:0 auto">下线</button>
</div>
</div>
</div>
</body>
</html>

4.效果

这是admin这个用户的界面

这是王小二的界面

Websocket --(3)实现的更多相关文章

  1. 漫扯:从polling到Websocket

    Http被设计成了一个单向的通信的协议,即客户端发起一个request,然后服务器回应一个response.这让服务器很为恼火:我特么才是老大,我居然不能给小弟发消息... 轮询 老大发火了,小弟们自 ...

  2. 细说WebSocket - Node篇

    在上一篇提高到了 web 通信的各种方式,包括 轮询.长连接 以及各种 HTML5 中提到的手段.本文将详细描述 WebSocket协议 在 web通讯 中的实现. 一.WebSocket 协议 1. ...

  3. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  4. WebSocket - ( 一.概述 )

    说到 WebSocket,不得不提 HTML5,作为近年来Web技术领域最大的改进与变化,包含CSS3.离线与存储.多媒体.连接性( Connectivity )等一系列领域,而即将介绍的 WebSo ...

  5. php+websocket搭建简易聊天室实践

    1.前言 公司游戏里面有个简单的聊天室,了解了之后才知道是node+websocket做的,想想php也来做个简单的聊天室.于是搜集各种资料看文档.找实例自己也写了个简单的聊天室. http连接分为短 ...

  6. Demo源码放送:打通B/S与C/S !让HTML5 WebSocket与.NET Socket公用同一个服务端!

    随着HTML5 WebSocket技术的日益成熟与普及,我们可以借助WebSocket来更加方便地打通BS与CS -- 因为B/S中的WebSocket可以直接连接到C/S的服务端,并进行双向通信.如 ...

  7. Cowboy 开源 WebSocket 网络库

    Cowboy.WebSockets 是一个托管在 GitHub 上的基于 .NET/C# 实现的开源 WebSocket 网络库,其完整的实现了 RFC 6455 (The WebSocket Pro ...

  8. 借助Nodejs探究WebSocket

    文章导读: 一.概述-what's WebSocket? 二.运行在浏览器中的WebSocket客户端+使用ws模块搭建的简单服务器 三.Node中的WebSocket 四.socket.io 五.扩 ...

  9. 细说websocket - php篇

    下面我画了一个图演示 client 和 server 之间建立 websocket 连接时握手部分,这个部分在 node 中可以十分轻松的完成,因为 node 提供的 net 模块已经对 socket ...

  10. webSocket and LKDBHelper的使用说明

    socketket与lkdbhelper来处理数据 客户需求: 当我们有需要从自己的后台推送消息给我们的用户时,用户需要实时的接收到来自我们的推送消息.前提是没有使用第三方的推送框架,那么这个使用we ...

随机推荐

  1. b2b推广方式有哪些-

    b2b推广方式有哪些 老黄牛推广软件订做 Q:935744345 专业团队,高效推广

  2. django开发博客01-页面展示数据库中的数据

    1.首先在views.py中引入models.py的 Category这个类 然后在函数中(blog)写执行逻辑 categorys 返回的对象是是一个list"<QuerySet [ ...

  3. maven项目创6 表现层整合

    springmvc.xm创建l 和 web.xml配置    ,报错先不管 springmvc.xml com.taotao.controller   空包 其中    资源映射   是等 web.x ...

  4. 16位masm汇编实现记忆化递归搜索斐波那契数列第50项

    .model small ;递归fib,使用压缩BCD码,小端派 .data y1 byte 6 dup(0) y2 byte 6 dup(0) vis byte 1,1,1,61 dup(0) ;便 ...

  5. 深度学习笔记(十二)车道线检测 LaneNet

    论文:Towards End-to-End Lane Detection: an Instance Segmentation Approach 代码:https://github.com/MaybeS ...

  6. 试用saucelabs进行浏览器兼容性测试

    Hi,all 跟大家分享下saucelabs,一个云测试平台,支持PC和手机(自带的)浏览器的兼容性测试,并且支持selenium/appium的自动化测试,不过是收费的,价格还挺贵,但是人工的测试是 ...

  7. Android 造炫目的圆形菜单 秒秒钟高仿建行圆形菜单

    1.概述 今天打开建行看存款,一看伤心欲绝,再看:我擦,这个圆形菜单挺炫.于是,为了掩盖我悲痛的心情,我决定是实现这个效果.好了,其实还有个原因,记得我初学android那会我做的应用被鄙视了,说我的 ...

  8. phoenix-hbase 使用

    建表命令 CREATE TABLE IF NOT EXISTS "person_mul"( "ROW" varchar primary key, //主键,必须 ...

  9. [CSP-S模拟测试]:笨小猴(随机化)

    题目传送门(内部题118) 输入格式 输入第一行是一个整数$n$,意义如以上所示. 接下来有$2n+1$行,每行为两个正整数,第$i$行的两个正整数分别代表$A_i$和$B_i$. 输出格式 如果无法 ...

  10. springboot 测试发送邮件

    首先在pom文件引入依赖: <!--email依赖 --> <dependency> <groupId>org.springframework.boot</g ...