Spring與MyBatis整合原理
先來說說我們通常使用在Spring環境中使用MyBatis的步驟:
- 加上mybatis-spring的maven坐標
- 在啟動類上加@MapperScan注解,在注解中標明你要掃描的mapper的包路徑
- 在service上使用spring的@Autowired注解把想要的mapper注入進service中
可能大家一直覺得挺神奇的一點就是,在我們不在spring環境中單獨使用MyBatis的時候,mapper物件是我們手動去通過呼叫sqlsession.getMapper得到mapper介面的代理物件,那么也就是說這個代理物件的創建程序是由我們自己來完成的,但是當與spring整合之后,我們只需要在mapper屬性物件上面加上@Autowired注解就能把mapper介面的代理物件注入進來了,而spring并不知道怎么去創建我們的代理物件的吧,為什么這個物件會被spring所創建出來放到容器中呢?其實它就是使用了spring中的FactoryBean組件去實作的,
@MapperScan注解
我們知道我們需要在配置類上面加上@MapperScan注解去掃描我們的mapper介面,所以這個注解的作業肯定是實作mapper代理物件注入到spring容器中的核心,

可以打看到這個注解里面使用了@Import注解去把MapperScannerRegister這個類放到容器中去,點進去這個類里面

可以發現該類不是簡單的類,而是實作了spring的ImportBeanDefinitionRegistrar介面,熟悉spring的同學應該知道@Import注解有3種用法,其中這種用法是通過介面暴露出的BeanDefinitionRegistry物件去put一個BeanDefinition(之后spring實體化物件的時候就是根據BeanDefinition來實體化的),簡單地說就是手動地往spring里面去注冊一個物件吧,
接下來主要去看它實作的介面方法registerBeanDefinition
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
可以看到這里主要是讀取了注解上面的一些屬性,然后給一個ClassPathMapperScanner物件去設定這些屬性值,那么這個ClassPathMapperScanner物件的是什么來的呢?這個物件的父類在spring里面及其重要,因為它的父類就是用來進行包掃描的,把掃描到的類封裝成一個個的BeanDefinition放在了spring的BeanDefinitionMap里面,其中的細節是spring的核心知識點了,這里就不展開說了,而上面的代碼重點是scanner.doScan,在看這句代碼之前我們先來看下ClassPathMapperScanner的類繼承結構圖:

其中ClassPathScanningCandidateComponentProvider和ClassPathBeanDefinitionScanner這兩個類是spring里面提供的,ClassPathMapperScanner是mybatus-spring包提供的,我們先看它的初始化方法:
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
這里的重點就是它的構造方法里面呼叫了父類的構造方法,并且把父類的useDefaultFilters屬性設定成false,這個useDefaultFilters的作用就是是否啟動spring里面默認的掃描規則(其中就包括了掃描@Component注解的filter),這里設定了false表示不啟動:
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
進去它的doScan方法:
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
可以看到這個方法是重寫了父類的doScan方法,進去父類的doScan方法:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
這個方法最關鍵的就是findCandidateComponents(basePackage),而這個方法是ClassPathBeanDefinitionScanner的父類ClassPathScanningCandidateComponentProvider提供的,功能就是掃描你提供的包路徑下面所有的類(默認是掃描加上了有@Component注解的類,但是上面我們把useDefaultFilter設定為false所以該過濾規則不存在了)并且封裝成一個個的BeanDefinition放在了一個set集合里面,ClassPathBeanDefinitionScanner的doScan方法拿到這個set集合之后,然后把集合里面的BeanDefinition用BeanDefinitionRegistry進行注冊(put到容器中的BeanDefinitionMap中),但是仔細一想我們不是只需要掃描這個路徑下面的介面類嗎,那么肯定有個地方是判斷掃描結果的吧,沒錯,ClassPathScanningCandidateComponentProvider的findCandidateComponents方法里面呼叫了一個isCandidateComponent方法用來判斷掃描出來的類是否符合自己想要的規則
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
而ClassPathMapperScanner 重寫了這個方法,定義了自己對于掃描出來的類是夠符合自己想要的規則
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
可以看到這里定義了掃描出來的類必須是一個介面型別的才會被注冊進容器中,
所以綜上所述
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
這句代碼的意思就是把指定的包名下面的所有介面類都掃描出來變成一個個的BeanDefinition并且都放進了容器的BeanDefinitionMap中,
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
因此就來到了processBeanDefinition方法了,我們上面說過mybatis與spring整合的原理就是利用了FactoryBean生成動態代理的介面物件,但是我們上面說了這么多都還沒講到FactoryBean,直到這句代碼,FactoryBean就登場了,
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
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
我們可以看到這個方法把我們已經put進BeanDefinitionMap的每個BeanDefinition物件都拿出來然后里面有一句代碼
definition.setBeanClass(this.mapperFactoryBean.getClass());
那么這個this.mapperFactoryBean是什么呢?
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
它是在創建scanner的時候讀取@MapperScan注解的factoryBean屬性去拿到的,而@MapperScan注解的factoryBean屬性默認就是MapperFactoryBean
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
所以把原來的BeanDefinition物件的BeanClass屬性換成了MapperFactoryBean,所以在根據這個BeanDefinition物件去實體化的時候就會實體化吃MapperFactoryBean的實體,但是由于MapperFactoryBean是一個FactoryBean,所以實體化的程序與普通的物件不一樣,這個程序會呼叫它的getObject方法去實體化真正的我們想要的物件,
還有上面的definition.getPropertyValues().add()就是在實體化MapperFactoryBean的時候把這些屬性設定到MapperFactoryBean里面去,
接下來我們來看下MapperFactoryBean的getObject方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
這句代碼最終呼叫了getMapper方法去產生代理物件,深入看下可以看到來到了SqlSessionTemplate類中
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
最侄訓是呼叫了mybatis原始碼里面的Configuration物件產生介面的動態代理物件并回傳,此時產生的動態代理物件最終就會放進了spring的單例緩沖池中了,所以我們就能在類里面去進行注入物件了,
總結:整個流程的話很清晰,就是mybatis-spring這個包里面有一個ClassPathMapperScan類,這個類重寫了ClassPathBeanDefinition類,目的就是為了能夠利用ClassPathBeanDefinition類里面的doScan方法去進行掃描,而且還重寫了里面的isCandidateComponent方法去對掃描出來的BeanDefinition物件進行一個過濾(只需要介面型別的BeanDefinition),在呼叫完了父類的doScan方法后此時符合條件的BeanDefinition已經被put到了spring容器中的BeanDefinitionMap中了,但是之后執行的processBeanDefinition方法又對剛才掃描到并且已經注冊進BeanDefinitionMap中的BeanDefinition進行一個加工處理,使得每一個BeanDefinition里面的beanClass屬性都為MapperFactoryBean,而MapperFactoryClass這個類又是實作了FactoryBean介面,所以在實體化的時候會呼叫它的getObject方法產生我們的動態代理物件(這個就是我們上面說的物件實體化的程序完成由我們自己去掌控)并且放進了spring容器中,其實整個程序的核心思想第一就是能夠干預到spring的BeanDefinitionMap,因為后續的實體化bean都是根據BeanDefinitionMap里面的BeanDefinition物件來進行的,所以能夠干預到BeanDefinition的話我們還可以想到的就是BeanDefinitionRegistryPostProcessor,創建一個類繼承自該類,利用該類暴露出的BeanDefinitionRegistry物件就能干預到BeanDefinitionMap了,其實mybatis-spring包中還有一個類叫MapperScannerConfigurer,該類就是使用的上面的思路去干擾了BeanDefinitionMap的,但是這個類一般是用在mybatis與spring整合的xml配置中,而注解版的配置并沒有使用到;第二就是利用了FactoryBean這個組件的特性,使得物件實體化的主動權完全交托在我們開發者手里然后再把實體化完的物件放進spring容器中,這樣就能實作利用自己的一套實體化物件邏輯去實體化物件并且注入到了容器中的效果了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/152660.html
標籤:其他
上一篇:anaconda使用問題
下一篇:JavaWeb開發筆記
