《穿越Spring Boot 》 第六章-Spring Boot 共享服务 | 第1节- Spring Boot 整合Spring Session

前题

基于:IntelliJ IDEAMaven构建工具Redis数据库LinuxNGINXSpringBoot 2.3.4 编写。

官人如需使用 IDEA 请阅读教程:IntelliJ IDEA

官人如需使用 Maven 请阅读教程:Maven 构建工具的下载与安装

官人如需使用Linux请阅读教程:CentOS Linux7 的安装

官人如需使用Redis请阅读教程:CentOS Linux下Redis的安装

更多干货

请阅读:《穿越SpringBoot》系列文章

请参考:Java学习资料

官网

官方网址:https://spring.io/projects/spring-session

在这里插入图片描述

目标

解决问题:当部署多台机器时,数据如何共享?

即nginx做负载均衡分发请求到多个tomcat,此时访问页面会把请求分发到不同的服务器。
session是存在服务器端,如果首次访问被分发到A服务器,那么session就会被存到A服务器。
再次访问时负载均衡会分发到B服务器,那么第一次访问的session信息就会获取不到之前的session信息.

本节将使用Spring Session 的session绑定来解决多台机器实现数据共享。

解决方案

Session 复制

通过对应用服务器的配置开启服务器的 Session 复制功能,在集群中的几台服务器之间同步 Session 对象,使得每台服务器上都保存所有的 Session 信息,这样任何一台宕机都不会导致 Session 的数据丢失,服务器使用 Session 时,直接从本地获取。这种方式的缺点也比较明显。因为 Session 需要时时同步,并且同步过程是有应用服务器来完成,由此对服务器的性能损耗也比较大。

Session 绑定

利用 hash 算法,比如 nginx 的 ip_hash,使得同一个 Ip 的请求分发到同一台服务器上。 这种方式不符合对系统的高可用要求,因为一旦某台服务器宕机,那么该机器上的 Session 也就不复存在了,用户请求切换到其他机器后么有 Session,无法完成业务处理。

利用 Cookie 记录 Session

Session 记录在客户端,每次请求服务器的时候,将 Session 放在请求中发送给服务器, 服务器处理完请求后再将修改后的 Session 响应给客户端。这里的客户端就是 cookie。 利用 cookie 记录 Session 的也有缺点,比如受 cookie 大小的限制,能记录的信息有限, 安全性低,每次请求响应都需要传递 cookie,影响性能,如果用户关闭 cookie,访问就不正常。

Session 服务器

Session 服务器可以解决上面的所有的问题,利用独立部署的 Session 服务器统一管理 Session,服务器每次读写 Session 时,都访问 Session 服务器。 对于 Session 服务器,我们可以使用 Redis 或者 MongoDB 等内存数据库来保存 Session 中的数据,以此替换掉服务中的 HttpSession。达到 Session 共享的效果。

使用

演示:开启两台Tomcat 让NGINX 代理, 模拟部署到两台机器上,实现登录功能。其中用到拦截器进行拦截,Redis数据库记录session值。

目录

在这里插入图片描述

pom.xml 依赖

集成springboot 用到的主要依赖:spring-session-data-redis , spring-boot-starter-data-redis

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>springsession</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springsession</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>
<!--web-->
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
<!--session-->
		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-data-redis</artifactId>
		</dependency>
<!--data-redis-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
<!--thymeleaf-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
<!--lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</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>
</project>

application.properties 配置类

连接Redis数据库,以及springsession的简单配置

spring.profiles.active=s1

spring.redis.host=172.16.2.166
spring.redis.password=123456

# 以下为 spring session的配置
# 选择使用redis 作为session存储
spring.session.store-type=redis
#配置session的超时
#规则: XD( 天)  T (分隔符)  XH(小时)  XM(分钟)  XS(秒) 演示21小时 :2dt1h(不区分大小写)
spring.session.timeout=30m

application-s1.properties

server.port=8850

application-s2.properties

server.port=8860

同时需要环境配置:引用两个端口,利用NGINX连接这两个端口如下图。

具体配置可参考:CentOS Linux7 下安装 NGINX
执行多个进程参考:SpringBoot 对 profile的支持

在这里插入图片描述

编写WebMvcConfig 类

此类实现了 WebMvcConfigurer 接口,重写了 addViewControllers视图跳转控制器 ,和addInterceptors拦截器。
具体参考:WebMvcConfigurer 详解

package com.example.springsession.config;

import com.example.springsession.interceptor.LoginInterceptor;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@AllArgsConstructor//基于lombok注入 可注入所有的
@Configuration//配置类
public class WebMvcConfig implements WebMvcConfigurer {
    
    

    //@Autowired注入
    private LoginInterceptor loginInterceptor;

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/login");
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(loginInterceptor);
    }
}

编写LoginInterceptor

拦截器的具体配置,及日志的输出。

@Slf4j是lombok的提供的日志注解

使用后可省略下图的 private static final Logger logger=LoggerFactory.getLogger(LoginInterceptor.class);

package com.example.springsession.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//lombok的日志注解
//@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    
    
    private static final Logger logger=LoggerFactory.getLogger(LoginInterceptor.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    

        //在目标控制器 方法执行之前进行拦截
        logger.info("拦截到请求:"+request.getRequestURI());
        //检查特殊路径:  /login  /doLogin
        if(request.getRequestURI().equals("/login") || request.getRequestURI().equals("/doLogin")){
    
    
            return true;
        }
        //检查当前用户是否存在,如果存在,放行。
        if(((String)request.getSession().getAttribute("user")).trim().length()>0 ){
    
    
            System.out.println(request.getSession().getAttribute("user"));
            return true;
        }
        //不存在,重定向到登录页面
        response.sendRedirect(request.getContextPath()+"/login");
        return false;
    }
}

编写 UserController

package com.example.springsession.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.http.HttpSession;
@Controller
public class UserController {
    
    
    @GetMapping(path = {
    
    "/","/index.html","/index"})
    public String showIndex(){
    
    
        return "index";
    }
    @PostMapping("/doLogin")
    public String doLogin(String name, String password, HttpSession session,Model model){
    
    
        session.setAttribute("user",name);
        return "redirect:/index";
    }
    @PostMapping("/logout")
    public String logout(HttpSession session){
    
    
        session.invalidate();
        return "login";
    }
}

编写 页面

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录界面</title>
</head>
<link rel="stylesheet" type="text/css" href="/reset.css" th:href="@{/reset.css}">
<link rel="stylesheet" type="text/css" href="/main.css" th:href="@{/main.css}">
<body>
<div class="login_form_bg" style="height: 615px;">
    <div class="login_form_wrap clearfix">
        <div class="login_form fr" style="margin-top:125px;margin-right: 389px;">
            <div class="form_input">
                <form th:action="@{/doLogin}" method="post">
                    <input type="text" name="name" id="name" class="name_input" placeholder="请输入用户名"><br>
                    <input type="password" name="password" id="password" class="pass_input" placeholder="请输入密码"><br>
                    <input type="submit" name="" value="登录" class="input_submit" style="margin-left: 0px">
                </form>
            </div>
        </div>
    </div>
</div>
</body>
</html>

index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<link rel="stylesheet" href="style.css">
<body>
<h1>这是我的测试系统首页</h1>
<h1><span th:text="${#session.getAttribute('user')}"></span>你好</h1>
<form th:action="@{/logout}" method="post">
    <input type="submit" value="注销">
</form>
</body>
</html>

编写启动类

package com.example.springsession;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringsessionApplication {
    
    
	public static void main(String[] args) {
    
    
		SpringApplication.run(SpringsessionApplication.class, args);
	}
}

测试

首先用到SwitchHost 访问本地地址:www.ceshi.com
在这里插入图片描述
idea中开启两个端口

在浏览器访问:www.ceshi.com发现已经进行了拦截。

未输入点击登录也进行了拦截

若将与SpringSession集成的代码 注销后,输入用户名,密码,依然登不上。

在这里插入图片描述
登录成功进入首页。
在这里插入图片描述

总结

待完善…

本教程基于最新的spring-boot-starter-parent:2.3.4RELEASE编写,目前很多大佬都写过关于SpringBoot的教程了,如有雷同,请多多包涵.

猜你喜欢

转载自blog.csdn.net/weixin_47371330/article/details/109232745