背景
是不是还在疑惑为什么我们在工程中定义了接口mybatis就可以直接操作我们的数据库?
是不是想了解spring和mybaits整合的原理?
了解原理后我们能复用在工程上的东西是什么?换句话说怎么提高代码的逼格?
目的
基于上述背景,笔者准备深入源码带大家一探究竟,读完这篇文章大家可以的到的收获
- 了解Mybatis和Spring整合的底层原理
- 知道为什么只定义了接口就可以直接操作数据库
- 了解Spring中的拓展点和FactoryBean的使用
- 可以自己定义插件提高代码逼格
- Spring中自动装配的类型到底是什么
分析问题
准备
代码环境:
- JDK :1.8
- Spring Boot :2.3
- 基于注解
- 忽略一些不重要的细节 项目代码如下
//对象
public class AD {
//id
private int id;
//名称
private String name;
}
//mapper类
public interface AdMapper {
@Select("select id , name from " + " ad " + "where id=#{id} ")
AD findADById(@Param("id") int id);
}
//接口
public interface AdService {
/**
* 通过id获取广告对象
*
* @param id
* @return
*/
AD findADbyId(int id);
}
//接口实现类
@Service
public class AdServiceImpl implements AdService {
@Resource
private AdMapper adMapper;
@Override
public AD findADbyId(int id) {
return adMapper.findADById(id);
}
}
//启动类
@MapperScan("com.learn.code.mybatis.mapper")
@SpringBootApplication
public class LearnCodeApplication {
public static void main(String[] args) {
SpringApplication.run(LearnCodeApplication.class, args);
}
}
问题1 Mapper
对象的BeanDefinition
是怎么加入到工厂中的
问题由来:一个对象只有被Spring
创建并且放入到工厂中才能被其他对象注入,比如AdServiceImpl
就是加了@Service
注解并且结合包扫描。这样环境中就会有这个对象,但是AdMapper
没有加任何的注解,而我们的 AdServiceImpl
却可以直接通过 @Resource
注入进来。说明这个对象是被Spring创建的。
回想Spring创建Bean的过程,几乎所有的对象都是先变成BeanDefinition
然后再通过工厂创建,所以我们只要找到这个Mapper类是什么时候变成BeanDefinition
的。
我们通过看代码发现和Mybatis
相关的只有开始在配置类中加入的注解@MapperScan
,难道是这个注解起的作用吗?
没错,这个注解是个入口,就像是把钥匙,像只有我们带了钥匙才能开门一样,只有加了这个注解(注意本文基于注解配置)才能实现上述的功能
那现在我们重点看一下这个注解
@MapperScan
屏蔽掉一些非关键信息 注解结构如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {
};
String[] basePackages() default {
};
Class<?>[] basePackageClasses() default {
};
}
在这个注解类中发现其实也没有做太多事,但是我们会发现类上边有@Import(MapperScannerRegistrar.class)
这行.对Spring启动源码有了解的同学可能知道,在准备工厂阶段,会把 @Import
引入的类当作配置类,后期通过Spring创建这个bean。所以我们应该能感觉MapperScannerRegistrar
这个类是有些作用的,照例点进去看一下源码。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
发现这个类继承了ImportBeanDefinitionRegistrar
实现了这个接口,这个接口是Spring当中的一个扩展点,基于接口中的registerBeanDefinitions方法我们可以做到向bean工厂中注入BeanDefinition,现在看来我们的方向是对的。
下面解析一下 registerBeanDefinitions 方法
/**
* importingClassMetadata 注解元素
* registry 用来向 BeanDefinitionRegistry 加入 BeanDefinition
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//获取MapperScan注解中的属性信息 @MapperScan("com.learn.code.mybatis.mapper")
// 类上可能会有很多注解 这里指定名称获取
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//初始化一个 scanner 用作扫描指定包下的类
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// Spring 3.1 版本需要有这个判断 特殊逻辑
if (resourceLoader != null) {
<