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

1、問題引出

在mybatis中,mapper都是像如下一個個的接口:java

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注入的方式使用的,以下:spring

@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的呢?sql

2、mybatis mapper接口被轉爲spring bean的過程

mybatis mapper接口被初始化爲spring bean大致分三步:mybatis

加載mybatis mapper bean的註冊器MapperScannerRegistrar---》MapperScannerRegistrar加載@MapperScan指定包路徑下面的接口爲bean並註冊到容器中---》將mybatis mapper bean與動態代理實現MapperProxy關聯起來。app

流程圖以下:ide

3、註冊器MapperScannerRegistrar加載

啓動類代碼以下:this

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上的註解會被解析出來,以下:spa

它會解析註解上面的@Import,並把對應的value屬性值放到imports中,imports會被返回。.net

其中,MapperScan接口的定義以下:3d

@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列表中:

4、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,還待進一步完善)

5、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就和具體的動態代理類綁定到了一塊兒。

相關文章
相關標籤/搜索