引言
- mybatis版本:3.5.1
- mybatis-spring:2.0.1
使用過Mybatis框架的開發人員應該都知道,在撰寫dao層時,只需要提供mapper介面與相應的xxxMapper.xml,無需實作類,便可以將mapper介面物件交由Spring容器管理,疑問:
- Mybatis是如何為mapper介面生成代理物件的?
- Mybatis又是如何將mapper物件交給Spring管理?
我們在整合mybatis與spring時,都會使用MyBatis-Spring 將 MyBatis 代碼無縫地整合到 Spring 中,
MyBatis-Spring出現的動機:
Spring 2.0 只支持 iBatis 2.0,那么,我們就想將 MyBatis3 的支持添加到 Spring 3.0 中(參見 Spring Jira 中的 問題 ),不幸的是,Spring 3.0 的開發在 MyBatis 3.0 官方發布前就結束了, 由于 Spring 開發團隊不想發布一個基于未發布版的 MyBatis 的整合支持,如果要獲得 Spring 官方的支持,只能等待下一次的發布了,基于在 Spring 中對 MyBatis 提供支持的興趣,MyBatis 社區認為,應該開始召集有興趣參與其中的貢獻者們,將對 Spring 的集成作為 MyBatis 的一個社區子專案,
那么,在mybatis-spring中,mybatis對于集成spring做了哪些擴展?
@MapperScan注解
首先需要掃描指定mapper包下面的介面,為這些介面生成代理物件,再通過FactoryBean將這些代理物件交給Spring管理,
注:對FactoryBean不熟悉的,可以查看:https://blog.csdn.net/a1036645146/article/details/111661211
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
// 省略部分,,,
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
這個注解的主要作用是將MapperScannerConfigurer放到BeanDefinitionMap當中,
那么它是如何作業的呢?在spring中,有一個內置類ConfigurationClassPostProcessor,這個類的作用是掃描帶spring注解的類添加到bdmap中,并且決議配置類上的@Import標簽,若這個標簽中的類實作了ImportBeanDefinitionRegistrar介面,會將這這個類實體化后放入到 importBeanDefinitionRegistrars 快取當中,掃描完成后會執行這個集合當中的物件的registerBeanDefinitions()方法,
mybatis-spring中MapperScannerRegistrar#registerBeanDefinitions()這個方法的作用就是將MapperScannerConfigurer注冊到BeanDefinitionMap當中,
//MapperScannerRegistrar
@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);
// 省略部分,,,
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
MapperScannerConfigurer
mybatis-spring框架中MapperScannerConfigurer 基于spring的擴展實作了BeanDefinitionRegistryPostProcessor介面,在Spring的內置掃描器掃描結束后會觸發這個類的postProcessBeanDefinitionRegistry方法,構建一個mybatis的掃描器(ClassPathMapperScanner)來執行掃描mapper介面,將mapper介面物件交給了Spring管理,
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 創建一個mybatis 的掃描器,掃描mapper介面包,可將mapper.class到BeanDefinition的轉換
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 下面的一系列set方法是對這個掃描器進行初始化
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
scanner.registerFilters();//注冊一些掃描用的過濾器
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
,,,
}
其中,ClassPathMapperScanner是mybatis實作的一個掃描器,繼承了spring內置的掃描器ClassPathBeanDefinitionScanner,但它并沒有重寫父類的scan方法,所以這里呼叫的是父類的scan方法,如下:
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 可由子類實作
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
,,,
}
重點在執行doScan方法,該方法由ClassPathMapperScanner重寫了,主要是將mapper.class到BeanDefinition的轉換,
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
// 掃描mapper介面包,將mapper.class轉為對應的beanDefinition
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);//掃描mapper包下的介面
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);// //將這些mapper介面變成FactoryBean
}
return beanDefinitions;
}
,,,
}
其中,processBeanDefinitions 這個方法會設定建構式的引數為這個介面,然后將beanClass修改為MapperFactoryBean,之后實體化的時候會呼叫這個FactoryBean的getObject()方法來構建一個代理Mapper物件,這就是如何將map介面轉變成一個物件交給Spring管理的關鍵
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + beanClassName + "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// 添加建構式需要的引數
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
//設定型別為mapperFactoryBeanClass,之后的實體化會呼叫MapperFactoryBean中的getObject()來構建出一個基于介面的代理物件交給spring管理
definition.setBeanClass(this.mapperFactoryBeanClass);
,,,
}
}
MapperProxyFactory
Mybatis框架中,MapperProxyFactory,是mapper代理工廠,可基于介面和MapperProxy來構建一個mapper代理物件,實作了將介面轉變成一個物件:

MapperProxy 實作了 InvocationHandler 介面的invoke方法,所以,我們明白了第1個問題,Mybatis是基于JDK動態代理來生成mapper介面的物件的,
注:對jdk動態代理不熟悉的,可以查看:https://blog.csdn.net/a1036645146/article/details/111881599

其中,MapperMethod#execute()如下:
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根據sql陳述句的型別,選擇不同的分支流程執行
switch (command.getType()) {
case INSERT: {// 新增陳述句
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {// 更新陳述句
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {// 洗掉陳述句
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:// 查詢
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
,,,
}
MapperFactoryBean
MapperFactoryBean實作了Spring的FactoryBean擴展介面,FactoryBean是一個工廠Bean,可以生成某一型別Bean實體,它最大的一個作用是:可以讓我們自定義Bean的創建程序,具體可以查看《【Spring原始碼:FactoryBean一】終于弄懂FactoryBean是如何自定義bean的創建程序了》,
它可以呼叫getObject()方法根據介面的不同,回傳不同的介面代理物件,這個物件也就是通過上面的MapperProxyFactory產生的,
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
,,,
}
總結
1. Mybatis首先掃描指定包下面的mapper介面,為每一個mapper介面創建一個代理工廠MapperProxyFactory,放入MapperRegistry的一個屬性集合快取中:
Map<Class<?>, MapperProxyFactory<?>> knownMappers
2. 使用這些代理工廠,通過實作JDK動態代理去創建mapper介面對應的代理物件,MapperProxy;
3. 通過代理物件去呼叫實際的業務邏輯,

參考:https://blog.csdn.net/gongsenlin341/article/details/108600008
●史上最強Tomcat8性能優化
●阿里巴巴為什么能抗住90秒100億?--服務端高并發分布式架構演進之路
●B2B電商平臺--ChinaPay銀聯電子支付功能
●學會Zookeeper分布式鎖,讓面試官對你刮目相看
●SpringCloud電商秒殺微服務-Redisson分布式鎖方案
查看更多好文,進入公眾號--撩我--往期精彩
一只 有深度 有靈魂 的公眾號0.0
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/246816.html
標籤:其他
上一篇:面試經驗
下一篇:簡單工廠模式(學習筆記)
