在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
mybatis mapper接口被初始化爲spring bean大致分三步:mybatis
加載mybatis mapper bean的註冊器MapperScannerRegistrar---》MapperScannerRegistrar加載@MapperScan指定包路徑下面的接口爲bean並註冊到容器中---》將mybatis mapper bean與動態代理實現MapperProxy關聯起來。app
流程圖以下:ide
啓動類代碼以下: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列表中:
在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,還待進一步完善)
在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就和具體的動態代理類綁定到了一塊兒。