一、问题引出
在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就和具体的动态代理类绑定到了一起。
来源:oschina
链接:https://my.oschina.net/u/779984/blog/3023505