使用SpringBoot的最大好处就是简化配置,它实现了自动化配置。
这里以SpringBoot 2.1.4.RELEASE版本和Spring 5.1.6.RELEASE版本为例。
API文档:https://docs.spring.io/spring-boot/docs/current/api/
自动化配置的原理如下:
一个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 { @AliasFor( annotation = EnableAutoConfiguration.class ) Class<?>[] exclude() default {}; @AliasFor( annotation = EnableAutoConfiguration.class ) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; }
在SpringBootApplication类上有一个重要的注解@EnableAutoConfiguration,它就是实现自动化配置的核心。当SpringBoot项目启动的时候,就会调用@EnableAutoConfiguration来进一步加载系统所需的一些配置信息,完成自动化配置。
@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 {}; }
在EnableAutoConfiguration类中,它使用@Import注解来导入配置类AutoConfigurationImportSelector。
使用@Import注解可以导入三种类型的配置类,如下:
(1)直接导入配置类:@Import({xxxConfiguration.class})
(2)依据条件选择配置类:@Import({xxxSelector.class})
(3)动态注册 Bean:@Import({xxxRegistrar.class})
我们进入查看AutoConfigurationImportSelector类的源码,(由于源码太多,在此不再展示),其中用到了一个重要的类SpringFactoriesLoader,该类位于org.springframework.core.io.support包下,它才是真正加载项目所需要的jar包的类,它主要用于加载 classpath下所有 JAR 文件的 META-INF/spring.factories 文件,并分析出其中定义的工厂类。这些工厂类进而被启动逻辑使用,应用于进一步初始化工作。
SpringFactoriesLoader类是spring框架自己使用的内部工具类,本身被声明为 final,表示不可以被其他类继承。
SpringFactoriesLoader类的源码如下:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.core.io.support; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Map.Entry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.io.UrlResource; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** * SpringFactoriesLoader#loadFactories设计用于加载和实例化指定类型的工厂,这些工厂类型的定义 * 来自classpath中多个JAR包内常量FACTORIES_RESOURCE_LOCATION所指定的那些spring.factories文件。 * spring.factories文件的格式必须是属性文件格式,每条属性的key必须是接口或者抽象类的全限定名, * 而属性值value是一个逗号分割的实现类的名称。 */ public final class SpringFactoriesLoader { /* * 要加载的资源路径,该常量定义了该工具类要从每个jar包中提取的工厂类定义属性文件的相对路径 * 在classpath中的多个JAR中,要扫描的工厂配置文件的在本JAR包中的路径。 * 实际上,Springboot的每个 autoconfigure包都包含spring.factories这个配置文件。 */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; //日志 private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap(); private SpringFactoriesLoader() { } /** * @param factoryClass 工厂所属接口/抽象类全限定名称 * @param classLoader 所要使用的类加载器 * * 该方法会读取classpath上所有的jar包中的所有 META-INF/spring.factories 属性文件,找出其中定义的匹配类型 factoryClass 的工厂类, * 然后创建每个工厂类的对象/实例,并返回这些工厂类对象/实例的列表 */ public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) { Assert.notNull(factoryClass, "'factoryClass' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoader == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } //加载类型为factoryClass的工厂的名称,其实是一个个的全限定类名,使用指定的classloader:classLoaderToUse List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse); if (logger.isTraceEnabled()) { logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames); } List<T> result = new ArrayList(factoryNames.size()); Iterator var5 = factoryNames.iterator(); // 实例化所加载的每个工厂类 while(var5.hasNext()) { String factoryName = (String)var5.next(); result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); } //对工厂类进行排序 AnnotationAwareOrderComparator.sort(result); return result; } /** * * @param factoryClass 工厂所属接口/抽象类全限定名称 * @param classLoader 类加载器 * @return * * 该方法会读取classpath上所有的jar包中的所有 META-INF/spring.factories 属性文件,找出其中定义的匹配类型 factoryClass 的工厂类, * 然后并返回这些工厂类的名字列表,注意是包含包名的全限定名。 */ public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); // 1. 使用指定的classloader扫描classpath上所有的JAR包中的文件META-INF/spring.factories,加载其中的多值工厂属性定义,使用多值Map的形式返回, // 2. 返回多值Map中key为factoryClassName的工厂名称列表,如果没有相应的entry,返回空列表而不是返回null return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } /** * @param classLoader 类加载器 * * 使用指定的classloader扫描classpath上所有的JAR包中的文件META-INF/spring.factories,加载其中的多值 * 工厂属性定义,使用多值Map的形式返回 **/ 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 { // 扫描classpath上所有JAR中的文件META-INF/spring.factories Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { // 找到的每个META-INF/spring.factories文件都是一个Properties文件,将其内容 // 加载到一个 Properties 对象然后处理其中的每个属性 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; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } } /** * @param instanceClassName 工厂实现类全限定名称 * @param factoryClass 工厂所属接口/抽象类全限定名称 * @param classLoader 所要使用的类加载器 **/ private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) { try { Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader); if (!factoryClass.isAssignableFrom(instanceClass)) { throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]"); } else { return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance(); } } catch (Throwable var4) { throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4); } } }
一般情况下,springboot提供的一些JAR包里面会带有文件META-INF/spring.factories,然后在Springboot启动的时候,根据启动阶段不同的需求,框架就会多次调用SpringFactoriesLoader加载相应的工厂配置信息。
使用了注解@EnableAutoConfiguration时,就会触发对SpringFactoriesLoader.loadFactoryNames()的调用。
看一下spring.factories所在的位置:
部分内容如下:
# 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
以上就是SpringBoot实现自动化配置的原理,或许你看到的源码会与我的不相同,那有可能是jar版本的不一致。
总结一下使用SpringBoot的好处:
(1)简化配置,不需要编写太多的xml配置文件;
(2)基于Spring构建,使开发者快速入门,门槛很低;
(3)SpringBoot可以创建独立运行的应用而不需要依赖于容器;
(4)内置tomcat服务器,不需要打包成war包,可以直接放到tomcat中运行;
(5)提供maven极简配置,以及可视化的相关监控功能,比如性能监控,应用的健康程度等;
(6)为微服务SpringCloud奠定了基础,使得微服务的构建变得简单;
(7)Spring可以整合很多各式各样的框架,并能很好的集成;
(8)活跃的社区与论坛,以及丰富的开发文档;
热门评论
大佬,我想问一下,springboot的自动化配置体现在哪里?因为springboot还是要写配置文件的呀,所以不是很理解自动化配置到底是什么意思?我的理解是不需要写配置文件。自动化配置是可以自动引入项目中的@Commponent和@Configration注解的类吗?可是这不是spring本身就有的吗?spring框架也会加载项目中的@Commponent和@Configration注解的类呀,麻烦大佬告知,谢谢。