所有分类
  • 所有分类
  • 未分类

SpringBoot原理-自动配置

简介

说明

本文介绍SpringBoot是如何实现自动配置的。

问题的引出

SpringBoot有如下功能:

  1. 创建好的SpringBoot项目(假如启动类是DemoApplication),可以直接运行
  2. 可以在配置文件自定义配置
  3. 在启动类上加个注解就可以使用某个功能

那么,SpringBoot是如何实现这些功能的呢?

入口

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

@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  // 对@Configuration 注解的封装,与@Configuration 作用相同。
    • @Configuration // 通过javaConfig的方式来添加组件到 IoC 容器中
  • @EnableAutoConfiguration
    • @AutoConfigurationPackage // 扫描@ComponentScan指定的路径下的类,添加到IOC
    • @Import(AutoConfigurationImportSelector.class) // 将META-INF/spring.factories中定义的bean 添加到 IoC 容器中
  • @ComponentScan // 包扫描。
    • 默认扫描的包的根路径是 Spring Boot 主程序启动类所在包的位置。
    • 在扫描过程中由前面介绍的 @AutoConfigurationPackage 注解进行解析,从而得到 Springboot 项目主程序启动类所在包的具体位置。

@SpringBootConfiguration

简介

对@Configuration 注解的封装,标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中。

源码

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

@EnableAutoConfiguration

总结

Springboot 底层实现自动装配的步骤是:

  1. Springboot 应用启动;
  2. @SpringBootApplication 起作用;
  3. @EnableAutoConfiguration;
  4. @AutoConfigurationPackage: 这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class), 它通过将 Registrar 类导入到容器中,而 Registrar 类作用是扫描主配置类同级目录及其子包,并将相应的组件导入到 Springboot创建管理容器中
  5. @Import(AutoConfigurationImportSelector.class): 它通过将 AutoConfigurationImportSelector 类导入到容器中,AutoConfigurationImportSelector 类作用是通过selectImports方法中,使用内部工具类SpringFactoriesLoader查找 classpath 上所用 jar 包中的  MATE-INF/spring.factories 进行加载, 实现将配置类信息交给 SpringFactory 加载器进行一系列的容器创建过程。

简介

@EnableAutoConfiguration 注解表示开启自动配置功能。

@Import注解用于导入配置类,本处导入AutoConfigurationImportSelector。容器刷新时,会调用AutoConfigurationImportSelector类的selectImports方法,扫描META-INF/spring.factories文件,将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置项通过反射实例化为对应的标注了@Configuration 的JavaConfig形式的配置类,并加载到 IoC 容器中。

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

@AutoConfigurationPackage

自动配置包 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 导入 Registrar 组件类
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
public abstract class AutoConfigurationPackages {
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // 将主程序类所在包及其子包下的组件扫描到 Spring 容器中
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }

        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
	}
    // 其他代码
}

@Import(AutoConfigurationImportSelector.class)

将 AutoConfigurationImportSelector 这个类导入到 Spring 容器中。AutoConfigurationImportSelector 可以帮助 SpringBoot 应用将所有符合条件的配置都加载到当前 SpringBoot 创建并使用的 IoC 容器(ApplicationContext) 中。
这个类中是通过 selectImports() 这个方法告诉 SpringBoot 都需要导入哪些组件

selectImports  //给容器中导入组件

简述

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
	// 这个方法告诉springboot都需要导入那些组件
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		//判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		//1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
		//作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
		// SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
		// 自动配置的类全名.条件=值
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
		
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	
	//其他方法
}

详解

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}

        // 获取自动配置项
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 获取所有默认支持的自动配置类名列表
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 去除重复的配置类,自己写的starter 可能存在重复的
		configurations = removeDuplicates(configurations);
        // 若不希望某些自动配置类自动配置,可通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
        // 也可在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        // 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
		checkExcludedClasses(configurations, exclusions);
        // 从 configurations 中,移除所有不希望自动配置的配置类
		configurations.removeAll(exclusions);
        // 筛选候选的自动配置类,根据项目pom.xml中加入的依赖筛选出最终符合当前项目运行环境对应的自动配置类
        // 判断是否要加载某个类的两种方式:
        //   根据spring-autoconfigure-metadata.properties进行判断。
        //   判断@Conditional是否满足
		configurations = getConfigurationClassFilter().filter(configurations); 
        将自动配置导入事件通知监听器
        // 过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
        // 并触发fireAutoConfigurationImportEvents事件。
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
    
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // getSpringFactoriesLoaderFactoryClass 获取默认的 EnableAutoConfiguration.class 类名,传入 loadFactoryNames 方法
		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;
	}
    
    // 默认的 EnableAutoConfiguration.class 类名
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }
}
public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

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

		try {
            // 如果类加载器不为 null,则加载"META-INF/spring.factories",将其中设置的配置类的全路径信息封装为 Enumeration 
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
            // 循环 Enumeration 类对象,根据相应的节点信息生成 Properties 对象,
            // 通过传入的键获取值,在将值切割为一个个小的字符串转化为 Array,方法result集合中
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
}
  • 首先注意到 selectImports 方法,其实从方法名就能看出,这个方法用于给容器中导入组件,然后跳到 getAutoConfigurationEntry 方法就是用于获取自动配置项的。
  • getCandidateConfigurations 方法就是 获取一个自动配置 List ,这个 List 就包含了所有的自动配置的类名 。
  • SpringFactoriesLoader#loadFactoryNames :跳转到 loadSpringFactories 方法发现 ClassLoader 类加载器指定了一个 FACTORIES_RESOURCE_LOCATION 常量。
  • 然后利用PropertiesLoaderUtils 把 ClassLoader 扫描到的这些文件的内容包装成 properties 对象,从 properties 中获取到 EnableAutoConfiguration.class 类(类名)对应的值,然后把他们添加在容器中。

默认配置

简介

说明

springboot里边已经有了很多默认的配置,对应的依赖为:spring-boot-starter-xxx,对于这些依赖,大多数情况下,导入它们就可以直接使用,不需要加@EnableXxx。

springboot默认支持的spring-boot-starter-xxx对应的自动配置类见:自动配置类(英文官网)

实例分析:AOP自动配置

配置类 

在上边“简介”中已经说过如何找配置类,本处spring-boot-starter-aop的配置类为:

spring-boot-autoconfigure-2.3.0.RELEASE.jar包里的org/springframework/boot/autoconfigure/aop/AopAutoConfiguration.class

package org.springframework.boot.autoconfigure.aop;

import org.aspectj.weaver.Advice;

import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
 * Auto-configuration} for Spring's AOP support. Equivalent to enabling
 * {@link EnableAspectJAutoProxy @EnableAspectJAutoProxy} in your configuration.
 * <p>
 * The configuration will not be activated if {@literal spring.aop.auto=false}. The
 * {@literal proxyTargetClass} attribute will be {@literal true}, by default, but can be
 * overridden by specifying {@literal spring.aop.proxy-target-class=false}.
 *
 * @author Dave Syer
 * @author Josh Long
 * @since 1.0.0
 * @see EnableAspectJAutoProxy
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class)
	static class AspectJAutoProxyingConfiguration {

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = false)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
				matchIfMissing = false)
		static class JdkDynamicAutoProxyConfiguration {

		}

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = true)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
				matchIfMissing = true)
		static class CglibAutoProxyConfiguration {

		}

	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	static class ClassProxyingConfiguration {

		ClassProxyingConfiguration(BeanFactory beanFactory) {
			if (beanFactory instanceof BeanDefinitionRegistry) {
				BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
				AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
		}

	}

}

分析

从上边代码可以看到如下两点:

aop的开启与关闭

类上有注解:@ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true)

也就是说,默认情况下,会启用aop。(可通过spring.aop.auto=false关闭)

 AspectJ的开启

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration{

}

添加spring-boot-starter-aop依赖后,会自动引入aspectjweaver-1.9.4.jar,而aspectjweaver-1.9.4.jar里边有Advice.class。所以,@ConditionalOnClass这个注解需要的条件已经满足了。

CGLIB的开启

@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
        matchIfMissing = true)
static class CglibAutoProxyConfiguration {

}

也就是说,如果不配置apring.aop.proxy-target-class,会使用CGLIB,而且,@EnableAspectJAutoProxy(proxyTargetClass = true)会生效。

也在另一处得到验证:在spring-boot-autoconfigure-2.3.0.RELEASE.jar的META-INFO/spring-configuration-metadata.json中找到如下配置信息:

1

评论4

请先

  1. 这俩题应该归类到SpringBoot里呀 :cool:
    love清浅 2024-09-04 0
    • 谢谢指出,已修复~
      自学精灵 2024-09-06 0
  2. 写的非常好!感谢!
    EstrellasZ 2023-12-20 0
    • 谢谢支持。持续更新高质量内容
      自学精灵 2023-12-20 0
显示验证码
没有账号?注册  忘记密码?

社交账号快速登录