示例
- 首先引入Mybatis的使用示例,其中两个关键变量:SqlSessionFactory ,SqlSession
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Mapper1 mapper = sqlSession.getMapper(Mapper1.class);
String result = mapper.select1();
- 问题引入,以下代码中(系统中存在Mapper1,Mapper2,Mapper3接口),如果直接运行main函数,后台会报错,mapper注入错误,此时可以引入FactoryBean机制,生成mapper的代理对象:
@Service
public class MybatisAndSpringService {
@Autowired
private Mapper1 mapper1;
// ......多个Mapper接口(省略)
@Autowired
private Mappern mappern;
public void test() {
String result1 = mapper1.select1();
System.out.println("result1 = " + result1);
String result2 = mapper2.select2();
System.out.println("result2 = " + result2);
//……
}
}
// 主函数
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
// 注册主配置类
ac.register(MyConfig.class);
// 刷新容器
ac.refresh();
MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
service.test();
}
- 引入FactoryBean接口,此时如果直接运行main函数依然会报错,因为自定义FactoryBean不在Spring容器中,所以需要采用声明式编程的方式,修改main函数:
public class ArtistFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
// JDK动态代理
Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{Mapper1.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("当前调用的方法名:" + method.getName());
return null;
}
});
return obj;
// Mybatis的代理逻辑
// return sqlSession.getMapper(mapperClazz);
}
@Override
public Class<?> getObjectType() {
return Mapper1.class;
}
}
// main函数
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
// 注册主配置类
ac.register(MyConfig.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(ArtistFactoryBean.class);
ac.registerBeanDefinition("facotrybean", beanDefinition);
// 刷新容器
ac.refresh();
MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
service.test();
}
- 到了这一步,直接运行是没有问题的,继续思考:FactoryBean的getObject方法,生成代理对象的接口是在代码中写死的,所以还需要通过构造函数传参的方式修改
// 修改FactoryBean
public class ArtistFactoryBean implements FactoryBean {
private Class<?> mapperClazz;
public ArtistFactoryBean(Class<?> mapperClazz) {
this.mapperClazz = mapperClazz;
}
@Override
public Object getObject() throws Exception {
// JDK动态代理
Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{mapperClazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("当前调用的方法名:" + method.getName());
return null;
}
});
return obj;
// Mybatis的代理逻辑
// return sqlSession.getMapper(mapperClazz);
}
@Override
public Class<?> getObjectType() {
return mapperClazz;
}
}
// 同时在main函数中也要修改
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
// 注册主配置类
ac.register(MyConfig.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(ArtistFactoryBean.class);
// 新增逻辑
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
ac.registerBeanDefinition("facotrybean", beanDefinition);
// 刷新容器
ac.refresh();
MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
service.test();
}
- 经过上述修改之后,FactoryBean中mapper接口的传入方式得到了解决,但是,如果此时要添加其他的mapper,比如:Mapper2,Mapper3……,就会出现下面的编码:
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
// 注册主配置类
ac.register(MyConfig.class);
// Mapper1
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(ArtistFactoryBean.class);
// 新增逻辑
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
ac.registerBeanDefinition("facotrybean", beanDefinition);
// ……很多Mapper(省略)
// Mappern
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(ArtistFactoryBean.class);
// 新增逻辑
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mappern.class);
ac.registerBeanDefinition("facotrybean", beanDefinition);
// 刷新容器
ac.refresh();
MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
service.test();
}
- 显然这种编码方式非常不优雅,那么接下来可以考虑包扫描的方式,把Mapper接口扫描出来保存到一个集合中,然后遍历集合,就可以替换掉这么一大片冗余的代码,可以利用Spring提供的ImportBeanDefinitionRegistrar接口,实现它的registerBeanDefinitions方法
- 同时结合Spring的类扫描器ClassPathBeanDefinitionScanner,但是Spring只扫描类,而这里我们只扫描接口,所以需要自定义一个类扫描器,需要修改isCandidateComponent的逻辑,只返回接口,否则Mapper接口无法扫描到
- 重写doScan逻辑,因为我们最终需要的是Mapper接口的代理对象,所以需要把之前定义的FactoryBean设置进去
- 需要注意,在配置类上需要加入注解:@Import(ArtistImportBeanDefinitionRegistrar.class),使Bean定义注册器成为Spring容器中的Bean
//自定义类扫描器
public class ArtistScanner extends ClassPathBeanDefinitionScanner {
public ArtistScanner(BeanDefinitionRegistry registry) {
super(registry);
}
// 需要扫描到接口
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> holders = super.doScan(basePackages);
for (BeanDefinitionHolder holder : holders) {
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) holder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(holder.getBeanDefinition().getBeanClassName());
// 为了获取被扫描到的Mapper接口的代理对象
beanDefinition.setBeanClass(ArtistFactoryBean.class);
}
return holders;
}
}
// 批量注册Bean定义
public class ArtistImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String scanPath = "com.xxxx.mapper";
// 此时要自定义包扫描器,因为Spring的包扫描器只扫描类,不扫描接口;而这里要扫描接口
ArtistScanner scanner = new ArtistScanner(registry);
// match方法返回true,保证接口能被扫描到
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
scanner.scan(scanPath);
}
}
- 经过上述的修改,就可以把main方法中冗余的代码替换掉,到此还有一个最大的问题,getObject中生成代理对象是手工操作的,而代理对象应该由Mybatis框架生成,对应到文章开头提到的SqlSessionFactory
// 修改后的FactoryBean
public class ArtistFactoryBean implements FactoryBean {
private Class<?> mapperClazz;
public ArtistFactoryBean(Class<?> mapperClazz) {
this.mapperClazz = mapperClazz;
}
@Autowired
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
this.sqlSession = sqlSessionFactory.openSession();
}
@Override
public Object getObject() throws Exception {
// Mybatis的代理逻辑
return sqlSession.getMapper(mapperClazz);
}
@Override
public Class<?> getObjectType() {
return mapperClazz;
}
}
- 可以看到,通过setter注入SqlSessionFactory对象,获取到SqlSession
- 在配置类中也要添加SqlSessionFactory的Bean对象
@Configuration
@ComponentScan("com.mybatisandspring")
public class MyConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
}
- 继续看,我们的扫描路径是在代码中写死的,可以进一步作修改
- 自定义一个注解,把mapper接口所在的包路径作为value,传入注解,然后在registerBeanDefinitions方法中,通过参数AnnotationMetadata获取该注解的value值,这样就可以灵活地配置了。
- 首先自定义注解(这里把配置类中的@Import移到此处,为了代码的可读性,这里不影响逻辑的描述)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
String value();
}
- 接着在配置类上加上自定义注解,最终的配置类如下:
@Configuration
@ComponentScan("com.mybatisandspring")
@ArtistMapperScan("com.mybatisandspring.mapper")
public class MyConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
}
- 到此为止,基本上mybatis-spring框架的核心原理基本都体现出来了,概括地说:就是把Mybatis生成的代理对象注入到Spring容器中,mybatis-spring.jar包起到了整合的桥梁的作用
- 下面对比自己写的类和mybatis-spring.jar中的核心类
mybatis-spring包的整合源码分析
- 对比Mapper扫描注解:
// @MapperScan注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}
// 自定义扫描注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
String value();
}
- MapperScannerRegistrar的registerBeanDefinitions方法会先解析MapperScan注解,然后再注册Bean定义
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
// MapperScannerConfigurer执行扫描逻辑
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
String defaultScope = annoAttrs.getString("defaultScope");
if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
builder.addPropertyValue("defaultScope", defaultScope);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
// 注册Bean定义
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
- 在MapperScannerConfigurer中有一个属性mapperFactoryBeanClass,类型是MapperFactoryBean,它的getObject方法就是返回Mapper接口的代理对象,mapperInterface也是作为被扫描的属性传入
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
总结:
- MapperFactoryBean完成的功能是生成Mapper接口的代理对象
- MapperScannerConfigurer扫描Mapper接口
- MapperScannerRegistrar完成的功能是把扫描到的Mapper接口的代理对象转为Bean定义,存入Spring容器
- SqlSessionFactory负责解析配置的mybatis配置文件,包括数据源、类型别名、映射文件的扫描路径等等信息