先上小结:
- 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配置项的加载流程。
(本文结束)