先上小結:
- MybatisAutoConfiguration類的@EnableConfigurationProperties({MybatisProperties.class})註解被Spring激活,準備注入MybatisProperties類。
- MybatisProperties類有@ConfigurationProperties(prefix="mybatis")註解,會被ConfigurationPropertiesBindingPostProcessor調用bind()方法來處理。
- 獲得MybatisProperties類的屬性列表,按照@ConfigurationProperties註解的prefix(也就是"mybatis"),加上MybatisProperties類的某具體屬性,向配置文件中查找是否有相關配置。
- 配置文件中有相關配置時,向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文件中有EnableAutoConfiguration和ConfigurationPropertiesAutoConfiguration類。下面分別看一下這兩個類。
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配置項的加載流程。
(本文結束)