第一节 SpringBoot项目基础搭建
spring.profiles.active 可以用于项目启动加载哪个分支的application-${env}.yaml
server:
servlet:
#项目根目录
context-path: /study/springboot
spring:
#激活的分支
profiles:
active: dev
第二节 SpringBoot使用日志
'%d{HH:mm:ss.SSS} %-5level %logger{36} [%line] - %msg%n'
第三节 SpringBoot参数校验
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;
@Data
public class Phone {
@NotNull(message = "电话号码不能为空")
private Long number;
@Max(value = 5000, message = "预算价格不能超过{value}元")
@Min(value = 1000, message = "预算价格不能低于{value}元")
private double price;
@NotBlank(message = "品牌不能为空")
@Length(max = 5, message = "品牌长度不能超过{max}")
private String brand;
}
GET请求方法,它没有使用BindingResult参数来接收校验结果。如果参数里面的dicTypeId为null,那么就会抛出异常BindingException。
@GetMapping("/dic/list")
public MultiResponse getDicList(@Valid DicListQry dicListQry) {
return null;
}
如果我再把Get请求转成Post请求,dicTypeId的值还是传null。那么抛出的异常不一样,抛出 MethodArgumentNotValidException。
@PostMapping("/dic/list")
public MultiResponse getDicListPost(@RequestBody
@Valid DicListQry dicList) {
return null;
}
还有一种情况就是校验集合。为了支持校验集合的方式,不能够直接使用List来接收,不然不会走校验框架。为了校验集合,需要按照下面方式。把接收的List做成一个类的成员变量,然后在List上添加@Valid注解。 请求的时候,需要配合使用@Validated注解。
@PostMapping("/dic/list")
public MultiResponse
getDicListPost(@RequestBody @Validated DicList dicList) {
return dictionaryService.getDicList(null);
}
@Data
private static class DicList {
@Valid
private List dicListQry;
}
第五节 SpringBoot统一全局响应
使用ResponseControllerAdvice注解,并且实现ResponseBodyAdvice接口
package com.zhoutianyu.learnspringboot.response;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter,
Class aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter methodParameter,
MediaType mediaType, Class aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
if (isResponseType(data)) {
return data;
}
return new BaseResponse<>(StatusCodeEnum.SUCCESS, data);
}
private boolean isResponseType(Object data) {
return data instanceof BaseResponse;
}
}
第七节 SpringBoot自定义参数
application.yaml文件配置如下
#自定义参数
define:
userinfo:
username: zhoutianyu
age: 24
position: java engineer
department:
name: 研发部门
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class Department {
@Value("${define.department.name}")
private String name;
}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "define.userinfo")
@Component
@Data
public class UserInfo {
private String username;
private int age;
private String position;
}
第十二节 SprnigBoot使用定时任务
org.springframework.boot
spring-boot-starter
每隔30秒执行一次:*/30 * * * * ? 开发人员自测
每隔10分钟执行一次:0 */10 * * * ? 给测试小姐姐用
每月1号凌晨1点执行一次:0 0 1 1 * ? 每月推送一次月报
每天凌晨5点执行一次:0 0 5 * * ? 生产上使用
#自定义参数
define:
quartz:
cron: "*/6 * * * * ?"
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@EnableScheduling
public class BaseQuartZ {
@Scheduled(cron = "${define.quartz.cron}")
public void schedule() {
System.out.println(Thread.currentThread().hashCode());
}
}
所有的定时任务都是同一个线程在跑。
构造一个线程池,然后使用@EnableAsync注解开启异步。在每个定时任务的方法上,增加@Async注解即可。
@Component
public class ThreadPoolConfig {
@Bean(value = "threadPoolTaskExecutor")
public Executor getExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
executor.setCorePoolSize(5);
//线程池维护线程的最大数量
executor.setMaxPoolSize(10);
//允许的空闲时间,当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
executor.setKeepAliveSeconds(60);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("demo.tyzhou-");
//缓存队列(阻塞队列)当核心线程数达到最大时,新任务会放在队列中排队等待执行
executor.setQueueCapacity(10);
/**
* 拒绝task的处理策略
* CallerRunsPolicy使用此策略,如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
* AbortPolicy丢掉这个任务并且抛出
* DiscardPolicy线程池队列满了,会直接丢掉这个任务并且不会有任何异常
* DiscardOldestPolicy队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
@Component
@EnableScheduling
@EnableAsync
public class CronDemo {
@Async
@Scheduled(cron = "${define.quartz.cron}")
public void schedule() {
System.out.println(Thread.currentThread().hashCode());
}
@Async
@Scheduled(cron = "${define.quartz.cron}")
public void schedule2() {
System.out.println(Thread.currentThread().hashCode());
}
}
第十四节 SpringBoot上传文件
① 由于分布式系统共享同一个数据库服务器。那么就可以把文件服务器搭建在与数据库同一台机器上。下面的代码中,nginxPath变量就是文件服务器的用于存放文件的文件夹路径。适合私有云与公有云部署。
② 把上传的文件直接存放到数据库中,但是这种解决方案被研发经理批评,原因是在数据库迁移会相当麻烦,而且如果有大文件的上传与下载,会占用过多的数据库资源,阻塞整个系统。此方案强烈反对在实际工作中使用,虽然曾经用过。
③ 购买云文件服务器。例如OSS文件服务器系统,把所有文件上传到公共得文件服务器中,这样就能让多个节点共享资源。缺点也有,如果客户的服务器不能支持访问公网的话,那么此方案会行不通,需要提前与客户确定运行环境。
第十九节 SpringBoot项目打包部署
org.springframework.boot
spring-boot-maven-plugin
第二十节 SpringBoot项目适配MySQL与Oracle
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class DateSourceConfg {
/**
* 自动识别使用的数据库类型
* 在mapper.xml中databaseId的值就是跟这里对应,
* 如果没有databaseId选择则说明该sql适用所有数据库
*/
@Bean
public DatabaseIdProvider getDatabaseIdProvider() {
DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
Properties properties = new Properties();
properties.setProperty("Oracle", "oracle");
properties.setProperty("MySQL", "mysql");
databaseIdProvider.setProperties(properties);
return databaseIdProvider;
}
}
第二十四节 SpringBoot使用spring.factories
META-INF/spring.factories文件
如何让我们的starter里的@Configuration在使用者的项目里生效呢?
在SpringBoot的启动类中,@SpringBootApplication注解的代码里面有一句@EnableAutoConfiguration
重要!
速记ComponentScan扫描自己,EnableAutoConfiguration扫描spring.factories包
@ComponentScan注解的作用是扫描@SpringBootApplication所在的Application类(即spring-boot项目的入口类)所在的包(basepackage)下所有的@component注解(或拓展了@component的注解)标记的bean,并注册到spring容器中。
@EnableAutoConfiguration注解加载的是资源目录META-INF文件下的spring.factories的文件。包括导入到项目中的Jar包的META-INF文件夹下的spring.factories文件。spring.factories文件是一个properties文件。
@ConditionalOnClass(SystemInit.class),如果容器里又SystemInit才加载使用它的Bean
@ConditionalOnMissingBean表示,如果Spring容器里没有SystemInit对象,那么就会为容器创建一个SystemInit对象。