104. Spring Boot 启动流程分析第三篇【从零开始学Spring Boot】

问题的提出:Spring Boot不像spring需要定义xml文件文件,那么spring boot是如何在没有配置文件的情况下为我们启动一个完整的WEB工程的呢。我们先@SpringBootApplication开始分析下这个启动流程,源代码取自:1.4.1.RELEASE。

       我们先看下@SpringBootApplication的代码:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))

public @interface SpringBootApplication {}

 

 

       可以看到这里是一个复合注解,如:@Target,@Document,@Retention是我们常见的注解,这里重点说下@SpringBootConfiguration注解:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Configuration

public @interface SpringBootConfiguration {}

 

       这个注解重要的就是@Configuration:这是标注当前类为Java Config类。

在@SpringBootApplication中另外一个重要的注解就是@EnableAutoConfiguration:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import(EnableAutoConfigurationImportSelector.class)

public @interface EnableAutoConfiguration {}

 

这个注解上@Import(EnableAutoConfigurationImportSelector.class)代表引入其它的Spring的JavaConfig接着进入EnableAutoConfigurationImportSelector.class。

关注一下以下的方法:

@Override

    public String[] selectImports(AnnotationMetadata metadata) {

        if (!isEnabled(metadata)) {

            returnNO_IMPORTS;

        }

        try {

            AnnotationAttributes attributes = getAttributes(metadata);

            List configurations = getCandidateConfigurations(metadata,

                    attributes);

            configurations = removeDuplicates(configurations);

            Set exclusions = getExclusions(metadataattributes);

            configurations.removeAll(exclusions);

            configurations = sort(configurations);

            recordWithConditionEvaluationReport(configurationsexclusions);

            return configurations.toArray(new String[configurations.size()]);

        }

        catch (IOException ex) {

            throw new IllegalStateException(ex);

        }

    }

进入:List configurations = getCandidateConfigurations(metadata,attributes);

   protected List getCandidateConfigurations(AnnotationMetadata metadata,

            AnnotationAttributes attributes) {

        List 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;

    }

在进入:List configurations = SpringFactoriesLoader.loadFactoryNames(

getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());

代码如下:

    public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) {

       String factoryClassName = factoryClass.getName();

       try {

           Enumeration urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :

                  ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

           List result = new ArrayList();

           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);

       }

    }

 

在上面的代码可以看到自动配置器会跟根据传入的factoryClass.getName()到spring.factories的文件中找到相应的key,从而加载里面的类但我们打开spring-boot-autoconfigure-1.4.1.RELEASE.jar里面的spring.factories可以发现很多key。查看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 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.MessageSourceAutoConfiguration,\

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

 

 

这里以org.springframework.boot.autoconfigure.data.Redis.RedisAutoConfiguration为例查看代码如下:

@Configuration

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

@EnableConfigurationProperties

public class RedisAutoConfiguration {

 

    @Bean(name = "org.springframework.autoconfigure.redis.RedisProperties")

    @ConditionalOnMissingBean

    public RedisProperties redisProperties() {

        return new RedisProperties();

    }

 

   

 

    /**

     * Redis connection configuration.

     */

    @Configuration

    @ConditionalOnMissingClass("org.apache.commons.pool2.impl.GenericObjectPool")

    protected static class RedisConnectionConfiguration

            extends AbstractRedisConfiguration {

 

        @Bean

        @ConditionalOnMissingBean(RedisConnectionFactory.class)

        public JedisConnectionFactory redisConnectionFactory()

                throws UnknownHostException {

            return applyProperties(new JedisConnectionFactory(getSentinelConfig()));

        }

 

    }

    //省略部分代码…

    /**

     * Standard Redis configuration.

     */

    @Configuration

    protected static class RedisConfiguration {

 

        @Bean

        @ConditionalOnMissingBean(name = "redisTemplate")

        public RedisTemplate redisTemplate(

                RedisConnectionFactory redisConnectionFactory)

                        throws UnknownHostException {

            RedisTemplate template = new RedisTemplate();

            template.setConnectionFactory(redisConnectionFactory);

            return template;

        }

 

    }

}

把类简化一下基本上就可以看出这就是一个Spring的注解版的配置

(1)@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })这个注解的意思是:当存在JedisConnection.class, RedisOperations.class, Jedis.class三个类时才解析RedisAutoConfiguration配置类,否则不解析这一个配置类

(2)@ConditionalOnMissingBean(name = “redisTemplate”)这个注解的意思是如果容器中不存在name指定的bean则创建bean注入,否则不执行

(3)内部代码可以看出里面又定义了两个带@Configuration注解的配置类,这两个配置类会向SpringIOC容器注入可能3个bean:

首先当类路径下存在(GenericObjectPool.class)时则注入JedisConnectionFactory 的实例如果Spring容器中不存在name = “redisTemplate”的实体,则创建RedisTemplate和StringRedisTemplate实例注入容器,这样在Spring的项目中,就可以用在任意的Spring管理的bean中注册用RedisTemplate和StringRedisTemplate的实例来对redis进入操作了。

 

通过以上分析的过程我们可以发现只要一个基于SpringBoot项目的类路径下存在JedisConnection.class, RedisOperations.class, Jedis.class就可以触发自动化配置,意思说我们只要在maven的项目中依赖了spring-data-redis-1.7.2.RELEASE.jar和C:jedis-2.8.2.jar就可以触发自动配置,但这样不是每集成一个功能都要去分析里其自动化配置类,那就代不到开箱即用的效果了。所以Spring-boot为我提供了统一的starter可以直接配置好相关触发自动配置的所有的类的依赖集如redis的start如下:

<dependency>

    <groupId>org.springframework.bootgroupId>

    <artifactId>spring-boot-starter-data-redisartifactId>

 

 

因为maven依赖的传递性,我们只要依赖starter就可以看在类路径下配置好所有的触发自动配置的所有类,实现开箱即用的功能。

你可能感兴趣的:(104. Spring Boot 启动流程分析第三篇【从零开始学Spring Boot】)