之前的博文可能会设计到Spring的解析流程,但是一句二句说不清,只能一笔带过,今天整理一下这一块的流程,记录下来
具体方法在ConfigurationClassParser#processConfigurationClass方法中
上图有两个标注的方法,我们在这里约定一下:
我们看一下,相关源码
如果有不清楚什么是配置类的小伙伴,可以看我之前写的一篇博文 《Spring之什么是配置类》
这个注解主要是处理一些自定义的环境变量,我们举例看一下,使用方法
创建一个application.properties,添加属性ping
创建一个配置类AppConfig
package com.test.spring.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
/**
* 这里可以不用加@Configuration
* AnnotationConfigApplicationContext会将构造方法注入的类,解析成bd
*/
@ComponentScan("com.test.spring")
@PropertySource("classpath:application.properties")
public class AppConfig {
}
运行main方法,查看运行结果
详细可以看这篇博文 《Spring之为什么标记了@Component的类会被扫描成bean》
大概流程就是调用ComponentScanAnnotationParser的parse方法,这个方法会创建一个ClassPathBeanDefinitionScanner对象,这个scanner对象的构造方法的useDefaultFilters属相决定会不会注册默认Filter(@Component @Named @ManagedBean标注的类),然后将@ComponentScans @ComponentScan注解的属性解析并设置到这个scanner对象中,然后调用这个scanner对象的doScan方法,之后就查看这个scanner的Filters(自定义+默认)方法是否返回true,如果返回值为true,就会将指定资源解析成ScannedGenericBeanDefinition对象,然乎将这些bd对象返回,如果bd对象是配置类,则调用大循环方法进一步解析
我们来看关键方法ConfigurationClassParser#processImports
方法分成三个分支,分别处理实现ImportSelector接口,实现ImportBeanDefinitionRegistrar接口,普通配置类三种方式处理,具体处理流程,我将整理的图贴出来
对于这几种情况,我们写几个简单例子,了解具体用法
创建一个类ModelM
package com.test.spring.model;
public class ModelM {
}
创建一个ImportSelector子类实现
package com.test.spring.imports;
import com.test.spring.model.ModelM;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class FirstImport implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{ModelM.class.getName()};
}
}
配置类加上此import
package com.test.spring.config;
import com.test.spring.imports.FirstImport;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
/**
* 这里可以不用加@Configuration
* AnnotationConfigApplicationContext会将构造方法注入的类,解析成bd
*/
@ComponentScan("com.test.spring")
@PropertySource("classpath:application.properties")
@Import(FirstImport.class)
public class AppConfig {
}
运行main方法,查看运行结果
selectImports方法返回的类名 最终会被解析并实例化成bean
创建一个类ModelN
package com.test.spring.model;
public class ModelN {
}
创建一个ImportBeanDefinitionRegistrar子类实现
package com.test.spring.imports;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class SecondImport implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.test.spring.model.ModelN");
registry.registerBeanDefinition("modelN", beanDefinition);
}
}
配置类加上此import
package com.test.spring.config;
import com.test.spring.imports.FirstImport;
import com.test.spring.imports.SecondImport;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
/**
* 这里可以不用加@Configuration
* AnnotationConfigApplicationContext会将构造方法注入的类,解析成bd
*/
@ComponentScan("com.test.spring")
@PropertySource("classpath:application.properties")
@Import({FirstImport.class, SecondImport.class})
public class AppConfig {
}
这种方法很类似bfpp的功能
创建类ModelO modelP
package com.test.spring.model;
public class ModelO {
}
package com.test.spring.model;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class ModelP implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{ModelO.class.getName()};
}
}
创建一个ImportSelector子类实现
package com.test.spring.imports;
import com.test.spring.model.ModelP;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class ThirdImport implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{ModelP.class.getName()};
}
}
配置类加上此import
package com.test.spring.config;
import com.test.spring.imports.FirstImport;
import com.test.spring.imports.SecondImport;
import com.test.spring.imports.ThirdImport;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
/**
* 这里可以不用加@Configuration
* AnnotationConfigApplicationContext会将构造方法注入的类,解析成bd
*/
@ComponentScan("com.test.spring")
@PropertySource("classpath:application.properties")
@Import({FirstImport.class, SecondImport.class, ThirdImport.class})
public class AppConfig {
}
运行main方法,查看运行结果
ImportSelector子类实现如果import了一个ImportSelector子类实现,则Spring容器中只存在最后一次非ImportSelector子类实现类型的bean
这个@Import有两个注意点需要大家注意
@Import是Spring重要的扩展点之一,上面举的例子主要讲解用法,有兴趣的小伙伴可以扩展一下,比如说可以在import的类上加上@ComponentScan注解,去扫描一个Spring扫描不到的路径。
最近更新了一篇博文 《@Import在Spring中的应用之@EnableAsync》希望能帮助大家加深对注入流程的理解
略 以后有机会补上
这个主要就是查看资源没有@Bean标记的方法,如果存在就将结果设置到ConfigurationClass对象相关属性上
这一种我们主要举例说明一下
创建接口ModelQ
package com.test.spring.model;
import org.springframework.context.annotation.Bean;
public interface ModelQ {
@Bean
default InnerModelQ getInnerModelQ() {
return new InnerModelQ();
}
class InnerModelQ {
}
}
创建实现类ModelQImpl
package com.test.spring.model;
import org.springframework.stereotype.Component;
@Component
public class ModelQImpl implements ModelQ {
}
运行main方法,查看运行结果
之前我约定了大循环方法和小循环方法,主要就是想说明一点,只有这种情况可能存在非null的返回值,从而进入小循环
这种就是如果符合条件的父类也和普通的配置类一样解析,但是不会put额外的ConfigurationClass对象到相关map对象中了,即以后不会产生同Type的多个bean
上面四个步骤的解析结果 都是在ConfigurationClass对象设置相关属性,具体执行在ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass方法中