二、MyBatis Mapper Bean初始化深度解析

£可爱£侵袭症+ 提交于 2020-04-14 20:55:48

【推荐阅读】微服务还能火多久?>>>

一、问题引出

在mybatis中,mapper都是像以下一个个的接口:


public interface UserMapper {
    long countByExample(UserDTOExample example);

    int deleteByExample(UserDTOExample example);

    int deleteByPrimaryKey(Integer id);

    int insert(UserDTO record);

    int insertSelective(UserDTO record);

    List<UserDTO> selectByExample(UserDTOExample example);

    UserDTO selectByPrimaryKey(Integer id);

    int updateByExampleSelective(@Param("record") UserDTO record, @Param("example") UserDTOExample example);

    int updateByExample(@Param("record") UserDTO record, @Param("example") UserDTOExample example);

    int updateByPrimaryKeySelective(UserDTO record);

    int updateByPrimaryKey(UserDTO record);
}
public interface UserMapperExt extends UserMapper {

    List<UserDTO> findUserListByName(String username);
}

但是在使用的时候,都是通过spring bean注入的方式使用的,如下:

@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserMapperExt userMapperExt;

    @GetMapping("get-userInfo")
    public String getUserInfo() {
        List<UserDTO> userList = userMapperExt.findUserListByName("ZHANGSAN");
        return "SUCCESS";
    }

}

那么,mybatis的mapper接口(例如:接口UserMapperExt)是怎么样被实例化为spring bean的呢?

二、mybatis mapper接口被转为spring bean的过程

mybatis mapper接口被初始化为spring bean大体分三步:

加载mybatis mapper bean的注册器MapperScannerRegistrar---》MapperScannerRegistrar加载@MapperScan指定包路径下面的接口为bean并注册到容器中---》将mybatis mapper bean与动态代理实现MapperProxy关联起来。

流程图如下:

三、注册器MapperScannerRegistrar加载

启动类代码如下:

package com.iwill.mybatis;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@ComponentScan("com.iwill.mybatis")
@MapperScan(value = {"com.iwill.mybatis.dao.mapper.ext", "com.iwill.mybatis.dao.mapper.gen"})
@EnableTransactionManagement(proxyTargetClass = true)
public class MyBatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBatisApplication.class, args);
    }
}

在加载MyBatisApplication的配置注解时,MyBatisApplication上的注解会被解析出来,如下:

它会解析注解上面的@Import,并把对应的value属性值放到imports中,imports会被返回。

其中,MapperScan接口的定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class{
  ......
}

可以看出,@Import的值为MapperScannerRegistrar.class,可以得知,注解@MapperScan使用MapperScannerRegistrar去加载mapper bean。

getImports源代码如下:

	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}

再返回到上一步,getImports会被processImports方法调用:

在processImports中,MapperScannerRegistrar会被放到importBeanDefinitionRegistrars列表中:

四、MapperScannerRegistrar加载mapper bean

在ConfigurationClassBeanDefinitionReader的loadBeanDefinitionsForConfigurationClass方法中,会从bean注册器中加载bean:

这里就包括了上一步被加载进来的MapperScannerRegistrar。loadBeanDefinitionsFromRegistrars的实现如下:

	private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
		registrars.forEach((registrar, metadata) ->
				registrar.registerBeanDefinitions(metadata, this.registry));
	}

这里会去调用实现类的registerBeanDefinitions方法去加载bean。前面说过,MapperScannerRegistrar实现了接口ImportBeanDefinitionRegistrar,具有方法:registerBeanDefinitions。因此,加载mapper bean就正式开始了。

MapperScannerRegistrar的registerBeanDefinitions方法实现如下:

 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry);
    }
  }

上述代码会去读取注解@MapperScan的值,虽然@MapperScan有很多属性可以配置,这里,我们只配置了value属性,其值为:

@MapperScan(value = {"com.iwill.mybatis.dao.mapper.ext", "com.iwill.mybatis.dao.mapper.gen"})

方法registerBeanDefinitions里面会设置扫描器(扫描器为ClassPathMapperScanner)以及配置读取类的包路径和注册扫描规则:

 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()));

    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));

也即是说:注解@MapperScan的value、basePackages、basePackageClasses等属性配置的包路径都会被扫描。

配置的扫描过滤器(扫描规则scanner.registerFilters())如下:

我们没有在@MapperScan里面指定需要扫描的标记指定注解的类(annotationClass属性来指定),所以acceptAllInterfaces会为true,即会扫描包路径下的所有接口,但是package-info结尾的去掉。

doScan方法会扫描出符合指定规则的类,并且设置bean的一些属性:

findCandidateComponents方法就是找出备选的bean,其内部调用scanCandidateComponents,scanCandidateComponents实现如下:

其工作过程:扫描指定包路径下面的所有.class文件,然后循环判断是否符合过滤规则。isCandidateComponent的实现如下:

	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}

如果这一步判断通过了,那么就会进行第二步判断:

	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		AnnotationMetadata metadata = beanDefinition.getMetadata();
		return (metadata.isIndependent() && (metadata.isConcrete() ||
				(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
	}

这一步会过滤掉不是接口的类。找到符合条件的接口,就会new一个ScannedGenericBeanDefinition,放入到Set<BeanDefinition>进行返回。返回到doScan方法,里面会对bean进行一些属性设置:beanName、scope(默认是singleton)等。

然后会注册到DefaultListableBeanFactory(就是将beanName与bean放到map中,防止重复加载)。扫描加载了mapper bean以后,processBeanDefinitions方法就会对这些bean进行处理:

这里设置了bean的beanClass,为后面的设置动态代理打下基础,beanClass的值为MapperFactoryBean(MapperFactoryBean实现了FactoryBean接口)。

至此,@MapperScan的流程就走完了,mapper接口被初始化为bean(非完整bean,还待进一步完善)

五、Mapper Bean与动态代理MapperProxy绑定

在spring容器初始化的refresh方法中,走到finishBeanFactoryInitialization时,会调用FactoryBean接口的getObject方法,针对mapper bean,会调用MapperFactoryBean的getObject方法。

最终会调用到MapperRegistry的getMapper方法:

  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的newInstance方法实现如下:

  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<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

MapperProxy的实现如下:

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
......
}

MapperProxyFactory的newInstance就返回了具体的动态代理类给mapper bean,这样,mapper bean就和具体的动态代理类绑定到了一起。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!