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

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中

 

全部评论

相关推荐

秋招进行到现在终于能写总结了。完全没想到战线会拉这么长,过程会如此狼狈,不过更应该怪自己太菜了。好在所有的运气都用在了最后,也是有个去处。背景:双2本硕科班,无竞赛,本科一段研究所实习,硕士一段大厂暑期实习但无转正。技术栈是C++&nbsp;&amp;&nbsp;Golang,实习是客户端音视频(而且是鸿蒙端开发),简历两个C++项目一个Golang项目。主要投递岗位:后端,cpp软开,游戏服务端,测开,以及一些不拘泥于Java的岗位。从8月起总共投递123家公司,笔试数不清了,约面大约30家。offer/oc/意向:友塔游戏(第一个offer,面试体验很好,就是给钱好少南瑞继保(计算机科班点击就送(限男生),不...
乡土丁真真:佬很厉害,羡慕~虽然我还没有到校招的时候,也想讲一下自己的看法:我觉得不是CPP的问题,佬的背书双2,技术栈加了GO,有两段实习。投了123,面了30.拿到11个offer。这个数据已经很耀眼了。这不也是CPP带来的吗?当然也不止是CPP。至少来说在这个方向努力过的也会有好的结果和选择。同等学历和项目选java就会有更好的吗?我个人持疑问态度。当然CPP在方向选择上确实让人头大,但是我觉得能上岸,至于最后做什么方向,在我看来并不重要。至于CPP特殊,有岗位方向的随机性,java不是不挑方向,只是没得选而已。也希望自己以后校招的时候能offer满满
点赞 评论 收藏
分享
头像
11-21 11:39
四川大学 Java
是红鸢啊:忘了还没结束,还有字节的5k 违约金
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务