【Spring Boot学习总结】8.Spring Boot的自动配置(源码分析)

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

之前我们在讲解Spring Boot核心注解、配置的时候提到过,Spring Boot的核心注解@SpringBootApplication,会开启“自动配置”,此时Spring Boot会根据项目中配置的jar包依赖,自动做出配置(如自动配置Tomcat容器(包括端口)、dispatcherServlet等MVC组件加载信息,Web.xml文件等)。
那么,Spring Boot的自动配置是如何实现的呢?我们下面通过剖析源码了解一下。

其实Spring boot自动加载的原理一句话就可以说明:
Spring Boot在进行SpringBootApplication对象实例化时会加载META-INF/spring.factories文件,将该配置文件中的配置载入到Spring容器。那么这个“META-INF/spring.factories”在哪呢?我们去原来的样例工程中找到引入的Maven依赖中的“spring-boot-xxx.jar”
20191102100844\_1.png
在该依赖中,最后的META-INF文件夹下,就是我们的spring.factories文件:
20191102100844\_2.png
打开该文件,我们可以看到以下内容:

    # PropertySource Loaders
    org.springframework.boot.env.PropertySourceLoader=\
    org.springframework.boot.env.PropertiesPropertySourceLoader,\
    org.springframework.boot.env.YamlPropertySourceLoader

    # Run Listeners
    org.springframework.boot.SpringApplicationRunListener=\
    org.springframework.boot.context.event.EventPublishingRunListener

    # Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
    org.springframework.boot.context.ContextIdApplicationContextInitializer,\
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
    org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer

    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.ClearCachesApplicationListener,\
    org.springframework.boot.builder.ParentContextCloserApplicationListener,\
    org.springframework.boot.context.FileEncodingApplicationListener,\
    org.springframework.boot.context.config.AnsiOutputApplicationListener,\
    org.springframework.boot.context.config.ConfigFileApplicationListener,\
    org.springframework.boot.context.config.DelegatingApplicationListener,\
    org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
    org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
    org.springframework.boot.logging.LoggingApplicationListener

    # Environment Post Processors
    org.springframework.boot.env.EnvironmentPostProcessor=\
    org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
    org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor

    # Failure Analyzers
    org.springframework.boot.diagnostics.FailureAnalyzer=\
    org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer

    # FailureAnalysisReporters
    org.springframework.boot.diagnostics.FailureAnalysisReporter=\
    org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

可以看到有许多的配置内容,例如“org.springframework.boot.SpringApplicationRunListener”就是就是Spring Boot的监听器,“org.springframework.context.ApplicationContextInitializer”是Spring容器的初始化对象。总的来说,spring.factories文件提供了很多核心的配置类的类名,Spring Boot在启动的时候,就会加载这些类名,使用类加载器进行类的实例的创建。那么spring.factories文件是什么时候进行加载的呢?我们在样例工程类中使用了SpringApplication类:

    package cn.springboot.test;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    //Sprnig Boot项目的核心注解,主要目的是开启自动配置
    public class SpringbootTestSimpleApplication {
        //该main方法作为项目启动的入口
        public static void main(String[] args) {
            SpringApplication.run(SpringbootTestSimpleApplication.class, args);
        }
    }

打开SpringApplication类的源码,我们可以找到一个名为“initialize”的初始化方法,该方法会set一些初始化的实例:

    @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(Class<T> type,new Class<?>[] {});
    }

然后getSpringFactoriesInstances(Class type,new Class<?>[] {})方法如下:

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            LinkedHashSet names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
            List instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
            AnnotationAwareOrderComparator.sort(instances);
            return instances;
    }

其中使用ClassLoader类加载器进行初始化类加载的时候,设置要加载的初始化类的名称使用的是LinkedHashSet,而这些名称的来源就是“SpringFactoriesLoader.loadFactoryNames(type, classLoader)”,找到SpringFactoriesLoader的loadFactoryNames方法:

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

    private static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            try {
                Enumeration<URL> urls = classLoader.getResources(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);
            }
    }

我们可以观察到这么一段代码:

    Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);

而其中的FACTORIES_RESOURCE_LOCATION即:

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

即是我们会读取”META-INF/spring.factories”下配置的类名,将这些类名的路径加载出来,以便于类加载器ClassLoader将对应的类转化为实例对象。上面的是Spring Boot的核心配置,Spring Boot默认会开启了自动配置(因为SpringApplication类的源码中包含@EnableAutoConfiguration注解),我们在样例工程的maven依赖找到“spring-boot-autoconfigure-xxx.jar”:
20191102100844\_3.png
然后打开它,可以发现在META-INF下面也有spring.factories文件:
20191102100844\_4.png
在该spring.factories文件下,罗列了自动配置需要加载的各种类路径名:

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

    # 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,\
    org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
    org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
    org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
    org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
    org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
    org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
    org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
    org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
    org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
    org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
    org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
    org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
    org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
    org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
    org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
    org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
    org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
    org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
    org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
    org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
    org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
    org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
    org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
    org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
    org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
    org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
    org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
    org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
    org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
    org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
    org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
    org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
    org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
    org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
    org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
    org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
    org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

    # Failure analyzers
    org.springframework.boot.diagnostics.FailureAnalyzer=\
    org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
    org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer

    # Template availability providers
    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.web.JspTemplateAvailabilityProvider

可以看到里面有aop、数据库、缓存、视图模板等各种自动化配置类路径,spring boot会加载这些自动化配置类,使其实例化,而这些自动化配置类就会帮我们自动进行一系列的默认配置。

例如redis的自动配置,我们在上面的文件中可以找到自动配置类:

    org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\

在“RedisAutoConfiguration”类的源码中我们可以看到:

    package org.springframework.boot.autoconfigure.data.redis;

    import java.net.URI;
    import java.net.URISyntaxException;
    import java.net.UnknownHostException;
    import java.util.ArrayList;
    import java.util.List;

    import org.apache.commons.pool2.impl.GenericObjectPool;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPoolConfig;

    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Cluster;
    import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisClusterConfiguration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.RedisNode;
    import org.springframework.data.redis.connection.RedisSentinelConfiguration;
    import org.springframework.data.redis.connection.jedis.JedisConnection;
    import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
    import org.springframework.data.redis.core.RedisOperations;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.util.Assert;
    import org.springframework.util.StringUtils;

    @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;
            }

            private void configureConnection(JedisConnectionFactory factory) {
                if (StringUtils.hasText(this.properties.getUrl())) {
                    configureConnectionFromUrl(factory);
                }
                else {
                    factory.setHostName(this.properties.getHost());
                    factory.setPort(this.properties.getPort());
                    if (this.properties.getPassword() != null) {
                        factory.setPassword(this.properties.getPassword());
                    }
                }
            }

            private void configureConnectionFromUrl(JedisConnectionFactory factory) {
                String url = this.properties.getUrl();
                if (url.startsWith("rediss://")) {
                    factory.setUseSsl(true);
                }
                try {
                    URI uri = new URI(url);
                    factory.setHostName(uri.getHost());
                    factory.setPort(uri.getPort());
                    if (uri.getUserInfo() != null) {
                        String password = uri.getUserInfo();
                        int index = password.lastIndexOf(":");
                        if (index >= 0) {
                            password = password.substring(index + 1);
                        }
                        factory.setPassword(password);
                    }
                }
                catch (URISyntaxException ex) {
                    throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url,
                            ex);
                }
            }

            protected final RedisSentinelConfiguration getSentinelConfig() {
                if (this.sentinelConfiguration != null) {
                    return this.sentinelConfiguration;
                }
                Sentinel sentinelProperties = this.properties.getSentinel();
                if (sentinelProperties != null) {
                    RedisSentinelConfiguration config = new RedisSentinelConfiguration();
                    config.master(sentinelProperties.getMaster());
                    config.setSentinels(createSentinels(sentinelProperties));
                    return config;
                }
                return null;
            }

            /**
             * Create a {@link RedisClusterConfiguration} if necessary.
             * @return {@literal null} if no cluster settings are set.
             */
            protected final RedisClusterConfiguration getClusterConfiguration() {
                if (this.clusterConfiguration != null) {
                    return this.clusterConfiguration;
                }
                if (this.properties.getCluster() == null) {
                    return null;
                }
                Cluster clusterProperties = this.properties.getCluster();
                RedisClusterConfiguration config = new RedisClusterConfiguration(
                        clusterProperties.getNodes());

                if (clusterProperties.getMaxRedirects() != null) {
                    config.setMaxRedirects(clusterProperties.getMaxRedirects());
                }
                return config;
            }

            private List<RedisNode> createSentinels(Sentinel sentinel) {
                List<RedisNode> nodes = new ArrayList<RedisNode>();
                for (String node : StringUtils
                        .commaDelimitedListToStringArray(sentinel.getNodes())) {
                    try {
                        String[] parts = StringUtils.split(node, ":");
                        Assert.state(parts.length == 2, "Must be defined as 'host:port'");
                        nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
                    }
                    catch (RuntimeException ex) {
                        throw new IllegalStateException(
                                "Invalid redis sentinel " + "property '" + node + "'", ex);
                    }
                }
                return nodes;
            }

            private JedisConnectionFactory createJedisConnectionFactory() {
                JedisPoolConfig poolConfig = this.properties.getPool() != null
                        ? jedisPoolConfig() : new JedisPoolConfig();

                if (getSentinelConfig() != null) {
                    return new JedisConnectionFactory(getSentinelConfig(), poolConfig);
                }
                if (getClusterConfiguration() != null) {
                    return new JedisConnectionFactory(getClusterConfiguration(), poolConfig);
                }
                return new JedisConnectionFactory(poolConfig);
            }

            private JedisPoolConfig jedisPoolConfig() {
                JedisPoolConfig config = new JedisPoolConfig();
                RedisProperties.Pool props = this.properties.getPool();
                config.setMaxTotal(props.getMaxActive());
                config.setMaxIdle(props.getMaxIdle());
                config.setMinIdle(props.getMinIdle());
                config.setMaxWaitMillis(props.getMaxWait());
                return config;
            }

        }

        /**
         * Standard Redis configuration.
         */
        @Configuration
        protected static class RedisConfiguration {

            @Bean
            @ConditionalOnMissingBean(name = "redisTemplate")
            public RedisTemplate<Object, Object> redisTemplate(
                    RedisConnectionFactory redisConnectionFactory)
                            throws UnknownHostException {
                RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
                template.setConnectionFactory(redisConnectionFactory);
                return template;
            }

            @Bean
            @ConditionalOnMissingBean(StringRedisTemplate.class)
            public StringRedisTemplate stringRedisTemplate(
                    RedisConnectionFactory redisConnectionFactory)
                            throws UnknownHostException {
                StringRedisTemplate template = new StringRedisTemplate();
                template.setConnectionFactory(redisConnectionFactory);
                return template;
            }

        }

    }

实际上RedisAutoConfiguration就是帮我们自动做了一些列的配置,我们在开发中只需要修改少量配置指向自己的redis服务地址即可。
其中:

    @ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })

表示在容器中如果有JedisConnection.class、RedisOperations.class、Jedis.class这三个指定的Bean时,才会加载RedisAutoConfiguration,否则不进行自动加载。在RedisProperties类中,加载的是redis的各种默认配置信息(主机地址、端口号、密码等信息):

    package org.springframework.boot.autoconfigure.data.redis;

    import java.util.List;
    import org.springframework.boot.context.properties.ConfigurationProperties;

    @ConfigurationProperties(prefix = "spring.redis")
    public class RedisProperties {

        /**
         * Database index used by the connection factory.
         */
        private int database = 0;

        /**
         * Redis url, which will overrule host, port and password if set.
         */
        private String url;

        /**
         * Redis server host.
         */
        private String host = "localhost";

        /**
         * Login password of the redis server.
         */
        private String password;

        /**
         * Redis server port.
         */
        private int port = 6379;

        /**
         * Enable SSL.
         */
        private boolean ssl;

        /**
         * Connection timeout in milliseconds.
         */
        private int timeout;

        private Pool pool;

        private Sentinel sentinel;

        private Cluster cluster;

        public int getDatabase() {
            return this.database;
        }

        public void setDatabase(int database) {
            this.database = database;
        }

        public String getUrl() {
            return this.url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public String getHost() {
            return this.host;
        }

        public void setHost(String host) {
            this.host = host;
        }

        public String getPassword() {
            return this.password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public int getPort() {
            return this.port;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public boolean isSsl() {
            return this.ssl;
        }

        public void setSsl(boolean ssl) {
            this.ssl = ssl;
        }

        public void setTimeout(int timeout) {
            this.timeout = timeout;
        }

        public int getTimeout() {
            return this.timeout;
        }

        public Sentinel getSentinel() {
            return this.sentinel;
        }

        public void setSentinel(Sentinel sentinel) {
            this.sentinel = sentinel;
        }

        public Pool getPool() {
            return this.pool;
        }

        public void setPool(Pool pool) {
            this.pool = pool;
        }

        public Cluster getCluster() {
            return this.cluster;
        }

        public void setCluster(Cluster cluster) {
            this.cluster = cluster;
        }

        /**
         * Pool properties.
         */
        public static class Pool {

            /**
             * Max number of "idle" connections in the pool. Use a negative value to indicate
             * an unlimited number of idle connections.
             */
            private int maxIdle = 8;

            /**
             * Target for the minimum number of idle connections to maintain in the pool. This
             * setting only has an effect if it is positive.
             */
            private int minIdle = 0;

            /**
             * Max number of connections that can be allocated by the pool at a given time.
             * Use a negative value for no limit.
             */
            private int maxActive = 8;

            /**
             * Maximum amount of time (in milliseconds) a connection allocation should block
             * before throwing an exception when the pool is exhausted. Use a negative value
             * to block indefinitely.
             */
            private int maxWait = -1;

            public int getMaxIdle() {
                return this.maxIdle;
            }

            public void setMaxIdle(int maxIdle) {
                this.maxIdle = maxIdle;
            }

            public int getMinIdle() {
                return this.minIdle;
            }

            public void setMinIdle(int minIdle) {
                this.minIdle = minIdle;
            }

            public int getMaxActive() {
                return this.maxActive;
            }

            public void setMaxActive(int maxActive) {
                this.maxActive = maxActive;
            }

            public int getMaxWait() {
                return this.maxWait;
            }

            public void setMaxWait(int maxWait) {
                this.maxWait = maxWait;
            }

        }

        /**
         * Cluster properties.
         */
        public static class Cluster {

            /**
             * Comma-separated list of "host:port" pairs to bootstrap from. This represents an
             * "initial" list of cluster nodes and is required to have at least one entry.
             */
            private List<String> nodes;

            /**
             * Maximum number of redirects to follow when executing commands across the
             * cluster.
             */
            private Integer maxRedirects;

            public List<String> getNodes() {
                return this.nodes;
            }

            public void setNodes(List<String> nodes) {
                this.nodes = nodes;
            }

            public Integer getMaxRedirects() {
                return this.maxRedirects;
            }

            public void setMaxRedirects(Integer maxRedirects) {
                this.maxRedirects = maxRedirects;
            }

        }

        /**
         * Redis sentinel properties.
         */
        public static class Sentinel {

            /**
             * Name of Redis server.
             */
            private String master;

            /**
             * Comma-separated list of host:port pairs.
             */
            private String nodes;

            public String getMaster() {
                return this.master;
            }

            public void setMaster(String master) {
                this.master = master;
            }

            public String getNodes() {
                return this.nodes;
            }

            public void setNodes(String nodes) {
                this.nodes = nodes;
            }

        }

    }

如果我们在application.properties或application.yml中设置了“spring.redis.*”的相关配置,那么我们自定义的设置就会覆盖原来的自动配置。
“spring.redis.*”在application.properties的相关配置如下:

    # REDIS (RedisProperties)
    spring.redis.cluster.max-redirects= # Maximum number of redirects to follow when executing commands across the cluster.
    spring.redis.cluster.nodes= # Comma-separated list of "host:port" pairs to bootstrap from.
    spring.redis.database=0 # Database index used by the connection factory.
    spring.redis.url= # Connection URL, will override host, port and password (user will be ignored), e.g. redis://user:password@example.com:6379
    spring.redis.host=localhost # Redis server host.
    spring.redis.password= # Login password of the redis server.
    spring.redis.ssl=false # Enable SSL support.
    spring.redis.pool.max-active=8 # Max number of connections that can be allocated by the pool at a given time. Use a negative value for no limit.
    spring.redis.pool.max-idle=8 # Max number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections.
    spring.redis.pool.max-wait=-1 # Maximum amount of time (in milliseconds) a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely.
    spring.redis.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive.
    spring.redis.port=6379 # Redis server port.
    spring.redis.sentinel.master= # Name of Redis server.
    spring.redis.sentinel.nodes= # Comma-separated list of host:port pairs.
    spring.redis.timeout=0 # Connection timeout in milliseconds.

这里我们就彻底能了解自动配置的原理,以及我们的配置是如何被加载的了。

补充:
刚刚在Redis 类源码中我们提到@ConditionalOnClass这个注解,根据容器中是否有指定Bean的条件来进行自身加载的判定,这一类型的注解被称为“条件注解”。
常用的“条件注解”如下:

20191102100844\_5.png

20191102100844\_6.png

参考:
《JavaEE 开发的颠覆者 Spring Boot实战》 汪云飞编著
传智播客《Spring Boot实战与原理分析》视频课程
(https://pan.baidu.com/s/1o9M2bGI 密码:jxg8)

转载请注明出处:**https://blog.csdn.net/acmman/article/details/81975407**


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 【Spring Boot学习总结】8.Spring Boot的自动配置(源码分析)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏