spring+mybatis源码解析

写这个文章的前因:我们在日常工作中使用@MapperScan("com.mybatis.dao") 就可以扫描到dao层下面的包,然后在service 或者 controller 层通过注入的方式就能够直接使用里面的方法,可是想一下,dao层里面是接口啊,那接口怎么可以直接调用呢?而接口又是如何交给spring管理的。这些问题将在下面的过程中一点点解析。

一、首先建立一下配置类,连接上数据库

import com.wengzw.mybatis.spring.MyImportBeanDefinitionRegistrar;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

@Configuration
@ComponentScan("com.mybatis")
// 加上这个注解就能扫描包下面的注解 加上代理,这里在演示第一个例子需要打开
//@MapperScan("com.mybatis.dao")
@Import(MyImportBeanDefinitionRegistrar.class)
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setPassword("11111");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/blog?useUnicode=yes&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC");
        return driverManagerDataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean (DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean. setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

}

二、日常使用

mapper层

public interface TestDao {
    @Select("select * from sys_user")
    List<Map<String,Object>> query();
}

测试类

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext(AppConfig.class);
       
        // 第一个知识点
        // 这是接口 是不能调用方法的,原因只有可能这个接口已经不是接口了,而是一个代理类,所以这里是使用了JDK动态代理
        TestDao dao = context.getBean(TestDao.class);
        System.out.println(dao.query());
    }
}

通过上面简单的例子可以知道,我们之所以能简单调用接口里的方法,是因为spring帮我们把扫描出来的接口都进行了动态代理,下面简单操作一下实现动态代理过程

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext(AppConfig.class);
 
        // 第二知识点 实现动态代理简单 但如何把代理出来的对象放到spring中比较难
        // 而且这个对象是存在内存中的,无法通过扫描 xml配置去放到spring容器中
        // 为什么要放到spring中,因为这个产生的代理对象在各个地方都可能需要用到
        Class[] clazz = new Class[]{TestDao.class};
        TestDao testDao = (TestDao) Proxy.newProxyInstance(Test.class.getClassLoader(), clazz, new TestInvocationHandler());
        testDao.query();

    }
}

动态代理处理类

public class TestInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getAnnotation(Select.class).value()[0]);
        System.out.println("查询DB");
        return null;
    }
}

实现动态代理简单 但如何把代理出来的对象放到spring中比较难,由于这个对象是存在内存中的,无法通过扫描 xml配置或者加注解@Bean去放到spring容器中
为什么要放到spring中?
因为这个产生的代理对象在各个地方都可能需要用到

三、接下来讲怎么将创建的代理对象优雅地放到spring容器中

在mybatis官网上,有这样的代码

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

所以依照这样的思想,我们首先创建自己的FactoryBean
先解释一下FactoryBean是什么
FactoryBean 是一个普通的bean,加了@Component 注解之后 可以被spring扫描到,最后这个bean就能被加入spring容器。
其次他是一个特殊bean,他是一个接口,可以通过实现该接⼝定制实例化Bean的逻辑,当使用容器中FactoryBean时,该容器不会返回FactoryBean本身,而是返回其生成的对象。

创建MyMapperFactoryBean

// 这里不需要扫描了因为是通过注入beanDefinition产生的
//@Component
public class MyMapperFactoryBean implements FactoryBean {
    // 可以通过xml配置,也可以使用下面的方法
    // 这里需要构造方法,在MyImportBeanDefinitionRegistrar循环的时候会传过来
    // 就不用写死 灵活运用
    Class aClass;

    public MyMapperFactoryBean(Class aClass) {
        this.aClass = aClass;
    }

// 这里会返回代理对象
    @Override
    public Object getObject() throws Exception {
        Class[] clazz = new Class[]{aClass};
        Object dao =  Proxy.newProxyInstance(MyMapperFactoryBean.class.getClassLoader(), clazz, new TestInvocationHandler());
        return dao;
    }

    @Override
    public Class<?> getObjectType() {
        return aClass;
    }
}

编写一个注册器。MyImportBeanDefinitionRegistrar
**这里需要普及一个知识点:**一个类变成一个对象,我们可以自己new出来。但spring的做法是先将类的信息先封装到BeanDefinition对象中,然后会put到一个键为beanName 值为BeanDefinition 的map中,然后循环扫描出每个beanName,根据他们的信息最终经过一系列的生命周期,最终才加到spring容器中。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param importingClassMetadata
     * @param registry      BeanDefinition的注册器
     * @param importBeanNameGenerator
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        // 本来这里是要把代理类的BeanDefinition传输进去,但是不行,因为他已经是一个创建好的对象了,拿不到BeanDefinition
        // 所以我不注入这个,注入MyMapperFactoryBean的BeanDefinition

        /**
         * 知识点 这里可以通过MapperScan 扫描处理 循环 就能产生不同的代理对象
         */
        // 这个类不需要扫描了,因为这里构建了MyMapperFactoryBean 的 BeanDefinition
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        // 非常重要 可以传类给构造方法,这里可以灵活,使用for循环后,将扫描的接口一个一个加入,我这里是写固定了
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("com.mybatis.dao.TestDao");
        // 添加完后再把这个完善后的beanDefinition注册进去,然后下一步spring扫描时就可以交给spring容器管理了,这样自然而然生成的代理对象就加入spring容器了
        registry.registerBeanDefinition("test",beanDefinition);
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext(AppConfig.class);
        // 证明FactoryBean是一个普通bean 交给spring进行管理,能够拿出来
        // 这里会打印MyMapperFactoryBean
//        System.out.println(context.getBean("&myMapperFactoryBean").getClass().getSimpleName());
        // 不加& 就是返回getObject返回的对象 而不是myMapperFactoryBean了
//        System.out.println(context.getBean("myMapperFactoryBean").getClass().getSimpleName());

        // 这里打印UserDao 由于是特殊bean 所以返回的是一个代理对象
        System.out.println(context.getBean("test").getClass().getSimpleName());
        TestDao test = (TestDao) context.getBean("test");
        test.query();

//        System.out.println(context.getBean(UserDao.class));
    }
}

通过以上就可以理解为什么接口里的方法可以直接调用,因为它已经被代理了,而代理对象又交给了spring管理,所以在任意位置注入就能够使用它。

**扩展点:**其实可以通过BeanFactoryPostProcessor后置处理器将代理对象注入到容器中,但会有缺点,以下代码有说明

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//        GenericBeanDefinition demoDao = (GenericBeanDefinition) beanFactory.getBeanDefinition("demoDao");
//        demoDao.setBeanClass(UserDao.class);

        /** 这种方法可以产生代理类 但有缺陷 因为TestInvocationHandler 是我们自己new出来的
         * 它并不属于spring管理,但里面要引用数据源这些东西却是要spring进行管理的,所以这种方案不行
         Class[] clazz = new Class[]{TestDao.class};
         TestDao dao = (TestDao) Proxy.newProxyInstance(this.getClass().getClassLoader(), clazz, new TestInvocationHandler());
         beanFactory.registerSingleton("dao",dao);
         */


    }
}

以上是对spring+mybatis源码的见解,有错误欢迎指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring整合Mybatis码分析可以分为以下几个步骤: 1. 创建Spring容器并加载配置文件。在Spring Boot中,可以通过@SpringBootApplication注解来创建Spring容器,并在配置文件中配置Mybatis相关的属性。 2. 创建Mybatis的SqlSessionFactory。Spring Boot会自动配置Mybatis的SqlSessionFactory,通过读取配置文件中的数据信息和Mybatis的配置信息,创建SqlSessionFactory对象。 3. 注册Mybatis的Mapper接口。Spring Boot会自动扫描项目中的Mapper接口,并将其注册到Spring容器中。 4. 创建Mapper代理对象。Spring Boot使用Mybatis的MapperFactoryBean来创建Mapper接口的代理对象。在创建代理对象时,会使用SqlSessionFactory来创建SqlSession,并将SqlSession注入到Mapper接口中。 5. 使用Mapper代理对象进行数据库操作。通过调用Mapper接口的方法,可以实现对数据库的增删改查操作。 整个过程中,Spring Boot通过自动配置和注解扫描的方式,简化了Spring整合Mybatis的配置和使用过程,使得开发者可以更方便地使用Mybatis进行数据库操作。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [SpringBoot整合Mybatis解析](https://blog.csdn.net/u013521882/article/details/120624374)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Spring-Mybatis整合码分析](https://blog.csdn.net/qq_42651904/article/details/111059652)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Spring解析之整合Mybatis](https://blog.csdn.net/heroqiang/article/details/79135500)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值