SpringBoot
2.3.x | 2.3.12.RELEASE |
---|
2.4及以上是不稳定版本,没有RELEASE后缀 2.3及以下是稳定版本,引入包需要加入 .RELEASE后缀
配置文件说明: https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.server
Starter说明: https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
版本匹配说明: https://blog.csdn.net/2301_77025309/article/details/130912352
1、Demo
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.1</version>
</dependency>
@SpringBootApplication
public class QuickStarter {
public static void main(String[] args) {
SpringApplication.run(QuickStarter.class, args);
}
}
@RestController
public class MyController {
@GetMapping("/say")
public String say() {
return "Hello SpringBoot Test !!!";
}
}
1.1、配置文件读取说明
1、配置文件路径优先级
配置文件所在目录 | 优先级 |
---|---|
项目根目录/config/application.properties | 1 |
项目根目录/application.properties | 2 |
resources/config/application.properties | 3 |
resources/application.properties | 4 |
2、配置文件类型优先级(统计目录之间)
配置文件类型 | 优先级 |
---|---|
application.properties | 1 |
application.yml | 2 |
application.yaml | 3 |
3、多环境切换
方式1、启动类直接指定加载的文件
@SpringBootApplication
public class QuickStarter {
public static void main(String[] args) {
// SpringApplication.run(QuickStarter.class, args);
new SpringApplicationBuilder(QuickStarter.class).properties("spring.config.location=classpath:/application-pro.yml").run(args);
}
}
方式2、配置多个文件,yml指定
spring:
profiles:
active: dev
配置多个文件有两种方式
1、yml里面用3个 ‘ - ’ 进行拆分不同的版本。例如
---
server:
port: 8081
spring:
config:
activate:
on-profile: dev
---
server:
port: 8082
spring:
config:
activate:
on-profile: test
2、配置不同的文件进行区分 application-{version}.yml
![image-20230804215346527](/Users/xuwl/Library/Application%20Support/typora-user-images/image-20230804215346527.png)
注意,外配置文件优先级要高于yml里面配置的文件
方式3、IDEA启动参数配置。
方式1、VM配置 -Dspring.profiles.active=test
方式2、层序参数 --spring.profiles.active=pro
方式2的优先级高于方式1
![image-20230804220746684](/Users/xuwl/Library/Application%20Support/typora-user-images/image-20230804220746684.png)
方式4、java命令指定
Java -jar xxx.jar --spring.profiles.active=dev 这个指定的版本优先级仅次于方式一指定版本
1.2、配置文件数据读取
想要读取配置文件,需要将Bean注解到Spring容器
方式1、@Value
@Value(“a”) 表示双引号内的值即为赋值
@Value(“${a}”) 表示配置文件中的a配置的值即为赋值
@Value(“#{2*3}”) EL表达式赋值为表达式结果 可以使用已经注入容器的Bean,也可以使用系统静态函数
方式2、@ConfigurationProperties(prefix=“info.userinfo”)
1、在启动类上加入@EnableConfigurationProperties(UserInfo.class) 指定配置类
2、在启动类上加入@ConfigurationPropertiesScan(“com.lc.properties”)指定包下面的所有的类为配置类
必须要将配置类上加@ConfigurationProperties(prefix=“info.userinfo”)注解才行。
方式3、Environment
Environment env = run.getBean(Environment.class);
String property = env.getProperty("info.userinfo.username");
1.2、配置文件书写说明
info:
userinfo:
username: 张三
age: ${random.int}
average: 97.3
list: a,b,c
list2: [1,2,3]
list3:
- x
- y
- z
map:
a: 1
b: 2
map2: {a1: 10,b1: 20}
pcInfos:
- pcname: xx
- pcname: yy
text: |
天下没有不散的宴席
人生没有不尽的缘分
word: >
天生我材必有用
千金散尽还复来
tdata: {aa: 10, bb: xw ,cc: 18.5}
1、:后面必须跟空格
2、可用$进行上述属性的引用或着系统函数的引用 ${random.int:11} 冒号后面表示取不到值时的默认值
3、List三种配置方式 Map类比对象都有两种配置方式
4、| 表示大文本 不去除换行符读取 > 表示大文本去除换行符合并
2、Starter配置
1、热部署
引入依赖:spring-boot-devtools
yam配置:
#热部署配置说明
spring:
devtools:
restart:
enabled: true #是否开启热部署 false直接关闭 不启动热部署
additional-paths: src/main #变更重启目录
exclude: static/** #哪些目录变更不重启项目
方式1: 直接使用默认配置,每次修改完后重新构建代码 即可进行重新部署
方式2:勾选标记部分,每次代码修改后自动部署
![image-20230811183929762](/Users/xuwl/Library/Application%20Support/typora-user-images/image-20230811183929762.png)
![image-20230811184032975](/Users/xuwl/Library/Application%20Support/typora-user-images/image-20230811184032975.png)
方式3、使用开源插件JRebel
![image-20230811184139334](/Users/xuwl/Library/Application%20Support/typora-user-images/image-20230811184139334.png)
2、log
引入依赖:spring-boot-starter-logging
配置方式:
1、yml配置
#日志配置
log-pattern-format: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n" #日志格式
logging:
file: #file下面配置以name为主,如果name不存在 则读取path 并新建一个spring.log文件,如果两个同时存在以name为主
#path: logs
name: logs/mylogInfo.log
charset: #日志编码 console控制台 file为日志文件
file: UTF-8
console: UTF-8
logback: #打包策略
rollingpolicy:
file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz #滚动日志文件名的模式
max-history: 7 #归档日志文件的最大数量
max-file-size: 10MB #最大日志文件大小
clean-history-on-start: true #是否在启动时清理归档日志文件
total-size-cap: 1GB #要保留的日志备份的总大小
pattern:
console: ${log-pattern-format} #Appender模式,输出到控制台。仅支持默认的Logback设置
file: ${log-pattern-format}
level:
root: info #跟目录日志级别
com.lc.controller.MyController: trace #指定文件路径日志级别
2、配置文件配置
3、如果集成了lombok则 在Bean上直接使用注解@Slf4j 否则使用
Logger logger = LoggerFactory.getLogger(this.getClass());
3、test
引入依赖:spring-boot-starter-test
配置方式:
![image-20230812192415407](/Users/xuwl/Library/Application%20Support/typora-user-images/image-20230812192415407.png)
弹出
![image-20230812192447959](/Users/xuwl/Library/Application%20Support/typora-user-images/image-20230812192447959.png)
新建配置:
@SpringBootTest(classes={QuickMain.class}) //需要制定springboot启动类,引入配置
class MyControllerTest {
@Autowired
private MyController controller;
@Test
void hello() {
controller.hello();
}
}
4、web
引入依赖:spring-boot-starter-web
配置yml:
server:
port: 80 #端口号
servlet:
context-path: / #请求路径前缀
tomcat:
max-connections: 8192 #最大连接数
threads:
max: 200 #工作线程的最大数量
min-spare: 10 #工作线程的最小数量
spring:
application:
name: quick #项目名称
1、静态资源错误页面配置 必须是resources/public/error下面 500.html 和 404.html
2、thymeleaf界面编程
引入依赖:spring-boot-starter-thymeleaf
Resources/templates文件夹下新建html。注意html需要引入xmlns:th=“http://www.thymeleaf.org”
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${welcome}" > 这里显示动态数据 </p>
<div th:text="${welcome}" > 这里显示动态数据 </div>
<span th:text="${welcome}" > 这里显示动态数据 </span>
<p th:text="#{login.error.a}"></p>
<p th:text="${message}">这里是国际化消息 </p>
</body>
</html>
Controller需要设定Model
@Controller
public class IndexController {
@GetMapping("/index")
public String index(Model model){
System.out.println("进入 日志");
String message = MessageUtils.getMessage("login.error.test");
model.addAttribute("welcome", "welcome to my html ");
model.addAttribute("message" , message);
return "index";
}
}
3、国际化编程
依赖为Web依赖 在resources/i18n 新建
新建如下demo文件 login.properties、login_en_US.properties、login_zh_CN.properties
如果是thymeleaf 参考上图,国际化#{}获取即可。 Java代码返回,则需要根据地区进行配置。配置代码如下
@Configuration
public class LanguageLocaleConfig implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale locale = Locale.CHINA;
String language = request.getHeader("Accept-Language");
if (StringUtils.isNotBlank(language)) {
String[] splits = language.split("-");
if (splits.length > 1) {
locale = new Locale(splits[0], splits[1]);
}
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
@Bean
public LocaleResolver localeResolver() {
return new LanguageLocaleConfig();
}
}
@Component
public class MessageUtils {
private static MessageSource messageSource;
public MessageUtils(MessageSource messageSource) {
this.messageSource = messageSource;
}
public static String getMessage(String key) {
return messageSource.getMessage(key, null, LocaleContextHolder.getLocale());
}
}
4、过滤器Filter
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String filterName = filterConfig.getFilterName();
log.info("filterConfig = {} , filterName = {}" ,filterConfig,filterName);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("filter----------doFilter");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
log.info("---destroy-----");
}
}
5、拦截器Interceptor
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("Interceptor-----preHandle");
return true; //True 才会继续往下执行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("Interceptor-----postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("Interceptor-----afterCompletion");
}
}
注册Bean
@Configuration
public class MyWebMVCConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {//拦截器过滤
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/index");
}
@Bean
public FilterRegistrationBean<MyFilter> myFilterRegistrationBean() {//过滤器注册
FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
}
5、actuator
引入依赖:spring-boot-starter-actuator
yaml配置:
#Actuator
management:
endpoints:
web:
exposure:
include: '*' #开放所有的端点
#exclude: '*' #关闭所有端点
base-path: /tab #actuator请求路径重写
endpoint:
shutdown:
enabled: true #服务下线 POST请求
health:
show-details: always #展示详细健康信息
server:
port: 9090 #actuator请求端口
info:
env:
enabled: true #启用自定义配置
info:
app:
name: zhangsan
age: 10
访问: http://localhost:9090/tab/
6、admin
Server端:
引入依赖:spring-boot-admin-starter-server 启动类注解 @EnableAdminServer 启动Server
client端:
引入依赖:spring-boot-admin-starter-client
yml配置,注册服务
spring.boot.admin.client.instance:
name: quick
url: http://localhost:9000/
如果启动服务监控,需要启动Actuator。
7、Data
yaml必须配置:
spring:
datasource:
druid:
url: jdbc:mysql://127.0.0.1:3306/lc_01_mybatis?serverTimezone=UTC&userUnicode=true&charsetEncoding=utf-8
username: mybatis
password: mybatis
driver-class-name: com.mysql.cj.jdbc.Driver
1、mybatis
引入依赖:mybatis-spring-boot-starter如果使用分页插件pagehelper-spring-boot-starter,则不需要引入mybatis-spring-boot-starter
配置:
1、启动类配置@MapperScan(basePackages = {“com.lc.dao”})
2、Dao接口配置:@Mapper
yaml配置:
mybatis:
mapper-locations: classpath:mappers/*.xml
type-aliases-package: com.lc.pojo
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
pagehelper:
helper-dialect: mysql # 指定分页插件使用哪种语言
params: count=countSql # 为了支持startPage(Object params)方法,增加该参数来配置参数映射,用于从对象中根据属性名取值,可以配置pageNum,pageSize,pageSizeZero, reasonable, 不配置映射是使用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
reasonable: true # 分页合理化参数,默认为false,当该值为true,pageNum<=0默认查询第一页,pageNum>pages时会查询最后一页,false时直接根据参数进行查询
support-methods-arguments: true # 默认为false, 为true时允许在运行时根据多数据源自动识别对应的方言进行分页
mapper配置:
<?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="com.lc.dao.StudentDao">
<resultMap id="baseMap" type="student">
<id column="stu_id" property="stuId"/>
<result column="stu_name" property="stuName"/>
<result column="stu_age" property="stuAge"/>
<result column="course_id" property="courseId"/>
</resultMap>
<insert id="insertStudent" parameterType="student">
insert into student(stu_name,stu_age,course_id) values(#{stuName},#{stuAge},#{courseId})
</insert>
<select id="findAll" resultMap="baseMap">
select stu_id, stu_name,stu_age,course_id from student
</select>
</mapper>
pageHelper使用:
@Override
public PageInfo<Student> getAllStudent() {
PageHelper.startPage(1, 2);
List<Student> list = studentDao.findAll();
return new PageInfo<Student>(list);
}
事务配置:
@Autowired
private PlatformTransactionManager transactionManager;
@Override
@Transactional//声明式事务
public void add() {
Student stu1 = new Student("add1-张三", 10, 9);
int a = studentDao.insertStudent(stu1);
// int i = 1 / 0;
Student stu2 = new Student("add1-李四", 11, 10);
int b = studentDao.insertStudent(stu2);
}
@Override
public void add2() {
TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());//编程式事务
try {
Student stu1 = new Student("add2-张三", 10, 9);
int a = studentDao.insertStudent(stu1);
// int i = 1 / 0;
Student stu2 = new Student("add2-李四", 11, 10);
int b = studentDao.insertStudent(stu2);
transactionManager.commit(transactionStatus);
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
}
}
2、jdbcTemplate
引入依赖:spring-boot-starter-jdbc
@Test
void jdbcTemplate() {
String sql = "select stu_id, stu_name,stu_age,course_id from student ";
List<Student> maps = jdbcTemplate.query(sql, new RowMapper<Student>() {
@Override
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student student = new Student();
student.setStuId(rs.getInt("stu_id"));
student.setStuName(rs.getString("stu_name"));
student.setStuAge(rs.getInt("stu_age"));
student.setCourseId(rs.getInt("course_id"));
return student;
}
});
maps.stream().forEach(System.out::println);
}
3、jpa
引入依赖:spring-boot-starter-data-jpa
@Data
@Entity
@Table(name = "student")
public class JPAStudent {
@Id
@GeneratedValue
private Integer stuId;
private String stuName;
private Integer stuAge;
private Integer courseId;
}
@Repository
public interface MyJPA extends JpaRepository<JPAStudent, Long> {
}
@Test
void jpa(){
List<JPAStudent> all = jpa.findAll();
all.stream().forEach(System.out::println);
}
4、mybatisPlus
引入依赖:mybatis-plus-boot-starter
@Autowired
private MP mp;
@Mapper
public interface MP extends BaseMapper<Student> {
}
@Test
void mp(){
List<Student> students = mp.selectList(null);
students.stream().forEach(System.out::println);
}
8、定时任务
@SpringBootApplication
@EnableScheduling //启动类激活 定时任务
public class QuickMain {
public static void main(String[] args) {
SpringApplication.run(QuickMain.class, args);
}
}
@Slf4j
@Component
public class MyTask {
@Scheduled(cron="*/10 * * * * ?") //每个10s执行一次
public void task1(){
log.info("Task1 每10秒执行一次 当前时间:{}" , new Date() );
}
@Scheduled(fixedRate =20000) //下一次执行 间隔20秒
public void task2(){
log.info("Task2 间隔20s执行一次 当前时间:{}" , new Date() );
}
@Scheduled(cron="0 0 14 * * ?")//定点 每天14点执行
public void task3(){
log.info("Task3 14点执行 当前时间:{}" , new Date() );
}
}
9、参数校验
SpringBoot 2.3.x 移除了validation依赖需要手动引入依赖。
引入依赖:spring-boot-starter-validation
Demo
@RestController
public class MyController {
@PostMapping("/test")
public String print(@Validated UserInfo userInfo){
System.out.println("user = "+ userInfo);
return userInfo.toString();
}
@GetMapping("/test1")
public String print2(@Validated @NotNull String id){
System.out.println("user = "+ id);
return id;
}
}
@RestControllerAdvice
public class ExceptionConfig {
@ExceptionHandler(BindException.class)
public String handleBindingResult(BindException bindException) {
String defaultMessage = bindException.getAllErrors().get(0).getDefaultMessage();
return defaultMessage;
}
}
public class UserInfo { //实体类
@NotNull(message="name 不能为空")
@Size(min = 6, max = 11, message = "账号长度必须是6-11个字符")
private String name;
}
验证注解 | 验证的数据类型 | 说明 |
---|---|---|
@AssertFalse | Boolean,boolean | 验证注解的元素值是false |
@AssertTrue | Boolean,boolean | 验证注解的元素值是true |
@NotNull | 任意类型 | 验证注解的元素值不是null |
@Null | 任意类型 | 验证注解的元素值是null |
@Min(value=值) | BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 | 验证注解的元素值大于等于@Min指定的value值 |
@Max(value=值) | 和@Min要求一样 | 验证注解的元素值小于等于@Max指定的value值 |
@DecimalMin(value=值) | 和@Min要求一样 | 验证注解的元素值大于等于@ DecimalMin指定的value值 |
@DecimalMax(value=值) | 和@Min要求一样 | 验证注解的元素值小于等于@ DecimalMax指定的value值 |
@Digits(integer=整数位数, fraction=小数位数) | 和@Min要求一样 | 验证注解的元素值的整数位数和小数位数上限 |
@Size(min=下限, max=上限) | 字符串、Collection、Map、数组等 | 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小 |
@Past | java.util.Date,java.util.Calendar;Joda Time类库的日期类型 | 验证注解的元素值(日期类型)比当前时间早 |
@Future | 与@Past要求一样 | 验证注解的元素值(日期类型)比当前时间晚 |
@NotBlank | CharSequence子类型 | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格 |
@Length(min=下限, max=上限) | CharSequence子类型 | 验证注解的元素值长度在min和max区间内 |
@NotEmpty | CharSequence子类型、Collection、Map、数组 | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@Range(min=最小值, max=最大值) | BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型 | 验证注解的元素值在最小值和最大值之间 |
@Email(regexp=正则表达式,flag=标志的模式) | CharSequence子类型(如String) | 验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式 |
@Pattern(regexp=正则表达式,flag=标志的模式) | String,任何CharSequence的子类型 | 验证注解的元素值与指定的正则表达式匹配 |
@Valid | 任何非原子类型 | 指定递归验证关联的对象如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证 |
3、原理
Demo说明:
package com.lc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
//1、注解 @SpringBootApplication
//2、启动方法 SpringApplication.run(DemoApplication.class, args);
1、注解说明:@SpringBootApplication
@SpringBootConfiguration //定义为一个配置类 参考下面 part1
@EnableAutoConfiguration //开启自动扫描 参考下面 part2
@ComponentScan// 包扫描 排除已经扫描进来的配置类、和自动配置类
(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{
}
//part1 核心就是Configuration注解
@Configuration
public @interface SpringBootConfiguration {}
//part2
@AutoConfigurationPackage //扫描主程序包:加载自己的组件 参考下面part3
@Import(AutoConfigurationImportSelector.class) //加载所有自动配置类:加载starter导入的组件 参考下面part5
public @interface EnableAutoConfiguration {}
//part3
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
//导入的核心配置类
//part4 加载主程序包
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override //将对应注解所在的包自动配置到容器
//AnnotationMetadata metadata这个就是注解的类即核心启动类,
//当方法启动时只加载该类所在包的Bean
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
//part5 加载starter
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override //加载starter
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);//核心步骤
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
//获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//[1] 得到spring.factories文件配置的所有自动配置类 2.7版本前加载META-INF/spring.factories文件中的Bean
//2.7 版本后同时加载META-INF/spring/%s.imports文件中的bean
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 利用LinkedHashSet移除重复的配置类
configurations = removeDuplicates(configurations);
// 得到要排除的自动配置类,比如注解属性exclude的配置类
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
checkExcludedClasses(configurations, exclusions);
// [2] 将要排除的配置类移除
configurations.removeAll(exclusions);
// [3] 因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载
//进内存,会造成内存浪费,因此这里需要进行过滤
注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合
//@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,后面会重点分
//析一下
configurations = filter(configurations, autoConfigurationMetadata);
// 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
// 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
// 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
fireAutoConfigurationImportEvents(configurations, exclusions);
// 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
return new AutoConfigurationEntry(configurations, exclusions);
}
}
/*@Import使用说明
1、直接引入一个类 自动装载成一个Bean
2、引入一个ImportBeanDefinitionRegistrar通过registerBeanDefinitions方法加载一系列Bean
3、引入一个ImportSelector通过selectImports加载一个包下面的所有Bean*/
2、启动说明:
SpringApplication.run(DemoApplication.class, args);做了两件事情
(1、创建SpringApplication对象;在对象初始化时保存事件监听器,容器初始化类以及判断是否为web应用,保存包含main方法的主配置类
2、调用run方法;准备spring的上下文,完成容器的初始化,创建,加载等。会在不同的时机触发监听器的不同事件)
1、创建SpringApplication对象
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
public SpringApplication(Object... sources) {
initialize(sources);
}
private void initialize(Object[] sources) {
// 在sources不为空时,保存配置类
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 判断是否为web应用
this.webEnvironment = deduceWebEnvironment();
// 获取并保存容器初始化类,通常在web应用容器初始化使用
// 利用loadFactoryNames方法从路径MEAT-INF/spring.factories中找到所有的ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 获取并保存监听器
// 利用loadFactoryNames方法从路径MEAT-INF/spring.factories中找到所有的ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 从堆栈信息获取包含main方法的主配置类
this.mainApplicationClass = deduceMainApplicationClass();
}
2、利用创建好的SpringApplication对象调用run方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
// 配置属性
configureHeadlessProperty();
// 获取监听器
// 利用loadFactoryNames方法从路径MEAT-INF/spring.factories中找到所有的SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动监听
// 调用每个SpringApplicationRunListener的starting方法
listeners.starting();
try {
// 将参数封装到ApplicationArguments对象中
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 准备环境
// 触发监听事件——调用每个SpringApplicationRunListener的environmentPrepared方法
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 从环境中取出Banner并打印
Banner printedBanner = printBanner(environment);
// 依据是否为web环境创建web容器或者普通的IOC容器
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
// 准备上下文
// 1.将environment保存到容器中
// 2.触发监听事件——调用每个SpringApplicationRunListeners的contextPrepared方法
// 3.调用ConfigurableListableBeanFactory的registerSingleton方法向容器中注入applicationArguments与printedBanner
// 4.触发监听事件——调用每个SpringApplicationRunListeners的contextLoaded方法
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 刷新容器,完成组件的扫描,创建,加载等
refreshContext(context);
afterRefresh(context, applicationArguments);
// 触发监听事件——调用每个SpringApplicationRunListener的finished方法
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 返回容器
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
![image-20230823235242823](/Users/xuwl/Library/Application%20Support/typora-user-images/image-20230823235242823.png)
-
导入
starter
-
依赖导入
autoconfigure
-
寻找类路径下
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件 -
启动,加载所有
自动配置类
xxxAutoConfiguration
-
- 给容器中配置功能
组件
组件参数
绑定到属性类
中。xxxProperties
属性类
和配置文件
前缀项绑定@Contional派生的条件注解
进行判断是否组件生效
- 给容器中配置功能
-
效果:
-
- 修改配置文件,修改底层参数
- 所有场景自动配置好直接使用
- 可以注入SpringBoot配置好的组件随时使用
![image-20230823235449137](/Users/xuwl/Library/Application%20Support/typora-user-images/image-20230823235449137.png)
4、手写一个starter
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId> //Bean 配置基础包
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId> // 配置文件提醒jar
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.7.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
编写配置文件
@Data
@ConfigurationProperties(prefix = "org.date", ignoreUnknownFields = true)
public class DateProperties { //properties配置类
private Integer year;
private Integer month;
private Integer day;
}
@Data
public class DateTemplate {//基础Bean 注解直接使用
private Integer year;
private Integer month;
private Integer day;
public Date getDate(){
System.out.println(year+"------"+month+"------"+day);
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR,year);
cal.set(Calendar.MONTH,month);
cal.set(Calendar.DAY_OF_MONTH,day);
Date time = cal.getTime();
return time;
}
}
@Configuration
@ConditionalOnClass(value = DateTemplate.class)
@EnableConfigurationProperties(value=DateProperties.class)
public class DateConfiguration { //核心加载类启动的时候加载
@Autowired
private DateProperties dateProperties;
@Bean
@ConditionalOnMissingBean()
public DateTemplate dateTemplate() {
DateTemplate dateTemplate = new DateTemplate();
dateTemplate.setDay(dateProperties.getDay());
dateTemplate.setMonth(dateProperties.getMonth());
dateTemplate.setYear(dateProperties.getYear());
return dateTemplate;
}
}
2.6版本之前:META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=date.DateConfiguration
2.7版本之后:可以如下配置 且兼容上述配置
文件:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容:date.DateConfiguration
使用就是简单的starter引入然后注入DateTemplate使用方法
Condition注解说明
注解 | 描述 |
---|---|
@ConditionalOnBean: | 当容器中有指定Bean的条件下进行实例化。 |
@ConditionalOnMissingBean: | 当容器里没有指定Bean的条件下进行实例化。 |
@ConditionalOnClass: | 当classpath类路径下有指定类的条件下进行实例化。 |
@ConditionalOnMissingClass: | 当类路径下没有指定类的条件下进行实例化。 |
@ConditionalOnWebApplication: | 当项目是一个Web项目时进行实例化。 |
@ConditionalOnNotWebApplication: | 当项目不是一个Web项目时进行实例化。 |
@ConditionalOnProperty: | 当指定的属性有指定的值时进行实例化。 |
@ConditionalOnExpression: | 基于SpEL表达式的条件判断。 |
@ConditionalOnJava: | 当JVM版本为指定的版本范围时触发实例化。 |
@ConditionalOnResource: | 当类路径下有指定的资源时触发实例化。 |
@ConditionalOnJndi: | 在JNDI存在的条件下触发实例化。 |
@ConditionalOnSingleCandidate: | 当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。 |