Mybatis源码分析Mapper创建和Spring的管理

3次阅读

共计 5955 个字符,预计需要花费 15 分钟才能阅读完成。

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 中进行注册

总结

整个过程主要分为

  1. mybatis 通过 MapperScan 把整个 mybatis 工厂 bean 注册到 ioc
  2. spring 负责 mybatis 创建的 mapper 注入 ioc

重点:

  • MapperScan 中的 ImportBeanDefinitionRegistrar 的 registerBeanDefinitions 将工厂 beanMapperFactoryBean 封装成 beanDefinition 并且把包下的 mapper 添加成构造函数参数传入并注册到 spring 中
  • 注册完成后再启动时 spring 将根据构造器参数和工厂 bean 调用 mybatis 创建 mapper,并且把 mapper 注册到 spring 管理的 bean 中
正文完
 0