上文我们只是知道了springboot的自动装配是怎么帮我们来创建SqlSessionFactory的,在上面的步骤后,容器里面此时就存在SqlSessionFactory的bean对象了,但是我们直接使用@Autowired注解Mapper就能直接使用的原因还是一脸懵逼的。
首先,debug可以发现此时的mapper的对象为org.apache.ibatis.binding.MapperProxy对象,显然在某个地方将这些Mapper接口代理为MapperProxy注入了Spring容器中的。
由包名可以知道,这个代理对象是mybatis的对象,Tb1Mapper本身只是一个接口,是没法创建对象的,这个对象应该是在某个地方通过动态代理的方式创建对象并注入到Spring容器中的。
而我们代码中唯一和这些mapper接口存在关系的,就是@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)这个类,@Import的作用就是导入配置类,所以先看看这个类。(当然,如果你没有使用这个注解,而是使用配置文件的方式,那么会在自动配置中一样去创建MapperScannerRegistrar这个bean)
MapperScannerRegistrar
代码简化如下:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// 一系列的addPropertyValue操作,主要就是将@MapperScan注解中的配置设置到该BeanDefinition中
builder.addPropertyValue("processPropertyPlaceHolders", true);
List<String> basePackages = new ArrayList<>();
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
// 将设置好的MapperScannerConfigurer的BeanDefinition注册到Spring中。
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}
MapperScannerRegistrar 这个类实现了ImportBeanDefinitionRegistrar这个接口,这个接口是spring 对外提供的接口,目的是实现bean的动态注入,只不过这个需要我们自己去构建BeanDefinition然后注册进去。
核心方法就是实现registerBeanDefinitions接口,如代码所示,这个方法的主要作用就是将 @MapperScan 注解配置的信息全部设置到MapperScannerConfigurer这个类的BeanDefinition中,本质上就是创建MapperScannerConfigurer这个bean对象。
MapperScannerConfigurer
经过上面的步骤,MapperScannerConfigurer这个bean的核心就是获取到@MapperScan里面配置的mapper接口的路径信息。简单看下该类的核心实现:
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
//... 一些属性
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//... 一些set操作,不过此时基本都为null,主要是这个scan方法。
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,该接口是BeanFactoryPostProcessor的子接口,提供了一个postProcessBeanDefinitionRegistry,该方法进一步往Spring中注册BeanDefinition。
特殊说明下:
BeanFactoryPostProcessor这个PostProcessor是在bean的Definition信息已经加载完成,但是还没有进行初始化的时候执行postProcessBeanFactory方法。一般实现该接口用于修改某个BeanDefinition的属性信息。BeanDefinitionRegistryPostProcessor是在BeanFactoryPostProcessor之前执行的,他处于BeanDefinition将要被加载,我们可以看到他接口方法的参数是BeanDefinitionRegistry,所以是可以再往Spring中注册一些新的BeanDefinition等组件的。
该方法主要做了2件事请:
- 创建了ClassPathMapperScanner对象
scanner,并将registry对象传进去。 - 调用
scanner的scan方法,这里就是扫描mapper的核心方法。
所以我们再去看看ClassPathMapperScanner。
ClassPathMapperScanner
代码简化如下:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
// 构造函数,registry继续传入ClassPathBeanDefinitionScanner
public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
// 扫描MapperScan配置的包下所有的接口,并创建为BeanDefinition注册到Spring中去。
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类的扫描方法
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
processBeanDefinitions(beanDefinitions);
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
// 遍历那些已经被临时封装为BeanDefinition的mapper接口
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
// 设置该BeanDefinition的class为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
}
}
ClassPathMapperScanner继承了Spring的包扫描器ClassPathBeanDefinitionScanner 同时覆写了doScan的方法,上面scanner.scan实际调用的是ClassPathBeanDefinitionScanner的scan方法。
我们看看这个方法做了哪些事情:
- 调用父类
ClassPathBeanDefinitionScanner的doScan扫描了mapper接口包的接口类,并将其初步封装为BeanDefinitionHolder(可以直接先看做就是BeanDefinition) - 这些BeanDefinition在
super.doScan(basePackages);调用完成后,就已经全部注册入register中去了。 - 下面针对获取的这些BeanDefinition,调用
processBeanDefinitions进行逐步加工,我这里只保留了核心:设置该BeanDefinition的class为MapperFactoryBean!
最终我们获取到的mapper接口的BeanDefinition如下图:
这下就很明了了,这步将mapper接口的BeanDefinition的class设置为MapperFactoryBean,那么Spring在初始化bean的时候,就会去调用MapperFactoryBean的构造方法。
我们继续去看看MapperFactoryBean这个类:
MapperFactoryBean
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
// 该属性是从SqlSessionDaoSupport摘取出来的,该类是个抽象类
private SqlSessionTemplate sqlSessionTemplate;
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
public MapperFactoryBean() {
// intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
}
可以看到MapperFactoryBean是一个FactoryBean,所以他最终往容器中注入的Bean是getObject返回的方法,即:
getSqlSession().getMapper(this.mapperInterface);
这个方法的追踪如下:
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
⬇️ # Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
⬇️ # MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
⬇️ # MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
⬇️ # MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
// 这里可以看到了,是基于JDK的动态代理创建了代理的Bean
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
所以,我们@Autowired注入的对象,实际上都是MapperProxy这个类的对象,我们再看看这个类。
MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是Object的方法,说明不是调用sql的方法,直接放行。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// mapper的sql调用方法
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
这部分具体是如何调用到执行的SQL的,这部分可以看我之间的Mybatis源码解析-快速一览 > 使用过程。