1.4、自动配置和主启动类
什么是自动配置?SSM在使用时需要进行一系列的配置,比如配置 DispatcherServlet、配置视图解析器、配置事务管理器等等、配置各个包的扫描,它的很多组件需要自己配置后才可以使用。而SpringBoot在没有任何配置的情况下,仅仅导入相应的启动器后,即可运行项目,访问Controller、视图跳转等等,这都要归功于其自动配置。
1.4.1、启动类注解
SprinBoot有内置的Servlet,所以它不要配置服务器,直接通过一个启动类来运行项目,而这个启动类就是SpringBoot能实现自动配置的核心所在。
我们在观察启动类时,会发现在启动类上有一个注解@SpringBootApplication,这个就是springboot的核心注解,很多原理都在这个注解里面。
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//启动SpringBoot的程序
SpringApplication.run(SpringbootApplication.class, args);
}
}
@SpringBootApplication是SpringBoot的启动类的注解,表示该类是一个启动类。它是一个组合注解,内部包含了多个注解,我们可以直接点击此注解看内部有什么注解:
它内部的注解还是挺多的,但是其中有三个核心的注解:@SpringBootConfiguration、@ComponentScan 、@EnableAutoConfiguration,一个底层是spring的核心的配置类,第二个用来扫描包,最后一个是 SpringBoot 可以自动配置的原因所在。所以说SpringBoot是在spring的基础上延伸的。
-
@SpringBootConfiguration:将类标记为SpringBoot的配置类,是Spring 中 代替XML配置的注解的相关延伸。
- @Configuration:spring的核心配置类
- @Component:spring一个组件
- @Configuration:spring的核心配置类
-
@ComponentScan:用于包的扫描,默认是启动类所在的包及其子包,主要是扫描@Component、@Service、@Controler、@Repository、@Configuration等等注解,并将其所在类注册为 bean对象。
-
@EnableAutoConfiguration:是SpringBoot能自动化配置的核心所在,用于开启 Spring Boot 的自动配置功能,进入后其内部也有两个重要的注解:
-
@AutoConfigurationPackage:用来自动配置包
-
@Import(AutoConfigurationPackages.Registrar.class):导入自动配置包的选择器
-
AutoConfigurationPackages类的内部类Registrar:首先获取到注解所标识的类,然后将这个类所在的包以及子包,并将其名字放入到一个String数组当中,再将该String数组中的包的所有组件导入到Spring的 容器当中(自动导入主程序所在包及其子包的所有组件)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //获取注解元信息,得到所有包名,封装到数组里面,然后批量注册 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):自动配置导入选择器(牵扯到自动配置机制,spring-boot-autoconfigure-x.x.x.jar包中 META-INF/spring.factories文件)
AutoConfigurationImportSelector类
:帮助SpringBoot将符合条件的@Configuration配置都加载到IOC容器中- getImportGroup() 方法
- process() 方法
- selectImports() 方法
-
1.5.2、导入选择器
AutoConfigurationImportSelector
自动配置导入选择器,是实现自动配置的核心类,他会自动会给容器中导入非常多的配置类(xxxAutoConfifiguration),它实现自动配置的步骤如下:
-
selectImports()方法:核心方法,它将 注解元数据 传递给 getAutoConfigurationEntry() 方法,获得自动配置的所有条目
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } //通过getAutoConfigurationEntry 方法获取需要加载的全部类名 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
-
getAutoConfigurationEntry()方法: 通过调用 getCandidateConfigurations() 方法来获取自动配置类的完全限定名,并在经过排除、过滤等处理后,将其缓存到成员变量中,
//根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations); //触发自动配置导入事件 fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
-
getCandidateConfigurations()方法:此方法负责获取所有的自动配置类的名称。
根据 Spring Factories 机制调用 SpringFactoriesLoader 的 loadFactoryNames() 方法,根据 EnableAutoConfiguration.class (自动配置接口)获取其实现类(自动配置类)的类名的集合
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = new ArrayList<>( SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())); ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add); //在 META-INFspring.factories 和 META-INFspringorg.springframework.boot.autoconfigure.AutoConfiguration.imports 中都找不到自动配置类 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
1.5.3、自动配置机制
Spring Boot 的自动配置是基于 Spring Factories 机制实现的,它是一种服务发现机制,这种扩展机制与 Java SPI 机制十分相似。Spring Boot 会自动扫描所有 Jar 包类路径下 META-INF/spring.factories 文件,并读取其中的内容,进行实例化,这种机制也是 Spring Boot Starter 的基础。
1、factories 文件
Spring Boot 的很多 Jar包内都有 META-INF/spring.factories 这个文件,比如:
spring.factories和 .properties文件格式类似通过键值的格式(key=value),key为接口的全限定名,value为接口实现类的全限定名。
2、实现原理
在 spring-core的包内有 SpringFactoriesLoader 类,它会扫描 META-INF/spring.factories 文件,并获取指定的接口配置
-
loadFactoryNames方法:AutoConfigurationImportSelector 中的 getCandidateConfigurations()方法就是调用此方法来获取配置类,它用来从"META-INF/spring.factories" 获取配置类的全限定名。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoader == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); //调用loadSpringFactories,获得一个Map<String, List<String>> 集合,然后从这个集合中拿出 configurations return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
-
loadSpringFactories():方法能够读取该项目中所有 Jar 包类路径下 META-INF/spring.factories 文件的配置内容,并以 Map 集合的形式返回 到loadFactoryNames 方法
Map<String, List>,值为一个list集合,一个注解的类名为键——多个Config配置类为值的形式
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { //从缓存那种获取。如果缓存中存在就直接返回,不存在再去相应的路径去加载 Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { //扫描所有 Jar 包类路径下的 META-INF/spring.factories 文件 Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while(urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); //将扫描到的 META-INF/spring.factories 文件中内容包装成 properties 对象 Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { //提取 properties 对象中的 key 值 String factoryTypeName = ((String) entry.getKey()).trim(); //提取 proper 对象中的 value 值(多个类的完全限定名使用逗号连接的字符串) // 使用逗号为分隔符转换为数组,数组内每个元素都是配置类的完全限定名 String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); //遍历配置类数组,并将数组转换为 list 集合 for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } //将 propertise 对象的 key 与由配置类组成的 List 集合一一对应存入名为 result 的 Map 中 result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); //这里将查询出来的都加入到缓存中这样下次就可以从缓存中获取了 cache.put(classLoader, result); }catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
-
loadFactories方法:使用给定的类加载器从"META-INF/spring.factories"加载并实例化给定类型的工厂实现
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) { Assert.notNull(factoryType, "'factoryType' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoader == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } // 调用loadFactoryNames获取接口的实现类 List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse); if (logger.isTraceEnabled()) { logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames); } List<T> result = new ArrayList<>(factoryImplementationNames.size()); for (String factoryImplementationName : factoryImplementationNames) { result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result; }
1.5.4、自动配置的图解
这些注解可以决定配置文件在什么情况下生效: