这篇文章主要分析一下这几个注解的原理。
SpringBoot中这几个注解关系比较紧密,少了@SpringBootApplication
注解,SpringBoot很多功能都没法使用,所以文章分析的内容涉及了该注解
另外还有个问题也与@SpringBootApplication
有关:
在上一篇文章上,加上两个类,Bean1和MyConfiguration,代码如下:
@Configuration
public class MyConfiguration {
@Bean
public Bean1 bean1(){
return new Bean1();
}
}
public class Bean1 {
public Bean1(){
System.out.println("bean init");
}
}
上面代码的作用是实例化一个Bean1对象(作用类似在Bean1类上加上@Component注解)。
这时候工程目录如下:
下面就以上面代码为例子,分析@Configuration和@Bean注解的原理。
在分析之前,需要先介绍一下启动类的@SpringBootApplication
注解,先看下其定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
@SpringBootApplication
注解上很多个注解,其中就包括@ComponentScan
注解,所以这里查询@ComponentScan
注解是能成功的,因为这里及上文中查询@Configuration
的方式都是:查找当前注解,如果注解没有则查找注解上的注解有没,所以一个@SpringBootApplication
就包含了@ComponentScan
、@SpringBootConfiguration
(即@Configuration)、@EnableAutoConfiguration
几个注解的功能(当然也可以不使用该注解,直接把该注解上的其他注解直接放到启动类上)
在后续的处理当中,当判断一个类上是否有某个注解的时候,即使一个类是间接依赖的某个注解,那么这种情况也是符合的,也会将该注解的信息给提取出来,例如,判断启动类是否拥有@Configuration
注解,流程如下:
@Configuration
注解,结果没有@SpringBootConfiguration
注解上是否有@Configuration
注解注解,结果没有@SpringBootConfiguration
注解上的所有注解,判断这些注解上是否有@Configuration
注解,重复类似23的流程由于@SpringBootConfiguration
注解上有个@SpringBootConfiguration
注解,而该注解上有@Configuration
注解,所以该查找成功
上文分析了,执行run方法后,最后会调用到org.springframework.context.support.AbstractApplicationContext#refresh
方法,这是Spring核心的方法,这篇文章主要分析几个注解的实现,所以这里只展示相关的代码:
@Override
public void refresh() throws BeansException, IllegalStateException {
//这里就是核心逻辑的入口,主要是对BeanFactoryPostProcessor的处理
invokeBeanFactoryPostProcessors(beanFactory);
}
BeanFactoryPostProcessor:类似于BeanPostProcessor,BeanPostProcessor主要用来后置处理Bean的,而BeanFactoryPostProcessor则是用来在Bean初始化完成之前,用来操作BeanFactory的,两者都是Spring开放的扩展点,用来扩展对应的功能
invokeBeanFactoryPostProcessors内将逻辑委托给PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
方法,该方法内部则会对所有的BeanFactoryPostProcessor进行排序并调用BeanFactoryPostProcessor接口对应的方法。
BeanFactoryPostProcessor具体处理这里不再详细展开,只需要知道@Configuration处理逻辑在某个BeanFactoryPostProcessor中,这个类就是ConfigurationClassPostProcessor
找到该类,发现其实现的接口是BeanDefinitionRegistryPostProcessor而不是BeanFactoryPostProcessor,而BeanDefinitionRegistryPostProcessor的父接口是BeanFactoryPostProcessor,和BeanFactoryPostProcessor其实差不多,如果实现了该接口,会先调用BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法,再调用BeanFactoryPostProcessor接口的postProcessBeanFactory方法
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
方法就是核心所在,其中会调用到processConfigBeanDefinitions
方法,直接看下这个方法的逻辑(省略一些非核心流程代码):
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// 获取当前注册到容器的BeanDefinition的名称集合
String[] candidateNames = registry.getBeanDefinitionNames();
// 筛选具有@Configuration注解信息的BeanDefinition
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
//判断BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE属性是否为full
//判断BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE属性是否为lite
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
//log忽略
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// 该BeanDefinition对应的类是否有@Configuration注解
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 如果当前没有,那么就不需要进行@Configuration的处理
if (configCandidates.isEmpty()) {
return;
}
//用来解析各种注解的解析器
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());
do {
parser.parse(candidates);//开始解析
parser.validate();//校验
// 这是解析完成后,得到的需要加载到容器中的配置类
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
//将这些解析到的类加载到容器中
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null) {
if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
}
}
processConfigBeanDefinitions整个方法可以大体划分为三个阶段:
这里还有几个注意点:
在上面第1步中,有@Configuration注解的会加入到集合当中,这个判断是在ConfigurationClassUtils#checkConfigurationClassCandidate
当中实现
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
//获取注解元数据信息
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
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;
}
}
// 查找当前注解是否是与@Configuration相关
// 该方法还会判断该注解上的注解是否有@Configuration,一直往上寻找
// 因为有的注解为复合注解
if (isFullConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// 查找当前注解上是否有ComponentScan、Component、Import、ImportResource注解
//如果没有则查找Bean注解,同上,一直往上查找
else if (isLiteConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
return true;
}
从这里可以猜测,我们使用如下的代码,程序应该也可以跑起来
//@SpringBootApplication
//@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@Component
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
上面说了@SpringBootApplication
等价于@SpringBootConfiguration
,@EnableAutoConfiguration
,@ComponentScan
注解,而这时,将@SpringBootConfiguration
替换成@Component
之后,checkConfigurationClassCandidate
方法仍然返回true,所以程序应该也可以跑起来。
试着跑了一下,代码没问题,Bean1仍然被初始化,而把MyConfiguration类上的注解换成@Component
也是正常没有问题,但是其实两者还是有差别的,但是不在这篇文章的讨论范围,故不详细分析。
解析工作交由ConfigurationClassParser
处理
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<>();
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
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);
}
}
processDeferredImportSelectors();
}
上面说了,这个时候configCandidates只有一个元素,即SpringBootDemoApplication,他属于AnnotatedBeanDefinition
,会走到第一个分支,其实无论哪个分支,最后都会走到如下方法
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
//....
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName));
}
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
参数为ConfigurationClass,即配置有@Configuration
注解的类对应的ConfigurationClass对象。
最后将逻辑交由doProcessConfigurationClass
处理,该方法会通过配置有ConfigurationClass对象去获取额外引入的类(也可能没有引入)。
最后该方法会又会返回SourceClass
对象,直到返回的对象为空才结束解析。
这里是为了解决有父类的情况,假设SpringBootDemoApplication有父类,那么这里返回的SourceClass
为其父类,接着进行解析
解析完成后,将ConfigurationClass对象放到Map中,表示需要加载到容器中的ConfigurationClass对象集合,后续会获取该Map的元素加载到容器中
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// 内部类的处理
processMemberClasses(configClass, sourceClass);
// @PropertySource注解处理
//....
// @ComponentScan注解处理
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
//ComponentScan属性可能有多个,因为可能配置了ComponentScans注解
for (AnnotationAttributes componentScan : componentScans) {
// 通过配置的@ComponentScan注解的信息进行包扫描
// 扫描后的类型将注册成BeanDefinitionHolder并返回
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 扫描出来的类,又调用了parse,进行递归处理
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(
holder.getBeanDefinition(), this.metadataReaderFactory)) {
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
}
}
}
}
// 处理@Import注解
//....
// @ImportResource注解处理
//....
// @Bean注解处理
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 父类处理
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// 返回父类,让程序继续处理父类的注解
return sourceClass.getSuperClass();
}
}
// 如果没有父类,那么返回null表示不需要继续处理
return null;
}
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
//获取成员类的SourceClass对象
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
//获取与@Configuration有关的
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) {
//....
processConfigurationClass(candidate.asConfigClass(configClass));
//....
}
}
}
代码逻辑比较简单,核心的逻辑还是processConfigurationClass
,该方法只是找到和@Conguration
注解有关的类
首先会调用AnnotationConfigUtils.attributesForRepeatable
方法获取@ComponentScan
和@ComponentScans
注解信息(该信息在@SpringBootApplication注解上,间接获取到),当获取到ComponentScan属性后,会调用ComponentScanAnnotationParser#parse
方法进行查找,主要看下大概的逻辑
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// ....
// 获取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);
basePackages.addAll(Arrays.asList(tokenized));
}
//获取basePackageClasses属性,以该类所在的包作为扫描路径
//ClassUtils.getPackageName为获取当前类所在的包路径
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
//如果上面两个属性都没配置,则以参数declaringClass所在的包作为扫描路径
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
//....
//开始扫描并注册
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
这里只列出了包扫描相关的代码,可以看到,获取包路径有三种形式,
basePackages
属性配置包路径basePackageClasses
属性配置类信息,扫描路径取自该类所在包的路径从这里可以看到SpringBoot即使不配置扫描路径也是可以正常跑的(当然也可以在@SpringBootApplication上特意配置指定信息),这时候会走到第3步,将启动类所在的包作为扫描路径,而这有个前提就是其他需要扫描的包需要放到启动类所在的包路径以下,否则将扫描不到
以Demo为例,通过@SpringBootApplication的ConfigurationClass为入口,扫描得到自定义的配置有@Configuration注解的MyConfiguration
类,然后又调用parse方法进行解析,最后又会调用到doProcessConfigurationClass
方法,只不过参数ConfigurationClass对象对应的是MyConfiguration
,这时候运行到retrieveBeanMethodMetadata
方法的时候,会获取MyConfiguration
下配置了@Bean
注解的方法,然后进行处理。
//获取所有配置了@Bean注解的方法元数据信息
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
//将其封装成BeanMethod,并放入到ConfigurationClass对象的集合中待后续处理
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
这里只是将配置了@Bean方法的信息收集起来,并没有做特殊处理
回到ConfigurationClassPostProcessor#processConfigBeanDefinitions
方法,当调用完parse方法之后,能得到一批ConfigurationClass集合,但是这时候只是获取到,而容器中还没有对应的注册信息,那么接下来就是对这批集合进行注册处理
//上面分析的解析流程,主要是获取一批ConfigurationClass集合
parser.parse(candidates);
//解析后得到的一批集合
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
//加载到容器中
this.reader.loadBeanDefinitions(configClasses);
loadBeanDefinitions
方法会调用到loadBeanDefinitionsForConfigurationClass
方法
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
TrackedConditionEvaluator trackedConditionEvaluator) {
//....
//与@Import注解相关,后续文章分析
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// 对@Bean注解的到的BeanMethod进行处理
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//与@Import注解相关,后续文章分析
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
主要看下loadBeanDefinitionsForBeanMethod方法,其他的和@Import注解有关,暂不分析
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
//....
//获取@Bean注解的元数据信息
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
Assert.state(bean != null, "No @Bean annotation attributes");
//....
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
beanDef.setResource(configClass.getResource());
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
//设置工厂方法
if (metadata.isStatic()) {
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
beanDef.setFactoryMethodName(methodName);
}
else {
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
}
//....
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
上面只列出了核心代码,主要是构造了BeanDefinition,然后注册进容器,而BeanDefinition的一些属性则是由注解中获取,这部分代码省略。
另外,可以看到@Bean的方式构造的BeanDefinition的时候,与普通的不同,这种方式是会设置工厂方法去初始化,也就是说,MyConfiguration类下的bean1方法被Spring当成一个工厂方法,也就是说这种方式与下列的初始化方式原理类似:
<bean id="myConfiguration"
class="com.example.springboot.springbootdemo.bean.MyConfiguration"/>
<bean id="bean1" factory-bean="myConfiguration" factory-method="bean1">
bean>
如果demo中的bean1方法加上static修饰,就类似xml中配置成静态工厂模式
上门介绍了三个注解相关的处理流程,流程中涉及的其他注解也会引入更多的ConfigurationClass,但是涉及篇幅较长,后续会有其他文章再进行分析。