一看就喷亏的小猿

一看就喷亏的小猿

一、auto-configuration introduction

     自动配置是springboot的一大特性,它能在我们添加相应的jar包依赖的时候,自动为我们配置了这些组件的相关配置,我们无需配置或者只需要少量的配置就能运行我们编写的项目。官网也对自动配置作了详细说明:

     Spring Boot auto-configuration attempts to automatically configure your Spring application based on the jar dependencies that you have added. For example, if HSQLDB is on your classpath, and you have not manually configured any database connection beans, then Spring Boot auto-configures an in-memory database.

     上诉语句翻译为中文为:SpringBoot自动配置尝试根据您添加的JAR依赖性自动配置您的Spring应用程序。例如,如果HSQLDB在您的类路径上,并且您没有手动配置任何数据库连接bean,那么spring boot会自动配置内存中的数据库。

     如果需要设置自动配置的话,方法是将@EnableAutoconfiguration注解添加到您的@configuration类中。

二、How to Realize Auto-Configuration(底层)

    1、要研究springboot的自动配置需要从主程序类开始入手,请看下面代码:

@SpringBootApplication
public class MMVirusScanApplication {

    public static void main(String[] args) {
        SpringApplication.run(MMVirusScanApplication.class, args);
    }
}

    2、这就是主程序类,这个类最主要的部分是@SpringBootApplication,正是通过添加了这个注解,springboot应用才能正常启动。再继续查看@SpringbootApplication注解组成部分

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

     这个注解最主要部分是:

  • @SpringBootConfiguration:这个注解标注在某类上,说明这个类是一个springboot配置类
  • @EnableAutoConfiguration:这个注解就是springboot能实现自动配置的关键
  • @ComponentScan:这个注解是组件扫描这个是我们最熟悉的注解,即使没有使用过注解也经常在spring的配置文件中使用过<context:component-scan base-package="com.xxx.xxx"/>, 组件扫描就是扫描指定的包下的类,并加载符合条件的组件。

     3、继续研究@EnableAutoConfiguration注解:

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

     可以发现它是一个组合注解,Spring 中有很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的bean,并加载到IoC容器。@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。里面最主要注解是:

  • @AutoConfigurationPackage:自动配置包,它也是一个组合注解,其中最重要的注解是@Import(AutoConfigurationPackages.Registrar.class),它是spring框架的底层注解,它的作用就是给容器中导入某个组件类,例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中,可查看Registrar类中registerBeanDefinitions方法,这个方法就是导入组件类的具体实现。
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata,
				BeanDefinitionRegistry registry) {
             //将注解标注的元信息传入,获取到相应的包名
			register(registry, new PackageImport(metadata).getPackageName());
		}

    通过对registerBeanDefinitions方法进行DeBug,运行结果如下:

 SpringBoot 2.X课程学习 | 第三篇:自动配置(Auto-configuration)-LMLPHP

     可以看到AnnotationMetadata(注解标注注的元信息中包含了使用了哪些注解,相应的注解作用在哪个类上)

     我们对new PackageImport(metadata).getPackageName()进行检索(idea工具可以圈出需要查询的值,使用快捷键“Ctrl+U”),看看其结果是什么?

   SpringBoot 2.X课程学习 | 第三篇:自动配置(Auto-configuration)-LMLPHP

SpringBoot 2.X课程学习 | 第三篇:自动配置(Auto-configuration)-LMLPHP

   因此可以得知使用@AutoConfigurationPackage注解就是将主程序类所在包及所有子包下的组件到扫描到spring容器中

  • @Import({AutoConfigurationImportSelector.class}):AutoConfigurationImportSelector这个类导入到spring容器中,AutoConfigurationImportSelector可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中。

    4、继续研究AutoConfigurationImportSelector这个类,通过源码分析这个类中是通过selectImports这个方法告诉springboot都需要导入那些组件:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
        //获得自动配置元信息,需要传入beanClassLoader这个类加载器
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);

		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
				autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

     4.1、深入研究loadMetadata方法

    protected static final String PATH = "META-INF/"
			+ "spring-autoconfigure-metadata.properties";  //文件中为需要加载的配置类的类路径

	public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {

            //读取spring-boot-autoconfigure-2.1.5.RELEASE.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();

            //解析urls枚举对象中的信息封装成properties对象并加载
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils
						.loadProperties(new UrlResource(urls.nextElement())));
			}

            //根据封装好的properties对象生成AutoConfigurationMetadata对象返回
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException(
					"Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

     4.2、深入研究getAutoConfigurationEntry方法

	protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}

        //将注解元信息封装成注解属性对象
		AnnotationAttributes attributes = getAttributes(annotationMetadata);

        //获取到配置类的全路径字符串集合
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

      4.2.1、深入getCandidateConfigurations方法

   这个方法中有一个重要方法loadFactoryNames,这个方法是让SpringFactoryLoader去加载一些组件的名字。

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {

         /**
         * 这个方法需要传入两个参数getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
         * getSpringFactoriesLoaderFactoryClass()这个方法返回的是EnableAutoConfiguration.class
         * getBeanClassLoader()这个方法返回的是beanClassLoader(类加载器)
         */
		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;
	}

    /**
	 * Return the class used by {@link SpringFactoriesLoader} to load configuration
	 * candidates.
	 * @return the factory class
	 */
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}


    protected ClassLoader getBeanClassLoader() {
		return this.beanClassLoader;
	}

   继续点开loadFactory方法

    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {

        //获取出入的键
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

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

                //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装 为Enumeration类对象
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;

  五、总结

     因此springboot底层实现自动配置的步骤是:

  1. springboot应用启动;
  2. @SpringBootApplication起作用;
  3. @EnableAutoConfiguration;
  4. @AutoConfigurationPackage:这个组合注解主要是@Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;
  5. @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程,具体实现可查看上面贴附的源码。
06-10 16:45