手动模拟一个Mybatis,让你对spring拓展点的使用心里有一点点数

spring官方给出了三个拓展点:
a、基于实现BeanPostProcessor。
b、基于BeanFactoryPostProcessor。
c、基于FactoryBean。(如果想要知道这三个拓展点的使用和原理可以留言可肝
今天我们就使用@Import和FactoryBean实现一个青春mini版的Mybatis,实现从sql到entity。

1、配置MySQL的连接

@Configuration
public class MysqlConfig {
    @Bean
    public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }
}

不了解@Configuration注解符的可以看这篇文章,现在我们已经能从spring拿到MySQL的连接了。

2、自定义需要到的注解符

@Import(HxlMapperScannerRegistrar.class) //我们用Import和ImportBeanDefinitionRegistrar来加载自己生成的bean
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HxlMapperScan {
    String value();
}

@Target(PARAMETER)
@Retention(RUNTIME)
public @interface HxlParam {
    String value();
}

@Target(METHOD)
@Retention(RUNTIME)
public @interface HxlQuery {
    String value();
}

不了解元注解的看这个,不了解@Import的看这个,我们想要的效果就是在启动类上加了HxlMapperScan就能生效去掉就不生效,升不生效完全由程序员自己决定。

实现ImportBeanDefinitionRegistrar来动态往beanFactory注册bean

public class HxlMapperScannerRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    	//spring会调用这个方法,importingClassMetadata是注解当中的信息,我们可以利用registry来修改添加和删除BeanDefinition
        Set<BeanDefinitionHolder> bds = scan(importingClassMetadata, registry); //先打到需要注册的dao接口
        bds.forEach(this::processBeanDefinition); //给这些bd添加beanClass
    }

	//这个方法用来扫描需要我们自己手动注册的dao接口
    private Set<BeanDefinitionHolder> scan(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annotation = fromMap(importingClassMetadata.getAnnotationAttributes(HxlMapperScan.class.getName()));
        return ofNullable(annotation).map(a -> scan(annotation, registry)).orElseGet(HashSet::new);
    }

    private Set<BeanDefinitionHolder> scan(final AnnotationAttributes annotation, final BeanDefinitionRegistry registry) {
        String basePackage = annotation.getString("value"); // value里面放的是包的路径,这个地方获得注解里面value里面的值用来扫描
        CustomScanner scanner = new CustomScanner(registry); //用register生成一个我们自己的scanner
        return scanner.doScan(basePackage);
    }

    private static class CustomScanner extends ClassPathBeanDefinitionScanner {

        public CustomScanner(BeanDefinitionRegistry registry) {
            super(registry, false);
        }

        public Set<BeanDefinitionHolder> doScan(String basePackage) {
            super.addIncludeFilter((metadataReader, metadataReaderFactory) -> true); //修改默认的筛选规则,
            return super.doScan(basePackage);
        }

        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        	//要修改这个判断规则,因为我们要扫描的是一些接口,原来的规则,要求他是一个具体类或者是一个抽象的类但是要加@lockUp注解这个点想看源码留言可肝
            return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
        }
    }

    private void processBeanDefinition(final BeanDefinitionHolder bd) {
    	//前面扫描的那些bd都是一些接口,我们需要为这些接口提供实现方法,这个时候我们就用到了FactoryBean来扩展spring
        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) bd.getBeanDefinition();
        beanDefinition.setBeanClass(HxlFactoryBean.class); //把自定义的FactoryBean class文件传进去
        //给构造方法添加参数,我们要用HxlFactoryBean给这些接口添加实现也是需要告诉HxlFactoryBean这个接口是什么样子的
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(HxlDao.class.getName()); 
    }
}

实现FactoryBean来给我们的接口添加实现

//这个类不仅实现了FactoryBean还实现了InvocationHandler
public class HxlFactoryBean<T> implements FactoryBean<T>, InvocationHandler { 
    private final Class<T> clazz;
    @Autowired
    private JdbcTemplate template; //想要实现查询你要获得一个连接

    public HxlFactoryBean(final Class<T> clazz) { //构造函数可以接收传进来的接口
        this.clazz = clazz;
    }

    @Override
    public T getObject() {
    	//使用jdk动态代生成代理对象
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, this);
    }

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
    	//代理对象的生成其实就是一堆判断然后再调用jdbcTemplate的查询方法
        HxlQuery query = method.getAnnotation(HxlQuery.class);
        String sql = assembleSql(query.value(), args, method.getParameterAnnotations());
        Class<?> returnType = method.getReturnType();
        if (returnType.getTypeName().equals("void")) {
            template.execute(sql);
            return null;
        } else if (returnType.getSimpleName().equals("Integer") || returnType.getSimpleName().equals("int")) {
            return template.queryForObject(sql, Integer.class);
        }
        List<?> result = template.query(sql, new BeanPropertyRowMapper<>(returnType));
        return result.size() > 0 ? result.get(0) : null;
    }

    private String assembleSql(String sql, final Object[] args, final Annotation[][] parameterAnnotations) {
    	//从dao层的方法中组装一个sql
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                HxlParam param = (HxlParam) parameterAnnotations[i][0];
                String value = args[i].toString();
                value = args[i].getClass().getSimpleName().equals("String") ? "'" + value + "'" : value;
                sql = sql.replace("#{" + param.value() + "}", value);
            }
        }
        return sql;
    }
}

好了到现在我们已经实现了一个简陋原始版的Mybatis

4、测试

@HxlMapperScan("com.example.hxl.dao")
public class DemoApplication { //启动类
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

@Component
public interface HxlDao { //我们的dao
    @HxlQuery("delete from user where 1=1")
    void deleteAll();

    @HxlQuery("select count(*) from user")
    Integer countAll();

    @HxlQuery("insert into user values(#{id}, #{name})")
    void save(@HxlParam("id") Integer id, @HxlParam("name") String name);

    @HxlQuery("select * from user where id = #{id} and name = #{name}")
    User findByIdAndName(@HxlParam("id") Integer id, @HxlParam("name") String name);
}

public class User { //entity
    private int id;
    private String name;
}



@Autowired  
private HxlDao hxlDao;
@Test
public void should_hxl_batis() { //测试方法
  hxlDao.deleteAll();
  System.out.println(hxlDao.countAll());
  hxlDao.save(1, "hxl");
  System.out.println(hxlDao.findByIdAndName(1, "hxl"));
  System.out.println(hxlDao.countAll());
}

测试的结果
customMybatis测试
好了大功告成,题外话不用纠结hxl这是我名字的缩写,欢迎评论区见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值