SpringBoot环境配置
大约 13 分钟
自定义启动 Banner
自定义启动 Banner
- 每一个 SpringBoot 程序启动时,都会在控制台利用指定的艺术结构生成有 Spring 启动 Banner 信息,同时考虑项目个性化的需要,也可以由使用者自定义启动 Banner

1、http://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20
2、
__ __
__ _ ____ _ ____ _ __ ___.__. ____ _____/ |_| | __ ____ ____ _____
\ \/ \/ /\ \/ \/ /\ \/ \/ / < | |/ _ \ / _ \ __\ |/ / _/ ___\/ _ \ / \
\ / \ / \ / \___ ( <_> | <_> ) | | < \ \__( <_> ) Y Y \
\/\_/ \/\_/ \/\_/ /\ / ____|\____/ \____/|__| |__|_ \ /\ \___ >____/|__|_| /
\/ \/ \/ \/ \/ \/
3、org.springframework.boot.Banner
4、
package org.springframework.boot;
import java.io.PrintStream;
import org.springframework.core.env.Environment;
@FunctionalInterface // 此为函数式接口
public interface Banner { // 由SpringBoot所提供的内部接口
/**
* 通过指定PrintStream(打印流)来实现启动Banner输出
* @param environment 项目启动时所指派的profile
* @param sourceClass 应用程序类
* @param out 实现Banner信息输出
*/
void printBanner(Environment environment, Class<?> sourceClass, PrintStream out);
enum Mode { // Banner的启动的模式
OFF, // 不输出Banner信息
CONSOLE, // 在控制台输出Banner
LOG // 在日志中输出Banner
}
}
5、
.___ __ __
____ __| _/_ __ ___.__. ____ _____/ |_| | __ ____ ____ _____
_/ __ \ / __ | | \ < | |/ _ \ / _ \ __\ |/ / _/ ___\/ _ \ / \
\ ___// /_/ | | / \___ ( <_> | <_> ) | | < \ \__( <_> ) Y Y \
\___ >____ |____/ /\ / ____|\____/ \____/|__| |__|_ \ /\ \___ >____/|__|_| /
\/ \/ \/ \/ \/ \/ \/ \/
6、
package com.yootk.banner;
import org.springframework.boot.Banner;
import org.springframework.core.env.Environment;
import java.io.PrintStream;
import java.util.Random;
// 如果现在只有一个Banner可以直接基于Lambda表达式来进行配置
public class YootkBanner implements Banner { // 实现了自定义的Banner输出
private static final String[] YOOTK_BANNER = {
" __ __",
"__ _ ____ _ ____ _ __ ___.__. ____ _____/ |_| | __ ____ ____ _____",
"\\ \\/ \\/ /\\ \\/ \\/ /\\ \\/ \\/ / < | |/ _ \\ / _ \\ __\\ |/ / _/ ___\\/ _ \\ / \\",
" \\ / \\ / \\ / \\___ ( <_> | <_> ) | | < \\ \\__( <_> ) Y Y \\",
" \\/\\_/ \\/\\_/ \\/\\_/ /\\ / ____|\\____/ \\____/|__| |__|_ \\ /\\ \\___ >____/|__|_| /",
" \\/ \\/ \\/ \\/ \\/ \\/ "
}; // 所有的Banner信息如果是复合的结构则必须使用数组进行配置
private static final String[] EDU_BANNER = {
" .___ __ __ ",
" ____ __| _/_ __ ___.__. ____ _____/ |_| | __ ____ ____ _____ ",
"_/ __ \\ / __ | | \\ < | |/ _ \\ / _ \\ __\\ |/ / _/ ___\\/ _ \\ / \\ ",
"\\ ___// /_/ | | / \\___ ( <_> | <_> ) | | < \\ \\__( <_> ) Y Y \\",
" \\___ >____ |____/ /\\ / ____|\\____/ \\____/|__| |__|_ \\ /\\ \\___ >____/|__|_| /",
" \\/ \\/ \\/ \\/ \\/ \\/ \\/ \\/ ",
};
private static final String MUYAN_BANNER = "沐言科技:www.yootk.com"; // 普通文本
private static final Random RANDOM = new Random();
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) { // 方法覆写
out.println(); // 输出一个换行
int num = RANDOM.nextInt(10); // 生成一个0 ~ 9的随机数字
if (num == 0) { // 第一种情况
for (String line : YOOTK_BANNER) {
out.println(line);
}
} else if (num % 2 == 1) {
for (String line : EDU_BANNER) {
out.println(line);
}
} else {
out.println(MUYAN_BANNER);
}
out.println(); // 输出一个换行
out.flush(); // 强制清空缓存
}
}
7、
package com.yootk; // 父包,这个包中的所有子包的类会被自动扫描
import com.yootk.banner.YootkBanner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 一个注解解决所有的问题
public class StartSpringBootApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(
StartSpringBootApplication.class); // 获取实例化对象
springApplication.setBanner(new YootkBanner()); // 配置自定义的Banner生成器
springApplication.run(args); // 运行SpringBoot程序
}
}
8、
package com.yootk; // 父包,这个包中的所有子包的类会被自动扫描
import com.yootk.banner.YootkBanner;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 一个注解解决所有的问题
public class StartSpringBootApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(
StartSpringBootApplication.class); // 获取实例化对象
springApplication.setBannerMode(Banner.Mode.OFF); // 关闭Banner输出
springApplication.setBanner(new YootkBanner()); // 配置自定义的Banner生成器
springApplication.run(args); // 运行SpringBoot程序
}
}
导入 Spring 配置文件
1、
package com.yootk.service;
public interface IMessageService { // 创建业务接口
public String echo(String msg);
}
2、
package com.yootk.service.impl;
import com.yootk.service.IMessageService;
import org.springframework.stereotype.Service;
@Service // 原始的Spring开发需要配置扫描包
public class MessageServiceImpl implements IMessageService {
@Override
public String echo(String msg) {
return "【ECHO】" + msg;
}
}
3、
package com.yootk.action;
import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // 直接基于Rest架构进行处理,省略了@ResponseBody注解
@RequestMapping("/message/*") // 添加父路径
public class MessageAction { // 控制层的实现类
@Autowired
private IMessageService messageService;
private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class); // 获取日志对象
@RequestMapping("echo") // 子路径
public String echo(String msg) { // 进行请求参数的接收以及请求内容的回应
LOGGER.info("接收msg的请求参数,内容为:{}", msg); // 日志输出
return this.messageService.echo(msg); // 直接进行Rest响应
}
}
4、
localhost:8080/message/echo?msg=沐言科技:www.yootk.com
5、
package com.yootk.service.impl;
import com.yootk.service.IMessageService;
public class MessageServiceImpl implements IMessageService {
@Override
public String echo(String msg) {
return "【ECHO】" + msg;
}
}
6、
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 依据Spring的配置要求配置指定的程序Bean对象(向Spring容器之中注册Bean) -->
<bean id="messageService" class="com.yootk.service.impl.MessageServiceImpl"/>
</beans>
7、
package com.yootk; // 父包,这个包中的所有子包的类会被自动扫描
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
@SpringBootApplication // 一个注解解决所有的问题
@ImportResource(locations = {"classpath:META-INF/spring/spring-*.xml"})
public class StartSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(StartSpringBootApplication.class,args); // 运行SpringBoot程序
}
}
项目热部署
sorng-boot-devtools
- 在 SpringBoot 项目开发中,如果要想获取每次代码更新后的结果往往都需要重新启项目,但是这样的方式在代码开发阶段并不友好,所以为了解决这一问题 Spring 提供了一个“spring-boot-devtools”热部署组件库,可以在项目运行期间动态的加载更新后的 Java 程序类。
- spring-boot-devtools 使用了两个不同的 ClassLoader,-个 ClassLoader 加载那些不会被改变的程序类(例如:Jar 包中的类),另外一个 ClassLoader 加载用户开发的程序类

同时按住 Ctrl + Shift + Alt + / 然后进入 Registry ,勾选自动编译并调整延时参数。
SpringBoot 热部署 2023 最新版 IDEA 详细步骤

1、
package com.yootk.action;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // 直接基于Rest架构进行处理,省略了@ResponseBody注解
@RequestMapping("/message/*") // 添加父路径
public class MessageAction { // 控制层的实现类
private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class); // 获取日志对象
@RequestMapping("echo") // 子路径
public String echo(String msg) { // 进行请求参数的接收以及请求内容的回应
LOGGER.info("接收msg的请求参数,内容为:{}", msg); // 日志输出
return "【ECHO】" + msg; // 直接进行Rest响应
}
}
2、
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools
implementation group: 'org.springframework.boot', name: 'spring-boot-devtools', version: '2.4.3'
3、
subprojects { // 子模块
dependencies { // 公共依赖库管理
compile('org.springframework.boot:spring-boot-devtools') // 允许进行项目的热部署
}
}
整合 JUnit5 用例测试
1、
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test
testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '2.4.3'
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.1'
// https://mvnrepository.com/artifact/org.junit.vintage/junit-vintage-engine
testImplementation group: 'org.junit.vintage', name: 'junit-vintage-engine', version: '5.7.1'
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.1'
// https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher
testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.7.1'
// https://mvnrepository.com/artifact/org.junit/junit-bom
implementation group: 'org.junit', name: 'junit-bom', version: '5.7.1', ext: 'pom'
2、
ext.versions = [ // 定义所有要使用的版本号
springboot: '2.4.1', // SpringBoot版本号
junit: '5.7.1', // 配置JUnit测试工具的版本编号
junitPlatformLauncher: '1.7.1' // JUnit测试工具运行平台版本编号
]
ext.libraries = [ // 定义所有的依赖库
// 以下的配置为SpringBoot项目所需要的核心依赖
'spring-boot-gradle-plugin': "org.springframework.boot:spring-boot-gradle-plugin:${versions.springboot}",
// 以下的配置为与项目用例测试有关的依赖
'junit-jupiter-api': "org.junit.jupiter:junit-jupiter-api:${versions.junit}",
'junit-vintage-engine': "org.junit.vintage:junit-vintage-engine:${versions.junit}",
'junit-jupiter-engine': "org.junit.jupiter:junit-jupiter-engine:${versions.junit}",
'junit-platform-launcher': "org.junit.platform:junit-platform-launcher:${versions.junitPlatformLauncher}",
'junit-bom': "org.junit:junit-bom:${versions.junit}",
]
3、
package com.yootk.test;
import com.yootk.StartSpringBootApplication;
import com.yootk.action.MessageAction;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
@ExtendWith(SpringExtension.class) // 使用JUnit5测试工具
@WebAppConfiguration // 启动WEB运行环境
@SpringBootTest(classes = StartSpringBootApplication.class) // 配置程序启动类
public class TestMessageAction { // 编写测试类
@Autowired
private MessageAction messageAction; // 注入MessageAction的对象实例
@BeforeAll
public static void init() {
System.err.println("【@BeforeAll】TestMessageAction类开始执行测试操作。");
}
@AfterAll
public static void after() {
System.err.println("【@AfterAll】TestMessageAction类测试完成。");
}
@Test
public void testEcho() { // 进行响应测试
String content = this.messageAction.echo("沐言科技:www.yootk.com");
String value = "【ECHO】沐言科技:www.yootk.com";
System.err.println("【@Test】测试echo()方法返回值,当前放回结果为:" + content);
Assertions.assertEquals(content, value); // 测试相等
}
}
4、
package com.yootk.service.impl;
import com.yootk.service.IMessageService;
import org.springframework.stereotype.Service;
@Service
public class MessageServiceImpl implements IMessageService {
@Override
public String echo(String msg) {
return "【ECHO】" + msg;
}
}
5、
package com.yootk.test;
import com.yootk.StartSpringBootApplication;
import com.yootk.service.IMessageService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
@ExtendWith(SpringExtension.class) // 使用JUnit5测试工具
@WebAppConfiguration // 启动WEB运行环境
@SpringBootTest(classes = StartSpringBootApplication.class) // 配置程序启动类
public class TestMessageService { // 编写测试类
@Autowired
private IMessageService messageService;
@Test
public void testEcho() { // 进行响应测试
String content = this.messageService.echo("沐言科技:www.yootk.com");
String value = "【ECHO】沐言科技:www.yootk.com";
System.err.println("【@Test】测试echo()方法返回值,当前放回结果为:" + content);
Assertions.assertEquals(content, value); // 测试相等
}
}
Lombok 简介与配置
1、
package com.yootk.test.vo;
public class Dept {
private Long deptno;
private String dname;
private String loc;
}
2、
compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.18'
// https://mvnrepository.com/artifact/org.projectlombok/lombok
compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.30'
3、
ext.versions = [ // 定义所有要使用的版本号
springboot :'2.4.1', // SpringBoot版本号
junit :'5.7.1', // 配置JUnit测试工具的版本编号
junitPlatformLauncher :'1.7.1', // JUnit测试工具运行平台版本编号
lombok :'1.18.18' // Lombok插件对应的版本号
]
ext.libraries = [ // 定义所有的依赖库
// 以下的配置为SpringBoot项目所需要的核心依赖
'spring-boot-gradle-plugin': "org.springframework.boot:spring-boot-gradle-plugin:${versions.springboot}",
// 以下的配置为与项目用例测试有关的依赖
'junit-jupiter-api': "org.junit.jupiter:junit-jupiter-api:${versions.junit}",
'junit-vintage-engine': "org.junit.vintage:junit-vintage-engine:${versions.junit}",
'junit-jupiter-engine': "org.junit.jupiter:junit-jupiter-engine:${versions.junit}",
'junit-platform-launcher': "org.junit.platform:junit-platform-launcher:${versions.junitPlatformLauncher}",
'junit-bom': "org.junit:junit-bom:${versions.junit}",
// 以下的配置为Lombok组件有关的依赖
'lombok': "org.projectlombok:lombok:${versions.lombok}"
]
3.
dependencies { // 公共依赖库管理
// 以下为Lombok插件的相关依赖配置
implementation(libraries.'lombok') // 编译时生效
annotationProcessor(libraries.'lombok') // 注解时生效
testCompileOnly(libraries.'lombok') // 注解时生效
testAnnotationProcessor(libraries.'lombok') // 注解时生效
}
生成类操作结构

1、
package com.yootk.vo;
import lombok.Data;
import java.util.Date;
@Data // 这就是Lombok注解,这个注解使用的是最频繁的
public class Message {
private String title;
private Date pubdate;
private String content;
}
2、http://java-decompiler.github.io/
3、
package com.yootk.test;
import com.yootk.vo.Message;
public class TestMessage {
public static void main(String[] args) {
Message message = new Message(); // 调用无参构造进行对象实例化
message.setTitle("沐言科技"); // 自动生成setter方法
message.setContent("www.yootk.com"); // 自动生成setter方法
System.out.println(message); // 调用toString()输出
}
}
4、
package com.yootk.vo;
import lombok.Data;
import lombok.NonNull;
@Data // 本身不会生成构造(默认的无参构造)
public class Dept {
@NonNull // 该属性不允许为空
private Long deptno;
@NonNull // 该属性不允许为空
private String dname;
private String loc; // loc属性没有是否强制为空的限制
}
5、
package com.yootk.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data // 会与@NonNull注解相集合生成有参构造
@NoArgsConstructor // 要求当前的类自动的生成无参构造方法
@AllArgsConstructor // 生成的是一个全参构造
public class Emp {
@NonNull // 一旦要生成无参构造,这个注解就有冲突了,该注解失效
private Long empno;
@NonNull // 一旦要生成无参构造,这个注解就有冲突了,该注解失效
private String ename;
private Double salary;
}
6、
package com.yootk.vo;
import lombok.*;
@Data // 会与@NonNull注解相集合生成有参构造
@NoArgsConstructor // 要求当前的类自动的生成无参构造方法
@AllArgsConstructor // 生成的是一个全参构造
@RequiredArgsConstructor(staticName="empno,ename")
public class Emp {
@NonNull // 一旦要生成无参构造,这个注解就有冲突了,该注解失效
private Long empno;
@NonNull // 一旦要生成无参构造,这个注解就有冲突了,该注解失效
private String ename;
private Double salary;
}
Accessor
Accessor
- Accessor 是在 Java 编程中提供的一种属性访问器,可以基于代码链的形式实现对象中所有属性的设置操作,在 Lombok 中提供了“@Accessors”注解可以直接生成相应代码,而在生成时提供有三种模式:fluent、chain、prefix,下面通过具体的定义以及生成的代码结果来对这三种模式进行说明。
fluent 模式
- 将属性操作的 setter/getter 方法的名称全部更换为属性名称,这样在进行属性设置以及属性获取时直接采用“属性名称()”的形式调用即可。

chain 模式
- 类中正常生成传统的“setter/getter”方法,但是在生成的 setter 方法时不再使用 void 作为返回值类型,而是直接返回当前对象实例,就可以采用代码链的方式实现对象属性设置。

prefix 模式
- 在根据属性生成 setter/getter 方法时可以排除特定的名称前缀后生成

1、
package com.yootk.vo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@Accessors(fluent=true)
public class Message {
private String title;
private Date pubdate;
private String content;
}
2、
package com.yootk.test;
import com.yootk.vo.Message;
public class TestMessage {
public static void main(String[] args) {
Message message = new Message(); // 调用无参构造进行对象实例化
message.title("沐言科技").content("www.yootk.com"); // 代码链形式访问
System.out.println(message); // 调用toString()输出
}
}
3
、
package com.yootk.vo;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@Accessors(fluent=true)
@Setter // 生成setter方法
@Getter // 生成getter方法
public class Message {
private String title;
private Date pubdate;
private String content;
}
4、
package com.yootk.vo;
import lombok.Data;
import lombok.experimental.Accessors;
@Data // 本身不会生成构造(默认的无参构造)
@Accessors(chain = true)
public class Dept {
private Long deptno;
private String dname;
private String loc;
}
5、
package com.yootk.test;
import com.yootk.vo.Dept;
public class TestDept {
public static void main(String[] args) {
Dept dept = new Dept();
dept.setDname("沐言科技教学部").setLoc("北京").setDeptno(10L);
System.out.println(dept);
}
}
6、
package com.yootk.vo;
import lombok.Data;
import lombok.experimental.Accessors;
@Data // 会与@NonNull注解相集合生成有参构造
@Accessors(prefix = "yootk") // 配置前缀
public class Emp {
private Long yootkEmpno; // 要在属性上增加前缀
private String yootkEname; // 要在属性上增加前缀
private Double yootkSalary; // 要在属性上增加前缀
}
建造者模式

1、
package com.yootk.vo;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder // 构建者模式
public class Message {
private String title;
private Date pubdate;
private String content;
}
2、
package com.yootk.test;
import com.yootk.vo.Message;
public class TestMessage {
public static void main(String[] args) {
// 此时将基于Lombok生成的构建者模式来进行Message对象的生成以及属性配置
Message message = Message.builder().title("沐言科技").content("www.yootk.com").build();
System.out.println(message);
}
}
异常处理

1、
package com.yootk.lombok;
public class MessageHandle { // 在此时进行异常的控制
public static void print(String message) { // 信息输出
if (message == null) { // 内容为空
try {
throw new Exception("传递的message参数内容为空,你疯了吗?");
} catch (Exception e) {}
}
System.out.println(message.toUpperCase()); // 打印信息
}
}
2、
package com.yootk.lombok;
import lombok.SneakyThrows;
public class MessageHandle { // 在此时进行异常的控制
@SneakyThrows // 会自动的生成try...catch
public static void print(String message) { // 信息输出
if (message == null) { // 内容为空
throw new Exception("传递的message参数内容为空,你疯了吗?");
}
System.out.println(message.toUpperCase()); // 打印信息
}
}
IO 流自动关闭

package com.yootk.lombok;
import lombok.Cleanup;
import lombok.Data;
import lombok.NonNull;
import lombok.SneakyThrows;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
@Data // 此时的程序会自动生成一个双参构造
public class MessageRead { // 在此时进行异常的控制
@NonNull
private String filePath; // 文件路径
@NonNull
private String fileName; // 文件名称
@SneakyThrows // 帮助用户手工处理异常
public String load() { // 数据读取
@Cleanup InputStream input = new FileInputStream(new File(this.filePath, this.fileName));
byte data [] = new byte[1024];
int len = input.read(data); // 数据读取
return new String(data, 0, len);
}
}
同步方法

1、
package com.yootk.lombok;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.Synchronized;
import java.util.concurrent.TimeUnit;
@Data
@AllArgsConstructor // 生成一个全参构造
public class SaleTicket {
private int ticket; // 设置票数
@SneakyThrows // 处理中断异常
@Synchronized // 同步处理
public void sale() { // 售票操作
while (this.ticket > 0) { // 此时有票
if (this.ticket > 0) { // 准备售票
TimeUnit.SECONDS.sleep(1); // 模拟异常
System.err.println("【" + Thread.currentThread().getName() + "】售票,ticket = " + this.ticket--);
}
}
}
}
2、
package com.yootk.test;
import com.yootk.lombok.SaleTicket;
public class TestSaleTicket {
public static void main(String[] args) {
SaleTicket saleTicket = new SaleTicket(10); // 一共10张票
for (int x = 0; x < 10; x++) {
new Thread(()->{
saleTicket.sale(); // 售票操作
}, "票贩子 - " + x).start();
}
}
}
demo
