Spring源码之手写MyBatis

1、回顾

前面连续开了两篇,写了Spring容器初始化的调用链。
第一篇主要写了,AnnotationConfigApplicationContext对象创建完成。AnnotatedBeanDefinitionReader(this)这个对象会在DefaultListableBeanFactory放入6个BD(BeanDefinition的简称)。其中有一个特别重要的BeanDefinitionRegistryPostProcessor有源码基础的老哥应该知道这个类有多重要。
第二篇主要写的,配置类的注册,以及DefaultListableBeanFactory的初始化。其中有一个非常重要的方法invokeBeanFactoryPostProcessors这个方法调用链实在太长了。有机会会专门开一篇来,针对它来写。

2、目标—手写mybatis

今天主要是利用前面讲到的一些知识,手写一个mybatis部分代码。

3、环境准备!

1、当然是先建一个工程,
2、引入依赖。

  <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

3、添加配置文件信息。(这些信息大家基本上开发中都是用烂了的)

@Configuration
@ComponentScan(value = "com.spring")
@MapperScan(value = "com.spring")
public class AppConfig {
    @Bean
    @Autowired
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
    @Bean
    public DataSource dataSource(){
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setPassword("root");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/mydb?characterEncoding=utf8&verifyServerCertificate=false&useSSL=true");
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        return driverManagerDataSource;
    }
}

4、添加一个 Mapper接口(这个大家也都懂无须多说吧。)

public interface IndexDao {
    @Select(value = "select * from user where name like '%${name}%'")
    public List<Map<Integer,String>> list(@Param(value = "name") String name);
}

5、添加一个Service方法,这个也不解释。

@Component
public class IndexService {
    @Autowired
    IndexDao indexDao;
    public void list(String str){
        System.out.println(indexDao.list(str));
        System.out.println(str);
    }
}

4、一阶段测试。

执行测试类,输出数据库要查的信息,这个大家也都懂吧。

    public static void main(String[] args) {
       org.springframework.context.annotation.AnnotationConfigApplicationContext annotationConfigApplicationContext =
               new AnnotationConfigApplicationContext(AppConfig.class);
        IndexService bean = annotationConfigApplicationContext.getBean(IndexService.class);
        bean.list("兵哥");
    }
    ------------------------------控制台输出----------------------------------
    [{name=兵哥, id=1}]
	兵哥

5、疑问?IndexDao什么时候实例化的?

不知道大伙,有没有疑问,我都没实例化,IndexDao这个Mapper spring是怎么注入的,怎么拿到这个对象,且执行这个方法的?
IndexDao这是一个接口,也没办法实例化。我加个注解。然后引到Service中,凭什么就能执行了?

我们就在配置类,加了这一样注解。@MapperScan(value = "com.spring")啊哈,它就可以执行了。
我们去掉这一样 它就报错了。
1、先来看一下这个注解它干了什么
2、它@Import(MapperScannerRegistrar.class) import了一个MapperScannerRegistrar

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {

3、再来看 MapperScannerRegistrar 实现了 ImportBeanDefinitionRegistrar 诶,这不就是上一篇写到的。导入一个BeanDefinition注册器

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

6、(利用)ImportBeanDefinitionRegistrar,手写mybatis

mybatis 源码比较简单,大家可以自己去看。今天这一篇,源码肯定也是写不了。
接上面可以看到mybatis实现了ImportBeanDefinitionRegistrar这个类完成 mybatis的功能。
代码如下,我自己写一个类,实现它。然后完成包扫描。然后拿到我们要去实例化的接口信息。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //第一步 先完成扫描部分
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
        //获取到扫描的包
        List<String> basePackages = new ArrayList<String>();
        for (String pkg : annoAttrs.getStringArray("value")) {
            if (StringUtils.hasText(pkg)) {
                System.out.println("拿到注解中的包"+pkg);
                basePackages.add(pkg);
            }
        }
        //拿到代码的当前路劲
        String rootPath = this.getClass().getResource("/").getPath();
        String  basePackagePath =basePackages.get(0).replaceAll("\\.","\\\\");
        List<Class> classes = new ArrayList<>();
        //获取到包路劲下的所有类
        Scanner(basePackagePath, rootPath,classes);
        for (Class aClass : classes) {
            //第一步得到一个BeanDefinition
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(aClass);
            AbstractBeanDefinition definition = beanDefinitionBuilder.getBeanDefinition();
            //第二步为MyFactoryBean生成一个构造方法 并且设置MyFactoryBean信息
            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
            definition.setBeanClass(MyFactoryBean.class);
            //注册一个BeanDefinition
            registry.registerBeanDefinition(Introspector.decapitalize(aClass.getSimpleName()),definition);
        }

    }

    public  List<Class> Scanner(String basePackagePath ,String rootPath, List<Class> classes){
        //当前路劲加上我们 传过来的包名,就是我们要扫描的类信息
        File file = new File(rootPath+"//"+basePackagePath);
        String names[]=file.list();
        for (String name : names) {
            if (name.endsWith(".class")) {
                name = name.replaceAll(".class", "");
                try {
                    String  ba =basePackagePath.replaceAll("\\\\","\\.");
                    System.out.println("拿到类的全称"+ba + "." + name);
                    Class aClass = Class.forName(ba + "." + name);
//                    //判断是否是一个接口类,且在Dao下
                    if (aClass.isInterface() && ba.contains("dao")) {
                        classes.add(aClass);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }else {
                Scanner(basePackagePath+"\\"+name,rootPath,classes);
            }
        }
        return classes;
    }
}

2、我们只需要将上面,扫描出来的类。然后传过来的,字节码生成一个代理对象。
3、MyFactoryBean 实现FactoryBean 至于FactoryBean的作用就不说了。前面的博客已有写到,大家可以去翻一番。mybatis框架就是利用这两个东西来完成与spring整合的。
3、然后让它执行这个代理方法。
4、代理方法中拿到注解。并且拿到sql语句。。

public class MyFactoryBean implements FactoryBean,InvocationHandler {
    Class classzz;
    public  MyFactoryBean( Class classzz){
        this.classzz = classzz;
    }
    @Override
    public Object getObject() throws Exception {
        //反射,拿到传入进来的字节码 生成代理对象。
        Class [] classes = new Class[]{classzz};
        Object dao =  Proxy.newProxyInstance(this.getClass().getClassLoader(),classes,this);
        return dao;
    }

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

    /**
     * 代理对象要执行的代理方法。
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("aaaa");
        //获取注解上的sql语句
        Class<?>[] classes = proxy.getClass().getInterfaces();
        for (Class<?> aClass : classes) {
            for (Object arg : args) {
                Method classMethod = aClass.getMethod(method.getName(), arg.getClass());
                String[] value = classMethod.getDeclaredAnnotation(Select.class).value();
                for (String s : value) {
                    System.out.println(s);
                }
            }
        }
        return null;
    }
}

6、为了B格,我们自己写一个自定义的注解类。
注解类中然后,导入我们写的那个 MyImportBeanDefinitionRegistrar

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyMapperScan {
    String[] value() default {};
}

7、替换成,我们自己写的这个 注解。传入要扫描的包。B格瞬间就上来了。有没有

@Configuration
@ComponentScan(value = "com.spring")
//@MapperScan(value = "com.spring")
@MyMapperScan(value = "com.spring")
public class AppConfig {

7、测试 mybatis低配版

如下图所示,sql语句是不是已经拿到。那我们要怎么让sql语句执行呢?

    public static void main(String[] args) {
       org.springframework.context.annotation.AnnotationConfigApplicationContext annotationConfigApplicationContext =
               new AnnotationConfigApplicationContext(AppConfig.class);
        IndexService bean = annotationConfigApplicationContext.getBean(IndexService.class);
        bean.list("兵哥");
    }
     ------------------------------控制台输出----------------------------------
     aaaa
	select * from user where name like '%${name}%'
	null
	兵哥

剩下的步骤。很简单。
第一步:替换参数。得到一条可以执行的sql语句
怎么替换,无须多说,太简单
第二步:执行sql语句 获取结果。这个也不多说。用过 JDBC c3p0这种基础框架的大佬。基本都懂!

源码地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值