入门

作者: 鼻涕不停的小眼睛 | 来源:发表于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