接口不能被实例化,Mybatis的Mapper/Dao为什么却可以@Autowired注入?
对于我们 java 来说,接口是不能被实例化的。而且接口的所有方法都是public的。
可是为什么 Mybaits 的mapper 接口,可以直接 @Autowired 注入 使用?
基于SpringBoot 的 @MapperScan 注解入手,分析。
带着问题分析代码:
@MapperScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
// 在使用MapperScan中,扫描包的路径。
// 填写的是 mapper 接口所在包名,对该value值下的所有文件进行扫描
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends Annotation> annotationClass() default Annotation.class;
Class<?> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
@SpringBootApplication
@MapperScan("cn.thisforyou.core.blog.mapper")
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
SpringApplication.run(App.class,args);
}
}
在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 的动态注入的。
public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
这个方法可以拿到 Spring的注册对象 BeanDefinitionRegistry 这也是一个接口,提供了好6、7个方法
public interface BeanDefinitionRegistry extends AliasRegistry
{
// 注册 BeanDefinition
void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException;
// 移除 BeanDefinition
void removeBeanDefinition(String var1) throws NoSuchBeanDefinitionException;
// 根据name 获取一个 BeanDefinition
BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;
// 判断 是否存在
boolean containsBeanDefinition(String var1);
// BeanDefinition 的适量
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
// 是否使用中
boolean isBeanNameInUse(String var1);
}
**BeanDefinition:**是Spring对Bean解析为,Spring内部的 BeanDefinition 结构,是对类的数据包装,类全限定名,是否是单例的,是否是懒加载的,注入方式是什么...
registerBeanDefinitions 注册方法
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry registry) {
// 拿到注解,目的是拿到注解里的属性值,拿到值后进行扫描,并且对结果进行一个转换 AnnotationAttributes
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
/**
* 对 MapperScan 的属性值进行一个解析处理
* @param annoAttrs
* @param registry
*/
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
/**
*
* 🌟
* 这是Mybatis的一个扫描器
* 也是 继承了 Spring的扫描器 ClassPathBeanDefinitionScanner
*/
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);
// ........
/**
* 读取包mapper包下的路径 和 要扫描的一组 mapper.class
*/
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()));
// 🌟这是自定义扫描规则,与Spring的默认机制不同
scanner.registerFilters();
// 🌟调用扫描器 对包路径进行扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
}
扫描器 ClassPathMapperScanner
classPathMapperScanner 是mybatis的一个类,继承了 ClassPathBeanDefinitionScanner,重写了doScan方法;然后也调用了 它的的扫描方法。并且定义了扫描规则,还有一些Bean的过滤,比如在一个包下,不单单有 mapper 接口的类,我们的@MapperScan主要处理的是 mapper 接口,所以将其排除:
排除掉非接口的类
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
扫描器 ClassPathMapperScanner 的doScan方法
重写了父类的doScan方法,并且也调用了它的方法,通过父类的扫描结果,就将 该包下的所有 Mapper接口,解析成了 BeanDefinitionHolder,放到了 set集合中。
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// 对扫描结果进行处理,如果不处理的话,这个接口就当作了
// 一个普通的Bean注入IOC了,在引入调用,就会出现错误了。
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
BeanDefinitionHolder:是BeanDefinition的持有者,包含了Be na的 名字,和Bean的别名,也包含了BeanDefinition。
processBeanDefinitions 处理BeanDefinition的BeanClass
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
// 循环遍历,一个个修改
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 关键步骤: 这将 GenericBeanDefinition 的BeanClass 修改成了mapperFactoryBeanClass;
// 扫描结果 这个beanClass 就是 mapper.class
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// .........
}
}
}
我们知道Spring 中有两种Bean,一种是 普通的Bean,另一种就是 FactoryBean,如果是FactoryBean在实例化的时候,就会调用它的 getObject方法获取对象。
FactoryBean 是一个接口:mybatis的 MapperFactoryBean 实现了它;
public interface FactoryBean<T> {
// 获取对像
@Nullable
T getObject() throws Exception;
// 类型
@Nullable
Class<?> getObjectType();
// 默认是单例Bean
default boolean isSingleton() {
return true;
}
}
MapperFactoryBean:
MapperFactoryBean 有两个属性,其中一个
private Class<T> mapperInterface;
这个就是mapper接口的class,看它重写的getObject()方法;
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
是由它的 SqlSession的具体类去调用 全局的配置文件 Configuration 对象中的一个MapperRegister对象的一个getMapper方法,然后根据class从 MapperRegister 中的属性Map -> knownMappers 拿到 MapperProxyFactory代理工厂,通过newInstance方法代理生成对像;
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
// ........
// --------
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
这就完成了一个对 mapper接口的处理。
在myBatis的启动过程中,会将扫描的mapper信息进行封装,所有的信息都会在 Configuration 中;
比如:一个 mapper 接口
package cn.thisforyou.blog.core.mapper;
public interface PayMapper{
pay(String outNo)
}
对接口进行解析,拿到 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; -
mapper接口是如何被实例化,然后可以使用@Autowired注入?
mapper接口没有被实例化,是通过 FactoryBean 的方式注入到 IOC 中,通过调用getObject方法生成代理对像 @Autowired的;