入门

作者: 鼻涕不停的小眼睛 | 来源:发表于2019-03-27 17:03 被阅读0次

Spring Boot是什么

从根本上来讲Spring Boot就是一些库的集合,是一个基于“约定优于配置”的原则,快速搭建应用的框架。本质上依然Spring,在这之上帮我们省去了很多样板化的配置,使得我们能够更专注于应用程序功能的开发。<br /><br />

springboot起步依赖

springboot要想写一个简单的HelloWorld程序只需要引入一个 spring-boot-starter-web 依赖即可,起步依赖本质上是一个Maven项目对象模型,定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。很多起步依赖的命名都暗示了他们提供的某种或某类功能。SpringBoot官方提供的起步依赖都和SpringBoot版本紧密相连,为我们传递的第三方依赖是经过足够测试后敲定下来最合适的版本。

springboot启动类

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

点进去发现run方法最后调用方法:

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
       return (new SpringApplication(sources)).run(args);
}

先看run方法的具体操作:

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);
        // 创建上下文
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
        // 上下文前置处理,这里会解析我们传入的入口类
        prepareContext(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, listeners, exceptionReporters, ex);
        throw new IllegalStateException(ex);
    }
    listeners.running(context);
    return context;
}

通过方法注释也可以看出来该run方法引发了一系列复杂的内部调用和加载过程,从而创建了一个SpringContext。
在prepareContext方法中会解析我们传入的入口类,解析其上的注解。下面来看下入口类上的注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...
}

其实他是SpringBootConfiguration、EnableAutoConfiguration、ComponentScan三个注解的组合。

@ComponentScan

@ComponentScan是Spring框架原有的注解,在spring-context组件下,用来开启自动扫描Bean并解析注解注入。<br />可以用basePackages指定扫描的包,缺省情况下默认扫描被注解类所在的包。SpringBoot项目中一般会将入口类放在顶层目录,这样默认就会扫描整个项目。

@SpringBootConfiguration

@SpringBootConfiguration是SpringBoot新增的注解,在spring-boot组件下,相当于注解@Configuration,配备了该注解的类就能够以JavaConfig的方式完成一些配置,可以不再使用XML配置。所以在入口类内也可以以JavaConfig的方式定义Bean。

@EnableAutoConfiguration

@EnableAutoConfiguration是SpringBoot新增的注解,在spring-boot-autoconfigurate组件下,它是SpringBoot开启自动配置的关键。

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

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

    String[] excludeName() default {};
}

看到@Import注解,参数是AutoConfigurationImportSelector类。
先来补充下@Import的知识,最早是为了方便引入java配置类,后来进行了扩展,目前有以下功能:

  • 参数为@Configuration注解的bean,那么就引入这个配置类。相当于用xml配置时的
  • 参数为ImportBeanDefinitionRegistrar接口或者ImportSelector接口的实现类,那么就通过接口方法来实现自定义注入
  • 参数为普通类,直接将该类创建到Spring的IoC容器

这里的AutoConfigurationImportSelector类属于第二种情况,实现了ImportSelector接口。ImportSelector接口只声明了一个方法,要求返回一个包含类全限定名的String数组,这些类将会被Spring添加到IoC容器。
AutoConfigurationImportSelector通过下面展示的方法获取配置类的路径,然后去重排序一系列处理之后返回出去交给Spring去处理。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
    // 从SpringFactoriesLoader取候选配置的类路径
    // 第一个参数getSpringFactoriesLoaderFactoryClass()得到的是EnableAutoConfiguration.class
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations,
                  "No auto configuration classes found in META-INF/spring.factories. If you "
                  + "are using a custom packaging, make sure that file is correct.");
    return configurations;
 }

调用到的SpringFactoriesLoader.loadFactoryNames()如下:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    // "org.springframework.boot.autoconfigure.EnableAutoConfiguration"
    String factoryClassName = factoryClass.getName();
    // 先调了下面的loadSpringFactories方法,得到一个Map
    // 从Map中用EnableAutoConfiguration类的全限定名取一个String列表(其实也是一些类的全限定名列表)
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

// 上面方法先从这里取了个Map
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 这里有个缓存,记一下,后面会提到
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null)
      return result;
    try {
      // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
      // 加载所有META-INF包下的spring.factories文件
      Enumeration<URL> urls = (classLoader != null ?
                               classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                               ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        UrlResource resource = new UrlResource(url);
        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
        for (Map.Entry<?, ?> entry : properties.entrySet()) {
          // 逗号分隔的String转为List
          List<String> factoryClassNames = Arrays.asList(
            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
          result.addAll((String) entry.getKey(), factoryClassNames);
        }
      }
      cache.put(classLoader, result);
      return result;
    }
    catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
                                         FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

整个流程其实就是读取项目中的META-INF/spring.factories文件,然后挑一挑交给Spring去加到上下文中。
工程中一共有2个META-INF/spring.factories文件,一个在spring-boot包中,一个在spring-boot-autoconfigure包中。

spring-boot-autoconfigure包中的部分内容:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\

...

看到一堆的xxxConfiguration,这些都注解了@Configuration。spring-boot-autoconfigure为我们引入了一大堆的java配置类作为组件的默认配置。包括常见组件的默认配置,有rabbit相关的,有redis相关的等等。
自动配置类生效的条件通常是我们引入了相关的组件,如果没有引入组件,那么就算包含在spring.factories文件中也不会被加载。而是否要注入Bean则要看当前上下文中是否已经存在相应的Bean。如果不存在,那么由默认配置来补充。如果已经存在了,自动配置会不满足注解条件,就不会被创建。
有了这两点,可以做到当我们不做任何配置的时候可以用默认配置来运用新组件,而当我们需要对配置进行调整的时候用自定义的配置来覆盖即可。
上面源码中标记了一个缓存。在读取META-INF/spring.factories文件后相关数据是会保存到SpringFactoriesLoader类的缓存中的。而这里第一次读取META-INF/spring.factories的时机并不是在自动配置这里,而在上面提到的run方法调用SpringApplication构造方法中就已经缓存了:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 这里的primarySources就是我们传入的入口类
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 推断应用类型
    this.webApplicationType = deduceWebApplicationType();
    // 设置初始化器,这里就已经读取了META-INF/spring.factories
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    // 设置监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 通过异常栈获取应用入口类  
    this.mainApplicationClass = deduceMainApplicationClass();
}

自定义配置

为了使应用能适应不同的环境,SpringBoot支持外化配置。<br />SpringBoot能从多种属性源获得属性,包括以下几处,优先级由上至下:

  1. 命令行参数
  2. JVM系统属性
  3. 操作系统环境变量
  4. 随机生成的带random.*前缀的属性(在设置其他属性时,可以引用它们,比如${random.long})
  5. 应用程序以外的application.properties或者application.yml文件
  6. 打包在应用程序内的application.properites或者application.yml文件
  7. 通过@propertySource标注的属性源
  8. 默认属性

如果不想用命令行定义参数可以用SpringApplication.setAddCommandLineProperties(false)来禁用。
SpringBoot会从以下位置加载.properties或.yml配置文件,优先级由上至下:

  1. 当前目录下的/config目录
  2. 当前目录
  3. classpath下的/config目录
  4. classpath的根目录

SpringBoot默认提供了大量的自动配置,我们可以通过启动时添加--debug参数来查看当前的配置信息。
debug可以用-Ddebug或--debug来启用,也可以在.properties或.yml文件中配置debug的值为true。
用@EnableAutoConfiguration注解的exclude参数去除指定的自动配置:

@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}

相关文章

网友评论

      本文标题:入门

      本文链接:https://www.haomeiwen.com/subject/lyftbqtx.html