Spring Boot实践八--用户管理系统

news2025/2/27 7:13:35

1,技术介绍

技术选型功能说明
springboot是一种基于 Spring 框架的快速开发应用程序的框架,它的主要作用是简化 Spring 应用程序的配置和开发,同时提供一系列开箱即用的功能和组件,如内置服务器、数据访问、安全、监控等,使开发者可以更加高效地构建和部署应用程序
Maven快速的引入jar包进行开发,自动构建部署
tomcatweb服务器,快速部署发布web 服务。Tomcat是一个开源的Java Servlet容器,它可以运行Java Servlet和JavaServer Pages(JSP)应用程序。作为一个Web服务器,它可以处理HTTP请求和响应,并将它们传递给Java Servlet和JSP应用程序进行处理。
ThymeleafThymeleaf是一种Java模板引擎,它可以将HTML、XML、JavaScript等文件转换为可执行的模板。在开发Web应用程序时,通常会使用Tomcat作为Web服务器,而Thymeleaf可以作为模板引擎来生成动态的Web页面。因此,Thymeleaf和Tomcat可以一起使用来构建动态Web应用程序。
junit单元测试框架
mybatis将Java对象与关系数据库进行映射,实现数据的持久化操作。mybatis的mapper文件是储存sql语句的一个xml文件,他替代了JDBC在类中写多条语句的问题,简化了步骤。
redis用作缓存。它的读写速度非常快,每秒可以处理超过10万次读写操作。高并发访问数据时直接走内存,和直接查询数据库相比,redis的高效性、快速性优势明显
mysql关系型数据库

1.1, Spring、Spring Boot和Spring Cloud的关系

在这里插入图片描述

我们可以这样理解:正是由于 IoC (控制反转,把创建好的对象给Spring进行管理)和 AOP(面向切面编程,不修改源代码的情况下进行功能增加) 这两个强大的功能才有了强大的轻量级开源JavaEE框架 Spring;Spring 生态不断地发展才有了 Spring Boot;Spring Boot 开发、部署的简化,使得 Spring Cloud 微服务治理方案彻底落地。

Spring Boot 在 Spring Cloud 中起到了承上启下的作用:

  • Springboot 将原有的 xml 配置,简化为 java 注解
  • 使用 IDE 可以很方便的搭建一个 springboot 项目,选择对应的 maven 依赖,简化Spring应用的初始搭建以及开发过程
  • springboot 有内置的 tomcat 服务器,可以 jar 形式启动一个服务,可以快速部署发布 web 服务
  • springboot 使用 starter 依赖自动完成 bean 配置,解决 bean 之间的冲突,并引入相关的 jar 包

1.2,Mybatis和Redis缓存的区别

Mybatis和Redis缓存的区别在于:

  • Mybatis缓存是基于内存的,而Redis缓存是基于磁盘的。即Mybatis缓存是在应用程序内部实现的,而Redis缓存是在外部服务器上实现的,这意味着Redis缓存可以在多个应用程序之间共享,而Mybatis缓存只能在单个应用程序实例中使用。
  • Mybatis缓存是局部缓存,只能缓存查询结果,而Redis缓存可以缓存任何类型的数据,包括对象、列表、哈希表等。
  • Mybatis缓存是默认开启的,但需要手动配置,而Redis缓存需要安装和配置Redis服务器。
  • Mybatis缓存是基于时间和空间的限制,而Redis缓存可以设置过期时间和最大内存使用量。

2,项目结构

 SpringBootRedis 工程项目结构如下:

  controller - Controller 层

  dao - 数据操作层

  model - 实体层

  service - 业务逻辑层

  Application - 启动类

  resources 资源文件夹

    application.properties - 应用配置文件,应用启动会自动读取配置

    generatorConfig.xml - mybatis 逆向生成配置(这里不是本文只要重点,所以不进行介绍)

    mapper 文件夹

      StudentMapper.xml - mybatis 关系映射 xml 文件

3,项目实现

配置

demospringboot\src\main\resources\application.properties:

# mysql 指定使用的数据库
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mysql5: spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 执行初始化sql
spring.datasource.initialize=true
spring.datasource.schema=classpath:schema.sql

# redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.shutdown-timeout=100ms

# 默认线程池
spring.task.execution.pool.core-size=2
spring.task.execution.pool.max-size=5
spring.task.execution.pool.queue-capacity=10
spring.task.execution.pool.keep-alive=60
spring.task.execution.pool.allow-core-thread-timeout=true
spring.task.execution.shutdown.await-termination=false
spring.task.execution.shutdown.await-termination-period=
spring.task.execution.thread-name-prefix=task-

# mybatis 指定mapper xml映射文件
mybatis.mapper-locations=classpath:mybatis/*.xml
# 打印mybatis的执行sql
# mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

demospringboot\pom.xml:

<?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.7.15-SNAPSHOT</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demospringboot</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demospringboot</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-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-commons</artifactId>
		</dependency>
		<!-- 添加mybatis依赖 -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.1</version>
		</dependency>

		<!-- 添加redis依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-pool2</artifactId>
		</dependency>

		<!-- 添加mysql依赖 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.30</version>
			<scope>runtime</scope>
			<!-- MySQL5.x时,请使用5.x的连接器(cmd执行mysql -V确定)
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.45</version>
			-->
		</dependency>

		<!-- 添加thymeleaf依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>


		<!-- 添加junit依赖 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.20</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<releases>
				<enabled>false</enabled>
			</releases>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<releases>
				<enabled>false</enabled>
			</releases>
		</pluginRepository>
	</pluginRepositories>

</project>

step0:数据库初始化

在前面的application.properties的sql配置中,我们指定了会自动创建mydatabase,并且指定了初始化sql:

spring.datasource.initialize=true
spring.datasource.schema=classpath:schema.sql

对应的demospringboot\src\main\resources\schema.sqlsql语句如下

drop database if exists mydatabase;

create database if not exists mydatabase character set utf8;

use mydatabase;

drop table if exists t_user;

CREATE TABLE `t_user` (
    `id` int NOT NULL AUTO_INCREMENT COMMENT '用户id',
    `username` varchar(100) DEFAULT NULL,
    `password` varchar(100) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';


INSERT INTO t_user VALUES(1,'admin','123456');
INSERT INTO t_user VALUES(2,'admin2','123456');
INSERT INTO t_user VALUES(3,'guanyu','1234');
INSERT INTO t_user VALUES(4,'zhangsan','1235');
INSERT INTO t_user VALUES(5,'lisi','1236');
INSERT INTO t_user VALUES(6,'wangwu','1237');
INSERT INTO t_user VALUES(7,'sunquan','1238');
INSERT INTO t_user VALUES(8,'sunwukong','1239');
INSERT INTO t_user VALUES(9,'zhubajie','1239');

然后主类实现CommandLineRunner run接口,执行initDatabase进行初始化:

进行如下调用:

package com.example.demospringboot;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.scheduling.annotation.EnableAsync;

import org.springframework.cache.annotation.EnableCaching;

import com.example.demospringboot.workmanager.WorkManager;
import com.example.demospringboot.workmanager.AsyncWorkManager;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@EnableCaching
@EnableAsync
@SpringBootApplication
// 需要指定扫描的类,并在配置文件指定mybatis.mapper-locations为对应的xml路径
@MapperScan(value = {"com.example.demospringboot.dao"})
public class DemospringbootApplication implements CommandLineRunner {
	@Autowired
	private DataSource dataSource;

	public static void main(String[] args) {
		SpringApplication.run(DemospringbootApplication.class, args);
	}

	@Override
	public void run(String... strings) throws SQLException {
		initDatabase();
	}

	private void initDatabase() throws SQLException {
		System.out.println("======== 自动初始化数据库开始 ========");
		Resource initData = new ClassPathResource("schema.sql");
		Connection connection = null;
		try {
			connection = dataSource.getConnection();
			ScriptUtils.executeSqlScript(connection, initData);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			if (connection != null) {
				connection.close();
			}
		}
		System.out.println("======== 自动初始化数据库结束 ========");
	}
}

如上我们通过DataSource.getConnection()总是从datasource或连接池返回一个新的连接,并通过ScriptUtils.executeSqlScript执行了我们的sql脚本。需要注意如果开发者没有手工释放这连接(显式调用 Connection.close() 方法),则这个连接将永久被占用(处于 active 状态),造成连接泄漏!

step1:数据库增删改查接口

首先,我们基于mabatis+redis+mysql实现一个user类数据库增删改查的基本功能。

实体类bean.User实现 Serializable 接口,因为 Spring 会将对象先序列化再存入 Redis

package com.example.demospringboot.bean;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String username;
    private String password;
}

dao层,定义UserMapper接口:

package com.example.demospringboot.dao;

import com.example.demospringboot.bean.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;

@Repository
@CacheConfig(cacheNames = "users")
public interface UserMapper {
    User findUserById(@Param("id") int id);
    User findUserByName(@Param("username") String username);
    String findPassword(String username);
    @Cacheable
    List<User> findAllUsers();
    void deleteUserById(@Param("id") int id);
    void deleteAllUsers();
    int insertUser(@Param("user") User user);
    void updateUserPassword(@Param("user") User user);
}

对应的demospringboot\src\main\resources\mybatis\UserMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- mapper标签要指定namespace属性,和实际的mapper文件一致-->
<mapper namespace="com.example.demospringboot.dao.UserMapper">
    <select id="findUserById" resultType="com.example.demospringboot.bean.User">
        select * from t_user where id = #{id}
    </select>
    
    <select id="findUserByName" resultType="com.example.demospringboot.bean.User">
        select * from t_user where username = #{username}
    </select>
    
    <select id="findAllUsers" resultType="com.example.demospringboot.bean.User">
        select * from t_user
    </select>

    <delete id="deleteAllUsers" >
        delete from t_user
    </delete>

    <delete id="deleteUserById" parameterType="int">
        delete from t_user where id=#{id}
    </delete>

    <insert id="insertUser" parameterType="com.example.demospringboot.bean.User">
        insert into t_user(id,username,password) values(#{user.id},#{user.username},#{user.password})
    </insert>

    <update id="updateUserPassword" parameterType="com.example.demospringboot.bean.User">
        update t_user set password=#{user.password} where id=#{user.id}
    </update>
</mapper>

主启动类:

package com.example.demospringboot;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Repository;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching
@EnableAsync
@SpringBootApplication
// 需要指定扫描的类,并在配置文件指定mybatis.mapper-locations为对应的xml路径
@MapperScan(value = {"com.example.demospringboot.dao"})
public class DemospringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemospringbootApplication.class, args);
    }

}

step2:Web页面

然后,我们基于springboot内嵌的tomcat,用Thymeleaf 模版实现一个存储用户账号密码的web界面如下:
在这里插入图片描述

实现userController:

package com.example.demospringboot.controller;

import com.example.demospringboot.bean.User;
import com.example.demospringboot.dao.UserMapper;

import com.sun.org.apache.bcel.internal.generic.ARETURN;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.ui.Model;

import javax.servlet.http.HttpSession;

@Controller
public class UserController {
    @Autowired
    UserMapper userService;

    // 访问http://localhost:8080/login时返回login.html页面
    @GetMapping(value = {"/login"})
    public String loginPage() {
        return "login";
    }

    //注册用户
    @GetMapping("gotoregister")
    public String register2Page(HttpSession session, Model model) {
        // 返回register.html
        return "register";
    }

    @PostMapping("register")
    public String RegisterUser(User user, Model model) {
        try {
            User userName = userService.findUserByName(user.getUsername());
            //没有用户可以进行注册
            if (userName == null) {
                if (user.getPassword().equals("") || user.getUsername().equals("")) {
                    model.addAttribute("tip", "请填写信息");
                    return "register";
                } else {
                    int ret = userService.insertUser(user);
                    if (ret > 0) {
                        model.addAttribute("tip", "注册成功,请返回登录页面进行登录");
                    }
                    return "register";
                }
            } else {
                model.addAttribute("tip", "用户已存在,请返回登录页面进行登录");
                return "register";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

    @PostMapping("login")
    public String loginSuccess(User user, HttpSession session, Model model) {
        try {
            //先查找一下有没有该账号
            User userName = userService.findUserByName(user.getUsername());
            if (userName != null) {
                //如果有账号则判断账号密码是否正确
                String password = userService.findPassword(user.getUsername());
                if (password.equals(user.getPassword())) {
                    //添加到session保存起来
                    session.setAttribute("loginUser", user);
                    //重定向到@GetMapping("success")
                    return "redirect:/success";
                } else {
                    //如果密码错误,则提示输入有误
                    model.addAttribute("msg", "账号或者密码有误");
                    return "login";
                }
            } else {
                model.addAttribute("msg", "账号或者密码有误");
                return "login";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

    @GetMapping("success")
    public String successPage(HttpSession session, Model model) {
        User loginUser = (User)session.getAttribute("loginUser");
        if (loginUser != null) {
            model.addAttribute("user", loginUser.getUsername());
            // 返回success.html
            return "success";
        } else {
            model.addAttribute("msg", "请登录");
            return "login";
        }
    }
}

访问http://localhost:8080/login时返回login.html页面。
点击注册,通过html的form表单th:action="@{/gotoregister}跳到Controller的@GetMapping("gotoregister"),返回registe.html页面。
点击登录,通过html的form表单th:action="@{/login}跳到Controller的@PostMapping("login"),查询成功后重定向到@GetMapping("success"),返回success.html

对应的html放置在demospringboot\src\main\resources\templates\目录下:

login.html:

<!DOCTYPE html >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>登录注册界面</title>
    <link rel="stylesheet" href="../static/style.css">
</head>

<body>
<!-- 整体布局 -->
<div class="container right-panel-active">
    <!-- 登录框 -->
    <div class="container_from container_signin">
        <form class="form" id="form" method="post" th:action="@{/login}">
            <h2 class="form_title">欢迎登录</h2>
            <div class="row">
                <span>用户名:</span>
                <input type="text" name="username" placeholder="请输入您的账号" class="input">
            </div>
            <div class="row">
                <span>&emsp;码:</span>
                <input type="password" name="password" placeholder="请输入您的密码" class="input">
            </div>
            <div class="row">
                <span th:text="${msg}"></span>
            </div>
            <input type="submit" class="btn" value="登录"/>
        </form>
        <form class="form" method="get" id="form1" th:action="@{/gotoregister}">
            <label id="register" class="form-label" >没有账号?请点击
            <input class="btn" type="submit" value="注册"/>
        </form>
    </div>
</div>
<script src="../static/login.js"></script>
</body>
</html>

register.html:

<!DOCTYPE html >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>登录注册界面</title>
    <link rel="stylesheet" href="../static/style.css">
</head>

<body>
<!-- 整体布局 -->
<div class="container right-panel-active">
    <!-- 注册框 -->
    <div class="container_from container_signup">
        <form class="from" method="post" id="from" th:action="@{/register}">
            <h2 class="form_title">注册账号</h2>
            <div class="row">
                <span>用户名:</span>
                <input type="text" id="username" name="username" placeholder="请输入账号" class="input">
            </div>
            <div class="row">
                <span>&emsp;码:</span>
                <input type="password" name="password" placeholder="请输入密码" class="input">
            </div>
            <!-- 提示注册信息${tip} -->
            <div class="row">
                <span th:text="${tip}"></span>
            </div>
            <input class="btn" type="submit" value="注册"/>
        </form>
    </div>
</div>
<script src="../static/login.js"></script>
</body>
</html>

success.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Thymeleaf Spring Boot Example</title>
</head>
<body>
<h1 th:text="'Welcome,'+${user}+'!'"></h1>
<a>You have successfully logged in !</a>
</body>
</html>

step3:多线程task

首先,实现两个UserService和AsyncUserService两个服务接口:

接口:

package com.example.demospringboot.service;

public interface UserService {
    void checkUserStatus();
}
package com.example.demospringboot.service;

public interface AsyncUserService {
    void checkUserStatus();
}

对应实现:

package com.example.demospringboot.service.impl;

import com.example.demospringboot.bean.User;
import com.example.demospringboot.service.UserService;
import com.example.demospringboot.dao.UserMapper;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public void checkUserStatus() {
        List<User> AllUsers = userMapper.findAllUsers();
        for (User u : AllUsers) {
           // System.out.println(ThreadUtils.getThreadName() + ": " + u);
            log.info("{}", u);
        }
    };
}

package com.example.demospringboot.service.impl;

import com.example.demospringboot.task.AsyncTasks;
import com.example.demospringboot.service.AsyncUserService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AsyncUserServiceImpl implements AsyncUserService {

    @Autowired
    private AsyncTasks asyncTasks;

    @Override
    public void checkUserStatus() {
        asyncTasks.doTaskOne("1");
        asyncTasks.doTaskOne("2");
        asyncTasks.doTaskOne("3");
    };
}

用到的task类如下:

package com.example.demospringboot.task;

import com.example.demospringboot.utils.ThreadUtils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.CompletableFuture;

@Slf4j
@Component
public class AsyncTasks {
    public static Random random = new Random();

    // @Async注解中的参数就是异步任务的线程池
   @Async("taskExecutor")
    public CompletableFuture<String> doTaskOne(String taskNo){
        log.info("开始任务:{}", taskNo);
        long start = System.currentTimeMillis();
        ThreadUtils.sleepUtil(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务:{},耗时:{} 毫秒", taskNo, end - start);
        return CompletableFuture.completedFuture("任务完成");
    }

}

(1)异步任务通过方法上的@Async("taskExecutor")和启动类的@EnableAsync注解实现,@Async中的参数指定了异步任务使用的的线程池。调用异步方法时不会等待方法执行完,调用即过,被调用方法在自己的线程池中奔跑。
(2)多线程执行的返回值是Future类型或void。Future是非序列化的,微服务架构中有可能传递失败。spring boot推荐使用的CompletableFuture来返回异步调用的结果。

用到的thread工具类如下:

package com.example.demospringboot.utils;

import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Repository;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;

@Repository
public class ThreadUtils {
    public static final int MAX_POOL_SIZE = 2;
    public static final String EXECUTOR_POOL_PREFIX = "exe-" + MAX_POOL_SIZE + "-";
    public static final String ASYNC_EXECUTOR_POOL_PREFIX = "async-exe-" + MAX_POOL_SIZE + "-";

    public static final String ASYNC_TASK_POOL_PREFIX = "async-task-" + MAX_POOL_SIZE + "-";

    // 自定义AsyncTask线程池
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(MAX_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(MAX_POOL_SIZE);
        executor.setKeepAliveSeconds(0);
        executor.setThreadNamePrefix(ASYNC_TASK_POOL_PREFIX);
        // 如果添加到线程池失败,那么主线程会自己去执行该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

    // 启动Executor的线程池
    public static ThreadPoolTaskExecutor getThreadPool(String threadNamePrefix) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(MAX_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(MAX_POOL_SIZE);
        executor.setKeepAliveSeconds(0);
        executor.setThreadNamePrefix(threadNamePrefix);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();

        return executor;
    }
    public static void sleepUtil(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            System.out.println(e);
        }
    }
}

线程池用的是ThreadPoolTaskExecutor 。Executor 顾名思义是专门用来处理多线程相关的一个接口,所有线程相关的类都实现了这个接口,里面有一个execute()方法,用来执行线程,线程池主要提供一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁的额外开销,提高了响应的速度。

ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理,是spring core包中提供的,而ThreadPoolExecutor是JDK中的JUC。

参数说明:

  • corePoolSize:核心线程数
  • queueCapacity:任务队列容量(阻塞队列)
  • maxPoolSize:最大线程数
  • keepAliveTime:线程空闲时间
  • rejectedExecutionHandler:任务拒绝处理器
    异步任务会先占用核心线程,核心线程满了其他任务进入队列等待;在缓冲队列也满了之后才会申请超过核心线程数的线程来进行处理。当线程数已经达到maxPoolSize,且队列已满,线程池可以调用这四个策略处理:
    • AbortPolicy策略:默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
    • DiscardPolicy策略:如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。
    • DiscardOldestPolicy策略:如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。
    • CallerRunsPolicy策略:如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。
    • 也可以自己实现RejectedExecutionHandler接口,可自定义处理器

为了控制异步任务的并发不影响到应用的正常运作,我们必须要对线程池做好相应的配置,防止资源的过渡使用。需考虑好默认线程池的配置和多任务情况下的线程池隔离。

上述服务我们就用不同线程池的两个WorkManager进行管理:

package com.example.demospringboot.workmanager;

import com.example.demospringboot.service.UserService;
import com.example.demospringboot.utils.ThreadUtils;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class WorkManager {
    private static final ThreadPoolTaskExecutor EXECUTOR_POOL =
            ThreadUtils.getThreadPool(ThreadUtils.EXECUTOR_POOL_PREFIX);

    @Autowired
    private UserService userService;

    public void startExecutor() {
        EXECUTOR_POOL.execute(new Executor(userService));
    }

     static class Executor implements Runnable {
        private UserService userService;

        public Executor(UserService userService) {
            this.userService = userService;
        }

        @Override
        public void run() {
            while (true) {
                userService.checkUserStatus();
                // sleep 1s
                ThreadUtils.sleepUtil(1000L);
            }
        }
    }
}

package com.example.demospringboot.workmanager;

import com.example.demospringboot.service.AsyncUserService;
import com.example.demospringboot.utils.ThreadUtils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class AsyncWorkManager {

    private static final ThreadPoolTaskExecutor ASYNC_EXECUTOR_POOL =
            ThreadUtils.getThreadPool(ThreadUtils.ASYNC_EXECUTOR_POOL_PREFIX);

    @Autowired
    private AsyncUserService asyncUserService;

    public void startSyncExecutor() {
        ASYNC_EXECUTOR_POOL.execute(new AsyncExecutor(asyncUserService));
    }

    static class AsyncExecutor implements Runnable {
        private AsyncUserService asyncUserService;

        public AsyncExecutor(AsyncUserService asyncUserService) {
            this.asyncUserService = asyncUserService;
        }

        @Override
        public void run() {
          while (true) {
                asyncUserService.checkUserStatus();
               // sleep 1s
               ThreadUtils.sleepUtil(1000L);
           }
        }
    }
}

主类如下:

package com.example.demospringboot;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

import org.springframework.cache.annotation.EnableCaching;

import com.example.demospringboot.workmanager.WorkManager;
import com.example.demospringboot.workmanager.AsyncWorkManager;

@EnableCaching
@EnableAsync
@SpringBootApplication
@MapperScan(value = {"com.example.demospringboot.dao"})
public class DemospringbootApplication implements CommandLineRunner {
    @Autowired
    private WorkManager workManager;

    @Autowired
    private AsyncWorkManager asyncWorkManager;

    public static void main(String[] args) {
        SpringApplication.run(DemospringbootApplication.class, args);
    }

    @Override
    public void run(String... strings) {
       //workManager.startExecutor();
       asyncWorkManager.startSyncExecutor();
    }
}

主启动类实现了CommandLineRunner 接口,会直接执行run方法。
我们在其中调用了WorkManager的startExecutor方法,用线程池execute方法启动了对应线程类的run方法。

test

package com.example.demospringboot;

import com.example.demospringboot.dao.UserMapper;
import com.example.demospringboot.bean.User;
import com.example.demospringboot.task.AsyncTasks;


import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.CompletableFuture;
import org.springframework.cache.CacheManager;

import java.util.List;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
@Rollback(value = false)
public class DemospringbootApplicationTests {
    @Autowired()
    private UserMapper userMapper;

    @Autowired
    private CacheManager cacheManager;

    @Test
    public void testUserMapper() throws Exception {
        // deleteAllUsers
        userMapper.deleteAllUsers();

        // insertUser 插入2条
        User user = new User();
        user.setId(100);
        user.setUsername("Jacky");
        user.setPassword("1000");
        userMapper.insertUser(user);
        user.setId(200);
        user.setUsername("Mike");
        user.setPassword("2000");
        userMapper.insertUser(user);

        // findUserById
        user = userMapper.findUserById(100);
        Assert.assertEquals("Jacky", user.getUsername());

        // updateUserPassword
        user.setPassword("1500");
        userMapper.updateUserPassword(user);
        Assert.assertEquals("1500", user.getPassword());

        // deleteUserById
        userMapper.deleteUserById(100);

        // findAllUsers
        List<User> AllUsers = userMapper.findAllUsers();
        for (User u : AllUsers) {
            System.out.println(u);
        }
        //Assert.assertEquals(1, AllUsers.size());

        System.out.println("CacheManager type : " + cacheManager.getClass());

    }


    @Autowired
    private AsyncTasks asyncTasks;


    @Test
    public void testTasks() throws Exception {
        long start = System.currentTimeMillis();

        // 线程池1
        CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");
        CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");
        CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");

        // 线程池2
        CompletableFuture<String> task4 = asyncTasks.doTaskTwo("4");
        CompletableFuture<String> task5 = asyncTasks.doTaskTwo("5");
        CompletableFuture<String> task6 = asyncTasks.doTaskTwo("6");

        // 一起执行
        CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();

        long end = System.currentTimeMillis();
        log.info("任务全部完成,总耗时:" + (end - start) + "毫秒");
    }

}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/896560.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一次性讲清OAuth2.0授权

文章目录 什么是OAuth2.0授权OAuth2.0工作原理OAuth2.0应用示例OAuth2.0授权模式授权码模式简化模式设备码模式 如何申请应用及密钥等信息 什么是OAuth2.0授权 OAuth2.0是一种用于授权的开放标准&#xff0c;它允许用户授权第三方应用访问其在另一个应用中存储的信息&#xff…

网络每日一练

吴泽彬 C Ip 网络层 Tcp udp 传输层&#xff0c; Http 应用层 收起 1 回复 发布于 2019-10-11 12:07 举报 fighting2016 Java A类地址中的私有地址和保留地址&#xff1a; ①10.0.0.0到10.255.255.255是私有地址&#xff08;所谓的私有地址就是在互联网上不使用&#xff0c;而被…

ASEMI探索整流桥GBU814的独特优势和应用领域

编辑-Z 整流桥GBU814在众多电子元件中独树一帜&#xff0c;可在多种设备中发挥其重要作用。作为一款集高效性能和可靠稳定性于一身的整流桥&#xff0c;GBU814已在全球范围内赢得了广泛的好评。在这篇文章中&#xff0c;我们将详细介绍GBU814整流桥的优势和应用领域。 让我们首…

对比 VPN 与远程桌面软件,为什么远程桌面更优越

数字格局不断演变&#xff0c;我们的工作和连接方式也在不断变化。企业纷纷转向远程运营&#xff0c;有关推进向远程过渡的最佳技术的争论从未停止。争论的焦点通常是虚拟专用网络&#xff08;VPN&#xff09;和远程桌面软件。 长期以来&#xff0c;VPN 一直被用作访问公司网络…

pytorch 实现VGG

VGG全称是Visual Geometry Group&#xff0c;因为是由Oxford的Visual Geometry Group提出的。AlexNet问世之后&#xff0c;很多学者通过改进AlexNet的网络结构来提高自己的准确率&#xff0c;主要有两个方向&#xff1a;小卷积核和多尺度。而VGG的作者们则选择了另外一个方向&a…

OJ练习第150题——分割回文串

分割回文串 力扣链接&#xff1a;131. 分割回文串 题目 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。 示例 Java代码 class Solution {List<List…

锐捷无线产品运维(Web登录、 命令行登录)

目录 登录AP产品 Console登录&#xff08;只可以现场登录&#xff09; Web/Telnet/SSH登录&#xff08;可以现场、远程登录&#xff09; 配置AP的管理地址 通过Web界面远程登录 通过Telnet、SSH等命令行的方式登录 登录AC产品 Console登录&#xff08;只可以现场登录&a…

el-button实现按钮,鼠标移入显示,移出隐藏

2023.8.18今天我学习了 如何实现鼠标移入显示按钮&#xff0c;鼠标移出隐藏按钮。 效果如图&#xff1a; 鼠标移入时&#xff1a; 鼠标移出时&#xff1a; mouseover //鼠标移入事件 mouseleave //鼠标移出事件 原本我是想直接在el-button写入这两个方法&#xff0c;但是elem…

【EI会议征稿】第四届经济管理与大数据应用国际学术会议(ICEMBDA 2023)

第四届经济管理与大数据应用国际学术会议(ICEMBDA 2023&#xff09; The 4th International Conference on Economic Management and Big Data Application(ICEMBDA 2023) 第四届经济管理与大数据应用国际学术会议(ICEMBDA 2023)定于2023年10月27-29日在中国天津举行。本次会…

linux 安装pycococreator和pycocotools

目录 1.&#x1f438;安装pycococreatortools&#x1f438; &#x1f353;&#x1f353;安装方式1 &#x1f353;&#x1f353;安装方式2 2.&#x1f438;安装pycocotools&#x1f438; &#x1f353;&#x1f353;安装方式1 &#x1f353;&#x1f353;安装方式2 整理不…

大模型时代,图像描述生成(image caption)怎么走?

背景 Image caption是计算机视觉研究领域中的一个重要分支&#xff0c;其主要目标是根据输入的图像信息&#xff0c;生成相应的文字描述&#xff0c;从而完成对图像内容的准确描述。对于图像描述任务而言&#xff0c;最关键的是能够将图片中的信息以清晰准确的文字形式展现出来…

OPPO A57刷机资源(附简略教程)

https://www.123pan.com/s/hcAqVv-fpHWd.html提取码:buAp 图中画框的为必须下载的&#xff08;xiaomiflash和twrp必须解压后使用&#xff09; ​ 打开xiaomiflash点击driver点击install&#xff08;就是框住的按钮&#xff09;等待安装完成 ​用数据线将oppo a57与电脑连接&a…

VMware Workstation搭建Centos7虚拟机详细步骤

直接按照图文步骤进行操作即可。 目录 1、新建虚拟机 2、典型安装 3、稍后安装操作系统 4、版本选择CentOS 7 64位 5、设置虚拟机的名称和位置 6、设置磁盘大小 7、虚拟机向导任务完成 8、虚拟机设置 9、开启虚拟机 10、正式安装虚拟机第一步 11、选择安装时的语言 …

嵌入式视觉将成为划时代的产品

随着PC、手机和互动游戏的出现&#xff0c;电脑视觉才走进消费性电子产品而为普通大众所熟悉。最近几年AI和微处理器的发展&#xff0c;更是极大地推动了电脑视觉在各个应用领域的发展&#xff0c;尤其是嵌入式视觉(Embedded Vision)&#xff0c;引起了业界的特别关注。 HAL 90…

行业追踪,2023-08-18

自动复盘 2023-08-18 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

期权就是股指期货吗,哪个好做一点?

近年来&#xff0c;场内ETF期权产品不断扩大&#xff0c;越来越多的投资者有投资期权的想法。当我们看到期权时&#xff0c;我们会不知不觉地想到期货&#xff0c;虽然期货与期权只有一个字的区别&#xff0c;但实际上有很大的不同&#xff0c;那么期权就是股指期货吗&#xff…

网格(mesh)点跟踪及在贴图中的应用

本文介绍网格跟踪的思路及其在贴图中的使用效果。网格跟踪即跟踪所有的网格点&#xff0c;然后根据网格点估算某一点的变形&#xff0c;相较于曲面跟踪可以在保证一定精度条件下大幅提高处理速度。这里介绍一种简单的网格跟踪思路&#xff0c;效果如下图所示&#xff1a; 创建网…

罗勇军 →《算法竞赛·快冲300题》每日一题:“质因子数量” ← 快速幂、素数筛

【题目来源】http://oj.ecustacm.cn/problem.php?id1780http://oj.ecustacm.cn/viewnews.php?id1023【题目描述】 给出n个数字&#xff0c;你可以任意选择一些数字相乘&#xff0c;相乘之后得到新数字x。 其中&#xff0c;x的分数等于x不同质因子的数量。 请你计算所有选择数…

<kernel>kernel 6.4 USB-之-hub_port_connect_change()分析

&#xff1c;kernel&#xff1e;kernel 6.4 USB-之-hub_port_connect_change()分析 kernel 6.4 USB系列文章如下&#xff1a; &#xff1c;kernel&#xff1e;kernel 6.4 USB-之-hub_event()分析 &#xff1c;kernel&#xff1e;kernel 6.4 USB-之-port_event()分析 本文是基…

kubernetes--技术文档--基本概念--《10分钟快速了解》

官网主页&#xff1a; Kubernetes 什么是k8s Kubernetes 也称为 K8s&#xff0c;是用于自动部署、扩缩和管理容器化应用程序的开源系统。 它将组成应用程序的容器组合成逻辑单元&#xff0c;以便于管理和服务发现。Kubernetes 源自Google 15 年生产环境的运维经验&#xff0c…