Mybatis 源码分析—Mapper 创建和 Spring 的管理
我们分析的时候先自己猜测实现方式再对比 mybatis 的源码实现方式
mapper 创建
- 因为 mybatis 可以脱离 spring 自己使用,所以 mapper 的 bean 创建是由 mybatis 完成的
- 创建方式,根据不同的 mapper,方法都是对应与注解或者配置文件对应名称的方法,所以我们猜测使用的是 spring 的动态代理创建方式
我们自己实现 mapper 创建工厂代理类:
public class MySessionFactoryProxy {public static Object getMapper(Class c){Class[] classes = new Class[]{c};
// 动态代理获取 mapper
Object o = Proxy.newProxyInstance(MySessionFactoryProxy.class.getClassLoader(), classes, new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 解析 sql
// 执行 sql
Select annotation = method.getAnnotation(Select.class);
String sql = annotation.value()[0];// 一个注解可能有多个 sql 语句
System.out.println("sql:"+sql);
return null;
}
});
return o;
}
}
那么由谁来调用这个 getMapper 方法呢,毫无疑问是 mybatis,这个时候需要一个工厂 bean,用来调用该方法,每次调用,创建一个 factoryBean,传入一个 mapper 类,来创建该 mapper(这样就可以解决代码写死的情况)
- MyMapperFactoryBean
public class MyMapperFactoryBean<T> implements FactoryBean<T> {
// 实例化的时候传入
public MyMapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}
// 使用全局变量存储不同的 mapper 类
private Class<T> mapperInterface;
public T getObject() throws Exception {System.out.println("get mapper");
return (T) MySessionFactoryProxy.getMapper(mapperInterface);
}
public Class<?> getObjectType() {return this.mapperInterface;}
}
再看 mybatis 的实现
- mapper 注册类获取 mapper
public class MapperRegistry {public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 主要调用
return mapperProxyFactory.newInstance(sqlSession);
}
- 再看 mybatis 实现的代理工厂类:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
// 主要方法如下
public T newInstance(SqlSession sqlSession) {MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
- 调用 getMapper 方法的 MapperFactoryBean
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}
public T getObject() throws Exception {return this.getSqlSession().getMapper(this.mapperInterface);
}
public Class<T> getObjectType() {return this.mapperInterface;}
public void setMapperInterface(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}
public Class<T> getMapperInterface() {return this.mapperInterface;}
}
与我们创建的大体一致
创建完成 mapper 后,我们需要把 mapper 交给 ioc 管理
创建 mapper 放到 Spring IOC
请区别
添加注解或者配置,将类交个 Spring 管理,由 Spring 为我们创建对象
,mapper 是由 mybatis 通过动态代理创建的
自己创建对象交给 Spring 管理
- 想法 1:由配置类创建
@Configuration
@ComponentScan("top.dzou.mybatis")
public class Appconfig {
@Bean
public UserMapper userMapper(){return (UserMapper) MySessionFactoryProxy.getMapper(UserMapper.class);
}
}
每一个都要创建一个 @bean 注解,不切实际,想想 Spring 怎么实现的?
- 我们想起来 bean 可以通过 spring 配置文件配置,我们想到使用 xml 格式配置 mapper bean
类似下面写法:
使用我们自己的 mapper 工厂代理类创建 mapper
<bean id="roleMapper" class="top.dzou.mybatis.mapper_to_spring.my_mybatis.MyMapperFactoryBean">
<property name="mapperInterface" value="top.dzou.mybatis.mapper_to_spring.RoleMapper"/>
</bean>
<bean id="userMapper" class="top.dzou.mybatis.mapper_to_spring.my_mybatis.MyMapperFactoryBean">
<property name="mapperInterface" value="top.dzou.mybatis.mapper_to_spring.UserMapper"/>
</bean>
</beans>
这样的代码是不是很熟悉,但是这样任然要配置很多的 bean,我们想到了 springboot 中使用的 mapperscan 注解
- 想法 3:自己实现一个注解
MyMapperScan
,包括一个包,在 MapperScan 注解上导入 mapper 导入类,把包下面的全部创建并放到 spring 中
这里我们需要知道的是 Spring 创建 bean 的时候是先加载类并创建BeanDefinition
,在通过 BeanDefinition 创建相应的 bean,我们因为 mapper
我们自己实现:
- MyMapperScan
根据 basePackages 导入 mapper
// 导入 ImportBeanDefinitionRegister
@Import(MyBeanDefinitionRegister.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMapperScan {String[] basePackages() default {};
}
- MyBeanDefinitionRegister
使用 Spring 注册 bean 时使用的 ImportBeanDefinitionRegistrar 注册 mapper
public class MyBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
//class->beanDefinition->map->bean
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 获取包信息并把包中类全部注册动态添加到 beanDefinition 参数中
{
// 伪代码
basePackages = 获取包名下的所用 mapper 类,保存到集合 basePackages
baseName = 从 mapper 类获取 beanName
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(basePackages);
beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);
}
}
- 配置类代替主启动类(用于测试)
@Configuration
@ComponentScan("top.dzou.mybatis.mapper_to_spring")
@MyMapperScan(basePackages = "top.dzou.mapper_to_spring.mapper")// 自定义 mapper 扫描注解
public class Appconfig {}
我们看一下 mybatis 怎么实现的
- MapperScan
它导入了一个 MapperScannerRegistrar
扫描注册 mapper 的类
@Import({MapperScannerRegistrar.class})
public @interface MapperScan {
- MapperScannerRegistrar
我们可以看到它实现了 ImportBeanDefinitionRegistrar
的 bean 定义导入注册类,实现了具体的 registerBeanDefinitions
注册 bean 定义的方法,把包下的 mapper 全部添加到一个集合中,然后把这个集合进行注册到 ioc 中,和我们的想法基本一致
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
List<String> basePackages = new ArrayList();
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
它使用一个集合保存了所有的 mapper 类,并把他们放在一个 beanDefinition 中进行注册
总结
整个过程主要分为
- mybatis 通过 MapperScan 把整个 mybatis 工厂 bean 注册到 ioc
- spring 负责 mybatis 创建的 mapper 注入 ioc
重点:
- MapperScan 中的 ImportBeanDefinitionRegistrar 的 registerBeanDefinitions 将工厂 beanMapperFactoryBean 封装成 beanDefinition 并且把包下的 mapper 添加成构造函数参数传入并注册到 spring 中
- 注册完成后再启动时 spring 将根据构造器参数和工厂 bean 调用 mybatis 创建 mapper,并且把 mapper 注册到 spring 管理的 bean 中