@Profile 原理

缘起

@Profile这个注解是Spring-Context包提供的一个非常强大的注解——它可以根据程序员配置的启动参数(不论是通过-D形式的jvm参数还是通过代码配置)来只让profile参数吻合的bean注入到ioc容器中去。那么原理是啥呢?

分析

其实整个的工作原理是这样的.分成两步骤

  1. 决定要加载哪些bean
  2. 去加载

我们首先来看看ioc容器的构造器 (详尽代码参见【1】)

1
2
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(
MainConfig.class);

AnnotationConfigApplicationContext的构造器源码如下

1
2
3
4
5
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}

this() 是调用无参构造器,做的事情是 初始化 AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner (这两个货分别注入通过java配置类的方式注入的bean和通过配置文件注入的bean) 第二件事情就是注入java配置类(就是【1】中的MainConfig). 第三件事就是开始注入bean(即重要而且内容极为丰富的refresh方法).

我们重点看一下refresh方法 , 它里面有做2件事情

1
2
3
4
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

先做的事情中就包含通过@Profile注解得到要加载的bean(即把不符合的bean不加载)得到一个List——beanDefinitionNames(确切讲是new ArrayList(256), List of bean definition names, in registration order ),后做的事情中就包含根据beanDefinitionNames将要加载的bean加载起来.

先做的事情的关键源码是

org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(String, BeanDefinition)

1
2
3
...
this.beanDefinitionNames.add(beanName);
...

以及

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(BeanMethod) , 顾名思义,就是根据 @Bean 方法加载bean.

1
2
3
4
5
// Do we need to mark the bean as skipped by its condition?
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}

你看,代码作者也说明了,这个代码的作用就是”我们是否需要标志这个bean要因为这个condition被跳过?” 而跟进shouldSkip方法,该方法做的事情首先就是得到一系列的conditions,然后遍历这些conditions来判断该bean要不要跳过.

1
2
3
4
5
6
7
8
ps:这里的调用栈是

refresh中的invokeBeanFactoryPostProcessors
-->ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod -->DefaultListableBeanFactory.registerBeanDefinition

但其实在refresh前面的this和register(annotatedClasses);也都会调用DefaultListableBeanFactory.registerBeanDefinition,但是不会调用ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod .

其中在this中触发的DefaultListableBeanFactory.registerBeanDefinition是注册诸如 internalEventListenerFactory这种Spring自带的bean定义, 而register(annotatedClasses)触发的DefaultListableBeanFactory.registerBeanDefinition注册的是 诸如 Demo中的 MainConfig这种AnnotationConfigApplicationContext的构造器中的入参的bean定义. 而MainConfig中的 @Bean方法注册的bean定义是在refresh的invokeBeanFactoryPostProcessors(beanFactory);中注册的, 所以这些东西要搞清楚才行
1
2
3
4
5
6
7
8
9
10
11
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if (requiredPhase == null || requiredPhase == phase) {
if (!condition.matches(this.context, metadata)) { // 进行判断
return true;
}
}
}

condition是一个接口, 对于Profile而言,其考察的实现类是 ProfileCondition, 它的matches方法的实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true; // 如果和环境参数匹配的话, 则返回true
}
}
return false;
}
}
return true;
}

于是,我们得到了 beanDefinitionNames. 然后再来看后做的事情

后做的事情的关键源码是

org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons()

1
2
3
4
5
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) { //beanNames 就是 beanDefinitionNames
...
getBean(beanName);
...

所以先做的事情中得到的 beanDefinitionNames就起到了作用. 则后面就不会加载不符合@Profile注解的bean了.

其中,调用栈是

AnnotationConfigApplicationContext构造器中的refresh中的finishBeanFactoryInitialization(beanFactory);

–>AbstractApplicationContext.finishBeanFactoryInitialization中的beanFactory.preInstantiateSingletons();

–>DefaultListableBeanFactory中的preInstantiateSingletons的for (String beanName : beanNames) { 将beanName从beanDefinitionNames中一一拿出初始化加到ioc容器中去.

DEMO

【1】https://github.com/yfsyfs/backend/tree/master/spring-annotation-profile