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配置项的加载流程。

(本文结束)

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