手写spring集成mybatis以及源码核心逻辑

手动实现spring-mybatis集成

我们知道在使用mybatis的时候,需要导入spring-mybatis的集成jar包,才能将mybatis集成到spring容器中,而这集成的jar包具体做了哪些事情,使用什么技术把mybatis的mapper接口当成一个bean注册到spring容器中去的呢?

首先前面提过FactoryBean的功能,它就是把实现的getObject()返回的对象解析为spring的一个bean,beanName为FactoryBean;而集成mybatis的第一步就是通过FactoryBean导入mybatis的mepper接口的代理对象

public class RickFactoryBean implements FactoryBean {

	private Class mapper;

	private SqlSession sqlSession;

	public RickFactoryBean(Class mapper) {
		this.mapper = mapper;
	}

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

	@Override
	public Object getObject() throws Exception {
		return sqlSession.getMapper(mapper);

	}

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

setSqlSessionFactory()会从spring容器中获得mybatis的会话工厂用来构建sqlSession,其次这个类的构造方法就是传入mapper接口的class,用来从mybatis从获取Mapper代理对象返回;

接下来就需要将这个FactoryBean解析为BeanDefinition,注册到spring容器中去。它本身可以直接通过家@Component注解的方式去实现,但是一般会有很多Mapper接口,故不能作为单例的方式注册到spring容器中,我们可以通过新建一个BeanDefinition来把它注册到spring容器中

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(RickFactoryBean.class);
beanDefinition.setBeanClassName(RickFactoryBean.class.getName());
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
context.registerBeanDefinition("userMapper", beanDefinition);

那么如何拿到所有mapper接口呢?我们知道mybatis有个@MapperScan的注解。我们可以手写一个@RickMapperScan通过扫描路径拿到mapper接口一个一个的去实例化FactoryBean并注册到spring容器中去。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({RickImportBeanDefinitionRegistry.class})
public @interface RickMapperScan {

	String value();
}

而这个注解上还导入了一个很重要的类RickImportBeanDefinitionRegistry,这个类里面可以拿到@RickMapperScan传入的路径来实现扫描。

public class RickImportBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		Map<String, Object> map = metadata.getAnnotationAttributes(RickMapperScan.class.getName());
		String path = (String) map.get("value");
		RickClassPathBeanDefinitionScanner scanner = new RickClassPathBeanDefinitionScanner(registry);
		scanner.doScan(path);
	}
}

spring已经提供了扫描器(ClassPathBeanDefinitionScanner),我们只需要继承它并重写一些方法,就可以实现扫描逻辑:首先调用doScan方法,再调用父类的doScan()方法,此时spring会做类的判断,如果是接口类是不能注册到spring容器中去的,需要重写isCandidateComponent()方法来判断如果是接口就可以进行扫描生成BeanDefinition。拿到spring扫描出来的BeanDefinition,遍历所有BeanDefinition,通过FactoryBean来构建一个构造方法,构造方法的入参就是扫描出来的mapper类,并重置BeanDefinition的beanClass和beanClassName,最后就实现了将mybatis的mapper代理对象注册到spring容器中去了。

public class RickClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
	public RickClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
		super(registry);
	}

	@Override
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
		beanDefinitionHolders.forEach(holder -> {
			AbstractBeanDefinition bd = (AbstractBeanDefinition) holder.getBeanDefinition();
			bd.getConstructorArgumentValues().addGenericArgumentValue(bd.getBeanClassName());
			bd.setBeanClass(RickFactoryBean.class);
			bd.setBeanClassName(RickFactoryBean.class.getName());
		});
		return beanDefinitionHolders;
	}

	@Override
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		return beanDefinition.getMetadata().isInterface();
	}

	@Override
	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		return true;
	}
}

spring-mybatis 源码实现核心逻辑

 看看MapperScan源码,它导入了MapperScannerRegistrar类,跟手写实现一样,该类也是实现了ImportBeanDefinitionRegistrar类。

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

故启动时会调用registerBeanDefinitions()方法,该方法会向spring容器注册MapperScannerConfigurer对象,这个对象本质是一个BeanPostProcessor,spring就会执行postProcessBeanDefinitionRegistry()方法。重点来了,最终会创建ClassPathMapperScanner对象调用扫描方法,而这之前会把FactoryBean作为一个属性设置到ClassPathMapperScanner对象中。 

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

而FactoryBean中getObject()方法,最终拿到SqlSession类型为SqlSessionTemplate,但是我们在自己使用mybatis框架查询的时候用的是DefaultSqlSession,那么为什么要这么设计呢?其实这里真正是为了解决DefaultSqlSession类线程不安全的问题,因为他有很多属性,比如:autoCommit。

当我们在执行某个需要执行sql的业务方法时,假设有多个sql需要执行,那就在每次执行sql的时候都会创建一个DefaultSqlSession对象;此时如果业务方法加了@Transaction注解,那执行该方法用得就是同一个DefaultSqlSession对象;实现技术就是ThreadLocal缓存,通过判断方法上是否加了@Transaction注解,加了就会缓存第一次创建的DefaultSqlSession对象,所以同一个线程就会使用同一个SqlSession对象。

最后回到MapperScannerConfigurer#postProcessBeanDefinitionRegistry()方法,scanner设置完相关的属性之后就会执行扫描逻辑,将所有mybatis的mapper代理对象注册到spring容器中。

仅个人笔记。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值