手写Spring整合mybatis

Mybatis整合到Spring中需要考虑两个问题

        1.Mybatis中的Mapper文件都是接口形式的,接口无法生成对象,该如何放到Spring的单例池中

        2.一个项目有很多个Mapper文件,Spring启动的时候该如何查找到对应的Mapper文件

        解决了以上两个问题,那么Mybatis就能顺利的整合到Spring中,解决思路如下,借助于Spring中的扫描器Scanner,自己定义扫描路径和规则,那么便能扫描到对应的Mapper接口,然后针对于这些接口生成代理对象(jdk动态代理),然后将动态代理生成的代理对象放到Spring的容器中,那么便是完成了Spring和mybatis的整合

       手写Spring整合Mybatis的步骤如下

                1.自定义一个MyMapperScan注解,然后自定义一个类(MyImportBeanDefinitionRegistrar)实现ImportBeanDefinitionRegistrar接口,用于生成自定义扫描器去扫描MyMapperScan传进来的路径

                2.继承了Spring中自带的Scanner,父类的doScan方法会把路径下的Mapper都生成BeanDefinition,我们要做的,是对其生成的BeanDefinition进行一个修改(通过FactoryBean),让其在后面生成Bean的过程中生成代理对象。最后扔到单例池中

        自定义注解(在启动类上添加该注解,value是扫描路径),直接在该注解中引入自定义的ImportBeanDefinitionRegistrar类,用于扫描和注册BeanDefinition的逻辑

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyMapperScan {

	String value();
}

       自定义ImportBeanDefinitionRegistrar,这个接口是如何注册BeanDefinition,可以参考文章(Spring配置类源码解析(下)

//mapperscan直接会import这个类,然后会回调里面的方法,以此来进行扫描和注入bean
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
		// 扫描自定义注解MyMapperScan中用户传进来的路径,用于作为扫描路径
		Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName());
		String path = (String) annotationAttributes.get("value");


		MyBeanDefinitionScanner scanner = new MyBeanDefinitionScanner(registry);
        //mapper全是接口,自定义添加过滤器,不然会被规则过滤掉
		scanner.addIncludeFilter(new TypeFilter() {
			@Override
			public boolean match(@NotNull MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
				return metadataReader.getAnnotationMetadata().isInterface();
			}
		});

		scanner.scan(path);
	}
}

        自定义扫描器,扫描规则是,只要路径下的接口,只要是接口的,我都统一返回true,然后改造一下生成的BeanDefinition,将其BeanClassName设置成FactoryBean的(这样的话后续生成Bean的时候就会走我们自定义的FactoryBean的特殊逻辑)

public class MyBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

	public MyBeanDefinitionScanner(BeanDefinitionRegistry registry) {
		super(registry);
	}

	@Override
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        //父类的doScan方法会将扫描到的符合匹配规则的全部生成BeanDefinition
        //但是这些BeanDefinition肯定不会是我们想要的,我们要的是根据接口生成的代理对象
        //所以要对这些BeanDefinition进行一个人为的修改,修改其BeanClass(将其设置为FactoryBean)
        //后面在生成Bean的时候会对FactoryBean进行额外逻辑的处理,会生成getObject方法中返回的对象
		for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
			BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();

			//指定构造方法,只用一个参数的,这边传入对应的Class下面的代码会有相呼应
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
            //然后要改变一下这里面的BeanClass,后续生成Bean的时候会用到
			beanDefinition.setBeanClassName(MyFactoryBean.class.getName());
		}

		return beanDefinitionHolders;
	}

    //自定义规则, 只有扫描到的,并且是接口,才会被注册BeanDefinition
	@Override
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		return beanDefinition.getMetadata().isInterface();
	}
}

        自定义实现FactoryBean,里面的getObject方法直接返回sqlSession中的代理对象(不过这边也可以人为的使用jdk代理,但是这样的话那些@Select注解有关的Aop啥的也要自己写一套了)


public class MyFactoryBean implements FactoryBean { // MyFactoryBean--->UserMapper代理对象

	private Class mapperInterface;

	private SqlSession sqlSession;

    这里和上面那坨代码传进来的构造参数相呼应
	public MyFactoryBean(Class mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	@Autowired
	public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
		sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
		this.sqlSession = sqlSessionFactory.openSession();
	}

	@Override
	public Object getObject() throws Exception {
        //要对哪一个Mapper接口进行动态代理(这里默认使用了sqlSession的,也可以自己通过jdk动态代理生成)
		return sqlSession.getMapper(mapperInterface);
	}

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

最后,我们可以在启动类上加上我们自定义的@MyMapperScan注解,然后传入我们想要扫描的路径,就可以直接使用我们自定义的这套Spring整合mybatis的功能了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值