技术选型 | 功能说明 |
---|---|
springboot | 是一种基于 Spring 框架的快速开发应用程序的框架,它的主要作用是简化 Spring 应用程序的配置和开发,同时提供一系列开箱即用的功能和组件,如内置服务器、数据访问、安全、监控等,使开发者可以更加高效地构建和部署应用程序 |
Maven | 快速的引入jar包进行开发,自动构建部署 |
tomcat | web服务器,快速部署发布web 服务。Tomcat是一个开源的Java Servlet容器,它可以运行Java Servlet和JavaServer Pages(JSP)应用程序。作为一个Web服务器,它可以处理HTTP请求和响应,并将它们传递给Java Servlet和JSP应用程序进行处理。 |
Thymeleaf | Thymeleaf是一种Java模板引擎,它可以将HTML、XML、JavaScript等文件转换为可执行的模板。在开发Web应用程序时,通常会使用Tomcat作为Web服务器,而Thymeleaf可以作为模板引擎来生成动态的Web页面。因此,Thymeleaf和Tomcat可以一起使用来构建动态Web应用程序。 |
junit | 单元测试框架 |
mybatis | 将Java对象与关系数据库进行映射,实现数据的持久化操作。mybatis的mapper文件是储存sql语句的一个xml文件,他替代了JDBC在类中写多条语句的问题,简化了步骤。 |
redis | 用作缓存。它的读写速度非常快,每秒可以处理超过10万次读写操作。高并发访问数据时直接走内存,和直接查询数据库相比,redis的高效性、快速性优势明显 |
mysql | 关系型数据库 |
我们可以这样理解:正是由于 IoC (控制反转,把创建好的对象给Spring进行管理)和 AOP(面向切面编程,不修改源代码的情况下进行功能增加) 这两个强大的功能才有了强大的轻量级开源JavaEE框架 Spring;Spring 生态不断地发展才有了 Spring Boot;Spring Boot 开发、部署的简化,使得 Spring Cloud 微服务治理方案彻底落地。
Spring Boot 在 Spring Cloud 中起到了承上启下的作用:
Mybatis和Redis缓存的区别在于:
SpringBootRedis 工程项目结构如下:
controller - Controller 层
dao - 数据操作层
model - 实体层
service - 业务逻辑层
Application - 启动类
resources 资源文件夹
application.properties - 应用配置文件,应用启动会自动读取配置
generatorConfig.xml - mybatis 逆向生成配置(这里不是本文只要重点,所以不进行介绍)
mapper 文件夹
StudentMapper.xml - mybatis 关系映射 xml 文件
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>
在前面的application.properties的sql配置中,我们指定了会自动创建mydatabase,并且指定了初始化sql:
spring.datasource.initialize=true
spring.datasource.schema=classpath:schema.sql
对应的demospringboot\src\main\resources\schema.sql
sql语句如下
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 状态),造成连接泄漏!
首先,我们基于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);
}
}
然后,我们基于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>密 码:</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>密 码:</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>
首先,实现两个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。
参数说明:
为了控制异步任务的并发不影响到应用的正常运作,我们必须要对线程池做好相应的配置,防止资源的过渡使用。需考虑好默认线程池的配置和多任务情况下的线程池隔离。
上述服务我们就用不同线程池的两个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方法。
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 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 task1 = asyncTasks.doTaskOne("1");
CompletableFuture task2 = asyncTasks.doTaskOne("2");
CompletableFuture task3 = asyncTasks.doTaskOne("3");
// 线程池2
CompletableFuture task4 = asyncTasks.doTaskTwo("4");
CompletableFuture task5 = asyncTasks.doTaskTwo("5");
CompletableFuture task6 = asyncTasks.doTaskTwo("6");
// 一起执行
CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();
long end = System.currentTimeMillis();
log.info("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
}