SpringBoot自动加载的原理

  • 首先我们在程序入口的位置点进SpringApplication这个类的run方法
@SpringBootApplication
public class Application {

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

run方法点进去

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

继续

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

可以看到run方法只是创建了SpringApplication对象,并执行run方法。继续跟进

public SpringApplication(Object... sources) {
        initialize(sources);
    }

initialize具体代码:

@SuppressWarnings({ "unchecked", "rawtypes" })
    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        this.webEnvironment = deduceWebEnvironment();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

getSpringFactoriesInstances点进去:

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

继续:

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<String>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

loadFactoryNames点进去:

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        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 factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

可以看到常量FACTORIES_RESOURCE_LOCATION 的定义:

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

找到org.springframework.boot.autoconfigurespring.factories文件:

以Redis为例,我们可以看到spring.factories文件中有Redis的配置

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {

    /**
     * Redis connection configuration.
     */
    @Configuration
    @ConditionalOnClass(GenericObjectPool.class)
    protected static class RedisConnectionConfiguration {

        private final RedisProperties properties;

        private final RedisSentinelConfiguration sentinelConfiguration;

        private final RedisClusterConfiguration clusterConfiguration;

        public RedisConnectionConfiguration(RedisProperties properties,
                ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
                ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
            this.properties = properties;
            this.sentinelConfiguration = sentinelConfiguration.getIfAvailable();
            this.clusterConfiguration = clusterConfiguration.getIfAvailable();
        }

        @Bean
        @ConditionalOnMissingBean(RedisConnectionFactory.class)
        public JedisConnectionFactory redisConnectionFactory()
                throws UnknownHostException {
            return applyProperties(createJedisConnectionFactory());
        }

        protected final JedisConnectionFactory applyProperties(
                JedisConnectionFactory factory) {
            configureConnection(factory);
            if (this.properties.isSsl()) {
                factory.setUseSsl(true);
            }
            factory.setDatabase(this.properties.getDatabase());
            if (this.properties.getTimeout() > 0) {
                factory.setTimeout(this.properties.getTimeout());
            }
            return factory;
        }
    ...

说明:

  1. @ConditionalOnClass激活一个配置,当类路径中存在这个类时才会配置该类
  2. @EnableConfigurationProperties 自动映射一个POJO从Spring Boot配置文件(默认是application.properties文件)的属性集。
  3. @ConditionalOnMissingBean 启用一个Bean定义,但必须是这个Bean之前未定义过才有效,还可以使用@ AutoConfigureBefore@AutoConfigureAfter注解来定义这些配置类的载入顺序

具体几个@Conditon*注解的含义

  • @ConditionalOnBean
    仅仅在当前上下文中存在某个对象时,才会实例化一个Bean

  • @ConditionalOnClass
    某个class位于类路径上,才会实例化一个Bean),该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类

  • @ConditionalOnExpression
    当表达式为true的时候,才会实例化一个Bean

  • @ConditionalOnMissingBean
    仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean,该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean,可以给该注解传入参数例如@ConditionOnMissingBean(name = "example"),这个表示如果name为“example”的bean存在,这该注解修饰的代码块不执行

  • @ConditionalOnMissingClass
    某个class类路径上不存在的时候,才会实例化一个Bean

  • @ConditionalOnNotWebApplication
    不是web应用时,才会执行

如若想关闭自动配置时我们就是把spring.factories文件不需要自动配置的类过滤掉
比如:

@SpringBootApplication(exclude = {RedisAutoConfiguration.class})
public class Application {

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

  • 总结

SpringBoot 的 自动配置得益于 SpringFramework 强大的支撑,框架早已有很多工具和注解可以自动装配 Bean 。SpringBoot 通过 一个封装,将市面上通用的组件直接写好了配置类。当我们程序去依赖了这些组件的 jar 包后,启动 SpringBoot应用,于是自动加载开始了。

我们也可以定义自己的自动装配组件,依赖之后,Spring直接可以加载我们定义的 starter 。笔者将在后续文章中进行编码和解读

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容