Condition 是在spring4.0 增加的條件注解,通過這個可以功能可以實現(xiàn)選擇性的注入Bean操作莺匠,接下來先學習下Condition是如何使用的浇衬,然后分析spring源碼了解其中的實現(xiàn)原理懒构。
更多可看==>Spring&SpringBoot 實踐和源碼學習
Demo
注意:以下三個代碼塊分屬不同的文件,便于說明具體問題
@Bean("contectService")
@Conditional(LoadConditional.class)
// 條件控制耘擂,如果對應的match操作返回true胆剧,則會注入該bean
// 否則會跳過處理該bean
public ContextService contextService() {
return new ContextService();
}
public class LoadConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 查看在Bootstrap的ENV的值是否等于test
// 后面會介紹這個context上下文,其中包含了整個的bean工廠內(nèi)容
return Bootstrap.ENV.equals("test");
}
}
@SpringBootApplication
@ComponentScan("com.demo.boot")
@EnableSwagger2
public class Bootstrap {
public static String ENV = "test";
public static void main(String[] args) {
SpringApplication.run(Bootstrap.class);
}
}
這樣就簡單的通過@Conditional實現(xiàn)選擇性的注入Bean操作醉冤,實際開發(fā)中秩霍,主要是為了區(qū)分測試環(huán)境和線上環(huán)境,其實在spring已經(jīng)添加了一部分常用的條件注解蚁阳,如下表格
Conditions | 描述 |
---|---|
@ConditionalOnBean | 在存在某個bean的時候 |
@ConditionalOnMissingBean | 不存在某個bean的時候 |
@ConditionalOnClass | 當前classpath可以找到某個類型的類時 |
@ConditionalOnMissingClass | 當前classpath不可以找到某個類型的類時 |
@ConditionalOnResource | 當前classpath是否存在某個資源文件 |
@ConditionalOnProperty | 當前jvm是否包含某個系統(tǒng)屬性為某個值 |
@ConditionalOnWebApplication | 當前spring context是否是web應用程序 |
源碼學習
在之前的一篇筆記SpringBoot 之 EnableAutoConfiguration 實踐和源碼學習 中寫道@Configuration實現(xiàn)bean的注入
铃绒,那么條件注解肯定就類似于一道閥門在實現(xiàn)bean的注入前通過條件篩選去完成選擇性的bean注入,接著上面那篇學習筆記螺捐,來到ConfigurationClassPostProcessor 類颠悬,關(guān)于這個類不再過多介紹
隨著Spring的BPP的invoke來到processConfigBeanDefinitions 方法
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// ......忽略前面一大推代碼
// 遍歷當前裝載到Spring 容器的所有類,獲取其中包含了@Configuration的bean到candidates中
// 依次遍歷解析Configuration的類
do {
parser.parse(candidates);
parser.validate();
// 解析完成归粉,獲取所有的類椿疗,暫時還不包含@Bean的信息
Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// 配置類讀取信息,進行條件篩選
// 也就是我們本篇學習筆記的Condition注解原理所在地
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
// ....
}
來到ConfigurationClassBeanDefinitionReader 類中糠悼,肯定會進行類的遍歷操作届榄,獲取其中的@Bean,這個方法非常的關(guān)鍵倔喂,包含了很多功能
ConfigurationClassBeanDefinitionReader 類
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
// 傳遞的configClass就是添加了@Configuration 的包裝類 // trackedConditionEvaluator 記錄著類是否會攔截的處理類
}
再來到其實現(xiàn)的細節(jié)方法中
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
// 如果該configClass被過濾掉铝条,也就是不應該實例化
// 先直接進入到該方法中看看實現(xiàn)細節(jié)
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
// 從context上下文中移除該config的bean信息
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
// 直接返回
}
// 。席噩。班缰。。悼枢。 待續(xù)
}
private class TrackedConditionEvaluator {
private final Map<ConfigurationClass, Boolean> skipped = new HashMap<ConfigurationClass, Boolean>();
// 保存著所有config類的篩選狀態(tài)
public boolean shouldSkip(ConfigurationClass configClass) {
Boolean skip = this.skipped.get(configClass);
if (skip == null) {
// 新處理的類信息
if (configClass.isImported()) {
// 如果該配置類包含@Import導入的類
boolean allSkipped = true;
for (ConfigurationClass importedBy : configClass.getImportedBy()) {
// 以此遍歷迭代執(zhí)行
if (!shouldSkip(importedBy)) {
// 迭代每一個被Import導入的類埠忘,如果返回false,也就是不應該被忽略的類
// 則直接break,是否會導致 后面的需要跳過的類被忽略莹妒?
allSkipped = false;
break;
}
}
if (allSkipped) {
// 所有通過@Import的類全部跳過處理
skip = true;
}
}
if (skip == null) {
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
// 獲取元信息(也就是類似注解信息)判斷是否可以跳過名船。重點關(guān)注!
}
this.skipped.put(configClass, skip);
}
return skip;
}
}
ConditionEvaluator 類
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
// 如果沒有元信息旨怠,或者注解類中不包含Conditional類渠驼,就直接返回false,表示不能跳過
// 注解類Conditional的用途就在這里了<濉C陨取!爽哎!
return false;
}
if (phase == null) {
// 默認傳遞的phase是ConfigurationPhase.REGISTER_BEAN
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<Condition>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
// 獲取包含了@Conditional 的value值
// 類比demo中就是@Conditional(LoadConditional.class)的LoadConditional類全稱
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
// 完成LoadConditional類的實例化(必須是不帶參數(shù)的構(gòu)造器)
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
// 按照order進行排序
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)) {
// 這個matchee 方法就是自定義實現(xiàn)的方法蜓席,具體如上demo中的LoadConditional的重載方法
// 這是個循環(huán)的方法,意味著可以有多個條件注解倦青,
// 而且一旦出現(xiàn)了一個被過濾了則直接認定為需要被跳過
return true;
}
}
}
return false;
}
該方法就是獲取configuration類的注解信息瓮床,然后調(diào)用相關(guān)條件過濾的matches方法獲取匹配結(jié)果
這個圖也正好說明了傳遞到matches方法的context包含的內(nèi)容,例如Spring的Bean工廠产镐,以及上下文環(huán)境等信息(一般這個上下文環(huán)境使用的比較多)
到這里整個過程感覺比較清晰隘庄,但是這是從類的角度觸發(fā),處理的也是@Configuration類癣亚,而沒有@Bean的處理過程
那么繼續(xù)來到loadBeanDefinitionsForConfigurationClass 類中
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
// 上面已經(jīng)說了
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
if (configClass.isImported()) {
// 包含了@Import的情況
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
// 這個地方是處理@Bean的地方丑掺,也就是我們需要關(guān)注的地方,先忽略下
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 處理使用了@ImportResource 導入的xml述雾、groovy文件等處理入口
// 肯定是按照讀取xml的原理一樣街州,聲明一個reader然后解析resource的內(nèi)容,然后填充到register中
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
回過頭關(guān)注處理@Bean的細節(jié)
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
// 獲取方法的@Configuration信息
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
// 這個就是上面說沒有處理method的另一個處理入口
// 返回true就意味著可以被跳過處理
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}
// Consider name and any aliases
// name值是一個String[] 的樣式玻孟,主要是處理別名的情況
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
// Register aliases even when overridden
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}
// Has this effectively been overridden before (e.g. via XML)?
// springboot本身的重名會被IDEA類似工具發(fā)現(xiàn)唆缴,但是無法判斷xml是否也存在
// 主要是去重操作
if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
return;
}
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
beanDef.setResource(configClass.getResource());
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
if (metadata.isStatic()) {
// static @Bean method
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
beanDef.setFactoryMethodName(methodName);
}
else {
// instance @Bean method
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
}
beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
Autowire autowire = bean.getEnum("autowire");
if (autowire.isAutowire()) {
beanDef.setAutowireMode(autowire.value());
}
String initMethodName = bean.getString("initMethod");
if (StringUtils.hasText(initMethodName)) {
beanDef.setInitMethodName(initMethodName);
}
String destroyMethodName = bean.getString("destroyMethod");
if (destroyMethodName != null) {
beanDef.setDestroyMethodName(destroyMethodName);
}
// 設(shè)置init、destroy方法等
// scope范圍等數(shù)據(jù)
// Consider scoping
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
beanDef.setScope(attributes.getString("value"));
proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = ScopedProxyMode.NO;
}
}
// Replace the original bean definition with the target one, if necessary
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);
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Registering bean definition for @Bean method %s.%s()",
configClass.getMetadata().getClassName(), beanName));
}
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
到這里整個的源碼學習就完全結(jié)束了黍翎,整個的過程也還是按照spring原本的套路去實現(xiàn)的面徽,從一個Spring BPP作為入口處理