接口不能被实例化,Mybatis的Mapper/Dao为什么

接口不能被实例化,Mybatis的Mapper/Dao为什么却可以@Autowired注入?

对于我们 java 来说,接口是不能被实例化的。而且接口的所有方法都是public的。

可是为什么 Mybaits 的mapper 接口,可以直接 @Autowired 注入 使用?

接下来看看Mybatis 是如何做的。

基于SpringBoot 的 @MapperScan 注解入手,分析。

带着问题分析代码:

  1. Mybatis 的mapper接口,是怎么被扫描的?
  2. mapper接口是如何被实例化,然后可以使用@Autowired注入?

@MapperScan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码@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;

}
1
2
3
4
5
6
7
8
9
10
java复制代码@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 的动态注入的。

1
2
3
4
java复制代码public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

这个方法可以拿到 Spring的注册对象 BeanDefinitionRegistry 这也是一个接口,提供了好6、7个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码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 注册方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
java复制代码@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 接口,所以将其排除:

排除掉非接口的类

1
2
3
4
java复制代码 @Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

扫描器 ClassPathMapperScanner 的doScan方法

重写了父类的doScan方法,并且也调用了它的方法,通过父类的扫描结果,就将 该包下的所有 Mapper接口,解析成了 BeanDefinitionHolder,放到了 set集合中。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码  @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码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 实现了它;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public interface FactoryBean<T> {

// 获取对像
@Nullable
T getObject() throws Exception;

// 类型
@Nullable
Class<?> getObjectType();
// 默认是单例Bean
default boolean isSingleton() {
return true;
}

}

MapperFactoryBean:

MapperFactoryBean 有两个属性,其中一个

1
java复制代码private Class<T> mapperInterface;

这个就是mapper接口的class,看它重写的getObject()方法;

1
2
3
4
java复制代码  @Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

是由它的 SqlSession的具体类去调用 全局的配置文件 Configuration 对象中的一个MapperRegister对象的一个getMapper方法,然后根据class从 MapperRegister 中的属性Map -> knownMappers 拿到 MapperProxyFactory代理工厂,通过newInstance方法代理生成对像;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码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 接口

1
2
3
4
java复制代码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方法,代理生成接口对象。

总结:

  1. Mybatis 的mapper接口,是怎么被扫描的?

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的;

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%