Mybatis-Spring源碼分析圖解
Mybatis-Spring
當我們使用mybatis和spring整合後為什麼下面的代碼可以運行?
一個問題:
我就寫瞭個mapper接口為什麼能用?
首先來看,在spring的配置xml中有一段
<bean id="configurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <property name="basePackage" value="com.jame.dao"/> </bean>
這段xml的作用是將一個類添加到spring容器中,點進這個類看看
它實現瞭一個BeanDefinitionRegistryPostProcessor
接口,關於這個接口的作用和執行時機上篇博客寫過瞭,這裡就不再贅述
那麼它必然實現postProcessBeanDefinitionRegistry
方法,點擊這個方法查看
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); .......... scanner.registerFilters(); scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
其中將接口註冊到spring容器中在最後一行,先來看ClassPathMapperScanner
這個類,它繼承瞭ClassPathBeanDefinitionScanner
這個掃描器
scan的具體代碼
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); }
這個是spring內部的掃描方法,當它走到doScan的時候,因為ClassPathMapperScanner這個類重寫瞭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; }
通過包名獲取BeanDefinitionHolder,現在它獲取到瞭User接口的BeanDefinitionHolder,然後判斷如果BeanDefinitionHolder的集合為空,也就是沒有找到mapper的情況則不做任何處理,而現在有一個UserMapper的,進入else
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { AbstractBeanDefinition definition; BeanDefinitionRegistry registry = getRegistry(); for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); ......... //主要看這行 definition.setBeanClass(this.mapperFactoryBeanClass); ......... if (!definition.isSingleton()) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true); if (registry.containsBeanDefinition(proxyHolder.getBeanName())) { registry.removeBeanDefinition(proxyHolder.getBeanName()); } registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); } } }
將MapperFactoryBean類設置為瞭UserMapperBeanDefinition的class
spring在創建這個userMapper這個Bean的時候會使用這個有參構造將當前這個UserMapper類型設置到mapperInterface屬性上(為啥使用有參構造而不是無參來初始化對象我也不知道…..這和spring推斷構造方法有關,以後學會瞭在來寫)
這個MapperFactoryBean實現瞭一個FactoryBean
接口,這個接口可以讓我們自定義獲取bean的操作
回到spring的代碼,例如當我們使用context.getBean(xxx.class)的時候
spring將xxx.class類型解析為bean名稱,通過名稱去獲取
protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { //獲取對應的beanName String beanName = transformedBeanName(name); Object bean; Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } ....... // Create bean instance. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { //真正創建對象的地方 return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } }
首先是調用getSingleton
方法,嘗試獲取存在緩存中的bean(其實就是三個Map,key為bean名稱,value是對象),那現在是首次獲取map中沒有
然後執行到下面的createBean,當創建完這個bean後spring需要判斷這個bean是一個普通bean還是一個FactoryBean,程序員是想要獲取普通bean還是FactoryBean,還是FactoryBean的getObject方法返回的從工廠生成的對象
咱們一段一段看
protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { if (BeanFactoryUtils.isFactoryDereference(name)) { if (beanInstance instanceof NullBean) { return beanInstance; } if (!(beanInstance instanceof FactoryBean)) { throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass()); } } ..... }
BeanFactoryUtils.isFactoryDereference(name)
的作用是一個字符串判斷,當返回傳入名稱是否為工廠,如果name不為空,並且以&開頭返回true
這個方法在下面的判斷也使用到瞭,記一下它的作用即可
來看例子
在我們使用FactoryBean通過context.getBean(“工廠Bean名稱”)的時候獲取的是FactoryBean的getObject生成的對象,如果我們想獲取FactoryBean的引用則需要在名稱前面加一個&
符號
回來看代碼,如果這個bean的引用是一個NullBean類型則直接返回引用,下面有做瞭一個判斷
if (!(beanInstance instanceof FactoryBean))
再次判斷這個bean是不是一個FactoryBean,如果為true則拋出異常,這個好理解,因為我們在getBean的時候完全可以將一個普通的bean名稱前面加上&符號
主要的判斷在下面的這個if
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { return beanInstance; }
現在有3中情況
1.當前的bean是一個普通的bean
第一個條件false 取反 true 第二個條件false 結果true,直接返回bean實例
2.當前是一個FactoryBean,想通過工廠獲取Bean
第一個條件 true 取反false 第二個條件false 結果false,進行下面的操作
3.當前是一個FactoryBean,想獲取工廠的引用
第一個條件 true 取反 false 第二個條件 true 結果 true 直接返回factoryBean實例
當前我們是想通過FactoryBean獲取對象,那麼不進if,繼續下面的代碼
Object object = null; // 如果beanDefinition為null,則嘗試從緩存中獲取給定的FactoryBean公開的對象 if (mbd == null) { //嘗試從緩存中加載bean object = getCachedObjectForFactoryBean(beanName); } // 未能從緩存中獲得FactoryBean公開的對象,則說明該bean是一個新創建的bean if (object == null) { FactoryBean<?> factory = (FactoryBean<?>) beanInstance; if (mbd == null && containsBeanDefinition(beanName)) { mbd = getMergedLocalBeanDefinition(beanName); } boolean synthetic = (mbd != null && mbd.isSynthetic()); // 從給定的FactoryBean中獲取指定的beanName對象 object = getObjectFromFactoryBean(factory, beanName, !synthetic); } return object;
主要來看getObjectFromFactoryBean
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) { if (factory.isSingleton() && containsSingleton(beanName)) { synchronized (getSingletonMutex()) { Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { //調用factoryBean的getObject方法 object = doGetObjectFromFactoryBean(factory, beanName); Object alreadyThere = this.factoryBeanObjectCache.get(beanName); if (alreadyThere != null) { object = alreadyThere; } } .......... } } }
doGetObjectFromFactoryBean
方法
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException { Object object; try { if (System.getSecurityManager() != null) { AccessControlContext acc = getAccessControlContext(); try { object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { //調用重寫的getObject方法 object = factory.getObject(); } } ....... return object; }
也就是說當我們getBean(“userMapper”)的時候其實是調用FactoryBean的getObject方法,代碼回到mybatis-spring項目的MapperFactoryBean類中的getObject方法
@Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } @Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } 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); } } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
到最後發現是通過jdk的動態代理來生成的對象,那麼回答開始的問題
我就寫瞭個接口為什麼能用?
因為mybatis在spring加載bean之前修改瞭beanDefinition,通過MapperScannerConfigurer類實現的BeanDefinitionRegistryPostProcessor接口中將我們定義的一些mapper接口的BeanDefinition的BeanClass屬性修改為瞭MapperFactoryBean,而這個類實現瞭FactoryBean,我們獲取接口實際上是通過FactoryBean的getObject方法
到此這篇關於Mybatis-Spring源碼分析的文章就介紹到這瞭,更多相關Mybatis-Spring源碼分析內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Spring IOC容器FactoryBean工廠Bean實例
- springboot mybatis調用多個數據源引發的錯誤問題
- spring獲取bean的源碼解析
- Dubbo3的Spring適配原理與初始化流程源碼解析
- Spring源碼解析容器初始化構造方法