在本专题博客(五)中讲解了BeanFactoryPostProcessor及子子类BeanDefinitionRegistryPostProcessor,我们讲过spring初始化过程中会默认注册几个后置处理器,在refresh()正式启动spring容器过程中会调用默认注册的后置处理器,其中有一个后置处理器叫
ConfigurationClassPostProcessor
,在sping中扮演着最最最重要的角色,本篇博文我们详细讲解该后置处理器。
问题
- @Configuration注解的作用是什么,Spring是如何解析加了@Configuration注解的类?
- Spring在什么时候对@ComponentScan、@ComponentScans注解进行了解析?
- Spring什么时候解析了@Import注解,如何解析的?
- Spring什么时候解析了@Bean注解?
作用
ConfigurationClassPostProcessor是一个BeanFactory的后置处理器,因此它的主要功能是参与BeanFactory的建造,在这个类中,会解析加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。
完成扫描啊!你说重要不重要!!!
注册时机
虽然之前讲过ConfigurationClassPostProcessor
在哪里注册的,但为保证本篇博文的完整性,笔者简单谈下这个问题,我们启动一个spring:
@ComponentScan("com")
public class Config {
}
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//注册配置类
context.register(Config.class);
context.refresh();
}
}
new AnnotationConfigApplicationContext();
会调用构造函数:
public AnnotationConfigApplicationContext() {
AnnotatedBeanDefinitionReader
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
跟进new AnnotatedBeanDefinitionReader(this);
的构造函数
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(environment, "Environment must not be null");
this.registry = registry;
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
/**
* registerAnnotationConfigProcessors
* 根据名字顾名思义就是->注册注解配置的的处理器
* 也就是这个方法里面会注册一些用于处理注解的处理器
*/
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
就是注册默认的后置处理器,这个方法跟到底,一直到registerAnnotationConfigProcessors
这个方法,这个方法里面就是真正的注册各种内置的后置处理器,我们找到这段代码:
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));
}
这段代码就是注册ConfigurationAnnotationProcessor
后置处理器哒。最后一行代码中的registerPostProcessor
方法完成向bean工厂注册功能:
private static BeanDefinitionHolder registerPostProcessor(
BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {
/**
* registry就是AnnotationApplicationContext
* 这里是调用父类GenericApplicationContext中的registerBeanDefinition方法
* 调用beanFactory将spring默认的BeanDefinition注册进去
*/
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(beanName, definition);
return new BeanDefinitionHolder(definition, beanName);
}
调用时机
context.refresh();
完成spring中的启动过程,根据去找到这行代码invokeBeanFactoryPostProcessors(beanFactory)
,调用所有注册的beanFactory后置处理器,继续跟进去:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
//getBeanFactoryPostProcessors获取后置处理器
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
跟进第一行代码invokeBeanFactoryPostProcessors
,这个方法实现了spring后置处理器的调用,包括spring内置的和程序员提供的,这个调用顺序在(五)中详细讨论了,这里我们只讨论ConfigurationClassPostProcessor
调用时机。
往下找到下面的代码:
//查询BeanDefinitionRegistryPostProcessor的实现类
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
//添加到数组中,后续进行遍历回调
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
//存储BeanDefinitionRegistryPostProcessor实现类的名字
processedBeans.add(ppName);
}
}
//对BeanDefinitionRegistryPostProcessor设置调用顺序
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
//执行BeanDefinitionRegistryPostProcessor
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
这段代码就是完成ConfigurationClassPostProcessor
后置处理器的调用!首先,查找实现BeanDefinitionRegistryPostProcessor
接口的bean,这里其实就有一个,就是ConfigurationClassPostProcessor
,然后判断是否还实现了PriorityOrdered
接口,ConfigurationClassPostProcessor
确实实现了这个接口,然后放到集合中,在下面的invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
方法里完成ConfigurationClassPostProcessor
的调用,我们看下这个方法:
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
没错,调用ConfigurationClassPostProcessor
的postProcessBeanDefinitionRegistry
方法,后续代码继续调用postProcessBeanFactory
方法。Spring在启动过程中,最先调用的是ConfigurationClassPostProcessor
后置处理器,改处理器通过以上两个方法完成扫描和注册。
类继承关系
ConfigurationClassPostProcessor
实现了 BeanDefinitionRegistryPostProcessor
接口,而 BeanDefinitionRegistryPostProcessor
接口继承了 BeanFactoryPostProcessor
接口,所以ConfigurationClassPostProcessor
中需要重写 postProcessBeanDefinitionRegistry()
方法和postProcessBeanFactory()
方法。而ConfigurationClassPostProcessor
类的作用就是通过这两个方法去实现的。
核心方法
上文讲过,spring启动过程中先调用ConfigurationClassPostProcessor
类的postProcessBeanDefinitionRegistry(registry)
方法,再调用postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
方法,我们来详细讨论这两个方法完成的功能。
postProcessBeanDefinitionRegistry
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 一个工厂的后置处理器只会执行一次
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
//跟进
processConfigBeanDefinitions(registry);
}
核心逻辑在processConfigBeanDefinition()方法中,这个方法完成了扫描注册,特别难,笔者看了好几遍,非常的核心,读者务必多读几遍,大家先大体浏览下。看完后我带大家详细分析下,很难,做好心理准备吧。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
/**
* registry就是DefaultListableBeanFactory,DefaultListableBeanFactory是bean工厂拉,之前的博文讲的太多拉
* 获取注册的所有beanName
*/
String[] candidateNames = registry.getBeanDefinitionNames();
/**
* 循环处理所有BeanDefinition,找到配置类
* 比如下面配置类,找到后spring就知道从哪里扫描啦
* @ComponentScan("com")
* public class Config { }
*/
for (String beanName : candidateNames) {
// 根据beanName获得BeanDefinition
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
/*
* BeanDefinition中有这么一个变量 Map<String, Object> attributes = new LinkedHashMap<>();
* 用来存储属性值,如果该attributes中存在configurationClass这个键,且对应的值是full或者lite
* 意味着已经处理过了,直接跳过
* 后面处理BeanDefinition时,会给bd设置一个属性(key为configurationClass,value为full或者lite)
*/
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
// checkConfigurationClassCandidate()会判断一个是否是一个配置类,并为BeanDefinition设置属性为lite或者full。
// 在这儿为BeanDefinition设置lite和full属性值是为了后面在使用
// 如果加了@Configuration,那么对应的BeanDefinition为full;
// 如果加了@Bean,@Component,@ComponentScan,@Import,@ImportResource这些注解,则为lite。
//lite和full均表示这个BeanDefinition对应的类是一个配置类
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
//添加到集合中,后面统一处理,BeanDefinitionHolder就是对BeanDefinition和名字简单封装而已
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
//如果没有配置类,就返回拉,啥也不执行了
if (configCandidates.isEmpty()) {
return;
}
//将配置类进行排序
//我们可以提供多个配置类,谈后在配置类上添加@Order注解决定配置类的先后调用顺序
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
/**
* 判断是否有自定义的beanName生成器,没有话就用默认的
* 因为后面会扫描出所有加入到spring容器中calss类,然后把这些class
* 解析成BeanDefinition类,此时需要利用BeanNameGenerator为这些BeanDefinition生成beanName
* 默认是类名首字母小写
*/
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
/**
* 实例化ConfigurationClassParser 为了解析各个配置类(带@Configuration注解的类)
* 初始化ConfigurationClassParser的一些属性
*/
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
/**
* 实例化两个set
* candidates用于将之前加入的configCandidates去重
* alreadyParsed用于判断是否处理过了
*/
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 开始扫描/注册包下的类
// 在此处会解析配置类上的注解(ComponentScan扫描出的类,@Import注册的类,以及@Bean方法定义的类)
// 注意:这一步只会将加了@Configuration注解以及通过@ComponentScan注解扫描的类才会加入到BeanDefinitionMap中
// 通过其他注解(例如@Import、@Bean)的方式,在parse()方法这一步并不会将其解析为BeanDefinition放入到BeanDefinitionMap中,而是先解析成ConfigurationClass类
// 真正放入到map中是在下面的this.reader.loadBeanDefinitions()方法中实现的
parser.parse(candidates);
parser.validate();
/**
* 获取在扫描时put进去的configurationClasses
*/
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);//去掉已经解析完成的
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
//在这里统一处理,没有注册的进行注册
// 将上一步parser解析出的ConfigurationClass类加载成BeanDefinition
// 实际上经过上一步的parse()后,解析出来的bean已经放入到BeanDefinition中了,但是由于这些bean可能会引入新的bean,例如实现了ImportBeanDefinitionRegistrar或者ImportSelector接口的bean,或者bean中存在被@Bean注解的方法
// 因此需要执行一次loadBeanDefinition(),这样就会执行ImportBeanDefinitionRegistrar或者ImportSelector接口的方法或者@Bean注释的方法
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
// 这里判断registry.getBeanDefinitionCount() > candidateNames.length的目的是为了知道reader.loadBeanDefinitions(configClasses)这一步有没有向BeanDefinitionMap中添加新的BeanDefinition
// 实际上就是看配置类(例如AppConfig类会向BeanDefinitionMap中添加bean)
// 如果有,registry.getBeanDefinitionCount()就会大于candidateNames.length
// 这样就需要再次遍历新加入的BeanDefinition,并判断这些bean是否已经被解析过了,如果未解析,需要重新进行解析
// 这里的AppConfig类向容器中添加的bean,实际上在parser.parse()这一步已经全部被解析了
// 所以为什么还需要做这个判断,目前没看懂,似乎没有任何意义。
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
// 如果有未解析的类,则将其添加到candidates中,这样candidates不为空,就会进入到下一次的while的循环中
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
方法伊始,我们拿到容器中所有的BeanDefinition的名字,包括系统内置的后置处理器及我们提供的Config
配置类
紧接着,循环遍历这些BeanDefinition,过滤掉spring内置的后置处理器,留下我们提供的配置类,解析配置类,完成扫描和注册,这个配置类只能解析使用一次。那么spring如何过滤出我们提供的配置类以及如何保证只解析使用一次呢?
保证使用一次很简单,只要第一次解析使用完成后添加一个标志即可。BeanDefinition维护一个变量, Map<String, Object> attributes = new LinkedHashMap<>();用来存储属性值,如果该attributes中存在configurationClass这个键,且对应的值是full或者lite意味着已经处理过了,直接跳过。下面的源码就是判断是否有该键值的:
private static final String CONFIGURATION_CLASS_FULL = "full";
private static final String CONFIGURATION_CLASS_LITE = "lite";
public static boolean isFullConfigurationClass(BeanDefinition beanDef) {
return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
}
public static boolean isLiteConfigurationClass(BeanDefinition beanDef) {
return CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
}
常量值full和lite表示什么意思下文会讲到。
最关键的是如何筛选出我们的配置类?答案就是根据类型。文章开头,我们通过代码往spring中添加我们的配置类context.register(Config.class);
,我们跟进这行代码看spring是如何把Config转成BeanDefinition的,一直跟到doRegisterBean
方法:
//beanClass就是Config.class
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
... 中间省略...
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
由此可见Config被转成了AnnotatedGenericBeanDefinition类型,还记得之前讲过的BeanDefinition继承图吗,笔者不厌其烦的再次祭出BeanDefinition的继承图:
AnnotatedGenericBeanDefinition在最底层右面第二个的位置。上文中的“注册时机”我们能看到,spring内置的后置处理器都是RootBeanDefinition类型的。由此可见AnnotatedGenericBeanDefinition和RootBeanDefinition没有半毛钱关系,我们自然通过instanceof类型匹配关键字过滤出我们的配置类了!分析过滤的源码:
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
跟进checkConfigurationClassCandidate
这行代码,这行代码的意思是找到我们的配置类并获取到他的注解信息。如果注解包含了@Configuration,则设置属性键值对configurationClass=full
,否则判断注解是否包含@Component、@ComponentScan、@Import、@ImportResource、有@Bean注解的方法其中之一,如果是则设置键值对configurationClass=lite
,如果都不是则返回false,说明Config不是配置类。看源码:
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
//获取class名字,这里是com.config.Config
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
AnnotationMetadata metadata;
//判断BeanDefinition是否是AnnotatedBeanDefinition类型,AnnotatedGenericBeanDefinition实现了AnnotatedBeanDefinition
//我们的配置类Config对应的BeanDefinition确实就是AnnotatedBeanDefinition类型
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
//获取配置类的注解信息
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
//如果BeanDefinition是AbstractBeanDefinition并且包装了业务类,RootBeanDefinition继承了如果BeanDefinition是AbstractBeanDefinition
//spring内置的后置处理器确实是AbstractBeanDefinition类型的
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 {//既不是AnnotatedBeanDefinition类型也不是AbstractBeanDefinition类型,或者是AbstractBeanDefinition类型但没有包装业务类
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
}
//判断注解中是否包含了@Configuration注解
if (isFullConfigurationCandidate(metadata)) {
//设置属性,相当于beanDef.setAttribute("configurationClass","full")
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
//判断注解中是否包含了@Component、ComponentScan、Import、ImportResource其中之一,或者是否有@Bean注解的方法。
else if (isLiteConfigurationCandidate(metadata)) {
//设置属性,相当于beanDef.setAttribute("configurationClass","lite")
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
//实际上,如果你打断点仔细运行,你会发现,sring内置后置处理器都没有上面那些注解
return false;
}
//如果有@Order注解,拿到注解的value值,并将值设置到属性中,后面执行会根据这个属性值的大小进行先后调用
Integer order = getOrder(metadata);
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}
由此可知,所谓的配置类就是要么含有@Configuration注解,要么含有@Component、@ComponentScan、@Import、@ImportResource其中之一,要么类中含有@Bean注解的方法。注意其中@Component包括他的子注解@Service、@Controller、@Repository。spring后续根据这些注解完成扫描和注册。
实际上上面的代码运行结束后,只有我们的配置类Config对应的BeanDefinition完成了属性值的设置,且返回true,最后添加到集合configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
。spring内置的后置处理器都没有上面所说的注解所以返回false,自然不会添加到集合中。源码中我也解释清楚了,希望读者仔细阅读思考,有疑问打在留言区一起探讨,我会一一回复。
继续往下看源码:
if (configCandidates.isEmpty()) {
return;
}
如果,我们没有提供配置类,直接返回,后续也不会扫描和注册。实际运用过程中,我们不可能让spring不扫描,难不成让我们手动封装BeanDefinition,可以,试问开发组有几个懂BeanDefinition的?所以这行代码永远不会执行。
继续看源码:
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
上面checkConfigurationClassCandidate
代码中的最后处理了@Order注解,将他的值放到了属性中,在这里,如果你有多个配置类,进行排序,后面会按照顺序进行处理。打个比方:
@ComponentScan("com")
@Order(1)
public class Config {
}
@Service
@Order(2)
public class Config2 {
}
public class SpringTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Config.class);
context.register(Config2.class);
context.refresh();
}
}
OK,继续看源码:
SingletonBeanRegistry sbr = null;
/**
* 由于当前传入的是DefaultListableBeanFactory是SingletonBeanRegistry的子类
*/
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
/**
* 判断是否有自定义的beanName生成器
*/
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
// 获取spring默认的beanName生成器,这里为空
if (generator != null) {
/**
* componentScanBeanNameGenerator与importBeanNameGenerator定义时就赋值了new AnnotationBeanNameGenerator()
* 如果spring有默认的beanName生成器,则重新赋值
*/
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
说实话,笔者不是很关心这几行源码,大概意思就是如果你提供了自定义的名字生成器那就用你的,否则spring用自己默认的名字生成器,我知道是Class类名首字母小写。读者可研究下名字生成器类AnnotationBeanNameGenerator,很简单。它实现了BeanNameGenerator接口,这个接口只有一个String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);方法,你可以实现这个接口生成自定义名字生成器。打个比方:
//自定义名字生成器
public class MyNameGenerator implements BeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return definition.getBeanClassName()+"源码之路";
}
}
public class SpringTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Config.class);
//创建一个名字生成器类
MyNameGenerator myNameGenerator = new MyNameGenerator();
//通过上面的源码可知,必须以单例的方式注册的bean工厂中
context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,myNameGenerator);
context.refresh();
System.out.println();
}
}
调试结果:
在读此篇博客之前,如果你要生成一个自定义名字生成器你怎么做?查百度对吧,百度没有呢?spring源码重要吗?不重要,工作中会用spring就行,我关注业务就行,谁会关注源码?谁会关注?你的技术领导会,你们的CTO会,因为他是建筑师,而你是建筑工人。我没有吹嘘我自己,我只是强调spring源码对你的整个职业生涯起着至关重要的作用。
不啰嗦,继续往下看:
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
标准资源环境,什么鬼,暂不解释,以后专门写一篇博客讲解环境抽象。
上面我们拿到了配置类集合configCandidates
,下一步就是解析处理这些配置类。
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Spring的工具类ConfigurationClassParser用于分析@Configuration注解的配置类,产生一组ConfigurationClass对象。下文会用到。
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
实例化两个set,,candidates用于将之前加入的configCandidates去重, alreadyParsed用于判断是否处理过了。
代码再往下就是开始循环do while 循环解析处理candidates配置类了。
//解释配置类
parser.parse(candidates);
跟进这个方法:
public void parse(Set<BeanDefinitionHolder> configCandidates) {
//遍历分析配置类
for (BeanDefinitionHolder holder : configCandidates) {
//得到配置类对应的BeanDefinition
BeanDefinition bd = holder.getBeanDefinition();
try {
// 这里根据Bean定义的不同类型走不同的分支,但是最终都会调用到方法
// processConfigurationClass(ConfigurationClass configClass)
// 判断是否是AnnotatedBeanDefinition类型,上文分析过了
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
// 执行找到的 DeferredImportSelector
// DeferredImportSelector 是 ImportSelector 的一个变种。
// ImportSelector 被设计成其实和@Import注解的类同样的导入效果,但是实现 ImportSelector
// 的类可以条件性地决定导入哪些配置。
// DeferredImportSelector 的设计目的是在所有其他的配置类被处理后才处理。这也正是
// 该语句被放到本函数最后一行的原因。
this.deferredImportSelectorHandler.process();
}
先关注里面的parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
方法,参数有两个,一个是注解元数据,一个bean的名称,继续跟进:
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
首先将注解元数据和bean名称封装成ConfigurationClass
,这个类翻译成中午就是“配置类”。继续跟进:
processConfigurationClass
中的第一行就是检查当前解析的配置bean是否包含Conditional注解,如果不包含则不需要跳过,如果包含了则进行match方法得到匹配结果,match方法返回true则跳过不解析,返回false不跳过。什么意思呢?我们继续打比方:
先生成一个业务类
public class Person {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
再创建一个配置类:
@Configuration
public class BeanConfig {
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
测试:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//注册配置类
context.register(Config.class);
context.refresh();
Map<String, Person> map = context.getBeansOfType(Person.class);
System.out.println(map);
}
}
打印结果:
{bill=Person{name='Bill Gates', age=62}, linus=Person{name='Linus', age=48}}
问题来了,如果我想根据当前操作系统来注入Person实例,windows下注入bill,linux下注入linus,怎么实现呢?这就需要我们用到@Conditional注解了。
首先,创建一个WindowsCondition类:
public class WindowsCondition implements Condition {
/**
* @param conditionContext:判断条件能使用的上下文环境
* @param annotatedTypeMetadata:注解所在位置的注释信息
* */
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//获取ioc使用的beanFactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//获取类加载器
ClassLoader classLoader = conditionContext.getClassLoader();
//获取当前环境信息
Environment environment = conditionContext.getEnvironment();
//获取bean定义的注册类
BeanDefinitionRegistry registry = conditionContext.getRegistry();
//获得当前系统名
String property = environment.getProperty("os.name");
//包含Windows则说明是windows系统,返回true
if (property.contains("Windows")){
return true;
}
return false;
}
}
接着,创建LinuxCondition类:
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
String property = environment.getProperty("os.name");
if (property.contains("Linux")){
return true;
}
return false;
}
}
接着就是使用这两个类了,因为此注解可以标注在方法上和类上,所以分开测试:
- 修改BeanConfig:
@Configuration
public class BeanConfig {
//只有一个类时,大括号可以省略
//如果WindowsCondition的实现方法返回true,则注入这个bean
@Conditional({WindowsCondition.class})
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
//如果LinuxCondition的实现方法返回true,则注入这个bean
@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
打印结果:{bill=Person{name='Bill Gates', age=62}}
,注意一个方法只能注入一个bean实例,所以@Conditional标注在方法上只能控制一个bean实例是否注入。
- 标注在类上:
一个类中可以注入很多实例,@Conditional标注在类上就决定了一批bean是否注入。我们试一下,将BeanConfig改写,这时,如果WindowsCondition返回true,则两个Person实例将被注入:
@Conditional({WindowsCondition.class})
@Configuration
public class BeanConfig {
//只有一个类时,大括号可以省略
//如果WindowsCondition的实现方法返回true,则注入这个bean
// @Conditional({WindowsCondition.class})
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
//如果LinuxCondition的实现方法返回true,则注入这个bean
// @Conditional({LinuxCondition.class})
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
@Conditional注解传入的是一个Class数组,存在多种条件类的情况。这种情况貌似判断难度加深了,测试一波,新增新的条件类,实现的matches返回false(这种写死返回false的方法纯属测试用,没有实际意义O(∩_∩)O)
public class ObstinateCondition implements Condition{
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return false;
}
}
BeanConfig修改一下:
@Conditional({WindowsCondition.class,ObstinateCondition.class})
@Configuration
public class BeanConfig {
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
请读者自行测试,这个例子就是说明processConfigurationClass
中的第一行代码的作用,如果不符合条件注册bean到IOC,否则不继续执行。
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
读到这里大家都知道,我们的配置类只有Config.java,我们在这个配置类上加一个Condition条件:
@Order(1)
@ComponentScan("com")
@Conditional({LinuxCondition.class})
public class Config {
}
讲道理,此时,上文中的判断为true,代码直接return,也就是说你这个配置类无效,spring不会继续解析你这个配置类了。但实际上,不会rerurn。为什么?spring启动过程中,我们是这样注册配置类的context.register(Config.class);
,这个register源码之前讲过,一直跟到doRegisterBean
中,有这么两行代码:
//@Conditional装配条件判断是否需要跳过注册
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
也就是说,我们在注册Config配置类的时候spring已经对Condition条件进行判断了,不会生成Config对应的BeanDefinition了,那怎么办?看:
// context.register(Config.class);
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(Config.class);
context.registerBeanDefinition("config",abd);
不让spring做判断,手动注册BeanDefinition,打断点调试:
好了,shouldSkip这行代码讲清楚了吧,全网没有我讲这么细的,你可以尽情搜百度,能找到我讲这么细的给我留言,我个你发红包。我说了,这部分内容很难,你能坚持看到这里而且你能看懂,最低阿里P6级别。
不吹了,继续看源码:
// 第一次进入的时候, configurationClasses size = 0,existingClass 肯定为 null
//在这里处理Configuration重复import
//如果同一个配置类被处理两次,两次都属于被import的则合并导入类,返回。如果配置类不是被导入的,则移除旧使用新的配置类
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
//如果要处理的配置类configClass在已经分析处理的配置类记录中已存在,
//合并二者的importedBy属性
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);
}
}
//递归地处理配置类及其超类层次结构。sourceClass包含了calss文件,后面获取父类用
SourceClass sourceClass = asSourceClass(configClass);
do {
// 真正的做解析
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
第一次调用existingClass 肯定为空,难道还有第二次调用?是的,@Import注解就是引入,先不管这个,代码会跳过判断,执行SourceClass sourceClass = asSourceClass(configClass);
,从当前配置类configClass开始向上沿着类继承结构逐层执行doProcessConfigurationClass,直到遇到的父类是由Java提供的类结束循环。然后就是真正的解析sourceClass = doProcessConfigurationClass(configClass, sourceClass);
,循环处理配置类configClass直到sourceClass变为null,doProcessConfigurationClass的返回值是其参数configClass的父类,如果该父类是由Java提供的类或者已经处理过,返回null。doProcessConfigurationClass()会对一个配置类执行真正的处理:
1. 一个配置类的成员类(配置类内嵌套定义的类)也可能适配类,先遍历这些成员配置类,调用processConfigurationClass处理它们;
2. 处理配置类上的注解@PropertySources,@PropertySource
3. 处理配置类上的注解@ComponentScans,@ComponentScan
4. 处理配置类上的注解@Import
5. 处理配置类上的注解@ImportResource
6. 处理配置类中每个带有@Bean注解的方法
7. 处理配置类所实现接口的缺省方法
8. 检查父类是否需要处理,如果父类需要处理返回父类,否则返回null
返回父类表示当前配置类处理尚未完成,调用者processConfigurationClass会继续处理其父类;返回null才表示该配置类的处理完成。从这里可以推断一旦一个配置类被processConfigurationClass处理完成,表示其自身,内部嵌套类,各个实现接口以及各级父类都被处理完成。
上面的步骤我们挨个分析,进入doProcessConfigurationClass
源码,首先处理的就是内部类:
// @Configuration 继承了 @Component
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 递归处理内部类,因为内部类也可能是一个配置类
//配置类上有@Configuration注解,该注解继承 @Component,if 判断为true,
// 调用processMemberClasses方法,递归解析配置类中的内部类。
processMemberClasses(configClass, sourceClass);
}
processMemberClasses
方法就是处理内部类,为什么要处理内部类?因为内部类也可能是一个配置类,打个比方:
@Order(1)
@Configuration
@ComponentScan("com.anno")
public class Config {
@Order(1)
@Configuration
@ComponentScan("com.scan")
class InnerClass{
}
@Order(2)
@Configuration
@ComponentScan("com.condition")
class InnerClass2{
}
}
进入到processMemberClasses
方法里面:
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) {
//判断内部类是不是配置类,上文讲过了如何判断是不是配置类
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
//对配置类进行那个排序,上文也讲过了
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates) {
if (this.importStack.contains(configClass)) {
//1)出现配置类循环导入,直接报错
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
//将配置类入栈
this.importStack.push(configClass);
try {
//还是调用processConfigurationClass方法,也许内部类还有内部类
//其实,工作中我们不会内部类嵌套内部类再嵌套内部类再嵌套。。。
processConfigurationClass(candidate.asConfigClass(configClass));
}
finally {
//解析完出栈
this.importStack.pop();
}
}
}
}
}
注释解释的很清楚,就是判断内部类是否是配置类,上文已经讲过如何判断是否是内部类了,如果是配置类还是要调用processConfigurationClass方法来处理,这是一个递归的过程,一直到没有内部配置类为止。
配置类内部类处理完,紧接着就是处理配置类上的注解@PropertySources,@PropertySource,如果配置类上有@PropertySource注解,则解析加载properties文件,并将属性添加到Spring上下文中。源码:
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
这段源码,小编没有分析过,我就不误人子弟了,但是这段代码的执行结果就是将我们自定义的资源文件添加到spring上下文环境中,我们通过一个例子来说明:
首先在resources中添加一个资源文件demo.properties:
demo.name=this is my properties
修改以下我们的配置类:
@Order(1)
@Configuration
@ComponentScan("com")
@PropertySource({"classpath:demo.properties"})
public class Config {
@Value("${demo.name}")
private String name;
public String getName() {
return name;
}
}
测试:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//注册配置类
context.register(Config.class);
context.refresh();
System.out.println(context.getBean(Config.class).getName());
}
}
打印结果:
demo.name=this is my properties
spring已经将配置文件加入到系统环境中了,在任何地方都可以通过@Value("${demo.name}")
方式进行调用拉!
OK,下一步处理配置类上的注解@ComponentScans,@ComponentScan。
第一步先获取@ComponentScans
和@ComponentScan
注解:
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
然后,开始循环遍历扫描注解,拿到注解里的值进行扫描注册:
// 备注:这个方法虽然有返回值,但是其实内部都已经把Bean定义信息加入到工厂里面去了
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
费这么大劲儿,终于要开始扫描了。进入parse方法,第一行代码创建一个ClassPathBeanDefinitionScanner
对象,这个东西我们在《逐行阅读Spring5.X源码(六) ClassPathBeanDefinitionScanner扫描器》和《逐行阅读Spring5.X源码(番外篇)自定义扫描器, Mybatis是如何利用spring完成Mapper扫描的》讲过。
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
我们知道,spring启动过程中会在构造方法中生成一个ClassPathBeanDefinitionScanner
对象:
手动扫描的时候就是利用这个对象完成扫描的:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//注册配置类
context.register(Config.class);
context.refresh();
context.scan("com");
}
}
@Override
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
this.scanner.scan(basePackages);
}
但是spring内部扫描的时候确又生成了这个对象,两个地方调用的构造函数不同,spring内部生成的这个扫描对象功能更丰富。我想spring是想给程序员提供扫描的功能,但是又不想给你更多的权限吧。
OK,下一行代码:
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
BeanName的生成器,我们可以单独制定。若不指定(大部分情况下都不指定),那就是默认的AnnotationBeanNameGenerator
,它的处理方式是:类名首字母小写。这个上面也讲过啦。
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));
}
这两个属性和scope代理相关的,这里略过,使用较少。
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
控制去扫描哪些.clsss文件的模版。一般不设置 默认值为:*/.class 全扫嘛
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);
}
}
之前讲过,就是扫描过滤器,如果你注解上写了过滤器,就复制到ClassPathBeanDefinitionScanner
扫描器中。
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
扫描的Bean,支持懒加载啦,默认为false。
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);
}
Spring在此处有强大的容错处理。虽然他是支持数组的,但是它这里也容错处理,支持 , ; 换行等的符号分隔处理,并且,并且更强大的地方在于:它支持${...}这种占位符的形式,非常的强大。我们可以动态的进行扫包了~~~~~厉害了我的哥。比如
@ComponentScan(value = "com,net" )
会根据逗号解析成com包和net包放到数组中。
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
basePackageClasses有时候也挺好使的。它可以指定class,然后这个class所在的包(含子包)都会被扫描,打个比方:
@Order(1)
@Configuration
@PropertySource({"classpath:demo.properties"})
@ComponentScan(value = "com,net",basePackageClasses = Person.class)
public class Config {
@Value("${demo.name}")
private String name;
public String getName() {
return name;
}
}
我们的Person类在com.scan.condition包下,调试结果:
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
如果我们没有指定此值,它会取当前配置类所在的包 ,是不是很熟悉?SpringBoot就是这么干的,第一次接触过springboot的时候,我就很奇怪为啥要把启动类放在根目录下。
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
这个行代码的意思增加一个排他过滤器,配置类本身不需要添加到容器中,他只是用作解析。
最后一行代码scanner.doScan(StringUtils.toStringArray(basePackages));
完成扫描,将符合规则的类添加到IOC容器中,在这里不深入分析了,前面讲了,我们在《逐行阅读Spring5.X源码(六) ClassPathBeanDefinitionScanner扫描器》详细讨论了,建议读者好好阅读这篇文章。
parse扫描解析讲完了,我们跳出parse方法继续往下看。
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());
}
}
parse扫描注册完成后,将生成的BeanDefinition返回,还需要把每个Bean检测一遍。因为Scan出来的Bean,还有可能是@Configuration的,因此需要再次交给parse一遍,防止疏漏,换句话说,扫描后的业务类中还有配置类,我们还需要处理这些配置类。getOriginatingBeanDefinition
返回原始的 BeanDefinition,没有的话返回 null,链式调用该方法,最终可获取到由用户定义的 BeanDefinition。
注解@ComponentScans,@ComponentScan讲完了,下一步看注解@Import的处理。
processImports(configClass, sourceClass, getImports(sourceClass), true);
processImports方法负责对@Import注解进行解析。configClass是配置类,sourceClass又是通过configClass创建的,getImports(sourceClass)从sourceClass获取所有的@Import注解信息,然后调用ConfigurationClassParser#processImports。
在应用中,有时没有把某个类注入到IOC容器中,但在运用的时候需要获取该类对应的bean,此时就需要用到@Import注解。spring首先把import进来的类当作配置类处理,然后递归处理该配置类。继续打比方:
首先创建一个业务类,且不要放到扫描包下,是不会扫描到IOC容器中的:
public class User {
}
然后import到配置类上
@Order(1)
@Configuration
@PropertySource({"classpath:demo.properties"})
@ComponentScan(value = "com,net",basePackageClasses = Person.class)
@Import(User.class)
public class Config {
@Value("${demo.name}")
private String name;
public String getName() {
return name;
}
}
我们看下processImports的源码怎么处理注解@Import的
if (importCandidates.isEmpty()) {
return;
}
这行代码的意思是如果没有@Import注解,就返回不做任何处理。
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
这行判断是解决循环引用的,暂不关心,以后会详细将循环引用问题。第一次的话执行else语句,然后拿出@Import注解中的所有类进行循环处理,这里只有User。if语句中有三种处理情况:
- 实现
ImportSelector
接口 - 实现
ImportBeanDefinitionRegistrar
接口 - 既没有实现
ImportSelector
接口也没有实现ImportBeanDefinitionRegistrar
接口
先看第一种 实现ImportSelector接口。
再看第二种实现ImportBeanDefinitionRegistrar接口。
最后看既没有实现ImportSelector
接口也没有实现ImportBeanDefinitionRegistrar
接口。
ImportSelector
接口:ImportSelector接口只定义了一个selectImports(),用于指定需要注册为bean的Class名称。当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。来看一个简单的示例,假设现在有一个接口HelloService,需要把所有它的实现类都定义为bean,而且它的实现类上是没有加Spring默认会扫描的注解的,比如@Component、@Service等。
首先让上文的User类实现ImportSelector接口,然后在方法中返回User2权限定名,这样spring就会将User2添加到容器中。
public class User2 {
}
public class User implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {User2.class.getName()};
}
}
我们看源码实现:
//如果实现了ImportSelector接口
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
// 反射创建这个类的实例对象
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
//是否有实现相关Aware接口,如果有,这调用相关方法
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
// 延迟加载的ImportSelector
if (selector instanceof DeferredImportSelector) {
// 延迟加载的ImportSelector先放到List中,延迟加载
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}else {
// 普通的ImportSelector ,执行其selectImports方法,获取需要导入的类的全限定类名数组
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归调用
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
代码很简单,相信大家能看懂,但是有这么行代码
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
判断是否有实现相关Aware接口,如果有,这调用相关方法。Aware,是感应和感知的意思。当bean实现了对应的Aware接口时,BeanFactory会在生产bean时根据它所实现的Aware接口,给bean注入对应的属性,从而让bean获取外界的信息。Aware是顶级接口,它有很多子接口:
如果我们被Import的User类实现了Aware接口,就会在上面invokeAwareMethods源码内调用实现该接口的方法,但是在此处不是所有有Aware接口都可以,invokeAwareMethods源码:
public static void invokeAwareMethods(Object parserStrategyBean, Environment environment,
ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
if (parserStrategyBean instanceof Aware) {
if (parserStrategyBean instanceof BeanClassLoaderAware) {
ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
if (classLoader != null) {
((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader);
}
}
if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry);
}
if (parserStrategyBean instanceof EnvironmentAware) {
((EnvironmentAware) parserStrategyBean).setEnvironment(environment);
}
if (parserStrategyBean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader);
}
}
}
只有BeanClassLoaderAware、BeanFactoryAware 、EnvironmentAware、ResourceLoaderAware接口才能被调用,其他Aware接口啥时候调用以后再说。举个例子:
public class User implements ImportSelector, BeanFactoryAware {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {User2.class.getName()};
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("获取到了bean工厂");
}
}
也就是说我们能在User实现接口的方法内操作以下方法:
Aware接口处理完后,就是判断User是否是延迟加载:
if (selector instanceof DeferredImportSelector)
显然,我们的User实现的ImportSelector接口,没有实现DeferredImportSelector接口,所以就不是延迟加载,DeferredImportSelector是ImportSelector的子接口。如果我们要实现延迟加载功能,让User实现DeferredImportSelector接口即可:
public class User implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("延迟加载User2");
return new String[] {User2.class.getName()};
}
}
启动spring,就会进入上面的if判断里的this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
方法,handle方法内会将selector再封装成DeferredImportSelectorHolder类型的对象,然后保存在数组中以后调用,调用时机之后再将,先记住这点。
如果不是延迟加载的话,if条件不执行,执行else中的语句:
// 普通的ImportSelector ,执行其selectImports方法,获取需要导入的类的全限定类名数组
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
////获取需要导入类的class
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归调用
processImports(configClass, currentSourceClass, importSourceClasses, false);
为什么要递归调用,因为你导入的类可能还有Import注解啊,那也要处理的。
ImportBeanDefinitionRegistrar
接口:ImportBeanDefinitionRegistrar接口是也是spring的扩展点之一,它可以支持我们自己写的代码封装成BeanDefinition对象;实现此接口的类会回调postProcessBeanDefinitionRegistry方法,注册到spring容器中。把bean注入到spring容器不止有 @Service @Component等注解方式;还可以实现此接口。我们继续打个比方,比方太惨了,全文被打:
public class User implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(User2.class.getName());
MutablePropertyValues values = beanDefinition.getPropertyValues();
values.addPropertyValue("id", 1);
values.addPropertyValue("name", "源码之路");
//这里注册bean
registry.registerBeanDefinition("testBean", beanDefinition );
}
}
其实,很小儿科,不讲了,在BeanDefinition博文中讲烂了。
最后,既没有实现ImportSelector接口也没有实现ImportBeanDefinitionRegistrar接口:
// 普通 @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 解析导入的@Configuration class
processConfigurationClass(candidate.asConfigClass(configClass));
按普通配置类处理拉~
@Import
注解处理暂时先告一段落,下面继续看@ImportResource
注解的处理。
@ImportResource
用来导入Spring 的配置文件,如核心配置文件 "beans.xml",从而让配置文件里面的内容生效。讲道理,大家平时都不用xml配置文件来做开发了吧,注解方式差不多完全替代XML方式了。这个也很简单,不讲了,这篇文章还能坚持看到这里的读者,想必有能力分析这块的源码了。注意,这块源码的处理并没有解析配置文件,而是先保存在map中,后续解析。先记着,整个方法分析完了你就知道了。
下一步,处理@Bean
注解的方法:
//找到含有@Bean注解的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
//先添加到集合中
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
将@Bean方法转化为BeanMethod对象,保存在集合中,后续再用。
下一步,处理配置类所实现接口的缺省方法:java8中,新增加了一个特性,接口中可以有默认实现方法:
public interface UserInterface {
/*
* 默认方法,有方法体
* 任何一个实现了UserInterface接口的类都会向动继承isEmpty的实现
*/
@Bean
default User2 getUser2() {
return new User2();
}
}
默认方法,有方法体,任何一个实现了Sized接口的类都会向动继承isEmpty的实现,这个默认方法加一个@Bean注解的话,且返回一个对象,spring会将这个对象注册到spring容器中。processInterfaces(configClass, sourceClass);
就是完成这个功能的。这个方法的源码也比较简单:
private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
//找到配置类的所有接口,遍历接口
for (SourceClass ifc : sourceClass.getInterfaces()) {
//找到含有@Bean注解的默认方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
for (MethodMetadata methodMetadata : beanMethods) {
if (!methodMetadata.isAbstract()) {
// A default method or other concrete method on a Java 8+ interface...
//添加到集合
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
}
//递归,因为接口可能有父接口
processInterfaces(configClass, ifc);
}
}
最后,最后,最后一步,如果有父类,则解析父类。坚持下!
if (sourceClass.getMetadata().hasSuperClass()) {
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();
}
}
如果有父类则返回父类Class对象,继续调用该方法。直到返回null,外层循环结束。怎么讲?doProcessConfigurationClass此时结束了,我们看调用此方法的上一层方法processConfigurationClass
以上省略
do {
// 真正的做解析
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
就是说,如果你有父类,此时返回来的sourceClass 是父类,继续递归调用doProcessConfigurationClass方法,直到返回的是null后while循环才结束,如果没有父类,则执行doProcessConfigurationClass方法的最后一行代码return null;
,此时的while循环也结束了,继续执行最后一行代码
this.configurationClasses.put(configClass, configClass);
这行代码的意思是,需要被处理的配置类configClass已经被分析处理,将它记录到已处理配置类记录。
至此,我们的processConfigurationClass
->doProcessConfigurationClass
方法分析完了,往回返,一直到入口处,即ConfigurationClassPostProcessor.processConfigBeanDefinitions
方法中parser.parse(candidates);
代码处,对吧,上面讲了那么多,就是讲了parse方法的处理。
我们继续分析parse的后续源码,坚持住,马上看到曙光了。
parser.validate();
将解析完的@Configuration配置类进行校验,主要包括两方面的校验:
- 配置类不能是final(CGLIB限制)
- @Configuration配置类中的@Bean方法必须可重写以容纳CGLIB
这是为CGLIB做准备,后面会开一篇博客专门将CGLIB,源码比较简单,这里不做分析啦。
继续:
this.reader.loadBeanDefinitions(configClasses);
在这里统一处理没有注册的进行注册,将上一步parser解析出的ConfigurationClass类加载成BeanDefinition,实际上经过上一步的parse()后,解析出来的bean已经放入到BeanDefinition中了,但是由于这些bean可能会引入新的bean,例如实现了ImportBeanDefinitionRegistrar或者ImportSelector接口的bean,或者bean中存在被@Bean注解的方法,因此需要执行一次loadBeanDefinition(),这样就会执行ImportBeanDefinitionRegistrar或者ImportSelector接口的方法或者@Bean注释的方法。是不是蒙蔽了这里,起始之前的源码都经历过了,建议大家收藏这篇博文,对照着源码反复看。
再有20多行代码就分析完了,加油!
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
// 如果有未解析的类,则将其添加到candidates中,这样candidates不为空,就会进入到下一次的while的循环中
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
这里判断registry.getBeanDefinitionCount() > candidateNames.length的目的是为了知道reader.loadBeanDefinitions(configClasses)这一步有没有向BeanDefinitionMap中添加新的BeanDefinition,实际上就是看配置类, 如果有,registry.getBeanDefinitionCount()就会大于candidateNames.length
,这样就需要再次遍历新加入的BeanDefinition,并判断这些bean是否已经被解析过了,如果未解析,需要重新进行解析,这里的Config类向容器中添加的bean,实际上在parser.parse()这一步已经全部被解析了,所以为什么还需要做这个判断,目前没看懂,似乎没有任何意义。
好了,到此为止,ConfigurationClassPostProcessor
中最牛逼的postProcessBeanDefinitionRegistry
的方法就分析完了,spring启动过程中,会自动调用改处理器的postProcessBeanDefinitionRegistry
方法完成扫描与注册。这篇博文,能看到这里的我想不会有,自己安慰一下自己吧,我觉着很多人没有耐心读到这里,spring源码太难讲了,调用关系很复杂。讲源码不能泛泛的讲,我也不知道咋讲好了。写这一篇博客,用了一个周的时间,回头看看感觉讲的还是有点乱,程序员喜欢看框图,一目了然,那就用框图总结一下吧:
·