开发实用篇
# 热部署
# 热部署基础知识
- 修改完功能后,能立即生效,而不需要重启服务器
- 关于热部署
- 重启(Restart): 自定义开发代码,包含类、页面、配置文件等,加载位置
restart类加载器 - 重载(ReLoad):
jar包,加载位置base类加载器
- 重启(Restart): 自定义开发代码,包含类、页面、配置文件等,加载位置
- 热部署仅仅是重启功能,即仅加载 当前开发者自定义的资源,不重新加载引入的
jar包。
# 手动启动热部署
开启开发者工具
<!-- 热部署 工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>激活热部署
Ctrl+F9或者 构建中的构建项目。
# 自动启动热部署
配置设置


当idea 失去焦点5秒后会自动启动热部署
# 热部署范围配置
默认热部署的排除文件

手动干预
devtools: restart: # 设置不参与热部署的文件 exclude: application.yml,static/**
# 关闭热部署
配置文件关闭————3
devtools: restart: # 设置不参与热部署的文件 exclude: application.yml,static/** enabled: false #关闭热部署java系统环境设置 关闭 ————5—— 这个优先级要高很多
@SpringBootApplication public class HotDeployApplication { public static void main(String[] args) { //设置高优先级属性禁用热部署 java系统 环境设置 比配置文件优先级要高 System.setProperty("spring.devtools.restart.enabled","true"); SpringApplication.run(HotDeployApplication.class);//为了安全 } }参数优先级

# 配置高级
# @ConfigurationProperties
#自定义配置
servers:
ipAdress: 192.168.1.1
port: 2345
timeout: -1
#自定义数据源配置
datasource:
driverClassName: com.mysql.jdbc.driver1234
使用注解设置关联后,代码中只需要设置好set方法,即可自动读取
为自定义
Bean关联配置@Data @ConfigurationProperties(prefix = "servers") public class ServletConfig { private String ipAdress; private int port; private Long timeout; }为第三方
Bean关联配置@Bean @ConfigurationProperties(prefix = "datasource") //读取配置文件属性 //直接写这个就行,不用在下方代码中进行配置了 public DruidDataSource dataSource(){ DruidDataSource dataSource=new DruidDataSource(); //dataSource.setDriverClassName("com.mysql.jdbc.Driver"); //直接写死 return dataSource; }@EnableConfigurationProperties———— 将对应类加入Spring容器//使用该注解将对应的类加入spring容器 与在该类上直接加@Component是一样的(两者不能同时存在),但是这个更容易管理 @EnableConfigurationProperties({ServletConfig.class}) @SpringBootApplication public class ConfigurationApplication { }//@EnableConfigurationProperties 有了这个 就不需要在这里使用 @Component了 //老版本会报错,新版本好像不会报错 //@Component //加入bean 容器 @Data //自动生成set get tostring 等方法 @ConfigurationProperties(prefix = "servers") public class ServletConfig {
# 宽松绑定/松散绑定
@ConfigurationProperties支持宽松绑定@Value不支持

前缀命名

# 常用计量单位绑定
时间与空间的计量单位
@DurationUnit(ChronoUnit.HOURS)//可以修改默认单位 —— 修改为小时 PT3H private Duration serverTimeout; //默认是毫秒 ——PT0.003S //@DataSizeUnit(DataUnit.MEGABYTES)// 可以修改默认单位 —— 修改为MB //104857600B=1024*1024*10=100MB private DataSize dataSize;//默认是 B —— 100BserverTimeout: 3 #dataSize: 100 #可以使用@DataSizeUnit 从参数定义时指定 单位 dataSize: 100MB #也可以在这里直接指定单位 不需要使用 @DataSizeUnit
# 数据校验
引入
JR303与Hibernate校验框架坐标<!-- 引入JSR303规范,用作数据校验 算是个接口--> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> </dependency> <!-- 使用 hibernate 框架提供的校验器做实现类 算是个实现类--> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency>使用
@Validated注解启用校验功能//开启对当前Bean的属性注入校验 @Validated public class ServletConfig { }使用具体校验规则规范数据校验格式
@Max(value = 8888,message = "最大值不能超过8888") //设置具体的规则 private int port; @NotEmpty private long timeout;- 通过点击
@Max——validation,和@NotEmpty--hibernate进入对应源文件,然后点击源文件最上方的包,可以查看具体的规则有哪些。
- 通过点击
# 小bug
在yml文件中 ,定义数字,读取结果与预期定义不符合
#自定义数据源配置 datasource: driverClassName: com.mysql.jdbc.driver1234 #password: 0127 #这里当成了8进制数,翻译成了87 #0(1-7) 表示8进制 #0x(1-9,a-f) 表示16进制 password: "0127" #最好用引号包裹,这样 就是0127 不会有进制错误@Value("${datasource.password}") private String password; // 0127——87 //"0127"——0127 System.out.println(password);所以尽量使用引号来传入字符串
# 测试
# 加载测试专用属性
properties&&args
//使用 properties属性,来增加临时属性 仅在测试文件中生效, 优先级较高,
@SpringBootTest(properties = {"test.prop=凉冰"})
//使用 args属性,命令行模式 来增加临时属性 仅在测试文件中生效, 优先级较高,
//这个一般不写 如果写的话就是用来模拟命令行参数
@SpringBootTest(args = {"--test.prop=戴安娜"})
//两个一块有的话, args 优先级高, 属于命令行参数,不过一般不会两个一块出来
@SpringBootTest(args = {"--test.prop=戴安娜"},properties = {"test.prop=凉冰"})
class PropertiesAndArgsTest {
@Value("${test.prop}")
private String msg;
@Test
void testProperties() {
System.out.println(msg);
}
}
# 记载测试专用配置
- 测试要使用的临时的配置Bean
//这个加载的外部Bean 仅供测试使用 所有要定义在test包下
@Configuration
public class MsgConfig {
@Bean //String 类型也可以作为一个Bean
public String msg(){
return "diana";
}
}
- 在测试类中,使用
@Import导入配置类 ———— 该配置类可以仅供该测试文件使用
//测试---仅在测试文件中生效的---外部Bean的加载
@SpringBootTest
@Import({MsgConfig.class})
public class ConfigTest {
@Autowired
private String msg;
@Test
void testConfig(){
System.out.println(msg);
}
}
# Web环境模拟测试
使用属性
webEnvironment开启web测试环境DEFINED_PORT——使用自定义的端口RANDOM_PORT——使用随机端口NONE——不使用web 环境,,默认值DEFINED_PORT
//测试Web 环境 即 表现层 --controller //使用提前定义好的端口 运行web环境 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class WebTest { @Test void test(){ } }模拟请求发送——
@AutoConfigureMockMvc开启虚拟MVC调用//测试Web 环境 即 表现层 --controller //使用提前定义好的端口 运行web环境 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) //开启虚拟MVC调用 @AutoConfigureMockMvc public class WebTest { @Test void testWeb(@Autowired MockMvc mvc) throws Exception { //可以通过这种方式进行参数注入 //创建了一个虚拟请求 当前访问的是 /books //左边是接口 右边是实现类 RequestBuilder requestBuilder= MockMvcRequestBuilders.get("/books"); //执行对应请求 mvc.perform(requestBuilder); } }匹配链接状态
@Test //链接状态匹配 void testStatus(@Autowired MockMvc mvc) throws Exception { //可以通过这种方式进行参数注入 RequestBuilder requestBuilder= MockMvcRequestBuilders.get("/books1"); ResultActions perform = mvc.perform(requestBuilder); //取得 测试值 //设置预定值,与真实值进行比较,成功则测试通过,失败则测试失败 //定义本次调用的预期值 StatusResultMatchers status = MockMvcResultMatchers.status(); //预计本次调用是成功的: 状态 200 ResultMatcher ok=status.isOk(); //添加预计值到本次调用过程中进行匹配 perform.andExpect(ok); //预期:200 ------- ok //实际:404 ------- perform }匹配执行结果 —— 字符串匹配
@Test //匹配执行结果———— 字符串匹配 void testBody(@Autowired MockMvc mvc) throws Exception { //可以通过这种方式进行参数注入 RequestBuilder requestBuilder= MockMvcRequestBuilders.get("/books"); ResultActions perform = mvc.perform(requestBuilder); //取得 测试值 //设置预定值,与真实值进行比较,成功则测试通过,失败则测试失败 //定义本次调用的预期值 —— 请求体 ContentResultMatchers content = MockMvcResultMatchers.content(); //预计本次调用的返回结果是 "springBoot" ResultMatcher str = content.string("springBoot2"); //添加预计值到本次调用过程中进行匹配 perform.andExpect(str); //错误匹配 //预期:springBoot2 ------- str //实际:springBoot ------- perform }匹配执行结果 —— json数据匹配
@Test //匹配执行结果———— json数据匹配 void testBodyJson(@Autowired MockMvc mvc) throws Exception { //可以通过这种方式进行参数注入 RequestBuilder requestBuilder= MockMvcRequestBuilders.post("/books"); ResultActions perform = mvc.perform(requestBuilder); //取得 测试值 //设置预定值,与真实值进行比较,成功则测试通过,失败则测试失败 //定义本次调用的预期值 —— 请求体 ContentResultMatchers content = MockMvcResultMatchers.content(); //预计本次调用的返回结果是 一个json串(手游--修改为端游) ResultMatcher json = content.json("{\n" + " \"id\": 1,\n" + " \"name\": \"崩坏三\",\n" + " \"type\": \"端游\",\n" + " \"description\": \"好玩\"\n" + "}"); //添加预计值到本次调用过程中进行匹配 perform.andExpect(json); //java.lang.AssertionError: type //Expected: 端游游 // got: 手游 }匹配响应头
@Test //匹配执行结果———— 响应头 void testHeader(@Autowired MockMvc mvc) throws Exception { //可以通过这种方式进行参数注入 RequestBuilder requestBuilder= MockMvcRequestBuilders.post("/books"); ResultActions perform = mvc.perform(requestBuilder); //取得 测试值 //设置预定值,与真实值进行比较,成功则测试通过,失败则测试失败 //定义本次调用的预期值 —— 请求体 HeaderResultMatchers header = MockMvcResultMatchers.header(); //预计本次调用的返回结果是 application/text(原为:application/json) ResultMatcher string = header.string("Content-type", "application/text"); //添加预计值到本次调用过程中进行匹配 perform.andExpect(string); //java.lang.AssertionError: Response header 'Content-type' expected:<application/text> but was:<application/json> //预期:application/text //实际:application/json }完整测试用例
@Test //完整测试用例 void testsave(@Autowired MockMvc mvc) throws Exception { //0.发送虚拟请求,并获得返回值 RequestBuilder requestBuilder= MockMvcRequestBuilders.post("/books"); ResultActions perform = mvc.perform(requestBuilder); //取得 测试值 //1.测试连接状态 StatusResultMatchers status = MockMvcResultMatchers.status(); ResultMatcher ok=status.isOk(); perform.andExpect(ok); //2.测试响应头 HeaderResultMatchers header = MockMvcResultMatchers.header(); ResultMatcher string = header.string("Content-type", "application/json"); perform.andExpect(string); //3.测试响应数据 ContentResultMatchers content = MockMvcResultMatchers.content(); ResultMatcher json = content.json("{\n" + " \"id\": 1,\n" + " \"name\": \"崩坏三\",\n" + " \"type\": \"手游\",\n" + " \"description\": \"好玩\"\n" + "}"); perform.andExpect(json); }
# 业务层测试回滚
使用maven打包时,会执行测试,测试产生的数据会影响原有的数据库
可以提前测试好,然后打包的时候,跳过测试,见文档 ,3种方法
使用回滚的方式
- 在测试类上开事务,会默认回滚,即 数据不会提交到数据库
//数据层测试回滚 @SpringBootTest @Transactional //开启事务 在测试类上开事务,会默认回滚,即 @Rollback(true)//默认就是true 会回滚,即不提交事务 如果想在测试过程中提交事务,可以设置为false public class Daotest { @Autowired private BookService bookService; @Test void testSave(){ Book book=new Book(); book.setId(1); book.setName("崩坏三"); book.setType("手游"); book.setDescription("好玩"); bookService.save(book); } }
# 测试用例数据设定
使用随机产生的数据来代替之前写好的测试数据
产生随机数据
#随机生成数据 testcase: book: id: ${random.int} id2: ${random.int(10)} # 指定10以内的int数 type: ${random.int(5,10)} #指定5到10之间的数 name: diana${random.value} #前面数据时固定的,后面是随机的 #随机字符串 MD5字符串 32位 uuid: ${random.uuid} #随机uuid publishTime: ${random.long}读取测试数据
//测试随机数据的产生 @SpringBootTest() public class RandomTest { @Autowired private Book2 book; @Test void testProperties() { System.out.println(book); } }
# 监控
# 监控基础
意义
- 监控服务状态是否宕机
- 监控服务运行指标(内存,虚拟机,线程,请求)
- 监控日志
- 监控服务(服务下线)
监控方式

# 可视化监控平台
配置监控服务端
导入坐标—— 版本号要尽量与SpringBoot版本号一致
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>2.6.5</version> </dependency>配置yml
#配置端口 server: port: 8080代码
- 使用注解
@EnableAdminServer开启adminserver
@SpringBootApplication @EnableAdminServer //开启adminserver public class AdminServerApplication { }- 使用注解
将web应用设置成被监控客户端
引入坐标
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>2.6.5</version> </dependency>配置yml(重要)
spring: boot: admin: client: url: http://localhost:8080 #我这个应用要注册在这个server上 management: #配置对外是否开放 默认13个都是对外开放的 endpoint: health: #默认开放,必须开放 show-details: always #总是展示health的细节 info: enabled: false #不开放info信息展示—————————— 上面禁止后,即使下面是*,也只有12个端点开放 endpoints: #配置在web端能否看到 web: exposure: include: "*" #开放所有——可对外开放的信息—— 最多13个端点 #include: health,info #开放2个端点代码
正常书写
# 监控原理
- Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息
- 端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息
- 访问当前应用所有端点信息:/actuator
- 访问端点详细信息:/actuator/端点名称——(/actuator/beans)(/actuator/mappings)
- 比较重要的指标
/actuator/info——显示通知信息——Insights-细节-信息/actuator/health——显示应用程序的健康信息——Insights-细节-健康/actuator/loggers——显示和修改应用程序中日志记录器的配置——日志配置/actuator/metrics——显示当前应用程序的指标度量信息——Insights-性能
# 自定义监控指标
# info端点
——在项目admin_client中
自定义信息显示方式一
配置yml——只能实现一些静态的简单的信息
#显示info信息的地方——springboot版本太高不好,显示不出来 info: author: diana appName: @artifactId@ #读取pom文件的值 使用 @ @
自定义信息显示方式二
- 编程实现接口——
InfoContributor - 两种放信息的方式,见下方程序
public class InfoConfig implements InfoContributor { @Override public void contribute(Info.Builder builder) { //放入一个runtime,后面是值 builder.withDetail("runtime",System.currentTimeMillis()); //放入一个map集合--要求map格式--Map<String,Object> Map<String,Object> infomap=new HashMap<String,Object>(); infomap.put("buildTime","2022"); infomap.put("崩坏三","琪亚娜"); builder.withDetails(infomap); } }- 编程实现接口——
# health端点
——在项目admin_client中
说明
- health 展示的是程序内部组件(数据库,redis,……)的工作状态,如果有一个为down,那么整个health为down
自定义小组件在health中显示
- --继承父类
AbstractHealthIndicator


public class HealthConfig extends AbstractHealthIndicator { @Override protected void doHealthCheck(Health.Builder builder) throws Exception { boolean condition =false;//对应自己组件的状态信息 if(condition){ builder.status(Status.UP);//设置为up状态--推荐 //builder.up(); 设置为up状态 //放入一个map集合--要求map格式--Map<String,Object> Map<String,Object> infomap=new HashMap<String,Object>(); infomap.put("buildTime","2022"); infomap.put("崩坏三","琪亚娜"); builder.withDetails(infomap); }else{ builder.status(Status.DOWN); builder.withDetail("上线了么?","你做梦"); } } }- --继承父类
# metrics端点
—— 在项目hot_deploy中
- 添加了一项 监控用户删除次数的服务
- “用户删除次数”为key,counter为值——
import io.micrometer.core.instrument.Counter;
private Counter counter;
//提供构造函数
public BookServiceImpl(MeterRegistry meterRegistry){
counter = meterRegistry.counter("用户删除次数:");
}
public boolean delete(int id) {
//添加 自定义监控信息---metrics
counter.increment(); //自增操作
return bookMapper.delete(id)>0;
}
# 自定义端点

- 说明是自定义端点
@Endpoint(id="diana",enableByDefault = true) - 当读取端点时,执行具体方法
@ReadOperation
//添加自定义端点
@Component
@Endpoint(id="diana",enableByDefault = true)
public class MyPoint {
@ReadOperation //当读取端点时,执行这个操作
public Object getpay(){
System.out.println("pay-------------------------");
Map<String,String> payMap=new HashMap();
payMap.put("崩坏三","琪亚娜");
payMap.put("英雄联盟","皎月女神");
payMap.put("天使","diana");
return payMap;
}
}
