springboot自动配置原理

概述
Springboot的基本认识

对于Spring框架,我们接触得比较多的是Spring mvc,Spring IOC、AOP、DI。而这框架如果在使用过程中,随着项目越来越大,引入的技术越来越多,会导致配置越来越复杂。因为要手动配置Bean,配置文件、配置类等,十分繁琐。

而spring boot框架的出现就是为了解决这一个问题,能够帮助Spring框架使用者快速构建一个基于Spring框架以及Spring生态体系的应用解决方案。他是对约定优于配置这个理念下的最佳实践,因此他是一个服务于框架的框架,服务的范围是简化配置开发。

约定优于配置是指约定好一些规范(默认规则),然后如果开发者遵守这个约定的规范,就可以不用多余的配置,直接可以达到使用效果。

约定优于配置的一些例子体现:

  • maven的目录结构就是约定优于配置的一个很好的体现,默认有 resources 文件夹存放配置文件,默认打包方式为jar。
  • spring-boot-starter-web 中默认包含 spring mvc 相关依赖以及内置的 tomcat 容器,使得构建一个 web 应用更加简单。
  • 默认提供 application.properties/yml 文件。
  • 默认通过 spring.profiles.active 属性来决定运行环境时读取的配置文件。
  • EnableAutoConfiguration 默认对于依赖的 starter 进行自动装载。
自动配置原理
  1. 首先要从@SpringBootApplication注解入手,该注解是一个复合注解。
    在这里插入图片描述

  2. 看@SpringBootApplication注解上的@SpringBootConfiguration注解。发现该注解与@configuration注解一样的效果,类似@Service与@Component之间的关系,为了使得语义更清晰,可读性更强。
    在这里插入图片描述

  3. 看@EnableAutoConfiguration,这个是Springboot自动配置的核心。也是一个复合注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
  1. 看@EnableAutoConfiguration上的@Import({AutoConfigurationImportSelector.class}),该注解的作用的导入一个AutoConfigurationImportSelector自动配置选择组件。

主要看selectImports方法,该方法是实现ImportSelector接口的方法,作用是自定义一些规则,返回Spring要加载的类的全限定名的数组。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
    private static final String[] NO_IMPORTS = new String[0];
    private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
    private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    private ConfigurableListableBeanFactory beanFactory;
    private Environment environment;
    private ClassLoader beanClassLoader;
    private ResourceLoader resourceLoader;

    public AutoConfigurationImportSelector() {
    }

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
        	//首先,判断是否启动通过这个Selector来实现自动配置,isEnabled方法在下面。
            return NO_IMPORTS;
        } else {
        //这段逻辑分支就是加载指定的类。
        	//加载元数据,方法定义在下面
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            //这里解释真正加载自动配置类了。自动配置类实际上就是一个配置类。方法定义在下面
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            //转成数组返回。
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
}

protected boolean isEnabled(AnnotationMetadata metadata) {
//一个判断,首先判断当前类是不是AutoConfigurationImportSelector类,如果不是,就返回True,表示启动。
//如果不是AutoConfigurationImportSelector类,
//那么就获取环境变量spring.boot.enableautoconfiguration的值来判断是否启动,如果没有配置该环境变量,默认使用默认值true。
        return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
    }

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		//加载类路径classpath下的META-INF/spring-autoconfigure-metadata.properties元数据文件。下面讲解 该文件,现在直接跳到该文件解析更佳。
        return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
    }

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
        	//同样的判断
            return EMPTY_ENTRY;
        } else {
        	//根据上面加载的元数据,生成条件属性。
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //真正返回要加载的自动配置类的地方。方法定义在下面
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //删除重复值,可能多个spring.factories文件会定义同样的自动配置类,在这里去重。
            configurations = this.removeDuplicates(configurations);
            //排除一些指定排除的类。会根据注解的excluded属性配置。
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            //排除
            configurations.removeAll(exclusions);
            //过滤一些类,使用那些条件
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            //返回
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		//返回要加载的自动配置类,这里使用的是一个SPI扩展机制实现。方法定义在下面
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		//这里是真正加载,方法定义在下面
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}


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

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			//加载FACTORIES_RESOURCE_LOCATION代表的路径的文件,生成URL集合
			//FACTORIES_RESOURCE_LOCATION 的值为"META-INF/spring.factories",
			//代表会加载类路径下的META-INF/spring.factories文件
			//该文件的定义语法在下面
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				//遍历
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				//加载文件形成k v 集合
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					//遍历kv集合,生成要加载的类的集合
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			//返回
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

META-INF/spring-autoconfigure-metadata.properties文件的说明

org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.neo4j.Neo4jBookmarkManagementConfiguration.Configuration=

上面是spring boot Autoconfiguration模块的自动配置元数据的部分配置,主要是用于配置一些自动配置类加载的一些条件,只有符合该条件才加载该自动配置类,以防止加载无用的没必要的自动配置类。

语法:
自动配置类全路径.条件类型(注解)= 条件的值
比如

org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration.ConditionalOnClass=org.influxdb.InfluxDB

#org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration 这个是要加载的自动配置类。
#ConditionalOnClass 这个是加载条件,表示某个类在类路径存在时才加载该自动配置类。
#org.influxdb.InfluxDB 条件的值
#这个的意思是,只有我的项目类路径中存在org.influxdb.InfluxDB类,才加载InfluxDbAutoConfiguration 配置类。

还有其他很多条件类型,比如:AutoConfigureAfter、AutoConfigureBefore、ConditionalOnSingleCandidate等。如果等于号右边没有值,代表该自动配置类不设置条件。

其实这些条件类型就是注解,那为什么不直接在配置类上加这些注解呢,因为这些注解是spring boot的注解,而我们要自动配置的工程不一定是spring boot工程,也有可能是spring工程

META-INF/spring.factories文件语法:
该文件是配置要被springboot加载的配置类:

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

# 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.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# 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,\

上面是Spring boot autoconfig模块的spring.factories的部分配置。
语法是:
注解全限定名=类全限定名1,类全限定名2…表示要使用该类来加载值配置类,比如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

#表示EnableAutoConfiguration加载SpringApplicationAdminJmxAutoConfiguration和AopAutoConfiguration配置。

总结:AutoConfigurationImportSelector就是选择类路径下的META-INF/spring.factories文件配置的配置类,再配合类路径下的META-INF/spring-autoconfigure-metadata.properties元数据文件来实现条件加载自动配置类。

  1. 看@EnableAutoConfiguration上的@AutoConfigurationPackage注解。
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

在这里,如果你对@Import注解和ImportSelector和ImportBeanDefinitionRegistrar不懂的,要去补下功课再回来,不然下面就看不懂了。
该注解的作用是加载一个注册器Registrar.class。

@Order(-2147483648)
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

		//ImportSelector是返回要加载的bean数组,而ImportBeanDefinitionRegistrar是直接就注册。
		//这个Registrar 的作用是根据注解的属性basePackages等属性用于路径扫描注册Bean。
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }
  1. 看@SpringBootApplication注解上的@ComponentScan
    这个注解设置了两个属性,一个是excludeFilters,值为TypeExcludeFilter,表示如果TypeExcludeFilter的match方法返回true就排除该Bean,返回false就不排除。

TypeExcludeFilter:

//下面是TypeExcludeFilter的match方法
//这个类TypeExcludeFilter相当于一个总类,委托类,他的逻辑就是获取容器中所有它的子类(被委托类),进行排除,我们编写自定义的TypeExcludeFilter加入容器中就是给这里使用的。
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        if (this.beanFactory instanceof ListableBeanFactory && this.getClass() == TypeExcludeFilter.class) {
        //只有beanFactory 是ListableBeanFactory 的子类并且这个类是TypeExcludeFilter才进来,
        //否则总是返回false。
        //获取容器中的所有TypeExcludeFilter的子类组件
            Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory)this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
            Iterator var4 = delegates.iterator();

            while(var4.hasNext()) {
            	//遍历,使用每个TypeExcludeFilter组件进行排除
                TypeExcludeFilter delegate = (TypeExcludeFilter)var4.next();
                if (delegate.match(metadataReader, metadataReaderFactory)) {
                    return true;
                }
            }
        }

        return false;
    }

第二个属性是Filter,,值是AutoConfigurationExcludeFilter。表示AutoConfigurationExcludeFilter的match方法返回true就加载Bean,否则不加载。
AutoConfigurationExcludeFilter:

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
		//同时符合this.isConfiguration(metadataReader)和this.isAutoConfiguration(metadataReader);才返回true
        return this.isConfiguration(metadataReader) && this.isAutoConfiguration(metadataReader);
    }

private boolean isConfiguration(MetadataReader metadataReader) {
		//要加载的类要被Configuration注解注解才返回true。
        return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
    }

    private boolean isAutoConfiguration(MetadataReader metadataReader) {
    	//要加载的类要在上面的AutoConfigurationImportSelector选中的类里面才加载。
        return this.getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
    }

至此,Springboot的自动配置原理大体已经阐述完毕。

自定义的自动配置
  1. 新建一个spring项目,编写一个配置类和一个业务类。
@Configuration
public class DemoConfig {

    @Bean
    public DemoService demoService(){
        return new DemoService();
    }
}


public class DemoService {

    public DemoService(){
        System.out.println("DemoService create");
    }
}

  1. 对该类使用maven打包到仓库。
  2. 新建一个springboot项目,引入刚刚打包的spring项目依赖,并尝试访问DemoService Bean是否存在。
public class DemoTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringbootstudyApplication.class);
        DemoService bean = context.getBean(DemoService.class);
        System.out.println(bean);
    }
}

结果:
在这里插入图片描述
结果不存在,原因springboot根本就不知道要加载他,原因是我们没有使用spring.factories文件定义自动配置该配置类。

  1. 在spring项目的类路径下新增META-INF/spring.factories指定加载DemoConfig配置类,并重新打包到仓库,再到springboot项目中重新测试。

在这里插入图片描述

运行结果:
在这里插入图片描述
自动配置成功。

  1. 测试spring-autoconfigure-metadata.properties文件定义元数据。该定义表名只有类路径下有com.springboot.springbootstudy.EnableDemoConfig类才加载DemoConfig配置类。
    在这里插入图片描述

打包,重新测试。
结果:又没有加载了。
在这里插入图片描述

  1. 最后一步,在springboot项目中创建一个com.springboot.springbootstudy.EnableDemoConfig再测试。
    结果:
    在这里插入图片描述
    又成功了。

其实这里就能算是一个spring boot Starter了,Starter的本质就是spring或者spring boot项目加jar包加Bean配置加spring.factories 加 spring-autoconfigure-metadata.properties文件(可有可无)。

至此,完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值