当前位置 : 首页 » 文章分类 :  开发  »  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/


Spring SPEL

SpelEvaluationException: EL1007E: Property or field ‘xx’ cannot be found on null

引用前加上 @
@Document(collection = "#{@cname.collectionName}")

SpelEvaluationException: EL1007E: Property or field ‘cname’ cannot be found on null #15707
https://github.com/spring-projects/spring-boot/issues/15707


SpringCloud和SpringBoot版本不兼容导致无法启动

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)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:102)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:68)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
    at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:74)
    at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:54)
    at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:358)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:317)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243)
    at com.masikkk.blog.BlogApplication.main(BlogApplication.java:22)

原因
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
或使用 http://start.spring.io/info 查看兼容性

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


SpringBoot自动配置

@EnableAutoConfiguration的作用

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

@EnableAutoConfiguration 自动配置原理

通过 @EnableAutoConfiguration 启用Spring应用程序上下文的自动配置
这个注解会导入一个名为 EnableAutoConfigurationImportSelector 的类, 而这个类会去读取配置文件 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 包即可完成自动加载及配置。


@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();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
      // 创建并初始化ApplicationContext, 即 Spring 的 Bean 容器
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);

      // 调用 AbstractApplicationContext.refresh(), 扫描并初始化所有Bean
            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, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, 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并初始化

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        //旧版本的Spring在这里初始化BeanFactory,读取xml配置,并解析成一个个Bean,
        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            //调用工厂后处理器
            //扫描Bean文件,并解析成一个个Bean
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            //注册Bean后处理器
            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            //初始化消息源
            // Initialize message source for this context.
            initMessageSource();

            //初始化应用上下文时间广播器
            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            //初始化其他特殊的Bean,由具体子类实现
            // Initialize other special beans in specific context subclasses.
            onRefresh();

            //注册事件监听器
            // Check for listener beans and register them.
            registerListeners();

            //初始化所有单例的Bean,使用懒加载模式的Bean除外
            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            //完成刷新并发布容器刷新事件
            // Last step: publish corresponding event.
            finishRefresh();
        }
        ...
    }
}

其中的 finishBeanFactoryInitialization() 负责 beanFactory 的初始化,包括所有非lazy-init的单例bean的初始化:

/**
 * Finish the initialization of this context's bean factory,
 * initializing all remaining singleton beans.
 */
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
  // Initialize conversion service for this context.
  if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
      beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
    beanFactory.setConversionService(
        beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
  }

  // Register a default embedded value resolver if no bean post-processor
  // (such as a PropertyPlaceholderConfigurer bean) registered any before:
  // at this point, primarily for resolution in annotation attribute values.
  if (!beanFactory.hasEmbeddedValueResolver()) {
    beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
      @Override
      public String resolveStringValue(String strVal) {
        return getEnvironment().resolvePlaceholders(strVal);
      }
    });
  }

  // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
  String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
  for (String weaverAwareName : weaverAwareNames) {
    getBean(weaverAwareName);
  }

  // Stop using the temporary ClassLoader for type matching.
  beanFactory.setTempClassLoader(null);

  // Allow for caching all bean definition metadata, not expecting further changes.
  beanFactory.freezeConfiguration();

  // 初始化所有非 lazy-init 的单例bean
  // Instantiate all remaining (non-lazy-init) singletons.
  beanFactory.preInstantiateSingletons();
}

最后一步,org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 进行 bean 的初始化, 该方法首先循环所有的BeanNames,并且调用getBean方法,该方法实际上就是创建bean并递归构建依赖关系。
getBean会调用org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 进行最终的 bean 初始化,这也就到了原生 Spring 的初始化过程,里面有一些处理循环依赖的逻辑。

@Override
public void preInstantiateSingletons() throws BeansException {
  if (logger.isDebugEnabled()) {
    logger.debug("Pre-instantiating singletons in " + this);
  }

  // Iterate over a copy to allow for init methods which in turn register new bean definitions.
  // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
  List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);

  // Trigger initialization of all non-lazy singleton beans...
  for (String beanName : beanNames) {
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
      if (isFactoryBean(beanName)) {
        final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
        boolean isEagerInit;
        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
          isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            @Override
            public Boolean run() {
              return ((SmartFactoryBean<?>) factory).isEagerInit();
            }
          }, getAccessControlContext());
        }
        else {
          isEagerInit = (factory instanceof SmartFactoryBean &&
              ((SmartFactoryBean<?>) factory).isEagerInit());
        }
        if (isEagerInit) {
          getBean(beanName);
        }
      }
      else {
        getBean(beanName);
      }
    }
  }

  // Trigger post-initialization callback for all applicable beans...
  for (String beanName : beanNames) {
    Object singletonInstance = getSingleton(beanName);
    if (singletonInstance instanceof SmartInitializingSingleton) {
      final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
      if (System.getSecurityManager() != null) {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
          @Override
          public Object run() {
            smartSingleton.afterSingletonsInstantiated();
            return null;
          }
        }, getAccessControlContext());
      }
      else {
        smartSingleton.afterSingletonsInstantiated();
      }
    }
  }
}

SpringBoot源码之旅——IoC容器
https://zhuanlan.zhihu.com/p/54578816

SpringApplication.run做了什么?

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


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 iscom.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应用

指定端口号启动

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应用的几种方法

调用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 获取本机ip和端口号

IP
String IP = InetAddress.getLocalHost().getHostAddress();

端口号

public class MyService {
    @Value("${server.port}")
    private int port;
}

SpringBoot 命令行参数

main 方法参数

java -jar xxx.jar value1 value2

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

System 参数

java -jar -Dkey1=value1 -Dkey2=value2 xxx.jar
通过 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
可通过 @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 可以用来定义应用级别的配置。

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

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

SpringCloud入门之常用的配置文件 application.yml和 bootstrap.yml区别
https://www.cnblogs.com/BlogNetSpace/p/8469033.html

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


found character ‘@’ that cannot start any token


SpringBoot应用配置项加密{cipher}

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


@ConfigurationProperties


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

上一篇 DataGrip使用笔记

下一篇 Phabricator使用笔记

阅读
评论
9,230
阅读预计42分钟
创建日期 2018-06-11
修改日期 2020-09-02
类别
目录
  1. Spring SPEL
    1. SpelEvaluationException: EL1007E: Property or field ‘xx’ cannot be found on null
  2. SpringCloud和SpringBoot版本不兼容导致无法启动
  3. SpringBoot自动配置
    1. @EnableAutoConfiguration的作用
    2. @EnableAutoConfiguration 自动配置原理
    3. spring.factories 文件
    4. 使用spring.factories指定自己的自动配置类
  4. @Conditional 条件Bean
    1. 匹配上下文中是否有某个注解
    2. @ConditionalOnProperty根据配置变量决定是否创建bean
    3. 自定义Condition
  5. SpringBoot加载应用上下文并启动过程
    1. SpringApplication.run做了什么?
  6. ApplicationRunner和CommandLineRunner
    1. ApplicationRunner
    2. CommandLineRunner
    3. ApplicationRunner 和 CommandLineRunner 的区别
  7. SpringBoot 数据源
    1. 不需要配置spring.datasource.driver-class-name
    2. 不连接数据库启动
    3. 排除reids自动配置
  8. 启动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
  9. 停止SpringBoot应用的几种方法
    1. 调用Actuator停止端点
    2. 暴露SpringApplication的exit静态方法
  10. 打包和部署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
  11. SpringBoot Undertow
    1. 使用undertow
    2. 配置undertow
  12. SpringBoot国际化
    1. 不同语言环境下看到不同内容如何实现的?
  13. SpringBoot 获取本机ip和端口号
  14. SpringBoot 命令行参数
    1. main 方法参数
    2. System 参数
      1. System参数必须放在jar包之前
    3. properties 参数
      1. properties参数必须放在jar包之后
  15. SpringBoot配置文件
    1. application.properties和application.yml同时存在
    2. application.yml 与 bootstrap.yml
    3. spring boot 配置中引用maven pom.xml 属性
      1. found character ‘@’ that cannot start any token
    4. SpringBoot应用配置项加密{cipher}
    5. @ConfigurationProperties
  16. 创建Spring boot项目
  17. Spring Boot父级依赖
  18. spring boot自定义属性乱码问题
  19. Spring Boot ApplicationRunner 实例

页面信息

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

评论