SpringBoot 配置类解析
本文首发于 vivo互联网技术 微信公众号
链接:https://mp.weixin.qq.com/s/NvPO5-FWLiOlrsOf4wLaJA
作者:Li Wanghong
SpringBoot作为Java领域非常流行的开源框架,集成了大量常用的第三方库配置,Spring Boot应用中这些第三方库几乎可以是零配置的开箱即用,大部分的 Spring Boot 应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。SpringBoot上手快,但是如果你的项目中业务场景需要一些特殊定制,甚至对源码进行定制化,那这时候了解原理就变成必需的了,只有充分了解源码,知道框架底层的工作原理,才能对源码中原有的机制进行修改 / 扩展等等。本文介绍了SpringBoot如何解析配置类、如何集成第三方配置。
一、基本概念介绍
在SpringBoot中推荐基于Java Config的方式来代替传统的XML方式去引入Bean,本文就是分析SpringBoot如何解析这些配置类,为容器中注入我们自定义的以及SpringBoot为我们提供的Bean。SpringBoot版本基于2.1.7.RELEASE。
1 2 3 4 5 6 7 8 | // 通常一个SpringBoot工程会含有这样一个主配置类,它位于我们项目的根包下,通过启动这个main方法就可以启动我们的项目 // 下面我们先分析@SpringBootApplication注解有哪些作用,在第二节中分析run方法,在run方法中会进行配置类的解析 @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication. class , args); } } |
1、@SpringBootConfiguration注解
1 2 3 4 5 6 7 | // 点进去发现它其实就是一个@Configuration注解,SpringBoot解析到就会知道这是一个配置类,会给容器中引入一些bean // 一个被@Configuration标注的类,相当于一个applicationContext.xml文件 // @Configuration点进去发现其实就是一个@Component注解 @Configuration public @interface SpringBootConfiguration { } |
1 2 3 4 5 6 7 8 9 10 11 | // 结合下面@AutoConfigurationPackage注解,发现@EnableAutoConfiguration注解就是通过@Import注解给容器中引入了两个bean, // 分别是AutoConfigurationImportSelector和AutoConfigurationPackages.Registrar,通过这两个类可以给容器中引入更多的类 // 下面先介绍下@Import注解的使用 @AutoConfigurationPackage @Import (AutoConfigurationImportSelector. class ) public @interface EnableAutoConfiguration { } @Import (AutoConfigurationPackages.Registrar. class ) public @interface AutoConfigurationPackage { } |
在原生SpringFramework中,装配组件有下面三种方式
- 使用@Component注解,Spring2.5+
- 使用配置类@Configuration与@Bean,Spring3.0+
- 使用模块装配@EnableXXX与@Import,Spring3.1+
如果要注册较多的Bean,通过1) 2) 两种方式不太方便,可以通过Spring提供的模块装配功能,通过给配置类标注@Enable注解,再在注解上标注@Import注解,即可完成组件装配的效果,下面通过一个例子讲解@EnableXXX和@Import的使用。
1 2 3 4 5 6 7 | // 步骤1) // 创建几个动物的实体类,如Cat、Bird、Chicken、Dog、Duck、Pig、Sheep、Snake、Tiger // 接下来会通过@Import的各种用法将这些bean注入到容器中 @Data public class Cat { private String name; } |
这个注解就是相当于XML中的<context:component-scan>,它会从定义的扫描包路径(默认是SpringBoot主配置所在的包及其子包)扫描标识了@Controller、@Service、@Repository、@Component注解的类到Spring容器中。
1 2 3 4 | // 我们可以看到这个@ComponentScan注解上显示指定了两个Filter过滤条件,它是SpringBoot提供的一种扩展机制,能让我们 // 向IOC容器中注册一些自定义的组件过滤器,以便在包扫描的过程中过滤一些Bean @ComponentScan (excludeFilters = { @Filter (type = FilterType.CUSTOM, classes = TypeExcludeFilter. class ), @Filter (type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter. class ) }) |
4、@Conditional注解
我们说SpringBoot约定大于配置,它通过一些xxxAutoConfiguration给容器中导入了一些组件,如果你需要但是没有配置视图解析器,则SpringBoot就会提供其默认配置的视图解析器,这样就简化了配置。那么如果自己定义了一个视图解析器,那么到底SpringBoot会往容器中注入哪个呢? 查看下面默认注入的视图解析器代码,发现其上面有一个@ConditionalOnMissingBean注解,意思就是若容器中没有这个则容器会给你注入一个这样的视图解析器,若容器中有就不注入了。
1 2 3 4 | // @ConditionalOnMissingBean是通过@Conditional注解和Condition接口的实现类(OnBeanCondition.class)来实现这个效果的 @Bean @ConditionalOnMissingBean public InternalResourceViewResolver defaultViewResolver() { |
我们也可以自己实现一个自定义的条件注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 自定义一个Condition接口的实现类MyCondition,通过其matches方法来判断是否符合指定条件 public class MyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 获取注解MyConditionAnotation注解上指定value对应的值,如果没有这个值,则不符合条件 String[] value = (String[]) metadata.getAnnotationAttributes(MyConditionAnotation. class .getName()).get( "value" ); for (String property : value){ if (StringUtils.isEmpty(context.getEnvironment().getProperty(property))){ return false ; } } return true ; } } |
1 2 3 4 5 6 7 | @Retention (RetentionPolicy.RUNTIME) @Target ({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional (MyCondition. class ) public @interface MyConditionAnotation { String[] value() default {}; } |
1 2 3 4 5 | // 通过@Component给容器中注入一个A类型Bean,条件是@MyConditionAnotation注解指定的key1、key2对应的值有配置 @Component @MyConditionAnotation ({ "key1" , "key2" }) public class A { } |
5、SpringFactoriesLoader
类似Java的SPI、Dubbo的SPI机制,SpringBoot也提供了一种机制,它通过读取META-INF/spring.factories文件(这些文件可能存在于类路径中的多个jar包中)来加载一些预先配置的类,而这个核心机制来源于SpringFactoriesLoader。spring.factories文件必须采用 properties 格式,其中key是接口或抽象类的全限定名,而value是用逗号分隔的实现类的全限定类名列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 我们先来看下上面讲解@Import注解讲到的AutoConfigurationImportSelector,我们说通过它就可以导入SpringBoot提供那些 // 自动配置类. 下面是Import注解讲到的AutoConfigurationImportSelector的selectImport方法 @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 忽略一些其他代码 // 这里的含义就是通过SpringFactoriesLoader去加载META-INF/spring.factories中配置的那些xxxAutoConfiguration // 并放入String数组返回 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } // 忽略中间过程调用,后面会分析,接下来就走到这里通过SpringFactoriesLoader去加载自动配置类 protected List<string> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 具体调用看下面分析 List<string> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); return configurations; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration. class ; } |
6、BeanFactoryPostProcessor
- BeanFactoryPostProcessor,BeanFactory后置处理器,是针对于BeanFactory的扩展点,即Spring会在BeanFactory初始化之后,beanDefinition都已经loaded,但是bean还未创建前进行调用,可以修改或增加BeanDefinition。
- BeanDefinitionRegistryPostProcessor,是BeanFactoryPostProcessor的子接口,是针对于BeanFactory的扩展点,即Spring会在调用BeanFactoryPostProcessor之前调用它。我们下面要重点分析的
- ConfigurationClassPostProcessor就是该接口的实现类,SpringBoot就是通过它去解析配置类,封装成一个个BeanDefinition注入到容器中。
- BeanPostProcessor,是针对Bean的扩展点,即Spring会在Bean初始化前后调用方法对Bean进行处理,AOP、依赖注入就是通过BeanPostProcessor实现的。
下面是自定义的一个BeanFactoryPostProcessor和BeanPostProcessor,发现通过BeanFactoryPostProcessor可以往容器中增加新的Bean或者修改原有的Bean定义,通过BeanPostProcessor可以修改已经创建好的Bean的属性值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; // 往容器中新增BeanDefinition GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(Chicken. class ); registry.registerBeanDefinition( "beanFactoryPostProcessor-Chicken" , beanDefinition); // 修改容器中原有的BeanDefinition BeanDefinition snake = registry.getBeanDefinition( "snake" ); snake.setLazyInit( true ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Component public class CatBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Cat){ Cat cat = (Cat) bean; cat.setName( "changeNameCat" ); } return bean; } } |
二、SpringBoot启动流程概述
第一节是SpringBoot解析自动配置类会用到的一些知识点,下面我们来看SpringBoot解析配置类的具体过程。上图是SpringBoot启动流程图,其中在refreshContext的第五步会调用容器的BeanFactoryPostProcessor的postProcessBeanDefinitionRegistry方法。其中有一个是ConfigurationClassPostProcessor,它是在创建ConfigurableApplicationContext时设置到容器中的,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // 图中说的创建ConfigurableApplicationContext,默认创建的是普通的Servlet Web容器,就是下面这个 // 通过反射创建会走到其默认的构造函数 public AnnotationConfigServletWebServerApplicationContext() { // 这里面进去会走到下面代码 this .reader = new AnnotatedBeanDefinitionReader( this ); this .scanner = new ClassPathBeanDefinitionScanner( this ); } // 走到这里 registerAnnotationConfigProcessors(registry, null ); // 走到这里 // 向容器中注入一个ConfigurationClassPostProcessor,它是BeanFactoryPostProcessor if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor. class ); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } // 向容器中注入一个AutowiredAnnotationBeanPostProcessor,它是BeanPostProcessor,用于解决依赖注入的 if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor. class ); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } |
三、配置类解析
上面说到在refreshContex中的第五步时,会调用容器中的BeanFactoryPostProcessor
的postProcessBeanDefinitionRegistry方法。其中有一个是ConfigurationClassPostProcessor,这是我们解析自动配置类的入口,下面分析其postProcessBeanDefinitionRegistry方法。
1、配置类解析流程概述
1 2 3 4 5 | @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { // 删掉一些非关键代码 processConfigBeanDefinitions(registry); } |
下面的processConfigBeanDefinitions方法就是对应上图中的步骤1、2、3、4、5,其中步骤4和步骤5比较长,单独拆出来分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<beandefinitionholder> configCandidates = new ArrayList<>(); // 获取容器中已注册的bean名字,见下图,注意,这里容器中这些BeanDefinition都是容器初始化过程中容器添加进去的 // 不是我们业务代码的beanDefinition,这段代码其实是连贯的,为了注释图片方便才分开 String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { // 获取BeanDefinition BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 判断这个BeanDefinition的configurationClass属性是不是full或者lite,如果是认为已经处理过了,第一次时默认为空, // 走下面分支 if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { // 打印日志记录下 } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this .metadataReaderFactory)) { // 1) 下面先分析下这个checkConfigurationClassCandidate方法,这边看方法名也可以猜到是检测该类是不是配置类 // 是配置类的意思就是它会给容器中引入bean,这个方法判断主要就是看这个类的元信息中有没有@Configuration注解 // 有没有@Component注解、有没有@ComponentScan、@Import、@ImportResource注解,有没有@Bean方法 // 构造一个BeanDefinitionHolder,放入configCandidates中 configCandidates.add( new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found // 上图中容易默认已经引入了7个BeanDefinition,经过上面检测发现默认就一个符合条件的配置类,即我们的主配置类 // 这里面configCandidates就一个,就是SpringBootApplication if (configCandidates.isEmpty()) { return ; } // Sort by previously determined @Order value, if applicable // 排序 configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // 删掉部分代码 // Parse each @Configuration class // 配置类解析工具 ConfigurationClassParser parser = new ConfigurationClassParser( this .metadataReaderFactory, this .problemReporter, this .environment, this .resourceLoader, this .componentScanBeanNameGenerator, registry); // 待处理集合 Set<beandefinitionholder> candidates = new LinkedHashSet<>(configCandidates); // 已处理集合 Set<configurationclass> alreadyParsed = new HashSet<>(configCandidates.size()); // 循环处理直到candidates.isEmpty() do { // 这边开始解析,对应步骤4 parser.parse(candidates); parser.validate(); // 取出第四步解析得到的一些configurationClasses集合 Set<configurationclass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // 删除一部分代码 // 这边也会去加载BeanDefinition,对应图中步骤五 this .reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); } while (!candidates.isEmpty()); } |
2、检测是否是配置类
在配置类解析流程图中,第二步,会获取容器中已经注册的BeanDefinition,放入candidateNames中,然后依次遍历这些BeanDefinition,判断它有没有被处理过,如果处理过就不管,否则通过checkConfigurationClassCandidate方法去判断它是不是配置类,判断方法如下。通过阅读这段代码,发现如果一个类上面有@Configuration注解、或者有@Component、@ComponentScan、@Import、@ImportResource注解、或者有@Bean标注的方法,则认为它是一个配置类。默认情况下,走到这里时最终只有一个candidateName符合,它是我们的主配置类,也就是SpringbootApplication这个Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this .metadataReaderFactory); public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { String className = beanDef.getBeanClassName(); // 获取下类名,如果类名为空或者该类为工厂类 if (className == null || beanDef.getFactoryMethodName() != null ) { return false ; } // 获取类的元数据信息 AnnotationMetadata metadata; // 上图的7个candidateNames中只有一个springbootApplication是AnnotatedBeanDefinition,其余全返回false if (beanDef instanceof AnnotatedBeanDefinition && className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) { // Can reuse the pre-parsed metadata from the given BeanDefinition... // springbootApplication走到这里 metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata(); } else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { // Check already loaded Class if present... // since we possibly can't even load the class file for this Class. Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass(); metadata = new StandardAnnotationMetadata(beanClass, true ); } else { try { MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className); // 读取类的元数据信息,这里面包括注解等信息 metadata = metadataReader.getAnnotationMetadata(); } catch (IOException ex) { return false ; } } // metadata.isAnnotated(Configuration.class.getName()),这个就是判断类上面有没有@Configuration注解 if (isFullConfigurationCandidate(metadata)) { // 如果true的话设置下这个属性,那么就标记为处理过了 beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (isLiteConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { // 其余6个返回false return false ; } // It's a full or lite configuration candidate... Let's determine the order value, if any. Integer order = getOrder(metadata); if (order != null ) { // 获取下类上的@Order信息 beanDef.setAttribute(ORDER_ATTRIBUTE, order); } return true ; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { return false ; } // Any of the typical annotations found? for (String indicator : candidateIndicators) { // 判断下类上面有没有这几个注解 if (metadata.isAnnotated(indicator)) { return true ; } } // Finally, let's look for @Bean methods... try { // 判断有没有@Bean的方法 return metadata.hasAnnotatedMethods(Bean. class .getName()); } return false ; } } private static final Set<string> candidateIndicators = new HashSet<>(); static { candidateIndicators.add(Component. class .getName()); candidateIndicators.add(ComponentScan. class .getName()); candidateIndicators.add(Import. class .getName()); candidateIndicators.add(ImportResource. class .getName()); } |
3、步骤四解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | public void parse(Set<beandefinitionholder> configCandidates) { // 删除部分代码,实际执行时这里的configCandidates就一个springBootApplication代表的主配置类 for (BeanDefinitionHolder holder : configCandidates) { // 获取BeanDefinition BeanDefinition bd = holder.getBeanDefinition(); // 我们的SpringBootApplication会走到这边,下面先分析这边 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } // (**)处这边也要留意下,这边会处理DeferredImportSelector,我们前面说的AutoConfigurationImportSelector就是在这边处理 // 给容器中导入xxxAutoConfiguration this .deferredImportSelectorHandler.process(); } protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass( new ConfigurationClass(metadata, beanName)); } protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { // 根据当前类上面的@Conditional注解标注的条件判断是否要解析这个配置类 if ( this .conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return ; } // 以configClass作为key去获取,第一次来肯定是获取不到的,走下面逻辑 ConfigurationClass existingClass = this .configurationClasses.get(configClass); if (existingClass != null ) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return ; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); this .knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy. // 这一步其实没有做啥,重点还是看下一步骤 SourceClass sourceClass = asSourceClass(configClass); do { // 这里是重点,里面具体分为8大步骤,单独拿一小节分析 // b) doProcessConfigurationClass sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null ); // 放入configurationClasses中 this .configurationClasses.put(configClass, configClass); } |
1 2 3 4 5 6 7 8 9 10 | // 上面的asSourceClass最终其实就是封装了一个SourceClass对象 public SourceClass(Object source) { this .source = source; if (source instanceof Class) { this .metadata = new StandardAnnotationMetadata((Class<?>) source, true ); } else { this .metadata = ((MetadataReader) source).getAnnotationMetadata(); } } |
下面这个doProcessConfigurationClass具体分为8个小步骤去解析,对应步骤四种的A-H步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // 判断这个类上面有没有@Component注解 if (configClass.getMetadata().isAnnotated(Component. class .getName())) { // Recursively process any member (nested) classes first // 如果有的话,遍历其内部类,然后也是调用doProcessConfigurationClass递归处理 processMemberClasses(configClass, sourceClass); } // Process any @PropertySource annotations // 处理PropertySource注解,之前讲解属性配置也分析过,就是将该注解对应的属性文件加载到Environment中 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources. class , org.springframework.context.annotation.PropertySource. class )) { if ( this .environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } } // Process any @ComponentScan annotations // 处理@ComponentScan注解,将其指定的包下的bean注册到框架中 Set<annotationattributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans. class , ComponentScan. class ); if (!componentScans.isEmpty() && ! this .conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<beandefinitionholder> scannedBeanDefinitions = this .componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null ) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this .metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations // 处理Import注解 processImports(configClass, sourceClass, getImports(sourceClass), true ); // Process any @ImportResource annotations // 处理@ImportResource注解,可以通过它来指定xml文件,BeanFactory就会读取这个xml文件将bean注册进去 AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource. class ); if (importResource != null ) { String[] resources = importResource.getStringArray( "locations" ); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass( "reader" ); for (String resource : resources) { String resolvedResource = this .environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods // 处理我们的类中使用@Bean注解的方法,添加到configClass的beanMethod中 Set<methodmetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod( new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces // 处理接口的默认方法,遍历这个类的接口,判断有没有使用@Bean注解的非抽象方法,添加到configClass的beanMethod中 processInterfaces(configClass, sourceClass); // Process superclass, if any // 递归处理父类,这边返回父类上层方法会递归处理 if (sourceClass.getMetadata().hasSuperClass()) { // 判断父类不为null且不在knownSuperclasses中且不以Java开头 String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith( "java" ) && ! this .knownSuperclasses.containsKey(superclass)) { this .knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null ; } |
(1)处理内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | if (configClass.getMetadata().isAnnotated(Component. class .getName())) { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass); } // Register member (nested) classes that happen to be configuration classes themselves. private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { Collection<SourceClass> memberClasses = sourceClass.getMemberClasses(); // 判断是否有内部类,没有的话直接不处理 if (!memberClasses.isEmpty()) { List<SourceClass> candidates = new ArrayList<>(memberClasses.size()); for (SourceClass memberClass : memberClasses) { // 判断是否是配置类,判断也很简单,之前分析过,判断类上面有没有@Configuration注解、@Import、@ImportResource // @Component、@ComponentScan以及@Bean标注的方法 if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) { // 加入到candidates中然后排个序 candidates.add(memberClass); } } OrderComparator.sort(candidates); for (SourceClass candidate : candidates) { // 防止A引入防止A引入B,B引入A if ( this .importStack.contains(configClass)) { this .problemReporter.error( new CircularImportProblem(configClass, this .importStack)); } else { this .importStack.push(configClass); try { // 放入栈中并遍历处理这些配置类,也是递归处理,调用之前的doProcessConfigurationClass处理这个配置类 processConfigurationClass(candidate.asConfigClass(configClass)); } finally { this .importStack.pop(); } } } } } |
(2)处理@PropertySource注解
1 2 3 | @SpringBootApplication @PropertySource ({ "demo.properties" }) public class Springboot2Application { |
1 2 3 4 5 6 7 8 | for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources. class , org.springframework.context.annotation.PropertySource. class )) { if ( this .environment instanceof ConfigurableEnvironment) { // 这边就不进去看了,主要是读取@PropertySource注解指定的文件,将其封装成一个属性集放入到环境中 processPropertySource(propertySource); } } |
(3)处理@ComponentScan注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans. class , ComponentScan. class ); if (!componentScans.isEmpty() && ! this .conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately // 下面先分析这个parse方法 Set<BeanDefinitionHolder> scannedBeanDefinitions = this .componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null ) { bdCand = holder.getBeanDefinition(); } // 如果是配置类,再递归处理 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this .metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner( this .registry, componentScan.getBoolean( "useDefaultFilters" ), this .environment, this .resourceLoader); Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass( "nameGenerator" ); boolean useInheritedGenerator = (BeanNameGenerator. class == generatorClass); // 设置一个bean名字生成器,默认就是使用org.springframework.beans.factory.support.BeanNameGenerator scanner.setBeanNameGenerator(useInheritedGenerator ? this .beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); // 就是默认的 ScopedProxyMode scopedProxyMode = componentScan.getEnum( "scopedProxy" ); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass( "scopeResolver" ); // 理解是元数据解析器 scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } // 设置下扫描的资源模式,是**/*.class scanner.setResourcePattern(componentScan.getString( "resourcePattern" )); // 添加IncludeFilter和ExcludeFilter for (AnnotationAttributes filter : componentScan.getAnnotationArray( "includeFilters" )) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray( "excludeFilters" )) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } // 设置是否懒加载 boolean lazyInit = componentScan.getBoolean( "lazyInit" ); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit( true ); } // 解析扫描的包路径加入到basePackages中 Set<String> basePackages = new LinkedHashSet<>(); String[] basePackagesArray = componentScan.getStringArray( "basePackages" ); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray( this .environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } for (Class<?> clazz : componentScan.getClassArray( "basePackageClasses" )) { // 解析basePackageClasses所在的包并加入到basePackages basePackages.add(ClassUtils.getPackageName(clazz)); } // 如果是空的,将声明该注解所在的类的包加入到basePackages if (basePackages.isEmpty()) { // 通常我们的主配置类是没有声明包扫描的路径的,所以这里会将主配置类所在的包加到这里面 basePackages.add(ClassUtils.getPackageName(declaringClass)); } // 添加一个ExcludeFilter,跳过声明该注解的类 scanner.addExcludeFilter( new AbstractTypeHierarchyTraversingFilter( false , false ) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified" ); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); // 遍历所有的包路径 for (String basePackage : basePackages) { // 获取该包下面所有符合条件的BeanDefinition,然后遍历处理,下面会分析 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this .scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); // 通过beanNameGenerator生成beanName String beanName = this .beanNameGenerator.generateBeanName(candidate, this .registry); // 这两个if判断逻辑比较简单,就是设置一些Lazy、DependsOn属性 if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // 这边是检查下有没有之前定义过这个BeanDefinition if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this .registry); // 将该beanDefinition加入到集合中,并注册到容器中 beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this .registry); } } } return beanDefinitions; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { // 扫描指定包路径及其子包下面的class文件,将其封装成Resource对象 // classpath*:com/lwh/springboot/**/*.class String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this .resourcePattern; Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); for (Resource resource : resources) { if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); if (isCandidateComponent(sbd)) { candidates.add(sbd); } } } } } } return candidates; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 通过之前设置的几个filter进行过滤 protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this .excludeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return false ; } } for (TypeFilter tf : this .includeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return isConditionMatch(metadataReader); } } return false ; } |
(4)处理@Import注解
1 2 3 | // Process any @Import annotations // getImports方法就是去递归扫描configClass上面所有的注解,将@Import注解标注的值放入importCandidates中,见下图 processImports(configClass, sourceClass, getImports(sourceClass), true ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return ; } if (checkForCircularImports && isChainedImportOnStack(configClass)) { this .problemReporter.error( new CircularImportProblem(configClass, this .importStack)); } else { this .importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { // 依次遍历判断类型 // 其中有一个是这个类型,@Import(AutoConfigurationImportSelector.class) // 这个就是自动配置原理,导入xxxAutoConfiguration这些类 if (candidate.isAssignable(ImportSelector. class )) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); // 实例化并调用xxxAware的方法并注入相关属性 ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector. class ); ParserStrategyUtils.invokeAwareMethods( selector, this .environment, this .resourceLoader, this .registry); // 它是DeferredImportSelector类型的 if (selector instanceof DeferredImportSelector) { // deferredImportSelectors = new ArrayList<>() // 这边会将两个参数封装下加入到deferredImportSelectors中,后面处理 // 加入到deferredImportSelectors中后,具体的处理是this.deferredImportSelectorHandler.process(); this .deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { // 不是的话获取@Import导入的类名数组 String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); // 然后再递归处理 processImports(configClass, currentSourceClass, importSourceClasses, false ); } } // @Import(AutoConfigurationPackages.Registrar.class),我们的主配置类上面的注解就是这个类型 // 这个是用于导入主配置类所在包及其子包下的BeanDefinition else if (candidate.isAssignable(ImportBeanDefinitionRegistrar. class )) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar. class ); ParserStrategyUtils.invokeAwareMethods( registrar, this .environment, this .resourceLoader, this .registry); // 这边就是将这两个参数作为key,value放入了一个map中 // this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this .importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); // 当做一个普通类处理,判断是不是配置类,递归处理 processConfigurationClass(candidate.asConfigClass(configClass)); } } } finally { this .importStack.pop(); } } } |
(5)处理@ImportSource注解
1 2 3 4 | // 这种就是Spring中常用的通过XML形式注入的方式 @SpringBootApplication @ImportResource ( "test.xml" ) public class Springboot2Application { |
(6)处理@Bean标注的方法
1 2 3 4 5 6 7 8 9 | // Process individual @Bean methods // 获取当前类中的Bean方法 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { // 这边也是加入到set中,见下面代码,也是在步骤五的3)中进行处理 configClass.addBeanMethod( new BeanMethod(methodMetadata, configClass)); } private final Set<BeanMethod> beanMethods = new LinkedHashSet<>(); |
(7)处理默认方法
1 2 3 4 5 6 7 8 | // 默认方法举例,主配置类实现这个接口就可以 public interface ConfigurationInterface { @Bean default Pig pig(){ return new Pig(); } } |
(8)递归处理父类
4、步骤五解析
1 2 3 4 5 6 7 | // 上面分析的是配置类解析的步骤四 parser.parse(candidates); // 下面来看第五步,步骤四中解析得到的配置类会放在configurationClasses中 Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); this .reader.loadBeanDefinitions(configClasses); |
1 2 3 4 5 6 | public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { // 判断是否应该跳过 if (trackedConditionEvaluator.shouldSkip(configClass)) { return ; } // 向容器中注入这个配置类所代表的的BeanDefinition if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } // 遍历这个配置类的所有的Bean方法,注入这些@Bean标注的方法要引入的BeanDefinition for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } // 处理该配置类上的@ImportResource指定的配置文件,就是Spring中常用的XML配置方式 // 这边会通过解析该XML文件给容器中注入BeanDefinition,SpringBoot中不推荐这种方式,具体就不往下分析了 loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); // 处理步骤四中D解析@Import注解时获得的importBeanDefinitionRegistrars,调用其registerBeanDefinitions // 方法给容器中注入BeanDefinition,这在前面讲解@Import方式时已经讲过了 loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); } |
1 2 3 | private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) { registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this .registry)); } |
四、总结
至此本文对SpringBoot的配置类解析就基本讲完了,简单回顾一下:
- 第一部分介绍了SpringBoot解析配置类时会用到的一些类和注解,讲解了它们的使用及原理。
- 第二部分介绍了SpringBoot启动流程,引出了SpringBoot在哪一步解析配置类。
- 第三部分介绍了SpringBoot解析配置类的8大步骤。
更多内容敬请关注vivo 互联网技术微信公众号
注:转载文章请先与微信号:Labs2020联系。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)