Spring框架解析之@Configuration

​本文是Spring IoC容器技术介绍系列文章之一。本文介绍@Configuration的使用以及原理。

1 概念介绍

Annotating a class with @Configuration indicates that its primary purpose is as a source of bean definitions.

上述是Spring官方文档中用于介绍@Configuration的一段话,大意是说在类上使用@Configuration标注则表明该类的主要目的是作为bean定义的源文件。

在Spring应用中,我们一般会定义一个使用@Configuration标注的类,然后让ApplicationContext加载它,来启动整个容器。比如下面这样:

@Configuration
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);
        System.out.println(applicationContext);
    }
}

在这段示例代码中,使用了@Configuration注解标注了Application类。在程序入口的main方法中,通过AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class)Application.class作为构造函数的入参传递给了新创建的applicationContext对象。这样,就完成了一个IoC容器的创建了。

然而,上面创建的IoC容器只是加载了Spring相关的内部bean。由于我们没有定义任何的@Bean对象,所以难以看出IoC容器加载完成后的效果。

@Bean也是Spring中定义的注解,一般会配合@Configuration一起用,标注在@Configuration类中的某个方法上。被@Bean标注的方法会在IoC容器初始化时被调用,并将方法的返回结果作为一个bean对象保存在IoC容器里。

我们稍微调整下代码便于测试IoC容器的功能:

@Configuration
public class Application {
    @Bean
    public NumberStyleFormatter numberStyleFormatter() {
        return new NumberStyleFormatter();
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);
        NumberStyleFormatter bean = applicationContext.getBean(NumberStyleFormatter.class);
        System.out.println(bean);
    }
}

在这段代码中,我们增加了一个@Bean方法。同时,在main方法中使用getBean(...)获取该对象并打印。控制台上显示的结果如下图:

IoC容器的简单测试

1.1 换成@Component试试

@Configuration注解的源码可以发现,@Configuration注解继承自@Component

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
 String value() default "";
 boolean proxyBeanMethods() default true;
}

那如果我直接使用@Component,而不是@Configuration呢,IoC容器还能正常启动吗?

事实证明是可以的,我们可以将上述测试代码中的@Configuration改成@Component,结果会发现依然可以从IoC容器中读取NumberStyleFormatter类型的bean对象。

@Configuration@Component有何区别呢?

1.2 proxyBeanMethods

最开始的那段官方描述大致是说:@Configuration表示这个类就是用来配置的,就是用来帮助IoC容器初始化的,所以大家要尽可能使用@Configuration标注初始化的类,便于理解。这算是一个区别。

另外还有一个很大的区别是官方文档中给出的这句话:

@Configuration classes let inter-bean dependencies be defined by calling other @Bean methods in the same class.

这说的是啥!别着急,我们先写一段代码来看一个现象:

@Configuration
public class Application {
    @Bean Parent parent() {
        return new Parent();
    }
    @Bean Child child() {
        return new Child(parent());
    }
    static class Parent {}
    static class Child {
        public Parent parent = null;
        public Child(Parent parent) {
            this.parent = parent;
        }
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);
        Parent parent = applicationContext.getBean(Parent.class);
        Child child = applicationContext.getBean(Child.class);
        System.out.println(child.parent == parent);
    }
}

上面这段代码中,我们定义了两个内部类。一个叫Parent,一个叫ChildChild的构造方法需要传入一个Parent对象。

同时我们定义了两个@Bean方法,一个创建了类型为Parent的bean,另一个创建了一个类型为Child的bean。

@Bean Child child()方法里,我们调用了parent()方法创建了一个Parent对象作为构造器入参传入给新创建的Child对象。

我们没有使用@Bean Child child(Parent parent)这样的方法创建Child类型的bean(即没有使用依赖注入的方式将Parent类型的bean对象注入到Child中)。因此,在IoC容器中,Parent类型的bean应该和Child类型的bean的parent属性是不同的对象。main方法在控制台上打印的结果应该是false,但实际输出却是true!

不合常理的相等

这是为什么?

这是由于@Configuration中有个proxyBeanMethods()属性,且默认设置为true了。

现在可以来解释上面官方文档中的那段话的意思了。上面那段话的意思是说@Configuration会给@Bean方法设置代理,这样@Configuration中其他的方法在调用该方法时,实际会返回IoC容器中对应bean对象,而不是创建新的对象。

因此,测试程序返回的结果是true,而非false。如果我们将注解改为@Configuration(proxyBeanMethods = false),则控制台上返回的就会是false,表明这两个对象是不同的对象。

2 源码解释

ApplicationContext初始化的核心方法是refresh()方法。

ConfigurableApplicationContext.refresh()

该方法会处理已经加载的配置对象,这里的配置对象可以简单理解为我们编写的@Configuration对象。

AnnotationConfigApplicationContext初始化的最后一步,便是调用refresh方法。在ApplicationContext中会保存一个beanFactory属性。beanFactory在IoC容器中非常重要,其内部有一个叫beanDefinitionMap的属性,这个属性用于保存IoC容器需要加载的bean对象的所有定义信息。

下面这张是在执行refresh()方法之前,applicationContext.beanFactory.beanDefinitionMap中存的数据。可以发现,我们定义的@Configuration就在里面,名称为application。

refresh方法之前的内存数据

程序继续往下执行,会走到一个ConfigurationClassPostProcessor类的processConfigBeanDefinitions()方法。这个ConfigurationClassPostProcessor是框架负责完成初始化和调起的,不用过多关注。

processConfigBeanDefinitions()方法中,会调用parser.parse(candidates)方法,这个时候传入进来的candidates也是我们的@Configuraiton对象。

解析方法入口处的内存数据

解析完之后,会拿到一个configClasses,这个configClasses就是所有的配置类。在这里,它还是我们的@Configuration对象。之后会调用this.reader.loadBeanDefinitions(configClasses)方法,在这个方法中加载所有@Configuration对象中定义的bean。

loadBeanDefinitions入口处的内存数据

ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForConfigurationClass(...)方法中,将我们在@Configuration对象中定义的BeanDefinition写入到applicationContext.beanFactory.beanDefinitionMap。程序执行到这里,我们便可以看到applicationContext中已经有了我们定义的两个bean对象的定义信息了。

BeanDefinition对象生成后的内存信息

在beanFactory中,有一个叫singletonObjects的属性,singletonObjects保存了IoC容器中的所有bean对象。

在完成BeanDefinition构建之后,程序会执行到AbstractApplicationContextfinishBeanFactoryInitialization(...)中,在其中调用beanFactory.preInstantiateSingletons()将所有bean初始化并保存到singletonObjects。

实例化bean之前的内存信息

在IoC进行Bean对象初始化时,最终会调用@Bean Child child()方法。在调用该方法时,可以发现实际使用的对象是通过CGLIB代理的类,进而改写了默认的特性,将IoC容器中已经存在的Parent对象注入到Child类型的bean中。

初始化Child时的堆栈

那Spring是什么时候使用代理类替换了我们的@Configuration呢?

首先是在ConfigurationClassPostProcessor的方法processConfigBeanDefinitions(...)中调用了ConfigurationClassUtils.checkConfigurationClassCandidate(...)。在该方法中,判断proxyBeanMethods的值为true,则在BeanDefinition中增加一个属性:

设置属性

然后在ConfigurationClassPostProcessorenhanceConfigurationClasses(...)方法中根据该属性筛选出BeanDefinition对象,将所有对象存在一个叫configBeanDefs的映射表中:

找到BeanDefinition

最后将BeanClass改成CGLIB代理类的类型,这样就改变了IoC容器中最终生成的@Configuration对象的类型了。

修改类型

3 总结

本文主要介绍Spring IoC容器中@Configuration的使用方法,并深入分析了其底层的实现机理。

源码解析部分,主要分析了IoC容器启动过程中和@Configuration相关的核心部分的逻辑和代码。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镜悬xhs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值