MyBatis源碼學習(五)——MyBatis配置項的加載過程

先上小結:

  1. MybatisAutoConfiguration類的@EnableConfigurationProperties({MybatisProperties.class})註解被Spring激活,準備注入MybatisProperties類。
  2. MybatisProperties類有@ConfigurationProperties(prefix="mybatis")註解,會被ConfigurationPropertiesBindingPostProcessor調用bind()方法來處理。
  3. 獲得MybatisProperties類的屬性列表,按照@ConfigurationProperties註解的prefix(也就是"mybatis"),加上MybatisProperties類的某具體屬性,向配置文件中查找是否有相關配置。
  4. 配置文件中有相關配置時,向MybatisProperties的屬性綁定配置值,完成MybatisProperties的初始化和賦值。

 

正文:

在MybatisAutoConfiguration創建SQLSessionFactory時有這麼一步:

if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
    factory.setMapperLocations(this.properties.resolveMapperLocations());
}

意思是把MybatisAutoConfiguration的MybatisProperties屬性,取出其中的mapperLocations參數值,賦值給factory。

mapperLocations參數的值來自配置文件中的:

mybatis.mapperLocations=classpath:mybatis/mappers/*.xml

類似這樣的配置項。

那麼mybatis的配置是如何加載到MybatisProperties實例中的,我們從頭看一下過程。

探索這個過程的難點一個在於類名都很長,而且類名還都挺像,另一個在於綁定過程調用棧非常長,而且方法名字也挺像。

 

1,在spring-boot-autoconfigure-xxxx.jar的spring.factories文件中有EnableAutoConfigurationConfigurationPropertiesAutoConfiguration類。下面分別看一下這兩個類。

2,首先,EnableAutoConfiguration類。此類會處理被@Configuration註解的類,過程略。

3,然後是ConfigurationPropertiesAutoConfiguration類:

@Configuration
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration {
    public ConfigurationPropertiesAutoConfiguration() {
    }
}

無代碼邏輯,功能就是啓動EnableConfigurationProperties註解,這裏的EnableConfigurationProperties註解中沒有參數,此註解無參數時不會加載某個具體類的配置,但會在掃描所有被EnableConfigurationProperties註解標識的類並處理,後文會說到。

另外因爲有@Configuration註解,所以會被ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry調用ConfigurationClassParser.parse()方法進行處理。

 

4,下面看一下EnableConfigurationProperties註解的源碼:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EnableConfigurationPropertiesImportSelector.class})
public @interface EnableConfigurationProperties {
    Class<?>[] value() default {};
}

可見EnableConfigurationProperties類通過@Import(EnableConfigurationPropertiesImportSelector.class)向容器中注入了EnableConfigurationPropertiesImportSelector,@Import註解也是在ConfigurationClassParser.parse()方法進行處理的。

 

@Import註解簡介:

@Import註解的value類是在ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()中調用ConfigurationClassParser.parse()方法來處理的。

@Import註解可以注入:

1,@Configuration註解標識的類。

2,ImportSelector接口實現類。

3,ImportBeanDefinitionRegistrar接口實現類。

4,常規組件類。

如果是ImportSelector接口實現類,則類中要求實現selectImports()方法,返回字符串數組,數組元素的內容就是要注入的bean的全路徑名。數組元素也有可能是上述四類接口,如果某數組元素又是一個ImportSelector接口實現類,則會遞歸處理。

如果是ImportBeanDefinitionRegistrar接口實現類,注入時會調用類的registerBeanDefinitions()方法。

如果是@Configuration註解標識的類或者常規類,則直接注入。

 

5,這裏的EnableConfigurationPropertiesImportSelector實現了ImportSelector接口,所以需要關注一下它的selectImports()方法。

EnableConfigurationPropertiesImportSelector類的selectImports()方法返回了兩個類, ConfigurationPropertiesBeanRegistrar(是個內部類)和ConfigurationPropertiesBindingPostProcessorRegistrar。這兩個類都是ImportBeanDefinitionRegistrar接口實現類,都會調用其registerBeanDefinitions()方法。

下面分別看一下這兩個類。

 

6,ConfigurationPropertiesBeanRegistrar類負責找到所有EnableConfigurationProperties註解中聲明的配置類並注入BeanFactory。

MybatisAutoConfiguration類就有這個註解:

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
...
}

MybatisAutoConfiguration類有註解:

@EnableConfigurationProperties({MybatisProperties.class})

於是注入MybatisProperties類。

 

7,ConfigurationPropertiesBindingPostProcessorRegistrar類,調用其registerBeanDefinitions方法,注入了ConfigurationPropertiesBindingPostProcessor類,這個類用來綁定參數。

 

8,ConfigurationPropertiesBindingPostProcessor類實現了BeanPostProcessor接口,經過Spring的處理,在實例化類時會調用其postProcessBeforeInitialization()方法,專門對有ConfigurationProperties註解的類進行配置綁定,看一下這個方法的代碼:

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    ConfigurationProperties annotation = (ConfigurationProperties)this.getAnnotation(bean, beanName, ConfigurationProperties.class);
    if (annotation != null) {
        this.bind(bean, beanName, annotation);
    }

    return bean;
}

只處理@ConfigurationProperties註解並且調用bind()方法。

看看MybatisProperties類的部分代碼:

@ConfigurationProperties(
    prefix = "mybatis"
)
public class MybatisProperties {
...
}

MybatisProperties類就有ConfigurationProperties註解,所以會開始根據配置文件中的內容對MybatisProperties類的屬性值進行綁定。

 

9,最後是參數綁定的過程。

綁定的代碼調用流程很長,從上面提到的ConfigurationPropertiesBindingPostProcessor類的bind()方法開始,主要集中在

  • org.springframework.boot.context.properties.bind. Binder
  • org.springframework.boot.context.properties.bind. JavaBeanBinder

這兩個類上,經過一大長串的調用,代碼來到了JavaBeanBinder的bind()方法:

private <T> boolean bind(BeanPropertyBinder propertyBinder, JavaBeanBinder.Bean<T> bean, JavaBeanBinder.BeanSupplier<T> beanSupplier) {
    boolean bound = false;

    Entry entry;
    for(Iterator var5 = bean.getProperties().entrySet().iterator(); var5.hasNext(); bound |= this.bind(beanSupplier, propertyBinder, (JavaBeanBinder.BeanProperty)entry.getValue())) {
        entry = (Entry)var5.next();
    }

    return bound;
}

這裏的bean.getProperties()就是MybatisProperties類的屬性列表,是個LinkedHashMap,有9項,key分別是:

  • configLocation
  • config
  • configuration
  • typeAliasesPackage
  • mapperLocations
  • typeHandlersPackage
  • configurationProperties
  • executorType
  • checkConfigLocation

其中config這一項不知道哪來的,MybatisProperties類沒有這個屬性。

value裏記錄的是type-aliases-package這種橫線格式,可能是爲了兼容mybatis和spring的整合。即使是在spring-boot中,配置文件拼裝成的上下文中配置項也是用type-aliases-package這種橫線格式記錄的,spring-boot在匹配的過程中做了轉換,不知道爲什麼spring-boot沒有沿用原來的橫線格式配置,而改成了駝峯格式。

比如要使其中的typeAliasesPackage屬性綁定一個值,可以在spring-boot配置文件中寫上:

mybatis.typeAliasesPackage=xxxxx

如果不是spring-boot,則需要配置:

mybatis.type-aliases-package=xxxxx

要以mybatis開頭,因爲MybatisProperties在@ConfigurationProperties註解中定義的:

prefix = "mybatis"

這是配置項的開頭。

從代碼可見,循環屬性列表,並調用this.bind()方法,和前面的bind()不是一個,這是重載的方法:

private <T> boolean bind(JavaBeanBinder.BeanSupplier<T> beanSupplier, BeanPropertyBinder propertyBinder, JavaBeanBinder.BeanProperty property) {
    String propertyName = property.getName();
    ResolvableType type = property.getType();
    Supplier<Object> value = property.getValue(beanSupplier);
    Annotation[] annotations = property.getAnnotations();
    Object bound = propertyBinder.bindProperty(propertyName, Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
    if (bound == null) {
        return false;
    } else {
        if (property.isSettable()) {
            property.setValue(beanSupplier, bound);
        } else if (value == null || !bound.equals(value.get())) {
            throw new IllegalStateException("No setter found for property: " + property.getName());
        }

        return true;
    }
}

此時的propertyName就已經是type-aliases-package這種格式了,調用propertyBinder.bindProperty()方法,這個方法是在propertyBinder對象初始化時定義的lambda表達式:

private Object bindBean(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler, Binder.Context context, boolean allowRecursiveBinding) {
    if (!this.containsNoDescendantOf(context.streamSources(), name) && !this.isUnbindableBean(name, target, context)) {
        BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> {
            return this.bind(name.append(propertyName), propertyTarget, handler, context, false);
        };
        Class<?> type = target.getType().resolve(Object.class);
        return !allowRecursiveBinding && context.hasBoundBean(type) ? null : context.withBean(type, () -> {
            Stream<?> boundBeans = BEAN_BINDERS.stream().map((b) -> {
                return b.bind(name, target, context, propertyBinder);
            });
            return boundBeans.filter(Objects::nonNull).findFirst().orElse((Object)null);
        });
    } else {
        return null;
    }
}

lambda表達式見其中的propertyBinder變量定義,又調了一個bind()方法,全程就是各種調bind()重載方法:

protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Binder.Context context, boolean allowRecursiveBinding) {
    context.clearConfigurationProperty();

    try {
        if (!handler.onStart(name, target, context)) {
            return null;
        } else {
            Object bound = this.bindObject(name, target, handler, context, allowRecursiveBinding);
            return this.handleBindResult(name, target, handler, context, bound);
        }
    } catch (Exception var7) {
        return this.handleBindError(name, target, handler, context, var7);
    }
}

關注一下其中的bindObject()方法:

private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Binder.Context context, boolean allowRecursiveBinding) throws Exception {
    ConfigurationProperty property = this.findProperty(name, context);
    if (property == null && this.containsNoDescendantOf(context.streamSources(), name)) {
        return null;
    } else {
        AggregateBinder<?> aggregateBinder = this.getAggregateBinder(target, context);
        if (aggregateBinder != null) {
            return this.bindAggregate(name, target, handler, context, aggregateBinder);
        } else if (property != null) {
            try {
                return this.bindProperty(target, context, property);
            } catch (ConverterNotFoundException var10) {
                Object bean = this.bindBean(name, target, handler, context, allowRecursiveBinding);
                if (bean != null) {
                    return bean;
                } else {
                    throw var10;
                }
            }
        } else {
            return this.bindBean(name, target, handler, context, allowRecursiveBinding);
        }
    }
}

第一行的

ConfigurationProperty property = this.findProperty(name, context);

就是從上下文中獲取配置的值,context是由properties配置文件生成的。

中間有這麼一行:

this.bindProperty(target, context, property);

這個方法就是把配置值和MybatisProperties參數綁定的方法。其實方法中還有很深的一串調用,比想象中的複雜很多。

至此MybatisProperties參數綁定完成。

 

現在,根據我們配置的

mybatis.mapperLocations=classpath:mybatis/mappers/*.xml

這種配置項,就加載到了MybatisProperties實例的mapperLocations參數中。

回到開始的代碼,在MybatisAutoConfiguration創建SQLSessionFactory時有這麼一步:

if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
    factory.setMapperLocations(this.properties.resolveMapperLocations());
}

調用this.properties.resolveMapperLocations()可以成功的獲取到配置值並且賦值給SQLSessionFactory。

 

以上便是mybatis配置項的加載流程。

(本文結束)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章