我们以一个最简单的例子来完成这个需求:定义一个注解EnableContentService,使用了这个注解的程序会自动注入ContentService这个bean。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(ContentConfiguration.class)
public @interface EnableContentService {} public interface ContentService {
void doSomething();
} public class SimpleContentService implements ContentService {
@Override
public void doSomething() {
System.out.println("do some simple things");
}
}

然后在应用程序的入口加上@EnableContentService注解。

这样的话,ContentService就被注入进来了。 SpringBoot也就是用这个完成的。只不过它用了更加高级点的ImportSelector。

ImportSelector的使用

用了ImportSelector之后,我们可以在Annotation上添加一些属性,然后根据属性的不同加载不同的bean。

我们在@EnableContentService注解添加属性policy,同时Import一个Selector。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(ContentImportSelector.class)
public @interface EnableContentService {
String policy() default "simple";
}

这个ContentImportSelector根据EnableContentService注解里的policy加载不同的bean。

public class ContentImportSelector implements ImportSelector {

    @Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<?> annotationType = EnableContentService.class;
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(
annotationType.getName(), false));
String policy = attributes.getString("policy");
if ("core".equals(policy)) {
return new String[] { CoreContentConfiguration.class.getName() };
} else {
return new String[] { SimpleContentConfiguration.class.getName() };
}
} }

CoreContentService和CoreContentConfiguration如下:

public class CoreContentService implements ContentService {
@Override
public void doSomething() {
System.out.println("do some import things");
}
} public class CoreContentConfiguration {
@Bean
public ContentService contentService() {
return new CoreContentService();
}
}

这样的话,如果在@EnableContentService注解的policy中使用core的话,应用程序会自动加载CoreContentService,否则会加载SimpleContentService。

ImportSelector在SpringBoot中的使用

SpringBoot里的ImportSelector是通过SpringBoot提供的@EnableAutoConfiguration这个注解里完成的。

这个@EnableAutoConfiguration注解可以显式地调用,否则它会在@SpringBootApplication注解中隐式地被调用。

@EnableAutoConfiguration注解中使用了EnableAutoConfigurationImportSelector作为ImportSelector。下面这段代码就是EnableAutoConfigurationImportSelector中进行选择的具体代码:

@Override
public String[] selectImports(AnnotationMetadata metadata) {
try {
AnnotationAttributes attributes = getAttributes(metadata);
List<String> configurations = getCandidateConfigurations(metadata,
attributes);
configurations = removeDuplicates(configurations); // 删除重复的配置
Set<String> exclusions = getExclusions(metadata, attributes); // 去掉需要exclude的配置
configurations.removeAll(exclusions);
configurations = sort(configurations); // 排序
recordWithConditionEvaluationReport(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}

其中getCandidateConfigurations方法将获取配置类:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
return SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
}

SpringFactoriesLoader.loadFactoryNames方法会根据FACTORIES_RESOURCE_LOCATION这个静态变量从所有的jar包中读取META-INF/spring.factories文件信息:

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); // 只会过滤出key为factoryClassNames的值
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);
}
}

getCandidateConfigurations方法中的getSpringFactoriesLoaderFactoryClass方法返回的是EnableAutoConfiguration.class,所以会过滤出key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值。

下面这段配置代码就是autoconfigure这个jar包里的spring.factories文件的一部分内容(有个key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,所以会得到这些AutoConfiguration):

# Initializers
org.springframework.context.ApplicationContextInitializer=\
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,\

当然了,这些AutoConfiguration不是所有都会加载的,会根据AutoConfiguration上的@ConditionalOnClass等条件判断是否加载。

上面这个例子说的读取properties文件的时候只会过滤出key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值。

SpringBoot内部还有一些其他的key用于过滤得到需要加载的类:

  • org.springframework.test.context.TestExecutionListener

  • org.springframework.beans.BeanInfoFactory

  • org.springframework.context.ApplicationContextInitializer

  • org.springframework.context.ApplicationListener

  • org.springframework.boot.SpringApplicationRunListener

  • org.springframework.boot.env.EnvironmentPostProcessor

  • org.springframework.boot.env.PropertySourceLoader

SpringBoot自动化配置的注解开关原理的更多相关文章

  1. SpringBoot自动化配置之一:SpringBoot内部的一些自动化配置原理

    springboot用来简化Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发. 比如不使用springboot而使用SpringMVC作为web框架进行开发 ...

  2. SpringBoot自动化配置之二:自动配置(AutoConfigure)原理、EnableAutoConfiguration、condition

    自动配置绝对算得上是Spring Boot的最大亮点,完美的展示了CoC约定优于配置: Spring Boot能自动配置Spring各种子项目(Spring MVC, Spring Security, ...

  3. 让SpringBoot自动化配置不再神秘

    本文若有任何纰漏.错误,还请不吝指出! 注:本文提到的Spring容器或者Bean容器,或者Spring Bean容器,都是指同一个事情,那就是代指BeanFactory.关于BeanFactory, ...

  4. SpringBoot自动化配置之四:@Conditional注解详解

    前言 之前在分析spring boot 源码时导出可见@ConditionalOnBean 之类的注解,那么它到底是如何使用的以及其工作流程如何,我们这里就围绕以下几点来分析: @Conditiona ...

  5. SpringBoot自动化配置之四:SpringBoot 之Starter(自动配置)、Command-line runners

    Spring Boot Starter是在SpringBoot组件中被提出来的一种概念,stackoverflow上面已经有人概括了这个starter是什么东西,想看完整的回答戳这里 Starter ...

  6. SpringBoot自动化配置之三:深入SpringBoot:自定义EnableAutoConfiguration

    前言 上面几篇文章介绍了SpringFramework的一些原理,这里开始介绍一下SpringBoot,并通过自定义一些功能来介绍SpringBoot的原理.SpringBoot在SpringFram ...

  7. springboot笔记-1.自动化配置的关键

    最近发现看过的东西容易忘,但是写一遍之后印象倒是会深刻的多. 总所周知springboot极大的简化了java开发繁琐性,而其最大的优势应该就是自动化配置了.比如要使用redis,我们直接引入相关的包 ...

  8. springBoot 自动配置原理

    在之前文章中说过,springBoot会根据jar包去添加许多的自动配置,本文就来说说为什么会自动配置,自动配置的原理时什么? springBoot在运行SpringApplication对象实例化时 ...

  9. SpringBoot自动配置原理学习

    介绍 构建Springboot项目时我们会创建一个启动类 @SpringBootApplication public class DemoApplication { public static voi ...

随机推荐

  1. LeetCode137:Single Number II

    题目: Given an array of integers, every element appears three times except for one. Find that single o ...

  2. c++ 日志输出库 spdlog 简介(4)- 多线程txt输出日志

    在上一节的代码中加入了向文本文件中写入日志的代码: UINT CMFCApplication1Dlg::Thread1(LPVOID pParam) { try{ size_t q_size = ; ...

  3. Android------------fragment数据传递

    一.activity向fragment的数值之间的传递 关键点:fragment.setArguments(bundle);---->activity发出的信息      Bundle bund ...

  4. Java基础学习篇---------static

    一.static的使用 1.使用static定义的属性往往通过类名直接调用,它的属性(方法)不属于某一个的对象的.所以对象没有创建之前就可以对static的属性的调用,方法亦如此. 2.static ...

  5. 【2019年OCP新题】OCP题库更新出现大量新题-10

    10.Which two statements are true about SQL*Loader Express Mode in an Oracle 12c database? A) It can ...

  6. (5)Oracle基础--约束

    · 约束的作用 <1> 定义规则 <2> 确保数据的完整性 · 约束 <1> 非空约束 ① 创建表时为字段添加非空约束 CREATE TABLE table_nam ...

  7. ActiveMQ使用线程池实现消息的生产与消费

    jar文件:spring3.1jar,以及 项目src路径下文件:config.properties 读取config.properties文件JAVA类: package com.lejob.lej ...

  8. 常见手机的useragent

    设备 系统 浏览器 User-Agent 华为Mate 10 Pro Android QQ浏览器 Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; BLA-AL ...

  9. POJ 2346

    #include<iostream> #include<stdio.h> using namespace std; ,,,,}; int main() { int num; c ...

  10. POJ 2242

    #include <iostream> #include <cmath> #include <iomanip> using namespace std; #defi ...