接口不能被实例化,Mybatis的Mapper/Dao为什么却可以@Autowired注入?
对于我们 java 来说,接口是不能被实例化的。而且接口的所有方法都是public的。
可是为什么 Mybaits 的mapper 接口,可以直接 @Autowired 注入 使用?
基于SpringBoot 的 @MapperScan 注解入手,分析。
带着问题分析代码:
@MapperScan
1 | java复制代码@Retention(RetentionPolicy.RUNTIME) |
1 | java复制代码@SpringBootApplication |
在SpringBoot中使用mybatis,那它的入口就在 @MapperScan中。@MapperScan注解,是在SpringBoot的启动类中。
@MapperScan中有个 @Import 注解。
@Import 注解,可以加载某个类,放到Spring的IOC中管理
在Spring中,要将Bean放到IOC容器中管理的话,有几种方式。
- @Import 此种方法
- @Configuration 与 @Bean 注解结合使用
- @Controller @Service @Repository @Component
- @ComponentScan 扫描。
- 重写BeanFactoryPostProcessor 的postProcessBeanFactory()方法,也可以实现Bean的注入
MapperScannerRegistrar
通过@Import 注解,将MapperScannerRegistrar
注入到了IOC容器中,而MapperScannerRegistrar
实现这两个接口,ImportBeanDefinitionRegistrar, ResourceLoaderAware
ImportBeanDefinitionRegistrar
在Spring需要配合@Impor使用,加载它的实现类,只有一个方法,是主要负责Bean 的动态注入的。
1 | java复制代码public interface ImportBeanDefinitionRegistrar { |
这个方法可以拿到 Spring的注册对象 BeanDefinitionRegistry
这也是一个接口,提供了好6、7个方法
1 | java复制代码public interface BeanDefinitionRegistry extends AliasRegistry |
**BeanDefinition:**是Spring对Bean解析为,Spring内部的 BeanDefinition 结构,是对类的数据包装,类全限定名,是否是单例的,是否是懒加载的,注入方式是什么…
registerBeanDefinitions 注册方法
1 | java复制代码@Override |
扫描器 ClassPathMapperScanner
classPathMapperScanner 是mybatis的一个类,继承了 ClassPathBeanDefinitionScanner,重写了doScan方法;然后也调用了 它的的扫描方法。并且定义了扫描规则,还有一些Bean的过滤,比如在一个包下,不单单有 mapper 接口的类,我们的@MapperScan主要处理的是 mapper 接口,所以将其排除:
排除掉非接口的类
1 | java复制代码 @Override |
扫描器 ClassPathMapperScanner 的doScan方法
重写了父类的doScan方法,并且也调用了它的方法,通过父类的扫描结果,就将 该包下的所有 Mapper接口,解析成了 BeanDefinitionHolder,放到了 set集合中。
1 | java复制代码 @Override |
BeanDefinitionHolder:是BeanDefinition的持有者,包含了Be na的 名字,和Bean的别名,也包含了BeanDefinition。
processBeanDefinitions 处理BeanDefinition的BeanClass
1 | java复制代码private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { |
我们知道Spring 中有两种Bean,一种是 普通的Bean,另一种就是 FactoryBean
,如果是FactoryBean
在实例化的时候,就会调用它的 getObject方法获取对象。
FactoryBean
是一个接口:mybatis的 MapperFactoryBean
实现了它;
1 | java复制代码public interface FactoryBean<T> { |
MapperFactoryBean:
MapperFactoryBean 有两个属性,其中一个
1 | java复制代码private Class<T> mapperInterface; |
这个就是mapper接口的class,看它重写的getObject()方法;
1 | java复制代码 @Override |
是由它的 SqlSession
的具体类去调用 全局的配置文件 Configuration 对象中的一个MapperRegister对象的一个getMapper方法,然后根据class从 MapperRegister 中的属性Map -> knownMappers 拿到 MapperProxyFactory代理工厂,通过newInstance方法代理生成对像;
1 | java复制代码public class MapperProxyFactory<T> { |
这就完成了一个对 mapper接口的处理。
在myBatis的启动过程中,会将扫描的mapper信息进行封装,所有的信息都会在 Configuration 中;
比如:一个 mapper 接口
1 | java复制代码package cn.thisforyou.blog.core.mapper; |
对接口进行解析,拿到 Class: PayMapper.class
然后 调用 MapperRegistry的 addMapper方法 包装成一个代理对像 MapperProxyFactory
放到map中,就是 key-> PayMapper.class,vaue:new MapperProxyFactory(class);
在注入的时候,就会getObect()方法,最后就调用了MapperProxyFactory.newInstance生成代理对像。
MapperRegistry 在 Configuration对象中;
最后:mapper的@Autowired 注入的其实就是 MapperFactoryBean
通过它的getObject方法,代理生成接口对象。
总结:
Mybatis 通过注解@MapperScan 下的@Import注解加载,MapperScannerRegistrar
类,此类继承了ImportBeanDefinitionRegistrar
类,对bean的注册处理,在注册之前 会拿到 @MapperScan 的 参数值,mapper 包路径,然后调用 new ClassPathMapperScanner(registry)
类去扫描,ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
,重写doScan方法,定义扫描规则,对扫描结果进行更改 BeanDefinition 的beanClass 进行替换成 MapperFactoryBeanClass
;
2. mapper接口是如何被实例化,然后可以使用@Autowired注入?
mapper接口没有被实例化,是通过 FactoryBean 的方式注入到 IOC 中,通过调用getObject方法生成代理对像 @Autowired的;
本文转载自: 掘金