当前位置 : 首页 » 文章分类 :  开发  »  Spring-Boot

Spring-Boot

Spring Boot 学习笔记

Spring Boot 官网
https://spring.io/projects/spring-boot

当前版本官方参考文档
Spring Boot Reference Documentation
https://docs.spring.io/spring-boot/docs/current/reference/html/index.html

源码
spring-projects / spring-boot
https://github.com/spring-projects/spring-boot

Spring Boot 纯洁的微笑(Java/JVM/Spring Boot/Spring Cloud)
http://www.ityouknow.com/


SpringBoot 和 SpringCloud 版本

查看 SpringBoot 和 SpringCloud 版本兼容性

在 SpringCloud 官方页面查看和 SpringBoot 的版本兼容性
https://spring.io/projects/spring-cloud

使用 http://start.spring.io/info 查看 Sprig Boot 和 Spring Cloud 版本兼容性

Spring Cloud 和 Spring Boot 版本不兼容导致无法启动

Spring Boot 无法启动,或者说启动后立即退出,不开 debug 看不到错误,日志级别开到 debug 后报错:
2019-11-15 18:10:38,222 [DEBUG] [main] org.springframework.boot.context.logging.ClasspathLoggingApplicationListener.onApplicationEvent [56]: Application failed to start with classpath:

在 ClasspathLoggingApplicationListener.onApplicationEvent 方法内打断点,发现错误是

java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.<init>([Ljava/lang/Object;)V
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:161)

原因
Spring Cloud 版本 和 Spring Boot 版本不兼容
出问题时,我的 Spring Boot 版本: 2.2.1.RELEASE,未指定 Spring Cloud 版本

解决
Spring Boot 2.2.1.RELEASE 应搭配 Spring Cloud Hoxton.RC2 使用
见 Spring Cloud Hoxton.RC2 的 release note https://spring.io/blog/2019/11/12/spring-cloud-hoxton-rc2-released Spring Cloud Hoxton.RC2 is built upon Spring Bot 2.2.1.RELEASE

java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder
https://stackoverflow.com/questions/55498624/java-lang-nosuchmethoderror-org-springframework-boot-builder-springapplicationb

https://stackoverflow.com/questions/44166221/spring-boot-2-0-0-m1-nosuchmethoderror-org-springframework-boot-builder-springa


Spring Boot 3.2.0

Jakarta EE validation-api

java ee 相关依赖的坐标由 javax 改为 jakarta 了:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>

改为:

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
</dependency>

Jakarta EE servlet-api

java ee 相关依赖的坐标由 javax 改为 jakarta 了:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
</dependency>

改为:

<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <scope>provided</scope>
</dependency>

Hibernate 相关依赖

Spring Boot 3.0 默认使用 Hibernate 6.1,Hibernate 相关依赖坐标由 org.hibernate 改为 org.hibernate.orm

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-jcache</artifactId>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
</dependency>
<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-jcache</artifactId>
</dependency>
<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-ehcache</artifactId>
</dependency>

升级MyBatisPlus

启动报错:
2023-12-03 19:41:36,653 [ERROR] [main] org.springframework.boot.SpringApplication.reportFailure [839]: Application run failed
java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration.propertySourcesPlaceholderConfigurer
Caused by: java.lang.IllegalArgumentException: Invalid value type for attribute ‘factoryBeanObjectType’: java.lang.String
Invalid value type for attribute ‘factoryBeanObjectType’: java.lang.String

解决参考 https://gitee.com/mybatis-flex/mybatis-flex/issues/I8CNDP
但我 SpringBoot 3.2.0 下还是不行,退到 3.1.6 后可以了。


Spring Boot 2.7

修改了 MySQL JDBC driver 依赖坐标

Spring Boot 2.7 开始,MySQL JDBC 驱动从 8.0.31 版本开始,依赖坐标从 mysql:mysql-connector-java 改为 com.mysql:mysql-connector-j

Spring Boot 2.7 之前使用下面的依赖坐标引入 MySQL 驱动:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.32</version>
</dependency>

Spring Boot 2.7 之后要改为:

<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <version>8.0.32</version>
</dependency>

Spring Boot 2.7 Release Notes / MySQL JDBC Driver
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes#mysql-jdbc-driver


Spring Boot 2.6

ClassNotFoundException CamelCaseToUnderscoresNamingStrategy

使用了 Spring Data JPA 的项目,升级 Spring Boot 2.6.5 后,启动报错:
java.lang.ClassNotFoundException: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy

原因:
org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
这个类是 hibenrate.5.5 新引进的类。但是我们用的 hibernate.5.3.x

解决:
加下面系统参数
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl


Failed to start bean ‘inputBindingLifecycle’

使用了 Spring Cloud Stream 的项目,升级 Spring Boot 2.6.5 后,启动报错:

2022-04-11 19:38:37.416 [main] ERROR org.springframework.boot.SpringApplication.reportFailure:830 - Application run failed
org.springframework.context.ApplicationContextException: Failed to start bean 'inputBindingLifecycle'; nested exception is java.lang.NoSuchMethodError: java.util.List.of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181)

原因:
https://github.com/spring-projects/spring-integration/issues/3761

解决:
使用 Java 9 或将 Spring Integration 降级到 5.5.9

<properties>
    <spring-integration.version>5.5.9</spring-integration.version>
</properties>

Failed to start bean ‘documentationPluginsBootstrapper’

使用了 swagger 2.9.2 的项目,升级 Spring Boot 2.6.5 后,启动报错

2022-04-12 10:06:10.347 [main] ERROR org.springframework.boot.SpringApplication.reportFailure:830 - Application run failed
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181)

SpringBoot2.6,在有spring-boot-starter-actuator依赖的情况下报错,2.5.7正常启动
https://gitee.com/xiaoym/knife4j/issues/I4JT89

Springboot 2.6.0 / Spring fox 3 - Failed to start bean ‘documentationPluginsBootstrapper’
https://stackoverflow.com/questions/70036953/springboot-2-6-0-spring-fox-3-failed-to-start-bean-documentationpluginsboot


Spring Boot 2.4

Config file processing in Spring Boot 2.4
https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4

properties 文件支持多文档

Spring Boot 2.4 开始,properties 配置文件也开始支持类似 yaml 的多文档功能,多文档属性文件使用注释 # 后跟三个 --- 破折号来分隔文档

比如,yaml

test: "value"
---
test: "overridden-value"

对应 properties 为:

test=value
#---
test=overridden-value

多profile配置

在 Spring Boot 2.3 及之前通过配置 spring.profiles 来实现多 profile。但在 Spring Boot 2.4 中 属性更改 为 spring.config.activate.on-profile

比如想实现只有在 dev profile 中覆盖 test 属性:

test=value
#---
spring.config.activate.on-profile=dev
test=overridden-value

选择profile

Spring Boot 2.4 中继续使用 spring.profiles.active 来选择 profile, 例如:

test=value
spring.profiles.active=local
#---
spring.config.activate.on-profile=dev
test=overridden value

SpringBoot 自动配置

@EnableAutoConfiguration 的作用

@SpringBootApplication 注解中包含了 @EnableAutoConfiguration , 所以无需额外再开启自动配置
Spring boot 的自动配置会基于你的 classpath 中的 jar 包,试图猜测和配置您可能需要的 bean。
@EnableAutoConfiguration 可以帮助 Spring Boot 应用将所有符合条件的 @Configuration 配置类的 bean 都加载到 Spring IoC 容器中。

@EnableAutoConfiguration 自动配置原理

通过 @EnableAutoConfiguration 启用 Spring 应用程序上下文的自动配置
这个注解会导入一个名为 EnableAutoConfigurationImportSelector 的类, 而这个类会通过 SpringFactoriesLoader.loadFactoryNames() 去读取配置文件 META-INF/spring.factories 中 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguratio 的 value, 这些 value 就是需要自动配置的标注有 @Configuration 的类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

spring.factories 文件

读取 spring.factories 文件的实现是通过 org.springframework.core.io.support.SpringFactoriesLoader 实现的。

SpringFactoriesLoader 的实现类似于 SPI(Service Provider Interface, 在 java.util.ServiceLoader 的文档里有比较详细的介绍。java SPI 提供一种服务发现机制,为某个接口寻找服务实现的机制。有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要)。

SpringFactoriesLoader 会加载 classpath 下所有 JAR 文件里面的 META-INF/spring.factories 文件, 并实例化其中的自动配置类

其中加载 spring.factories 文件的代码在 loadFactoryNames 方法里:

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

  public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    // org.springframework.core.io.support.SpringFactoriesLoader
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String propertyValue = properties.getProperty(factoryClassName);
                for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) {
                    result.add(factoryName.trim());
                }
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

第5章 Spring Boot自动配置原理
https://www.jianshu.com/p/346cac67bfcc

使用spring.factories指定自己的自动配置类

如果不在 spring.factories 中指定, 我们写的 @Configuration 配置类需要 @Import{MyAutoConfiguration.class}) 才能起作用
假如我们自己提供了一个 Jar 包模块,里面有个自动配置类,想要使用方不做任何代码侵入就让自动配置类生效,可以增加一个 src/main/resources/META-INF/spring.factories 文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.nio.uds.user.config.MyAutoConfiguration

在日常工作中,我们可能需要实现一些 Spring Boot Starter 给被人使用,这个时候我们就可以使用这个 Factories 机制,将自己的 Starter 注册到 org.springframework.boot.autoconfigure.EnableAutoConfiguration 命名空间下。这样用户只需要在服务中引入我们的 jar 包即可完成自动加载及配置。


@AutoConfigureBefore/@AutoConfigureAfter

@AutoConfigureAfter 在加载配置的类之后再加载当前类

springboot情操陶冶-@Conditional和@AutoConfigureAfter注解解析
https://www.cnblogs.com/question-sky/p/9427245.html


可直接注入自动配置的属性类使用

比如代码中想读取 Spring Boot 自动配置的 redis 属性,可以直接注入 RedisProperties 使用。
比如默认端口就是 redisProperties.getPort()

import org.springframework.boot.autoconfigure.data.redis.RedisProperties;

@Autowired
private RedisProperties redisProperties;

@Conditional 条件Bean

@Conditional 是 Spring4 开始新提供的注解,能够实现当 Condition 条件为 true 时才将 Bean 实例化到 Spring 容器中。
比如,当某一个 jar 包在一个类路径下的时自动配置一个或多个 Bean;或者只有某个 Bean 被创建才会创建另外一个 Bean。

SpringBoot 也正是使用 @Conditional 的这项功能来实现自动配置的。SpringBoot 对该注解进行了相应个扩展,形成了以下组合注解,以满足更多的情况。

下面的的 @Conditional 都表示条件为 true 时才创建 Bean
@ConditionalOnBean:当容器中有指定Bean的条件下。
@ConditionalOnClass:当classpath类路径下有指定类的条件下。
@ConditionalOnCloudPlatform:当指定的云平台处于active状态时。
@ConditionalOnExpression:基于SpEL表达式的条件判断。当表达式为true的时候,才会实例化一个Bean
@ConditionalOnJava:基于JVM版本作为判断条件。
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean:当容器里没有指定Bean的条件。
@ConditionalOnMissingClass:当类路径下没有指定类的条件下。
@ConditionalOnNotWebApplication:当项目不是一个Web项目的条件下。
@ConditionalOnProperty:当指定的属性有指定的值的条件下。
@ConditionalOnResource:类路径是否有指定的值。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean。
@ConditionalOnWebApplication:当项目是一个Web项目的条件下。

Spring4.x高级话题(四):条件注解@Conditional
https://juejin.im/entry/59f67c01518825603a37e400


匹配上下文中是否有某个注解

只能判断 @Conditional 注解所在的类/方法上是否存在其他注解,不能在整个 spring 上下文中判断

Spring Annotation based Condition Example(这篇文字特别好,简介清晰)
http://www.javarticles.com/2016/01/spring-annotation-based-condition-example.html

Spring高级装配之条件化创建Bean
https://www.jianshu.com/p/0761ba179625


@ConditionalOnProperty根据配置变量决定是否创建bean

Spring Boot中有个注解 @ConditionalOnProperty, 这个注解能够控制某个 configuration 是否生效。具体操作是通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值,如果该值为空,则返回false;如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。如果返回值为false,则该configuration不生效;为true则生效。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
    String[] value() default {}; //数组,获取对应property名称的值,与name不可同时使用
    String prefix() default "";//property名称的前缀,可有可无
    String[] name() default {};//数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用
    String havingValue() default "";//可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
    boolean matchIfMissing() default false;//缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错
    boolean relaxedNames() default true;//是否可以松散匹配,至今不知道怎么使用的
}

例如,当 application.properties 中有 com.masikkk.blog.enabled=true 配置时。

@Configuration
@ConditionalOnProperty(prefix = "com.masikkk.blog", name = "enabled", havingValue = "true")
public class TestConfiguration {
}

配置Spring Boot通过@ConditionalOnProperty来控制Configuration是否生效
https://blog.csdn.net/dalangzhonghangxing/article/details/78420057

ConditionalOnProperty的使用
https://blog.csdn.net/u010002184/article/details/79353696


自定义Condition

自定义条件需要实现 Condition 接口,实现其中唯一的 matches 方法。

package org.springframework.context.annotation;

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

SpringBoot 加载应用上下文并启动过程

SpringApplication 部分源码如下:

package org.springframework.boot;

public class SpringApplication {
  // 构造方法
  public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
  }

  public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //通过Resource和ResourceLoader加载\META-INF\spring.factories文件
    //并通过反射,创建对应的实例
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
  }

  public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        ConfigurableApplicationContext context = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
}

其中的 run() 方法中,调用 createApplicationContext() 创建并初始化 ApplicationContext, 可以看到其实就是通过反射实例化了一个 AnnotationConfigApplicationContext 并将其初始化:

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context.annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
  Class<?> contextClass = this.applicationContextClass;
  if (contextClass == null) {
    try {
      switch (this.webApplicationType) {
      case SERVLET:
        contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
        break;
      case REACTIVE:
        contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
        break;
      default:
        contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
      }
    }
    catch (ClassNotFoundException ex) {
      throw new IllegalStateException(
          "Unable create a default ApplicationContext, "
              + "please specify an ApplicationContextClass",
          ex);
    }
  }
  return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

然后 refreshContext 中最终调用了 AbstractApplicationContextrefresh 方法,其中会扫描所有的 bean 并初始化

SpringApplication.run做了什么?

1、创建并初始化应用上下文 ApplicationContext, 即 Spring 的 IOC 容器, 或者叫 Bean 容器。
2、扫描所有 Bean 并初始化所有非 lazy-init 的单例 bean。

BootstrapContext 和 ApplicationContext

启动上下文时,Spring Cloud 会创建一个 Bootstrap Context,作为 Spring 应用的 Application Context 的父上下文。

初始化的时候,Bootstrap Context 负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的 Environment。Bootstrap 属性有高优先级,默认情况下,它们不会被本地配置覆盖

两个容器的区别:
Bootstrap 容器会多一个步骤:读取 spring.factories 文件中的 BootstrapConfiguration,SpringBoot 子容器读取spring.factories文件中的EnableAutoConfiguration。

bootstrap主要有以下几个应用场景:
使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息
一些固定的不能被覆盖的属性
一些加密/解密的场景


ApplicationRunner 和 CommandLineRunner

如果想在 SpringBoot 容器启动后执行一些操作,比如配置文件加载、数据库初始化、打印 “xx服务启动了” 等等,我们可以考虑使用 ApplicationRunner 或者 CommandLineRunner,二者使用方式大体一致,差别主要体现在参数上。

ApplicationRunner

Spring Boot 项目在启动时会遍历所有的 ApplicationRunner 的实现类并调用其中的 run 方法。
如果整个系统中有多个 ApplicationRunner 的实现类,同样可以使用 @Order(value = 整数值) 注解对这些实现类的调用顺序进行排序(数字越小越先执行)。

package org.springframework.boot;

@FunctionalInterface
public interface ApplicationRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming application arguments
     * @throws Exception on error
     */
    void run(ApplicationArguments args) throws Exception;

}

CommandLineRunner

Spring Boot 项目在启动时会遍历所有的 CommandLineRunner 的实现类并调用其中的 run 方法。

  • 如果整个系统中有多个 CommandLineRunner 的实现类,那么可以使用 @Order(value = 整数值) 注解对这些实现类的调用顺序进行排序(数字越小越先执行)。
  • run 方法的参数是系统启动时传入的参数,即入口类中 main 方法的参数(在调用 SpringApplication.run 方法时被传入 Spring Boot 项目中),所以main方法的arg参数 和 CommandLineRunner方法的arg数组的值是一样的。
package org.springframework.boot;

@FunctionalInterface
public interface CommandLineRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming main method arguments
     * @throws Exception on error
     */
    void run(String... args) throws Exception;

}

ApplicationRunner 和 CommandLineRunner 的区别

ApplicationRunner 和 CommandLineRunner 都是在容器启动成功后回调(类似开机自启动),都是只规定了一个 run() 方法的函数式接口。
CommandLineRunner 的参数 和 main 方法参数完全一致,都是 String 数组, ApplicationRunner 的参数是 ApplicationArguments 对象。

ApplicationArguments 区分选项参数和非选项参数:

  • 对于非选项参数:我们可以通过 ApplicationArguments 的 getNonOptionArgs() 方法获取,获取到的是一个数组。
  • 对于选项参数:可以通过 ApplicationArguments 的 getOptionNames() 方法获取所有选项名称。通过 getOptionValues() 方法获取实际值(它会返回一个列表字符串)。

ApplicationRunner 默认先于 CommandLineRunner 执行
两个接口中都有run方法,负责接收来自应用外部的参数,ApplicationRunner的run方法会将外部参数封装成一个ApplicationArguments对象,而CommandLineRunner接口中run方法的参数则为String数组。

SpringBoot - 实现启动时执行指定任务(CommandLineRunner、ApplicationRunner)
https://www.hangge.com/blog/cache/detail_2508.html


SpringBoot 数据源

不需要配置spring.datasource.driver-class-name

数据源配置:

spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&useAffectedRows=true
spring.datasource.username = username
spring.datasource.password = password

启动时提示
Loading class com.mysql.jdbc.Driver'. This is deprecated. The new driver class is com.mysql.cj.jdbc.Driver’. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
使用 mysql-connector-java 8+ 时, 不需要再指定 spring.datasource.driver-class-name 驱动类会通过 SPI 机制自动注册,不需要手动指定。
如果你非要手动指定,com.mysql.jdbc.Driver 已废弃,改为 com.mysql.cj.jdbc.Driver 即可。

不连接数据库启动

spring boot 默认会加载
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
类,DataSourceAutoConfiguration 类使用了 @Configuration 注解向 spring 注入了 dataSource bean。
对于不需要连接数据库的项目,因为工程中没有关于 dataSource 相关的配置信息,如果不显示排除此自动配置,当 spring 创建 dataSource bean 因缺少相关的信息就会报错。

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

Action:

Consider the following:
    If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

解决:

// 不需要连接数据源,不手动排除自动配置无法启动
@SpringBootApplication(
        exclude = {DataSourceAutoConfiguration.class}
)

排除reids自动配置

@SpringBootApplication(exclude={
        RedisAutoConfiguration.class,
        RedisRepositoriesAutoConfiguration.class
})

SpringBoot Logging 日志配置

SpringBoot 默认使用 Logback 日志
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.logging


启动SpringBoot应用

指定端口号启动

Springboot 默认以端口号 8080 启动容器

在Spring配置文件中配置server.port

application.properties 配置:

server.port=8888

application.yml 配置:

server:
  port: 8888

server.port修改容器端口原理

为什么配置 server.port 就可以实现修改SpringBoot的默认端口呢?
因为在SpringBoot中有这样的一个类 ServerProperties

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
 private Integer port;

 @Override
 public void customize(ConfigurableEmbeddedServletContainer container) {
   if (getPort() != null) {
     container.setPort(getPort());
   }
 }
}

在这个类里有一个 @ConfigurationProperties 注解,这个注解会读取 SpringBoot 的默认配置文件 application.properties 的值注入到 bean 里。
这里定义了一个 server 的前缀和一个 port 字段,所以在 SpringBoot 启动的时候会从 application.properties 读取到 server.port 的值。
重写了 EmbeddedServletContainerCustomizer 接口的 customize 的方法,这个方法里会给容器设置读取到的端口号。
仅限 spring boot 1.5中, spring boot 2.x 中没有这个类了

使用命令行参数–server.port

如果你只是想在启动的时候修改一次端口号的话,可以用命令行参数来修改端口号。
java -jar app.jar --server.port=8000

使用虚拟机参数-Dserver.port

你同样也可以把修改端口号的配置放到JVM参数里。
-Dserver.port=8009

启动类实现EmbeddedServletContainerCustomizer接口

仿照 ServerProperties 类,实现 EmbeddedServletContainerCustomizer 接口的 customize 方法来设定容器属性
仅限 spring boot 1.5中, spring boot 2.x 中没有这个类了

@SpringBootApplication
public class Application implements ApplicationRunner, EmbeddedServletContainerCustomizer {
    public static void main(String[] args) {
        SpringApplication.run(ConfigMain.class, args);
    }

    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        //指定项目名称
        container.setContextPath("/demo");
        //指定端口地址
        container.setPort(8090);
    }

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        // do something
        logger.info("welcomes you!");
    }
}

指定profile启动

配置文件spring.profiles.active

spring:
  profiles:
    active: dev

使用命令行参数–spring.profiles.active

java -jar app.jar --spring.profiles.active=prod

使用虚拟机参数-Dspring.profiles.active

也可以把修改端口号的配置放到JVM参数里。
-Dspring.profiles.active=prod


停止SpringBoot应用的几种方法

SpringBoot启动后自动关闭原因

现象:
Spring Boot 应用启动后马上自动结束,没有任何报错信息。

2021-05-26 17:47:31.696 [main] INFO  com.masikkk.Application.logStarted:61 - Started McApplication in 8.331 seconds (JVM running for 9.767)
Process finished with exit code 0

原因:
没有引入类似 spring-boot-starter-web 或 Tomcat 之类的 web 组件,不需要监听事件循环,所以启动后马上就停止了。
也可能是因为引入的 scope 不对,例如 scope 是 provided

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

解决:
引入 web 组件,或者去掉 web 组件的 provided scope

调用Actuator停止端点

Actuator 有个 shutdown 端点

这个关闭应用程序对应的Endpoint是ShutdownEndpoint,直接调用ShutdownEndpoint提供的rest接口即可。得先开启ShutdownEndpoint(默认不开启),以及不进行安全监测:

endpoints.shutdown.enabled: true
endpoints.shutdown.sensitive: false

然后调用rest接口:

curl -X POST http://localhost:8080/shutdown

可以使用spring-security进行安全监测:

endpoints.shutdown.sensitive: true
security.user.name: admin
security.user.password: admin
management.security.role: SUPERUSER

然后使用用户名和密码进行调用:

curl -u admin:admin -X POST http://127.0.0.1:8080/shutdown

这个ShutdownEndpoint底层其实就是调用了Spring容器的close方法:

暴露SpringApplication的exit静态方法

SpringApplication提供了一个exit静态方法,用于关闭Spring容器,该方法还有一个参数exitCodeGenerators表示ExitCodeGenerator接口的数组。ExitCodeGenerator接口是一个生成退出码exitCode的生成器。

public static int exit(ApplicationContext context,
        ExitCodeGenerator... exitCodeGenerators) {
    Assert.notNull(context, "Context must not be null");
    int exitCode = 0; // 默认的退出码是0
    try {
        try {
            // 构造ExitCodeGenerator集合
            ExitCodeGenerators generators = new ExitCodeGenerators();
            // 获得Spring容器中所有的ExitCodeGenerator类型的bean
            Collection<ExitCodeGenerator> beans = context
                    .getBeansOfType(ExitCodeGenerator.class).values();
            // 集合加上参数中的ExitCodeGenerator数组
            generators.addAll(exitCodeGenerators);
            // 集合加上Spring容器中的ExitCodeGenerator集合
            generators.addAll(beans);
            // 遍历每个ExitCodeGenerator,得到最终的退出码exitCode
            // 这里每个ExitCodeGenerator生成的退出码如果比0大,那么取最大的
            // 如果比0小,那么取最小的
            exitCode = generators.getExitCode();
            if (exitCode != 0) { // 如果退出码exitCode不为0,发布ExitCodeEvent事件
                context.publishEvent(new ExitCodeEvent(context, exitCode));
            }
        }
        finally {
            // 关闭Spring容器
            close(context);
        }

    }
    catch (Exception ex) {
        ex.printStackTrace();
        exitCode = (exitCode == 0 ? 1 : exitCode);
    }
    return exitCode;
}

写个Controller直接调用exit方法:

@Autowired
private ApplicationContext applicationContext;

@PostMapping("/stop")
public String stop() {
        // 加上自己的权限验证
    SpringApplication.exit(applicationContext);
    return "ok";
}

SpringBoot应用程序的关闭
https://fangjian0423.github.io/2017/06/28/springboot-application-exit/


打包和部署SpringBoot应用

spring-boot-maven-plugin插件

spring-boot-maven-plugin 插件是将 springboot 的应用程序打包成 fat jar 的插件。
fat jar 比普通 jar 包包含的东西更多,普通 jar 只是将 .class 文件以及 resources 目录下的东西打包进去,但 fat jar 还将应用所依赖的 jar 包也包含进去,也就是 jar 包中有 jar 包。
所以,spring boot 借助 spring-boot-maven-plugin 将所有应用启动运行所需要的 jar 都包含进来,从逻辑上将具备了独立运行的条件。

将普通插件 maven-jar-plugin 生成的包和 spring-boot-maven-plugin 生成的包解压后对比,发现使用 spring-boot-maven-plugin 生成的 jar 中主要增加了两部分,第一部分是 lib 目录,这里存放的是应用的 Maven 依赖的 jar 包文件,第二部分是 spring boot loader 相关的类。

所以,只有在需要独立打成jar包 run 的模块的 pom 里才需要 spring-boot-maven-plugin 插件,不要在 parent 项目里包含这个插件

IDEA spring boot打包

View -> Tool Windows -> Maven Projects 调出 Maven Projects 窗口,打开“项目名”下的 Lifecycle,双击 package,开始自动打包

跳过测试

1、在 Maven Projects 边栏中,点击闪电图标 Toggle ‘Skip Tests’ Mode,选中后即打开跳过测试模式。

2、或者使用 maven 命令:mvn clean package -DskipTestsmvn clean package -Dmaven.test.skip=true

3、或者在 pom 中配置:

<plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <skip>true</skip>
        </configuration>
      </plugin>
</plugins>

打包成可运行的Runnable Jar

Spring Boot Maven 插件可以将 spring boot 打包成可执行 jar,通过 java -jar 来启动。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

上面的配置就是 Spring Boot Maven 插件,Spring Boot Maven 插件提供了许多方便的功能:
1、把项目打包成一个可执行的超级 JAR, 或者叫 fat jar,包括把应用程序的所有依赖打入 JAR 文件内,并为 JAR 添加一个描述文件,其中的内容能让你用 java -jar 来运行应用程序。
2、搜索 public static void main() 方法来标记为可运行类。

打包成完全可执行的Executable Jar

给 spring boot maven 插件增加 <executable>true</executable> 参数后,可以打包成一个直接可运行的应用,不需要 java -jar 运行,还可以注册为 init.dsystemd 服务。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <executable>true</executable>
    </configuration>
</plugin>

安装为init.d服务

将 spring boot 打包为完全可执行 jar,并链接到 init.d 即可支持 linux 标准的 start, stop, restart, status 命令
此后,可在 /var/run/<appname>/<appname>.pid 看到应用 pid,在 /var/log/<appname>.log 看到应用日志。

将打包为完全可执行 jar 的 spring boot 应用 /var/myapp/myapp.jar 链接到 init.d
sudo ln -s /var/myapp/myapp.jar /etc/init.d/myapp
启动
service myapp start

安装为systemd服务

/var/myapp/myapp.jar 是打包为完全可执行 jar 的 spring boot 应用。
创建脚本 myapp.service,并放到 /etc/systemd/system 目录:

[Unit]
Description=myapp
After=syslog.target

[Service]
User=myapp
ExecStart=/var/myapp/myapp.jar
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

启动
systemctl enable myapp.service

定制启动和运行脚本

假如有打包为完全可执行 jar 的 spring boot 应用 /var/myapp/myapp.jar
可以配置同名的 .conf 结尾配置文件 /var/myapp/myapp.conf 实现定制化参数,比如

JAVA_OPTS=-Xmx1024M
LOG_FOLDER=/custom/log/folder

又比如打包了一个 spring-boot-search-1.0.0.jar 可以配置一个 spring-boot-search-1.0.0.conf 来改变端口 和 jvm 配置

MODE=service
JAVA_OPTS="-Xms1g -Xmx1g -Dfile.encoding=utf-8"
RUN_ARGS="--server.port=8000"

Installing Spring Boot Applications
https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment-install


SpringBoot Jar包原理

JarWriter

package org.springframework.boot.loader.tools;
public JarWriter(File file, LaunchScript launchScript)
    throws FileNotFoundException, IOException {
  FileOutputStream fileOutputStream = new FileOutputStream(file);
  if (launchScript != null) {
    // 将启动脚本写入文件
    fileOutputStream.write(launchScript.toByteArray());
    // 设置文件可执行属性
    setExecutableFilePermission(file);
  }
  this.jarOutput = new JarArchiveOutputStream(fileOutputStream);
  this.jarOutput.setEncoding("UTF-8");
}

当执行 gradle build 或 mvn package 时,会使用 JarWriter 重新生成 jar 文件。JarWrite 构造函数中,会首先将启动脚本写入文件,并设置文件的可执行属性。
除此之外,JarWriter 还有众多方法,如 writeManifest 写入 manifest 文件、writeNestedLibrary 写入第三方依赖等等,通过 JarWriter 以构建 Executable Jar

LaunchScript

public DefaultLaunchScript(File file, Map<?, ?> properties) throws IOException {
  // 加载启动脚本
  String content = loadContent(file);
  this.content = expandPlaceholders(content, properties);
}

private String loadContent(File file) throws IOException {
  if (file == null) {
    // 默认launch.script
    return loadContent(getClass().getResourceAsStream("launch.script"));
  }
  return loadContent(new FileInputStream(file));
}

默认的 LaunchScript 为 DefaultLaunchScript,在构造 DefaultLaunchScript 时,若不指定启动脚本,则取默认的 launch.script,内容见
https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script
launch.script 实现了 start stop restart 等功能,方便安装为 systemd 服务
此外,launch.script 会解析与 jar 文件同名的 conf 文件,以实现启动脚本定制化

springboot应用启动原理(一) 将启动脚本嵌入jar
https://segmentfault.com/a/1190000013489340

springboot应用启动原理(二) 扩展URLClassLoader实现嵌套jar加载
https://segmentfault.com/a/1190000013532009


SpringBoot Undertow

Undertow 是红帽公司开发的一款基于 NIO 的高性能 Web 嵌入式服务器

Untertow 的特点:

  • 轻量级:它是一个 Web 服务器,但不像传统的 Web 服务器有容器概念,它由两个核心 Jar 包组成,加载一个 Web 应用可以小于 10MB 内存
  • Servlet3.1 支持:它提供了对 Servlet3.1 的支持
  • WebSocket 支持:对 Web Socket 完全支持,用以满足 Web 应用巨大数量的客户端
  • 嵌套性:它不需要容器,只需通过 API 即可快速搭建 Web 服务器

默认情况下 Spring Cloud 使用 Tomcat 作为内嵌 Servlet 容器,可启动一个 Tomcat 的 Spring Boot 程序与一个 Undertow 的 Spring Boot 程序,通过 VisualVM 工具进行比较,可看到 Undertow 性能优于 Tomcat

使用undertow

从starter-web中排除Tomcat,同时增加undertow依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

配置undertow

# Undertow 日志存放目录
server.undertow.accesslog.dir
# 是否启动日志
server.undertow.accesslog.enabled=false
# 日志格式
server.undertow.accesslog.pattern=common
# 日志文件名前缀
server.undertow.accesslog.prefix=access_log
# 日志文件名后缀
server.undertow.accesslog.suffix=log
# HTTP POST请求最大的大小
server.undertow.max-http-post-size=0
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
server.undertow.io-threads=4
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
server.undertow.worker-threads=20
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分
server.undertow.buffer-size=1024
# 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region
server.undertow.buffers-per-region=1024
# 是否分配的直接内存
server.undertow.direct-buffers=true

SpringBoot 国际化

spring boot默认就支持国际化的,而且不需要你过多的做什么配置,只需要在resources/下定义国际化配置文件即可,注意名称必须以messages开头。

定义如下几个文件:
messages.properties (默认,当找不到语言的配置的时候,使用该文件进行展示)。
messages_zh_CN.properties(中文)
messages_en_US.properties(英文)

不同语言环境下看到不同内容如何实现的?

为了让web应用程序支持国际化,必须识别每个用户的首选区域,并根据这个区域显示内容。在Spring MVC应用程序中,用户的区域是通过区域解析器来识别的,它必须是实现LocaleResolver接口。Spring MVC提供了几个LocaleResolver实现,让你可以按照不同的条件来解析区域。除此之外,你还可以实现这个接口创建自己的区域解析器。如果没有做特殊的处理的话,Spring 采用的默认区域解析器是AcceptHeaderLocaleResolver。它通过检验HTTP请求的头部信息accept-language来解析区域。这个头部是由用户的wb浏览器底层根据底层操作系统的区域设置进行设定的。请注意,这个区域解析器无法改变用户的区域,因为它无法修改用户操作系统的区域设置。

58 Spring Boot国际化(i18n)【从零开始学Spring Boot】
http://412887952-qq-com.iteye.com/blog/2312274


SpringBoot 命令行参数

main 方法参数

java -jar xxx.jar value1 value2

public static void main(String[] args) {
}

-D System 参数

java -jar -Dkey1=value1 -Dkey2=value2 xxx.jar
System 参数通过 System.getProperty("key1") 获取值
如果是预定义参数,可覆盖配置文件中的,比如修改容器端口号 -Dserver.port=9090

System参数必须放在jar包之前

-Dkey=value 这种 System 参数必须在 xxx.jar 之前,紧随 java -jar 或者放到其他 -D 参数之后,放在 xxx.jar 之后是无效的。

假如 -Dkey=value 放在了 xxx.jar 之后,不会报任何错误,但这个参数配置是无效的。通过 System.getProperty("key") 获取到的是 null
这也就是为什么有时候加了 -D 配置参数,但却不起作用,就是因为位置错了,放在了 xxx.jar 之后。

properties 参数

java -jar xxx.jar --key1=value1 --key2=value2
properties 参数可通过 @Value("${key1}") 获取,一般用于覆盖 appliaction.properties 或 appliaction.yml 文件中的配置参数。
比如修改容器端口号 --server.port=9090

properties参数必须放在jar包之后

properties 参数必须在 xxx.jar 之后,放在 xxx.jar 之前会报错无法启动

比如 java -jar –server.port=8080 xxx.jar
会报错如下错误,直接无法启动 JVM,这样还好,直接就能排查出错误,不像 System 参数写在 xxx.jar 之后没有报错但无法读到参数值。

Unrecognized option: --server.port=8080
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

注意,System.getProperty("key1") 只能读取 System 参数,无法读取 properties 参数。但 @Value("${key1}") 既可以获取 properties 参数,也能获取 System 参数。

Spring Boot启动命令参数详解及源码分析
https://juejin.im/post/6844904025905725454


SpringBoot 配置文件

实际上,Spring Boot应用程序有多种设置途径,Spring Boot能从多重属性源获得属性,包括如下几种:
根目录下的开发工具全局设置属性(当开发工具激活时为~/.spring-boot-devtools.properties)。
测试中的 @TestPropertySource 注解。
测试中的 @SpringBootTest#properties 注解特性。
命令行参数 --key=value 例如 java -jar target/xxx.jar --server.port=9090 --server.context-path=/test
SPRING_APPLICATION_JSON 中的属性(环境变量或系统属性中的内联JSON嵌入)。
ServletConfig初始化参数。
ServletContext初始化参数。
java:comp/env里的JNDI属性
JVM参数 -Dkey=value 例如 java -jar target/xxx.jar -Dserver.port=9090 -Dserver.context-path=/test
操作系统环境变量
随机生成的带random.* 前缀的属性(在设置其他属性时,可以应用他们,比如${random.long})
应用程序以外的 application.properties 或者 appliaction.yml 文件
打包在应用程序内的 application.properties 或者 appliaction.yml 文件
通过 @PropertySource 标注的属性源
默认属性(通过 SpringApplication.setDefaultProperties 指定).

这里列表按组优先级排序,也就是说,任何在高优先级属性源里设置的属性都会覆盖低优先级的相同属性,列如我们上面提到的命令行属性就覆盖了application.properties的属性。


application.properties和application.yml同时存在

spring boot 项目中同时存在 application.properties 和 application.yml 文件时,两个文件都有效,但是 application.properties 的优先级会比 application.yml 高。
如果工程中同时存在 application.properties 文件和 application.yml 文件,yml 文件会先加载,而后加载的 properties 文件会覆盖 yml 文件。
所以建议工程中,只使用其中一种类型的文件即可。


application.yml 与 bootstrap.yml

yml 和 properties 文件是一样的原理,随意选择哪种格式都行。

bootstrap.yml(bootstrap.properties)先加载
application.yml(application.properties)后加载

bootstrap.yml 可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。
application.yml 可以用来定义应用级别的配置。

bootstrap.yml 用于应用程序上下文的引导阶段。bootstrap.yml 由父 Spring ApplicationContext 加载。

启动上下文时,Spring Cloud 会创建一个 Bootstrap Context,作为 Spring 应用的 Application Context 的父上下文。

初始化的时候,Bootstrap Context 负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的 Environment。Bootstrap 属性有高优先级,默认情况下,它们不会被本地配置覆盖

也就是说如果加载的 application.yml 的内容标签与 bootstrap 的标签一致,application 也不会覆盖 bootstrap,而 application.yml 里面的内容可以动态替换。

典型使用场景:
搭配 spring-cloud-config 使用时在 bootstrap.yml 中指定配置中心地址 spring.cloud.config.uri 等参数, 而 application.yml 中的配置内容保存在配置中心中。

当使用 Spring Cloud 时,通常从服务器加载配置数据。为了获取URL(和其他连接配置,如密码等),您需要一个较早的或“bootstrap”配置。因此,您将配置服务器属性放在bootstrap.yml中,该属性用于加载实际配置数据(通常覆盖application.yml 中的内容)。

What is the difference between putting a property on application.yml or bootstrap.yml in spring boot?
https://stackoverflow.com/questions/32997352/what-is-the-difference-between-putting-a-property-on-application-yml-or-bootstra


additional-location

通过 spring.config.additional-location 指定在其他位置的额外配置文件,可以覆盖默认读取的 yml 和 properties 配置。


spring boot 配置中引用maven pom.xml 属性

想要在 spring boot 中访问 maven pom.xml 中的属性,只需要在配置项前后加 @ 引用即可

例如 application.properties 中:

app.version=@project.version@
app.name=@project.name@

java 代码中

@Service
public class SomeService {

   @Value("${app.version}")
   private String appVersion;

   // other stuff
}

Cannot get maven project.version property in a Spring application with @Value
https://stackoverflow.com/questions/38983934/cannot-get-maven-project-version-property-in-a-spring-application-with-value


idea启动时无法读取@profile.active@

springboot application.properties 中配置

spring.profiles.active=@profile.active@
logging.config: classpath:logback-@profile.active@.xml

idea 中启动时无法读取 logback 的配置文件,报错如下,看起来像是没解析到 @profile.active@ 的值

Logging system failed to initialize using configuration from 'classpath:logback-@profile.active@.xml'
java.io.FileNotFoundException: class path resource [logback-@profile.active@.xml] cannot be resolved to URL because it does not exist

原因:
IDEA 在 Build 时并不会处理 Maven Profile 的 filtering 设置,在 Run 的时候会直接复制 application.properties 文件到 target/class 目录下,而由于文件中包含 @profile.active@(没有被 maven 替换)且 @ 是非法字符,因此没有办法运行。

解决方法是让 IDEA Run 之前执行 resouces 插件的 mvn resources:resouces


found character ‘@’ that cannot start any token


springboot2.0中logging.level:info配置报错

springboot 2.x 中配置

logging:
  config: classpath:logback-dev.xml
  level: info

启动时报错

18:32:46.286 [main] ERROR o.s.boot.SpringApplication [826]  - Application run failed
org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'logging.level' to java.util.Map<java.lang.String, org.springframework.boot.logging.LogLevel>
    at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:364)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:324)

Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.util.Map<java.lang.String, org.springframework.boot.logging.LogLevel>]
    at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194)

原因是从 springboot 2.x 开始 logging.level 配置格式改为 logging.level.<logger-name>=<level> ,也就是针对具体的 logger 配置级别,所以我们不能直接在 logging.level 后加 level,还得指定具体的 logger name

4.4.5. Log Levels
https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-custom-log-levels


SpringBoot应用配置项加密{cipher}

SpringBoot应用配置项加密
http://isouth.org/archives/364.html


@ConfigurationProperties 配置类

通过 @ConfigurationProperties 读取配置属性

1、配置文件增加配置:

myapp:
  retryTaskCron: "0 0/3 * * * ?"
  retryTaskEnabled: true

2、增加 @ConfigurationProperties 注解的属性 POJO 类

@Data
@ConfigurationProperties("myapp")
public class MyAppProperties {
    private String retryTaskCron;

    private boolean retryTaskEnabled;
}

3、想要直接注入使用 MyAppProperties 实例,有三个选择:
方法1、通过 @EnableConfigurationProperties 启动配置类扫描
例如

@EnableConfigurationProperties({MyAppProperties.class})
public class MyApplication {
}

方法2、通过 @ConfigurationPropertiesScan 启动配置类扫描

方法3、配置类上增加 @Component@Configuration 暴露给 Spring 上下文。


application.yml 中配置 Map/List

@Data
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
   private Map<String, List<String>> mapList;
}

application.yml 中

app:
  mapList: {
    "app1": "item1, item2, item3",
    "app2": "item0, item1"
  }

创建Spring boot项目

通过IntelliJ IDEA使用(个人推荐)
IntelliJ IDEA是非常流行的IDE,IntelliJ IDEA 14.1已经支持Spring Boot了。
创建Spring Boot操作步骤如下:
1.在File菜单里面选择 New > Project,然后选择Spring Initializr


Spring Boot父级依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

这块配置就是Spring Boot父级依赖,有了这个,当前的项目就是Spring Boot项目了,spring-boot-starter-parent是一个特殊的starter,它用来提供相关的Maven默认依赖,使用它之后,常用的包依赖可以省去version标签。

在maven页面查看 spring-boot-starter-parent/2.2.1.RELEASE 中管理的依赖版本
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent/2.2.1.RELEASE

如果你不想使用某个依赖默认的版本,您还可以通过覆盖自己的项目中的属性来覆盖各个依赖项,例如,要升级到另一个Spring Data版本系列,您可以将以下内容添加到pom.xml中。


spring boot自定义属性乱码问题

#设置spring-boot 编码格式
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8

设置 File Encodings的Transparent native-to-ascii conversion为true,具体步骤如下:
依次点击
File -> Settings -> Editor -> File Encodings
将Properties Files (*.properties)下的Default encoding for properties files设置为UTF-8,将Transparent native-to-ascii conversion前的勾选上。

Spring Boot 自定义属性 以及 乱码问题
https://blog.csdn.net/m0_37995707/article/details/77506184

Spring Boot干货系列:(一)优雅的入门篇
http://tengj.top/2017/02/26/springboot1/

Spring Boot干货系列总纲
http://tengj.top/2017/04/24/springboot0/


Spring Boot ApplicationRunner 实例

使用的一个 Spring Boot 应用类

package com.masikkk;

import com.masikkk.common.config.MyServiceConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
import org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableAsync;

@Import({
        MyServiceConfig.class,
        CassandraConfig.class,
        MariadbConfig.class,
        RedisConfig.clas
})
@MapperScan(value = {
        "com.masikkk.mariadb.mapper",
        "com.masikkk.mariadb.data.mapper",
        "com.masikkk.common.mapper",
        "com.masikkk.job.mapper",
        "com.masikkk.tidb.mapper"
})
@SpringBootApplication(exclude = {
        FlywayAutoConfiguration.class,
        ErrorMvcAutoConfiguration.class,
        GsonAutoConfiguration.class,
        JdbcTemplateAutoConfiguration.class,
        JmxAutoConfiguration.class,
        MultipartAutoConfiguration.class,
        RedisAutoConfiguration.class,
        WebClientAutoConfiguration.class,
        WebSocketAutoConfiguration.class,
        DataSourceAutoConfiguration.class
})
@EnableAsync
@EnableDiscoveryClient
public class Application implements ApplicationRunner {

    private static final Logger logger = LoggerFactory.getLogger(Application.class);

    @Value("${service.name}")
    private String serviceName;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        // 服务启动时所作的初始化动作
        logger.info("{} welcomes you!", serviceName);
    }
}

问题

Unable to map duplicate endpoint operations

IDEA 中 spring boot 启动失败,报错:
Caused by: java.lang.IllegalStateException: Unable to map duplicate endpoint operations: [MBean call ‘topology’] to topologyEndpoint

原因:
新版 IDEA 的 spring boot application 默认勾选了 Enable JMX agent, 去掉勾选即可。

Error in Spring Boot app - Unable to map duplicate endpoint operations: [MBean call ‘topology’] to topologyEndpoint
https://stackoverflow.com/questions/61712319/error-in-spring-boot-app-unable-to-map-duplicate-endpoint-operations-mbean-c/61732734#61732734


上一篇 DataGrip使用笔记

下一篇 Phabricator使用笔记

阅读
评论
11.2k
阅读预计51分钟
创建日期 2018-06-11
修改日期 2023-12-03
类别
目录
  1. SpringBoot 和 SpringCloud 版本
    1. 查看 SpringBoot 和 SpringCloud 版本兼容性
    2. Spring Cloud 和 Spring Boot 版本不兼容导致无法启动
  2. Spring Boot 3.2.0
    1. Jakarta EE validation-api
    2. Jakarta EE servlet-api
    3. Hibernate 相关依赖
    4. 升级MyBatisPlus
  3. Spring Boot 2.7
    1. 修改了 MySQL JDBC driver 依赖坐标
  4. Spring Boot 2.6
    1. ClassNotFoundException CamelCaseToUnderscoresNamingStrategy
    2. Failed to start bean ‘inputBindingLifecycle’
    3. Failed to start bean ‘documentationPluginsBootstrapper’
  5. Spring Boot 2.4
    1. properties 文件支持多文档
    2. 多profile配置
    3. 选择profile
  6. SpringBoot 自动配置
    1. @EnableAutoConfiguration 的作用
    2. @EnableAutoConfiguration 自动配置原理
    3. spring.factories 文件
    4. 使用spring.factories指定自己的自动配置类
    5. @AutoConfigureBefore/@AutoConfigureAfter
    6. 可直接注入自动配置的属性类使用
  7. @Conditional 条件Bean
    1. 匹配上下文中是否有某个注解
    2. @ConditionalOnProperty根据配置变量决定是否创建bean
    3. 自定义Condition
  8. SpringBoot 加载应用上下文并启动过程
    1. SpringApplication.run做了什么?
    2. BootstrapContext 和 ApplicationContext
  9. ApplicationRunner 和 CommandLineRunner
    1. ApplicationRunner
    2. CommandLineRunner
    3. ApplicationRunner 和 CommandLineRunner 的区别
  10. SpringBoot 数据源
    1. 不需要配置spring.datasource.driver-class-name
    2. 不连接数据库启动
    3. 排除reids自动配置
  11. SpringBoot Logging 日志配置
  12. 启动SpringBoot应用
    1. 指定端口号启动
      1. 在Spring配置文件中配置server.port
      2. server.port修改容器端口原理
      3. 使用命令行参数–server.port
      4. 使用虚拟机参数-Dserver.port
      5. 启动类实现EmbeddedServletContainerCustomizer接口
    2. 指定profile启动
      1. 配置文件spring.profiles.active
      2. 使用命令行参数–spring.profiles.active
      3. 使用虚拟机参数-Dspring.profiles.active
  13. 停止SpringBoot应用的几种方法
    1. SpringBoot启动后自动关闭原因
    2. 调用Actuator停止端点
    3. 暴露SpringApplication的exit静态方法
  14. 打包和部署SpringBoot应用
    1. spring-boot-maven-plugin插件
      1. IDEA spring boot打包
      2. 跳过测试
    2. 打包成可运行的Runnable Jar
    3. 打包成完全可执行的Executable Jar
      1. 安装为init.d服务
      2. 安装为systemd服务
      3. 定制启动和运行脚本
    4. SpringBoot Jar包原理
      1. JarWriter
      2. LaunchScript
  15. SpringBoot Undertow
    1. 使用undertow
    2. 配置undertow
  16. SpringBoot 国际化
    1. 不同语言环境下看到不同内容如何实现的?
  17. SpringBoot 命令行参数
    1. main 方法参数
    2. -D System 参数
      1. System参数必须放在jar包之前
    3. properties 参数
      1. properties参数必须放在jar包之后
  18. SpringBoot 配置文件
    1. application.properties和application.yml同时存在
    2. application.yml 与 bootstrap.yml
    3. additional-location
    4. spring boot 配置中引用maven pom.xml 属性
      1. idea启动时无法读取@profile.active@
      2. found character ‘@’ that cannot start any token
    5. springboot2.0中logging.level:info配置报错
    6. SpringBoot应用配置项加密{cipher}
    7. @ConfigurationProperties 配置类
      1. application.yml 中配置 Map/List
  19. 创建Spring boot项目
  20. Spring Boot父级依赖
  21. spring boot自定义属性乱码问题
  22. Spring Boot ApplicationRunner 实例
  23. 问题
    1. Unable to map duplicate endpoint operations

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论