Java教程

SpringBoot学习笔记

本文主要是介绍SpringBoot学习笔记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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

时序图-SpringBoot启动流程

一、初始化器解析

Demo - Spring 自定义初始化器

  • 加载
# 解析 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)

二、监听器解析

监听器模式代码示例

Demo - Spring 自定义监听器

了解 Spring 中的事件

SpringApplicationEvent 类图

Spring事件发送顺序

获取监听器列表

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 详解

supportsEvent判断监听器是否支持给定事件

三、bean 解析

xml方式启动Spring

annotation方式启动Spring(含多种 bean 的注册方式)

四、启动加载器解析

Demo - Spring自定义启动加载器

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);
            }
        }
    }

    // ...
}

五、属性配置解析

Demo - Spring 属性配置

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
    • 接口默认方法
    • 父类
    • 内部类

执行流程解析

时序图-ConfigurationClassPostProcessor解析

七、Servlet 容器启动解析

启动入口

时序图-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"));
    }

}
这篇关于SpringBoot学习笔记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!