承接前文springboot情操陶冶-SpringApplication(二),本文將在前文的基礎上分析下@Configuration注解是如何一步一步被解析的
@Configuration
如果要了解與明白@SpringBootApplication
的工作機制,必須了解@Configuration
的注解應用。因為前者依賴后者,此處看下源碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
/**
* Explicitly specify the name of the Spring bean definition associated
* with this Configuration class. If left unspecified (the common case),
* a bean name will be automatically generated.
* <p>The custom name applies only if the Configuration class is picked up via
* component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
* If the Configuration class is registered as a traditional XML bean definition,
* the name/id of the bean element will take precedence.
* @return the suggested component name, if any (or empty String otherwise)
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
根據上面的代碼可知,其是Spring框架中最常見的注解@Component
的復用類。下面筆者將通過AnnotatedBeanDefinitionReader類對該注解的解析進行詳細的解讀
1.AnnotatedBeanDefinitionReader
根據前文的描述,我們知道針對main()方法所在的類進行注解解析的是通過BeanDefinitionLoader來操作的,而具體的解析操作其實是
其內部的屬性annotatedReader來實現的。可能有點雲里霧里,筆者此處就直接進入至對應類看個究竟
構造函數
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;
// @Conditional注解表達式解析類
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
// processor接口集合注冊
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
由上面可知,在構造函數初始化的過程中,順便還注冊了一發BeanDefinitionRegistryPostProcessor集合,這個接口會在ApplicationContext#refresh()操作中會被統一調用。
AnnotationConfigUtils#registerAnnotationConfigProcessors()
注解配置類的注冊,具體配置了哪些processor,筆者此處粗看下
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
....
....
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(4);
// @Configuration注解解析處理類
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));
}
// @Autowired注解解析處理類
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));
}
// @Required注解解析處理類
if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// @WebServiceDef/@EJB/@Resource/@PostConstruct/@PreDestroy注解解析
if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// JPA注解解析
if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
try {
def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
AnnotationConfigUtils.class.getClassLoader()));
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
}
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
}
....
....
return beanDefs;
}
筆者此處只關注針對@Configuration
注解spring是如何解析的,本文就重點分析ConfigurationClassPostProcessor處理類
2.ConfigurationClassPostProcessor
我們直接去查看其復寫的postProcessBeanFactory()方法,里面出現了關鍵的處理方法
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
if (!this.registriesPostProcessed.contains(factoryId)) {
// 關鍵方法
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}
// 對bean工廠的含Configuration注解實例進行CGLIB代理
enhanceConfigurationClasses(beanFactory);
// 對類型為ImportAware的bean進行額外處理
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
筆者此處只追蹤processConfigBeanDefinitions()方法,看下對@Configuration
注解是如何處理的
ConfigurationClassPostProcessor#processConfigBeanDefinitions()
源碼有點長,部分省略
/**
* Build and validate a configuration model based on the registry of
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
// 遍歷注冊在bean工廠上的所有bean,篩選出未加載過的@Configuration類
for (String beanName : candidateNames) {
// 避免重復加載
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
// 以當前類循環遍歷注解類以查找有無@Configuration注解
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 無@Configuration注解bean則直接返回
if (configCandidates.isEmpty()) {
return;
}
....
....
// 解析@Configuration
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();
...
candidates.clear();
// 檢查是否含有新的bean沒有被解析
if (registry.getBeanDefinitionCount() > candidateNames.length) {
...
}
}
while (!candidates.isEmpty());
...
}
@Configuration
注解的搜尋策略是根據當前類如果不存在此注解則會根據當前類的注解的注解再搜索,依次類推
由上可知具體的解析類為ConfigurationClassParser,我們關注下它的公有方法parse()
3.ConfigurationClassParser#parse()
源碼如下
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<>();
// 對不同類型的bean調用不同的parse負載方法
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);
}
}
// 針對DeferredImportSelector延遲選擇類作下處理
processDeferredImportSelectors();
}
我們繼續對上述的代碼作下分析
4.ConfigurationClassParser#doProcessConfigurationClass()
首先分析下parse()方法,內部均會調用如下的代碼
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// 優先遍歷其內部類,找尋其被@Bean和@Configuration等下述注解修飾的內部類,對內部類進行注入工廠
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
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
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
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
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// Process superclass, if any
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();
}
}
// No superclass -> processing is complete
return null;
}
上述代碼闡述了對@PropertySource
、@ComponentScan
、@Import
、@ImportResource
、@Bean
注解的處理,並且會遞歸遍歷父類來進行相同的解析。
筆者下面便針對上述的五個注解分別作下簡單的分析
@PropertySource注解
分析前我們先看下@PropertySource
注解的代碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
/**
* Indicate the name of this property source. If omitted, a name will
* be generated based on the description of the underlying resource.
* @see org.springframework.core.env.PropertySource#getName()
* @see org.springframework.core.io.Resource#getDescription()
*/
String name() default "";
/**
* Indicate the resource location(s) of the properties file to be loaded.
* <p>Both traditional and XML-based properties file formats are supported
* — for example, {@code "classpath:/com/myco/app.properties"}
* or {@code "file:/path/to/file.xml"}.
* <p>Resource location wildcards (e.g. **/*.properties) are not permitted;
* each location must evaluate to exactly one {@code .properties} resource.
* <p>${...} placeholders will be resolved against any/all property sources already
* registered with the {@code Environment}. See {@linkplain PropertySource above}
* for examples.
* <p>Each location will be added to the enclosing {@code Environment} as its own
* property source, and in the order declared.
*/
String[] value();
/**
* Indicate if failure to find the a {@link #value() property resource} should be
* ignored.
* <p>{@code true} is appropriate if the properties file is completely optional.
* Default is {@code false}.
* @since 4.0
*/
boolean ignoreResourceNotFound() default false;
/**
* A specific character encoding for the given resources, e.g. "UTF-8".
* @since 4.3
*/
String encoding() default "";
/**
* Specify a custom {@link PropertySourceFactory}, if any.
* <p>By default, a default factory for standard resource files will be used.
* @since 4.3
* @see org.springframework.core.io.support.DefaultPropertySourceFactory
* @see org.springframework.core.io.support.ResourcePropertySource
*/
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
然后我們再看下處理方法ConfigurationClassParser#processPropertySource()
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
for (String location : locations) {
try {
// if resource has ${},use environment to resolve it
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
// via DefaultResourceLoader to resolve file grammer or classpath grammer
Resource resource = this.resourceLoader.getResource(resolvedLocation);
// store into environment
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
針對上述代碼作下小結
屬性value,支持"classpath:"和"file://"方式加載,可指定多個資源路徑,以
,
分隔。並且支持properties/xml后綴屬性ignoreResourceNotFound,是否忽略找不到的資源,默認為false,即會對找不到的資源拋出異常
支持在文件路徑添加
${}
表達式,其會被Enviroment環境的對應屬性所解析
@PropertySource
主要用於加載外部文件資源,最終加載的資源會被保存至Enviroment環境的Map集合中,可通過Environment#getProperty()方法獲取
@ComponentScan
@Component注解掃描類,先看下注解的本身源碼
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* Alias for {@link #basePackages}.
* <p>Allows for more concise annotation declarations if no other attributes
* are needed — for example, {@code @ComponentScan("org.my.pkg")}
* instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
*/
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
/**
* Controls the class files eligible for component detection.
* <p>Consider use of {@link #includeFilters} and {@link #excludeFilters}
* for a more flexible approach.
*/
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
/**
* Indicates whether automatic detection of classes annotated with {@code @Component}
* {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
*/
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
/**
* Specifies which types are not eligible for component scanning.
* @see #resourcePattern
*/
Filter[] excludeFilters() default {};
/**
* Specify whether scanned beans should be registered for lazy initialization.
* <p>Default is {@code false}; switch this to {@code true} when desired.
* @since 4.1
*/
boolean lazyInit() default false;
/**
* Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
* include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
/**
* The type of filter to use.
* <p>Default is {@link FilterType#ANNOTATION}.
* @see #classes
* @see #pattern
*/
FilterType type() default FilterType.ANNOTATION;
/**
* Alias for {@link #classes}.
* @see #classes
*/
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
此處注解的配置就和spring的<context:component-scan>
配置一樣,本文就不延伸了
再看下對應的解析類ComponentScanAnnotationParser
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// if useDefaultFilters true,@Repository/@Service/@Controller will be also considered to resolve
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);
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));
}
// the resourcePattern of files.default to **/**.class
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
// includeFilters and excludeFilters configuration
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);
}
}
// if lazyInit is true,the beanDefination will be loading when first to use
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
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);
}
// load concret class's packageName
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// be aware of this. when basePackages and basePackageClasses property are not configurated,it will load current annotated class's packageName
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
具體的解析過程筆者就不展開了,對上述的代碼稍微作下小結
屬性useDefaultFilters,默認為true。表明支持也對
@Repository/@Service/@Controller
注解進行掃描屬性resourcePattern,默認為
**/**.class
。代表掃描何種模式的文件屬性basePackages和basePackageClasses,代表掃描的包名列表。如果都沒有指定的話,其會以
@ComponentScan
注解的當前類所在的包名作為掃描路徑
@ComponentScan
注解類默認是不掃描所注解的當前類的
@Import
此注解用於引入相應的類來進行多元的解析,擴展性比較好。我們可以看下其源碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
再看下具體的解析方法processImports()
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
// if current sourceClass has no @Import annotation.return directly
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// recursively
for (SourceClass candidate : importCandidates) {
// 1. aim to process ImportSelector interface
if (candidate.isAssignable(ImportSelector.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
// configurate common properties
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
// store DeferredImportSelector interface
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
// recursively process @Import based on importClassNames
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
// 2. aim to process ImportBeanDefinitionRegistrar interface
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// 3. aim to process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
針對上述的代碼作下小結
@Import
注解中的屬性value,支持的類型有ImportSelector.class
、ImportBeanDefinitionRegistrar.class
接口和普通的@Configuration
注解類
ImportSelector.class
接口的selectImports()方法用於引入更多的篩選類以滿足不同注解的解析。這利於擴展,用戶可自定義去實現
ImportBeanDefinitionRegistrar
接口的registerBeanDefinitions()方法主要用於注冊特定的beanDefinition。這也利於擴展,用戶可自定義去實現
@Import
注解對於帶有@Configuration
的普通類則會再次執行本文開頭的方法遞歸解析
@Import
注解的最終目的也是為了將特定的bean注冊至spring上下文的bean工廠中
@ImportResource
根據注釋描述,其類似於spring中的<import/>
標簽,用於引入外部的<beans>
配置。具體的筆者就不闡述了,有興趣的讀者可自行分析。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ImportResource {
@AliasFor("locations")
String[] value() default {};
/**
* Resource locations from which to import.
* <p>Supports resource-loading prefixes such as {@code classpath:},
* {@code file:}, etc.
* <p>Consult the Javadoc for {@link #reader} for details on how resources
* will be processed.
* @since 4.2
* @see #value
* @see #reader
*/
@AliasFor("value")
String[] locations() default {};
/**
* {@link BeanDefinitionReader} implementation to use when processing
* resources specified via the {@link #value} attribute.
* <p>By default, the reader will be adapted to the resource path specified:
* {@code ".groovy"} files will be processed with a
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader GroovyBeanDefinitionReader};
* whereas, all other resources will be processed with an
* {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader XmlBeanDefinitionReader}.
* @see #value
*/
Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
}
@Bean
此注解主要用於對類的方法上,用於將指定的方法返回的值包裝成BeanDefinition,並注入至spring中的bean工廠中。具體筆者也不分析了,有興趣的讀者可自行分析
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
/**
* Alias for {@link #name}.
* <p>Intended to be used when no other attributes are needed, for example:
* {@code @Bean("customBeanName")}.
* @since 4.3.3
* @see #name
*/
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
Autowire autowire() default Autowire.NO;
/**
* The optional name of a method to call on the bean instance during initialization.
* Not commonly used, given that the method may be called programmatically directly
* within the body of a Bean-annotated method.
* <p>The default value is {@code ""}, indicating no init method to be called.
* @see org.springframework.beans.factory.InitializingBean
* @see org.springframework.context.ConfigurableApplicationContext#refresh()
*/
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
小結
根據上述的代碼描述我們基本了解@Configuration
注解是如何被解析的,讀者只需要依次閱讀下來基本就明白了。大致邏輯如下
獲取bean工廠里的所有beanDefinition,對含有
@Configuration
注解進行篩選得到ConfigurationCandidate集合遍歷ConfigurationCandidate集合,對
@Configuration
注解進行解析,其中包括@Bean
、@Import
、@PropertySource
、@ImportResource
、@ComponentScan
注解的解析。其中bean注冊順序為 內部類>@ComponentScan>@Import導入的類>@Bean>@ImportResource>ImportBeanDefinitionRegistrar,但也是有前提的,前提就是帶有@Conditional
注解的條件判斷通過方可對Import導入的類型為DeferredImportSelector進行解析,其中涉及
@AutoConfigureBefore
的注解排序解析。一般是用於springboot的自帶配置類解析,其的bean解析順序是最靠后的通過ConfigurationClassBeanDefinitionReader類對上述@Import導入的類、@Bean注解的類方法、@ImportResource注解的類均注冊至bean工廠中
對於
@Import
導入的非ImportBeanDefinitionRegistrar/ImportSelector實現類,即使其上面沒有@Configuration注解其也會被注入至bean工廠;有則可能會被重載
基於上述的解讀,那么@SpringBootApplication
注解的解讀就迫在眉睫了,里面肯定蘊含了一些玄機等待我們去發現。