@Configuration到底在干什么是怎么干的你了解过吗?
@Configuration的用法和相关原理和设计到的源码解析
@Configuration的作用和原理解析
1、@Configuration你干啥的
关于@Configuration spring官方文档是这样说的:@Configuration是一个类级别的注释,表明一个对象是 bean 定义的来源。@Configuration类通过带@Bean注释的方法声明 bean 。@Bean对@Configuration类方法的调用也可用于定义 bean 间的依赖关系
@Configuration在spring的注解开发中占有很重要的地位,你当你想要定义一个配置类并交给spring管理的时候你就可以在相关类上面加这个注解,并配合@Bean注解把对象交个spring去管理
@Slf4j /*@Slf4j 是lombok的注解 */
@Configuration
public class CustomConfiguration {
@Bean
public CustomBean customBean(){
log.info("call customBean");
return new CustomBean();
}
public static class CustomBean {
}
}
这样在spring容器启动起来之后在customBean()方法中我们自己new出来的对象会被放到容器中,下面我们写一个测试
@Autowired
private CustomConfiguration.CustomBean customBean;
@Test
void testBean() {
log.info("customBean exist ? {}", customBean != null);
}
从日志中可以看到首先customBean()方法的调用是在spring容器初始化完成之前,而且我们也能通过@Autowired把它从spring的容器中拿出来。
除此之外我们还可以调整他的生命周期比如我在customBean()上面加@Scope(“prototype”)那么这个就成了原型的了,不多比比上日志
从日志中可以看出方法的调用时在容器初始化完成之后。
2、@Configuration你凭啥能这么干
现在我们从spring的源码中看看凭什么你@Configuration可以这么干,首先我们要知道加了这个注解的类是怎么被spring知道的呢?
第一种方式,你直接告诉他例如
AnnotationConfigApplicationContext context1 = new AnnotationConfigApplicationContext(AppConfig.class);
//AppConfig.class 类上面加了@Configuration注解
spring接受到这个参数之后会把这个类(例子中的AppConfig)转换成BeanDefinition放到beanFactory中( Map<String, BeanDefinition> beanDefinitionMap),然后有一些类去统一处理这些BeanDefinition,这个时候就要提一嘴spring的后置处理器 (一种实现了BeanFactoryPostProcessor接口的类,spring中有好多的这种类而且程序员也可以实现这个接口,他们之间有个执行顺序如果想知道可以留言这个知识点可肝) 处理这个后置处理器是ConfigurationClassPostProcessor类。
在众多的BeanDefinition中你加了@Configuration的是怎么被ConfigurationClassPostProcessor一眼发现的呢,BeanDefinition通俗的来说可以是说对bean的描述这里面放着这个bean是不是需要懒加载作用域是什么类上面的注解等等信息,ConfigurationClassPostProcessor就是在Metadata里面拿到的注解信息(getAnnotationAttributes 方法),然后去判断是否加了@Configuration 不多比比上代码
/*ConfigurationClassUtils.java checkConfigurationClassCandidate 方法*/
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
/*....中间省略若干*/
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
//加了@Configuration标签的会被标记为full
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}else if (config != null || isConfigurationCandidate(metadata)) {
/*@Component、@ComponentScan、@Import、@ImportResource*/
//加了以上标签的会被标记为lite
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}else {
return false;
}
return true;
从上面可以看出在ConfigurationClassPostProcessor筛选的时候不只是加了@Configuration会被筛选出来,加了@Component、@ComponentScan、@Import、@ImportResource也会被筛选出来只是打的标签是不一样的,在筛选出来并打上标签以后是怎么解析的我们接着往下看,筛选出来的BeanDefinition被转换成了BeanDefinitionHolder(equals和hashCode都重写了)放在了set中(为了去重),然后先依次解析@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean然后解析过程就结束了。
小看一下@Bean的解析 其他的留到以后再讲
// Process individual @Bean methods
//拿到sourceClass内所有加了@Bean的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
//添加到configClass里的一个Set<BeanMethod> 中 这里只是把方法扫描出来
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
//ConfigurationClassBeanDefinitionReader.java loadBeanDefinitionsForConfigurationClass 方法
//这段代码负责解析上面生成的那个set
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
//loadBeanDefinitionsForBeanMethod负责用方法生成一个BeanDefinition放到beanFactory中
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//代码有删减 原方法详见 ConfigurationClassBeanDefinitionReader.java loadBeanDefinitionsForBeanMethod
MethodMetadata metadata = beanMethod.getMetadata();
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class); //拿到了类上的@Scope注解
if (attributes != null) {
beanDef.setScope(attributes.getString("value")); //用注解中的内容修改bd内容
proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = ScopedProxyMode.NO;
}
}
BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
new BeanDefinitionHolder(beanDef, beanName), this.registry,
proxyMode == ScopedProxyMode.TARGET_CLASS);
beanDefToRegister = new ConfigurationClassBeanDefinition(
(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, beanName);
}
//把这个生成的bd交给bean工厂,后续再bean工厂中就能按照bd生成bean了
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
上debug图
让spring知道你在类上加了@Configuration的第二种方式,这个和第一种方式是紧密相连的,在上面处理第一种方式的时候最后类上面的@ComponentScan注解被解析了 他会根据你给的参数去扫描类如果发现你这个类加了@Configuration 也会去解析一遍,至此所有的要交给spring管理的bean都已经扫描成BeanDefinition 也是不多比比上代码
// ConfigurationClassParser.java doProcessConfigurationClass方法
// 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) {
// 根据注解上配置的路径 扫描出来需要交给spring管理的类
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
//这个checkConfigurationClassCandidate就是上面那个方法
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
//调用parse方法来解析类上面的注解,解析的路数和你传给spring是相同的
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
至此所有的要交给spring管理的bean都已经扫描成BeanDefinition存放在beanFactory里面了,后面在用BeanDefinition生成bean的时候会用到full和lite属性,具体这两种属性的区别是什么我们下次再说。
更多推荐
所有评论(0)