Springboot启动原理之@SpringBootApplication

栏目: Java · 发布时间: 5年前

内容简介:从代码上来看,显然注解@SpringBootApplication和SpringApplication类和他的run方法为核心。那么本篇文章我们先来分析一下@SpringBootApplication其中重要的三个注解,分别为@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。下面我们来分别看一下。

大家好我是初晨,之前写了很多关于SpringBoot的文章,相信大家已经感受到了SpringBoot相对于传统Spring带来的便捷,那么本篇文章我们就来分析一下SpringBoot带来的便捷到底便捷在哪

不知道大家有没有注意到,当我们创建一个springboot项目时,都会用到如下的启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}复制代码

从代码上来看,显然注解@SpringBootApplication和SpringApplication类和他的run方法为核心。

那么本篇文章我们先来分析一下@SpringBootApplication

@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。下面我们来分别看一下。

一:@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}复制代码

我们发现他是应用了@Configuration。

@Configuration这个注解用于以JavaConfig的方式来定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。也是SpringBoot社区推荐使用的配置形式。

@Configuration与传统xml配置文件的区别:

  1. 文件结构

    传统xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
        xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
            http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
            http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
            http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false">
    
    
    </beans>复制代码

    采用@Configuration:

    @Configuration
    public class TestConfig {
    }复制代码
  2. bean的定义

    传统xml:

    <bean id="testService" class="TestServiceImpl">
    </bean>复制代码

    采用@Configuration:

    @Configuration
    public class TestConfig {
        @Bean
        public TestService testService(){
             return new TestServiceImpl();
        }  
    }复制代码

所以任何一个 java 类上使用了@Configuration,都代表他是一个配置类。

任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

二:@ComponentScan

这个注解在spring中很重要,如果你理解了@ComponentScan,你就理解了spring。

大家都知道spring是一个依赖注入的框架,所有的内容都是围绕bean定义及其依赖关系。

但是spring并不知道你定义了哪些个bean,除非你告诉spring可以从哪里找到你定义的那些bean。

而@ComponentScan的作用就是告诉Spring可以从哪里找到定义的bean

通过basePackages属性可以控制@ComponentScan自动扫描的范围,如果没有指定@ComponentScan的扫描范围,那么默认的扫描范围是从声明@ComponentScan所在类的package进行扫描。

所以我们可以发现,创建的springboot项目目录结构一般是这样的

Springboot启动原理之@SpringBootApplication

启动类一直在项目的根目录下,这样才能在不配置扫描范围的情况下扫描到所有定义的bean

三:@EnableAutoConfiguration

@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 {};
}复制代码

这里重要的注解有两个,分别是@AutoConfigurationPackage和@Import

@AutoConfigurationPackage

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}复制代码

这里发现他使用@Import注解引入了一个类Registrar

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
    }

    public Set determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
    }
}复制代码

那么这个类是用来干嘛的呢?我们断点跟一下,看一下registerBeanDefinitions方法是做什么的。

Springboot启动原理之@SpringBootApplication

这里发现 new PackageImport(metadata).getPackageName() 返回的返回了当前主程序类的同级以及子级的包组件。

这也证明了@ComponentScan默认扫描其所在类的package。

@Import({AutoConfigurationImportSelector.class})

接下来我们具体看一看AutoConfigurationImportSelector这个类

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}复制代码

这个方法调用了getAutoConfigurationEntry方法

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        configurations = this.removeDuplicates(configurations);
        Set exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.filter(configurations, autoConfigurationMetadata);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}复制代码

这里我们重点看一下getCandidateConfigurations方法,先断点看一下方法的返回值

Springboot启动原理之@SpringBootApplication

该方法返回的是需要实例化的类信息列表,有了他spring就可以通过类加载器将需要实例化的类加载到jvm中。

现在我们看一下该方法的代码,发现他是借用了SpringFactoriesLoader类的方法

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}复制代码

再来看一下loadFactoryNames方法

public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            //....省略

            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
        }
    }
}复制代码

发现他读取了一个名为spring.factories的文件

Springboot启动原理之@SpringBootApplication

比如我们找一下 redis 的配置

Springboot启动原理之@SpringBootApplication

点击去看一下

@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    ......
}复制代码

@ConditionalOnClass({RedisOperations.class})    表示必须存在RedisOperations这个类,否则不解析该注解修饰的配置类。

再看看RedisProperties这个类

public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    private String password;
    private int port = 6379;
    private boolean ssl;
    private Duration timeout;
    private RedisProperties.Sentinel sentinel;
    private RedisProperties.Cluster cluster;
    private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
    private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
    ......省略
}复制代码

是不是眼熟了!这个就是我们在使用redis时写在application.properties里面,需要配置redis的信息

对应的配置应为:

spring.redis.host=127.0.0.1
spring.redis.port=6379   
spring.redis.timeout=0
spring.redis.password=复制代码

同时也可以看到redis默认为我们配置了host和port属性。所以在配置redis时,如果端口号围为默认的6379,我们也可以不写的原因。

所以@EnableAutoConfiguration的大致原理就是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。

四:结尾

@SpringBootApplication我们分析完了,下篇文章我们分析SpringApplication这个类

对于springBoot还不了解的朋友可以看我的SpringBoot系列教程


以上所述就是小编给大家介绍的《Springboot启动原理之@SpringBootApplication》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python 3面向对象编程

Python 3面向对象编程

[加]Dusty Phillips(达斯帝•菲利普斯) / 肖鹏、常贺、石琳 / 电子工业出版社 / 2015-6 / 79.00元

Python 是一种面向对象的解释型语言,面向对象是其非常重要的特性。《Python 3面向对象编程》通过Python 的数据结构、语法、设计模式,从简单到复杂,从初级到高级,一步步通过例子来展示了Python 中面向对象的概念和原则。 《Python 3面向对象编程》不是Python 的入门书籍,适合具有Python 基础经验的开发人员阅读。如果你拥有其他面向对象语言的经验,你会更容易理解......一起来看看 《Python 3面向对象编程》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具