SpringBoot学习笔记
示例代码跳转链接无效,查看完整笔记点击:
https://gitee.com/pingWurth/study-notes/blob/master/springboot/spring-boot-demo/SpringBoot学习笔记.md
官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/index.html
application.properties 详解:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html
一、初始化器解析
- 加载
# 解析 META-INF/spring.factories 文件中的配置
org.springframework.boot.SpringApplication.setInitializers(SpringApplication.java:1188)
org.springframework.boot.SpringApplication.<init>(SpringApplication.java:268)
org.springframework.boot.SpringApplication.<init>(SpringApplication.java:249)
org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
- 触发
# DelegatingApplicationContextInitializer Order 为零先执行,它能读取 context.initializer.classes 属性配置,独立完成初始化器的调用
org.springframework.boot.context.config.DelegatingApplicationContextInitializer.initialize(DelegatingApplicationContextInitializer.java:52)
org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:649)
org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:373)
org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
二、监听器解析
了解 Spring 中的事件
获取监听器列表
# 调用 supportsEvent 判断监听器是否支持给定事件
org.springframework.context.event.AbstractApplicationEventMulticaster.supportsEvent(AbstractApplicationEventMulticaster.java:304)
org.springframework.context.event.AbstractApplicationEventMulticaster.retrieveApplicationListeners(AbstractApplicationEventMulticaster.java:240)
org.springframework.context.event.AbstractApplicationEventMulticaster.getApplicationListeners(AbstractApplicationEventMulticaster.java:196)
org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:133)
org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:402)
org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:359)
- supportsEvent 详解
三、bean 解析
annotation方式启动Spring(含多种 bean 的注册方式)
四、启动加载器解析
ApplicationRunner 和 CommandLineRunner 在 Spring 启动完成后调用
可以捕获启动参数信息
具体方法如下:
public class SpringApplication {
// ...
public ConfigurableApplicationContext run(String... args) {
// ...
context = createApplicationContext();
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.started(context, timeTakenToStartup);
/** ------------------------------------------------ 调用点 */
callRunners(context, applicationArguments);
// ...
return context;
}
/**
* 按 @Order 顺序执行,顺序相同 ApplicationRunner 优先于 CommandLineRunner
*
* @param context Spring 上下文
* @param args 启动参数
*/
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
// ...
}
五、属性配置解析
Environment 的创建和属性的配置
- 追踪 Environment 实例的创建 -
getOrCreateEnvironment
Environment 实例创建出来后,
org.springframework.core.env.AbstractEnvironment.propertySources
字段中就包含了
- servletConfigInitParams 属性集
- servletContextInitParams 属性集
- Jndi 属性集
- systemProperties 属性集
- systemEnvironment 属性集
org.springframework.core.env.StandardEnvironment
#customizePropertySources(StandardEnvironment.java:99)
org.springframework.web.context.support.StandardServletEnvironment
#customizePropertySources(StandardServletEnvironment.java:113)
org.springframework.core.env.AbstractEnvironment.<init>(AbstractEnvironment.java:140)
org.springframework.core.env.AbstractEnvironment.<init>(AbstractEnvironment.java:124)
org.springframework.core.env.StandardEnvironment.<init>(StandardEnvironment.java:68)
org.springframework.web.context.support.StandardServletEnvironment.<init>(StandardServletEnvironment.java:67)
org.springframework.boot.ApplicationServletEnvironment.<init>(ApplicationServletEnvironment.java:30)
org.springframework.boot.SpringApplication
#getOrCreateEnvironment(SpringApplication.java:468)
org.springframework.boot.SpringApplication
#prepareEnvironment(SpringApplication.java:336)
org.springframework.boot.SpringApplication
#run
- 追踪 PropertySource 的配置过程 -
configurePropertySources
# 添加合并 defaultProperties 和 命令行参数
org.springframework.boot.SpringApplication.configurePropertySources
org.springframework.boot.SpringApplication.configureEnvironment
org.springframework.boot.SpringApplication.prepareEnvironment
org.springframework.boot.SpringApplication.run
使用 Profile
- 使用示例
- 追踪 Profile 处理源码 -
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment
- since 2.4.0
- 旧版
org.springframework.boot.context.config.ConfigFileApplicationListener.postProcessEnvironment
org.springframework.boot.context.config.ConfigDataEnvironment.withProfiles(ConfigDataEnvironment.java:275)
org.springframework.boot.context.config.ConfigDataEnvironment.processAndApply(ConfigDataEnvironment.java:231)
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:102)
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:94)
# 由 ApplicationEnvironmentPreparedEvent 事件监听调用
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:102)
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:87)
org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
# 发布 environmentPrepared 事件
org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:85)
org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:66)
java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
# 触发监听器
org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)
org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:114)
# environment 准备完成
org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:65)
org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:339)
org.springframework.boot.SpringApplication.run
六、配置类解析
处理 @Configuration 类的核心方法
- org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass
- @PropertySource
- @ComponentScan
- @Import
- @ImportResource
- @Bean
- 接口默认方法
- 父类
- 内部类
执行流程解析
七、Servlet 容器启动解析
启动入口
八、使用 starter
创建一个 starter 的步骤
第 1 步:创建一个 AutoConfiguration 类,用于 starter 项目的初始化工作
@Configuration
@EnableConfigurationProperties(ExampleProperties.class)
public class ExampleAutoConfiguration {
/**
* {@link ConditionalOnProperty} 当 spring.studydemo.enabled 值为 true 时才会调用该方法.
* {@link Bean} 生成由 Spring 容器管理的 Bean
* <p>
*
* @param exampleProperties
* @return
*/
@ConditionalOnProperty(prefix = "spring.studydemo", value = "enabled", havingValue = "true")
@Bean
public ExampleClient exampleClient(ExampleProperties exampleProperties) {
return new ExampleClient(exampleProperties);
}
}
第 2 步:添加属性配置类,用于获取 application.properties 中的配置
/**
* 配置类,用于读取 application.properties 中的配置信息.
* <p>
*
* @author Ping Wurth
* @date 2020/1/5 3:23
*/
@Data
@ConfigurationProperties(prefix = "spring.studydemo")
public class ExampleProperties {
private String name;
}
第 3 步:配置 spring.factories 文件或自定义 Enable 注解
resources/META-INF/spring.factories
文件中添加如下配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pingwurth.studydemo.ExampleAutoConfiguration
引入该 starter 包后,项目启动时 spring.factories 会被 Spring 扫描到, ExampleAutoConfiguration 就会被加载。
- 也可以使用自定义的 Enable 注解,实现开关功能
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ExampleAutoConfiguration.class})
public @interface EnableExampleClient {
}
在 Spring Boot 启动类上使用该注解, 就可以在启动时激活 ExampleAutoConfig
第 4 步:更细粒度的开关控制
- @Conditional
- @ConditionalOnBean
- @ConditionalOnClass
- @ConditionalOnMissingBean
- @ConditionalOnMissingClass
- ...
本质上都是 @Conditional,可以自定义新的 @ConditionalXXX 注解。
自定义的注解需要用 @Conditional 标记,并设置 value 属性
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { Class<? extends Condition>[] value(); }
需要自己定义一个 Condition 类,实现
matches
方法
九、日志系统解析
日志发展历程
- JDK1.3 之前(含1.3),通过 System.(out|err).println 打印,存在巨大缺陷
- 解决系统打印缺陷问题,出现 log4j, 2015 年 8 月停止更新
- 收到 log4j 影响,SUN 公司推出 java.util.logging, 即 JUL
- 由于存在两个系统实现,为了解决兼容性问题,推出 commons-logging, 即 JCL, 但存在一定缺陷
- log4j 作者推出 slf4j, 功能完善兼容性好,成为业界主流
- log4j 作者在退出 log4j 后进行新的改进思考,退出 logback
- log4j2 对 log4j 进行重大征集,修复已知缺陷,极大提升性能
- 最佳组合:slf4j + logback(springboot 使用)、slf4j + log4j2
日志实现寻址
org.slf4j.LoggerFactory.findPossibleStaticLoggerBinderPathSet(LoggerFactory.java:317)
org.slf4j.LoggerFactory.bind(LoggerFactory.java:146)
org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417)
org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
org.apache.commons.logging.LogAdapter$Slf4jAdapter.createLocationAwareLog(LogAdapter.java:130)
org.apache.commons.logging.LogAdapter.createLog(LogAdapter.java:91)
org.apache.commons.logging.LogFactory.getLog(LogFactory.java:67)
org.apache.commons.logging.LogFactory.getLog(LogFactory.java:59)
org.springframework.boot.SpringApplication.<clinit>(SpringApplication.java:174)
日志配置说明
<logger>
和 <root>
说明
<logger name="com.example.controller" level="error" additivity="false">
<appender-ref ref="APPLICATION" />
</logger>
<root level="warn">
<appender-ref ref="APPLICATION" />
<appender-ref ref="STDOUT" />
</root>
- root 是全局的日志输出配置
- logger 是局部的(细粒度)配置:
additivity=false
表示<logger>
处理过<root>
就不需要处理了additivity=true
表示<logger>
处理过还会让<root>
处理
<configuration>
说明
<configuration scan="true" scanPeriod="60 seconds" debug="false" />
- scan: 设置为 true 时,配置文件若发生改变将会重新加载
- scanPeriod: 扫描时间间隔(监听配置文件变化),不指定时间单位时,默认为毫秒
- debug: 设置为 true 将打印出 logback 内部日志信息
configuration 子节点说明
- contextName: 上下文名称
- property: 属性配置
- appender: 格式化日志输出
- root: 全局日志输出配置
- logger: 具体包或类输出配置
configuration 上下文及属性配置
<contextName>demo</contextName>
<!-- 用来区分不同应用程序的记录,默认为 default -->
<!-- name: 变量名, value: 变量值 -->
<property name="LOG_PATH" value="/tmp/logs" />
<!-- 引入属性资源文件 -->
<property resource="application.properties" />
<!-- 后续配置可以引用 property 定义的变量和资源文件中定义的变量 -->
<property name="LOG_PATH" value="${logging.path}:-${user.home}/${spring.application.name}/logs" />
日志使用说明
- 损耗字符串拼接性能
logger.debug("xyz " + i + " is " + j);
- 损耗 if 判断性能
if (logger.isDebugEnabled()) {
logger.debug("xyz " + i + " is " + j);
}
- 推荐方式
logger.debug("xyz {} is {}", i, j);
配置实战
按业务类型将日志输出到不同文件
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
<!-- 代码中可以通过 MDC.put("bizType", "goods") 来控制日志输出到不同文件 -->
<discriminator>
<key>bizType</key>
<defaultalue>OTHER</defaultalue>
</discriminator>
<sift>
<property name="BIZ_FILE" value="${LOG_PATH}/application-${bizType}.log" />
<appender name="APPLICATION-${bizType}" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${BIZ_FILE}</file>
<encoder>
<pattern>%date{HH:mm:ss} %contextName [%t] %p $logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${BIZ_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>7</maxHistory>
<maxFileSize>50MB</maxFileSize>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
</appender>
</sift>
</appender>
<root level="info">
<appender-ref ref="SIFT" />
</root>
-
总结
- 使用 SiftingAppender
- 在
<discriminator> 中定义好使用的 key
- 在
<sift>
中给每个业务类型配置<appender>
- 在程序上下文中通过 MDC 注入业务信息
-
扩展 —— MDC 其他用法
/**
* MDC 线程上下文映射 Thread Context Map.
* <p>
* 自定义日志打印格式的时候,如果设置了 %X{example},意味着 example 变量的值需要从 MDC 中取
* 我们需要向 MDC 中添加 key 为 “example” 的值,否则 %X{example} 取不到值
*
* @author ship
* @date 2021/8/15 0015 9:08
*/
@Component
public class InputMDC implements EnvironmentAware {
private static Environment environment;
@Override
public void setEnvironment(Environment environment) {
InputMDC.environment = environment;
}
public static void putMDC() {
MDC.put("hostname", NetUtils.getLocalHostName());
MDC.put("ip", NetUtils.getLocalIp());
MDC.put("applicationName", environment.getProperty("spring.application.name"));
}
}
标签:java,SpringBoot,boot,springframework,学习,笔记,context,SpringApplication,org
From: https://www.cnblogs.com/pingWurth/p/17494498.html