SpringBoot- 自动配置源码解析

本贴最后更新于 2357 天前,其中的信息可能已经时移世改

接着上一篇博客《SpringBoot-快速搭建 WEB 工程》提出的需要分析的三个方面:我们来深入的探究 SpringBoot 是如何在没有一个配置文件的情况下为我们启动好一个完整的 WEB 工程的,首先我们从 @SpringBootApplication 开始这里的分析会剖出一些次要的信息沿着主干走,所以可能会有一些略过的地方。以下源码截取自 spring-boot-1.4.0.RELEASE


@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented  @Inherited  @SpringBootConfiguration  @EnableAutoConfiguration  @ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)) public @interface SpringBootApplication {}

可以看到这是一个复合注解:其中 @Target,@Retention,@Documented,@Inherited 这四个注解不做过多的解释。
@ComponentScan 这个是 Spring 很常用的注解也不做解释。
下面来看一看 @SpringBootConfiguration:


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

这个注解,有作用的也是 @Configuration:这是标注当前类为:JavaConfig 类
现在就来看看最后的一个注解 @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)) {
        return NO_IMPORTS;
    }
    try {
        AnnotationAttributes attributes = getAttributes(metadata);
        List<String> configurations = getCandidateConfigurations(metadata,
                attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(metadata, attributes);
        configurations.removeAll(exclusions);
        configurations = sort(configurations);
        recordWithConditionEvaluationReport(configurations, exclusions);
        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<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;

}```

在进入:List configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
代码如下:


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_RESOURCE_LOCATION + "]", ex);
    }
}
public static final String FACTORIES_RESOURCE_LOCATION = 
"META-INF/spring.factories";```

在上面的代码可以看到自动配置器会跟根据传入的factoryClass.getName()到spring.factories的文件中找到相应的key,从而加载里面的类但我们打开spring-boot-autoconfigure-1.4.0.RELEASE.jar里面的spring.factories可以发现很多key,那么这里是怎么样的一个加载的流程呢这里只把代码贴出来不进和讲解,下一篇博客会对SpringBoot启动的整个流程进入深入的分析《[SpringBoot-启动流程分析](http://blog.csdn.net/doegoo/article/details/52471310)》.

public ConfigurableApplicationContext run(String... args) {

StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();
try {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
    Banner printedBanner = printBanner(environment);
    context = createApplicationContext();
    prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
    refreshContext(context);
    afterRefresh(context, applicationArguments);
    listeners.finished(context, null);
    stopWatch.stop();
    if (this.logStartupInfo) {
        new StartupInfoLogger(this.mainApplicationClass)
                .logStarted(getApplicationLog(), stopWatch);
    }
    return context;
}
catch (Throwable ex) {
    handleRunFailure(context, listeners, ex);
    throw new IllegalStateException(ex);
}

}```

这篇文章只是说明自动配置功能,所以这里只指明自动配置是的加载是发生在 refreshContext(context);这一句。
由于篇幅原因这里截取了一小部分,完整的请到 spring-boot-autoconfigure-1.4.0.RELEASE.jar 包里查看 spring.factories 文件。


# 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.PropertyPlaceholderAutoConfiguration,\
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.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.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.data.redis.RedisAutoConfiguration为例查看代码如下:

@Configuration

@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Configuration
@ConditionalOnClass(GenericObjectPool.class)
protected static class RedisConnectionConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
}
}
@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;
}

}

}```

把类简化一下基本上就可以看出这就是一个 Spring 的注解版的配置
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })这个注解的意思是:当存在 JedisConnection.class, RedisOperations.class, Jedis.class 三个类时才解析 RedisAutoConfiguration 配置类,否则不解析这一个配置类
@ConditionalOnMissingBean(name = “redisTemplate”)这个注解的意思是如果容器中不存在 name 指定的 bean 则创建 bean 注入,否则不执行
内部代码可以看出里面又定义了两个带 @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.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>```

这里是截取的spring-boot-starter-data-redis的源码中pom.xml文件中所有依赖:

org.springframework.boot spring-boot-starter org.springframework.data spring-data-redis redis.clients jedis ```

因为 maven 依赖的传递性,我们只要依赖 starter 就可以看在类路径下配置好所有的触发自动配置的所有类,实现开箱即用的功能。
这里只是大概的说明了一下 SpringBoot 的 Starter 的用法,后面在说明自制自已的 starter 时还会做深入的说明。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    945 引用 • 1460 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
Ethan
从前现在过去不在回来,红红落叶望眼尘世外! 苏州

推荐标签 标签

  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 4 关注
  • 叶归
    5 引用 • 15 回帖 • 9 关注
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    210 引用 • 2040 回帖
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    5 引用 • 16 回帖 • 3 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    20 引用 • 37 回帖 • 563 关注
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    83 引用 • 37 回帖 • 1 关注
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖 • 2 关注
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    15 引用 • 7 回帖
  • 工具

    子曰:“工欲善其事,必先利其器。”

    295 引用 • 749 回帖 • 1 关注
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 589 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 604 关注
  • Office

    Office 现已更名为 Microsoft 365. Microsoft 365 将高级 Office 应用(如 Word、Excel 和 PowerPoint)与 1 TB 的 OneDrive 云存储空间、高级安全性等结合在一起,可帮助你在任何设备上完成操作。

    5 引用 • 34 回帖
  • 996
    13 引用 • 200 回帖 • 7 关注
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    345 引用 • 724 回帖
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    132 引用 • 1115 回帖 • 118 关注
  • Word
    13 引用 • 40 回帖 • 1 关注
  • 大疆创新

    深圳市大疆创新科技有限公司(DJI-Innovations,简称 DJI),成立于 2006 年,是全球领先的无人飞行器控制系统及无人机解决方案的研发和生产商,客户遍布全球 100 多个国家。通过持续的创新,大疆致力于为无人机工业、行业用户以及专业航拍应用提供性能最强、体验最佳的革命性智能飞控产品和解决方案。

    2 引用 • 14 回帖 • 1 关注
  • Flume

    Flume 是一套分布式的、可靠的,可用于有效地收集、聚合和搬运大量日志数据的服务架构。

    9 引用 • 6 回帖 • 652 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    88 引用 • 1235 回帖 • 407 关注
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 293 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 22 关注
  • SMTP

    SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。

    4 引用 • 18 回帖 • 636 关注
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖 • 1 关注
  • 智能合约

    智能合约(Smart contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于 1994 年由 Nick Szabo 首次提出。

    1 引用 • 11 回帖
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    24477 引用 • 100107 回帖
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 678 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    20 引用 • 193 回帖