Spring对使用@Configuration @Component的类中 “内部类的特殊处理”

基于springboot 2.6.0分析
Spring对@Configuration @Component配置类中 内部类的特殊处理

整合SpringBoot和Mybatis的一个Starter时发现:

  1. Starter在spring.factories中配置了一个自动装配的配置类MapperAutoConfiguration并标记了@Configuration
  2. MapperAutoConfiguration中有个静态内部类MapperScannerRegistrarNotFoundConfiguration
  3. 该静态内部类虽然使用了@Configuration注解,但并没有配置在spring.factories中,启动时spring也不会扫描到这个包下。

SpringBoot启动后MapperAutoConfiguration能正常实例化成一个Bean,这个能理解。

但是 静态内部类MapperScannerRegistrarNotFoundConfiguration竟然也被创建成了一个Bean,为什么? ? ?

示例代码如下:
简单的一个启动类

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Demo1Application {
    public static void main(String[] args) {
        SpringApplication.run(Demo1Application.class, args);
    }
}

MapperAutoConfiguration及静态内部类,注意: MapperAutoConfiguration并不在工程默认的扫描包下。

package com.out.scan;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

// Created by bruce on 2021/12/20 15:07
@Slf4j
@Configuration
public class MapperAutoConfiguration {
    
    public MapperAutoConfiguration() {
        log.info("MapperAutoConfiguration init...");
    }

    //@Import(value = ImportedBean1.class)
    @Configuration
    public static class MapperScannerRegistrarNotFoundConfiguration implements BeanNameAware {

        @PostConstruct
        public void afterPropertiesSet() {
            log.info("MapperScannerRegistrarNotFoundConfiguration init...");
        }

        @Override
        public void setBeanName(String s) {
            System.out.println("内部类的Bean名称:" + s);
        }
    }
}

spring.factories中的配置及整体工程结构如下:在这里插入图片描述
针对上面的疑问,来跟源码一探究竟:

Spring在启动过程中会将扫描的类,(通过类路径扫描得到,或是在spring.factories配置的类),包装成ConfigurationClass对其进行再次解析,以为MapperAutoConfiguration例,ConfigurationClass中存放信息如下:
在这里插入图片描述
同时MapperAutoConfiguration还会包装成ConfigurationClassParser.SourceClass
在这里插入图片描述
ConfigurationClassParser.SourceClass中有个方法#getMemberClasses则是用于获取一个类中的内部类。
当解析到MapperAutoConfiguration时,会先调用#getMemberClasses获取所有的内部类,遍历这些内部类并通过ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata())判断是否是一个符合要求的配置类,如果是的话同样会被包装成ConfigurationClass调用ConfigurationClassParser#processConfigurationClass解析。
所以默认情况下,内部类中的Bean将会优先被创建。
源码如下:
在这里插入图片描述
在这里插入图片描述
最后这些ConfigurationClass就会在ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中调用this.reader.loadBeanDefinitions(configClasses);注册所有的BeanDefinition在spring容器中。

再看下ConfigurationClassUtils#isConfigurationCandidate方法中是怎么定义哪些是符合要求的类,

private static final Set<String> candidateIndicators = new HashSet<>(8);

static {
	candidateIndicators.add(Component.class.getName());
	candidateIndicators.add(ComponentScan.class.getName());
	candidateIndicators.add(Import.class.getName());
	candidateIndicators.add(ImportResource.class.getName());
}
public static boolean isConfigurationCandidate(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...
	return hasBeanMethods(metadata);
}

static boolean hasBeanMethods(AnnotationMetadata metadata) {
	try {
		return metadata.hasAnnotatedMethods(Bean.class.getName());
	} catch (Throwable ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
		}
		return false;
	}
}

即类上标记@Component@ComponentScan@Import@ImportResource这几个注解的其中一个的,或者有标记@Bean注解方法的都可以.

所以文章例子中的内部类,如下两种写法,实际上也会被注册成Bean
写法 1

@Configuration
public class MapperAutoConfiguration {

    public MapperAutoConfiguration() {  log.info("MapperAutoConfiguration init...");   }
    //写法 1 在内部类上使用@Component, @ComponentScan, @Import, @ImportResource中的任意一个注解
    @Import(value = ImportedBean1.class)
    public static class MapperScannerRegistrarNotFoundConfiguration {

        public MapperScannerRegistrarNotFoundConfiguration() {
            log.info("MapperScannerRegistrarNotFoundConfiguration init...");
        }

        @Bean
        public ImportedBean1 importedBean1() {
            return new ImportedBean1();
        }
    }
}

写法 2

@Configuration
public class MapperAutoConfiguration {

    public MapperAutoConfiguration() {  log.info("MapperAutoConfiguration init...");   }
    //写法 2 内部类上不使用任何注解,但是内部类中有个@Bean方法
    public static class MapperScannerRegistrarNotFoundConfiguration {

        public MapperScannerRegistrarNotFoundConfiguration() {
            log.info("MapperScannerRegistrarNotFoundConfiguration init...");
        }

        @Bean
        public ImportedBean1 importedBean1() {
            return new ImportedBean1();
        }
    }
}

如果spring.factories中的配置不变,但是去掉MapperAutoConfiguration上的@Configuration注解,会如何呢?

MapperAutoConfiguration仍然会被注册到Spring中,但是内部类 MapperScannerRegistrarNotFoundConfiguration 不会再被注册.
原因在于调用ConfigurationClassParser#processMemberClasses之前会判断外部类是否有@Component
在这里插入图片描述
Spring是如何获取一个类中的内部类的呢?
JDK11之前版本并没有提供api来获取一个类中有哪些内部类,jdk11中提供了如下的方式来获取

// @since 11
Class<?>[] nestMembers = MapperAutoConfiguration.class.getNestMembers();

而Spring其实是通过org.ow2.asm:asm包读取class文件中字节码的方法获取的,但Spring并没有直接使用asm包,而是将asm包中源码完全的复制了一份到spring-core包中。源码见:
org.springframework.core.type.classreading.SimpleAnnotationMetadataReadingVisitor#visitInnerClass在这里插入图片描述
在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值