Mybatis的初始化和結合Spring Framework後初始化的
帶著下面的問題進行學習:
(1)Mybatis 框架或 Spring Framework 框架對資料層 Mapper 介面做了代理,那是做了 JDK 動態代理還是 CGLIB 代理?
(2)Mapper 介面使用和不使用 @Mapper 註解有什麼區別?
(3)Spring Framework 框架引入 Mybatis 的 jar 包後,Spring Framework 是怎麼管理的?
(4)@MapperScan註解的作用是什麼?
在探究上面的問題前,先了解什麼是 FactoryBean,FactoryBean 和 BeanFactory有什麼區別?
BeanFactory 是 Spring Framework 中的一個 Bean 工廠(AnnotationConfigApplicationContext、ClassPathXmlApplicationContext等),可以產生類,它有一個 getBean 方法可以獲取到類;
FactoryBean 是一個 Bean,受 Spring Framework 管理的一個物件,定義 Bean 有好幾種方式,比如 xml 的 標籤,註解 @Bean、@Service 等;
下面是案例:實現 FactoryBean 介面,重寫方法
public class TempBean { public void query(){
System.out.println("TempBean");
}
}
@Configuration("MyFactroyBean") public class MyFactroyBean implements FactoryBean { public void test() {
System.out.println("MyFactroyBean MyFactroyBeanTest");
}
@Override public Object getObject() throws Exception { return new TempBean();
}
@Override public Class> getObjectType() { return TempBean.class;
}
@Override public boolean isSingleton() { return true;
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context= new AnnotationConfigApplicationContext(MyFactroyBean.class);
MyFactroyBean myFactroyBean = (MyFactroyBean) context.getBean("&MyFactroyBean");
myFactroyBean.test();
TempBean tempBean = (TempBean) context.getBean("MyFactroyBean");
tempBean.query();
}
//=========結果======
MyFactroyBean MyFactroyBeanTest
TempBean
如果你的類實現了 FactoryBean,那麼 Spring 存在兩個物件:
一個是 getObject() 返回的物件:當前類指定的名字;
一個是當前類:"&"+當前類指定的名字;
當實現了 FactoryBean 後,Spring 在容器例項化過程中,會對專案中的自定義類進行例項化,當前類會被代理成一個 CGLIB 類(使用了@Configuration註解),當你獲取當前類時,傳遞“&xx”,Spring 會進行判斷,如果獲取是當前類,返回代理類物件,如果是獲取 getObject 返回的物件,會直接呼叫 getObject 方法中的 new xxx();
下面是Spring容器初始化時,在執行一些後置處理器(ConfigurationClassPostProcessor是對專案的 ComponentScan 註解的路徑、@Configuration 註解的類進行掃描解析,超級重要的一個類)過程中,對實現了 FactoryBean 介面的子類進行代理:
下面是對實現了 FactoryBean 介面的子類的獲取:先判斷是不是 FactoryBean 型別,如果是加字首“&"再進行獲取;因為前面已經對子類進行了代理,並且存入了 beanDefinitionMap 中
但即使上面加了字首”&“,在後面還是會剔除掉,那怎麼判斷是獲取當前類還是獲取getObject方法中的類呢?在 Spring 容器過程中,只會對當前類(實現了 FactoryBean 的子類)進行例項化,當獲取時,getSingleton 直接從快取中獲取(每個物件例項化後會放入快取中 singletonObjects)共享物件,然後 getObjectForBeanInstance 獲取例項物件返回,在獲取例項物件過程中,會判斷是不是 FactoryBean 型別,如果是直接將代理物件返回;
下面判斷是否是一個FactoryBean物件:
public static boolean isFactoryDereference(@Nullable String name) { return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));//**FACTORY_BEAN_PREFIX=”&"**
}
如果是獲取 getObject() 返回的物件,跟上面一樣,但在判斷是不是一個 FactoryBean 物件時,由於name沒有**“&”**的字首,所以走下面的流程,不會直接 return beanInstance;
FactroyBean是一個 Bean(可以當作一個業務類,比如連線資料庫做一些業務操作),當一個類的依賴關係很複雜時,只需要提供一個簡單的介面給外部使用時(將內部的一些關係進行封裝維護好)可以使用 FactroyBean 來實現,比如 mybatis 的 SqlSessionFactoryBean 是一個 FactoryBean 實現類,裡面的 getObject 方法有一個 afterPropertiesSet 方法,內部將很多依賴(Configuration 配置、TransactionFactory 事務工廠等)進行了處理封裝維護到了 SqlSessionFactoryBean,外部只需要傳入一個 DataSource 資料來源即可,新增 MapperScan 掃描 xml 檔案會自動把 xml 的資訊維護到 SqlSessionFactoryBean中;
那麼 SqlSessionFactoryBean 什麼時候被呼叫呢?可以看下idea除錯的方法呼叫棧:
從上面幾張圖片可以看出,當IOC容器Context進行類(Service)初始化時,發現有屬性變數(Mapper),會對屬性變數(Mapper)進行建立獲取自動注入進去,其中會獲取到Mapper封裝成的一個MapperFactoryBean,執行到 SqlSessionFactoryBean的 afterPropertiesSet 方法對Mapper介面對應的xml進行解析獲取;
MapperFactoryBean 也是一個 FactoryBean,每個 Mapper 介面都會轉換成一個 MapperFactoryBean,所以 doCreateBean() 建立獲取時可以透過 mapperInterface(Mapper介面)獲取到對應的 MapperFactoryBean,它的值是儲存到 Configuration的 MapperRegistry變數中;
那它是什麼時候儲存到 Configuration的 MapperRegistry變數中的呢?透過下面的圖片可以看出,是在解析完xml後放入的:
當 Mapper 介面在 MapperFactoryBean#getObject() 時,它對Mapper介面進行了代理:**MapperRegistry#getMapper()
**
public T getMapper(Class type, SqlSession sqlSession) { //獲得代理物件工廠:裡面包含Mapper介面資訊
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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() 的原始碼:從中可以看到,Mapper 介面對應的代理物件是使用了 JDK 動態代理產生的;【解決了上文的第一個問題,其實從 Mapper 是介面也可以猜出是使用 JDK 動態實現的】
public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy);
} protected T newInstance(MapperProxy mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
其中 MapperProxy 是一個 InvocationHandler 實現類,從前文可以得知呼叫 Mapper.xxx方法 會進入 MapperProxy#invoke(),最後會執行 mapperMethod.execute(sqlSession, args) 進行SQL查詢返回結果;
綜上所述:每個Mapper介面轉化成一個MapperFactoryBean,當呼叫Mapper介面方法執行JDBC操作時,Mapper 介面透過 MapperFactoryBean#getObject() 對Mapper介面進行了 JDK 動態代理。
對於第三個問題“Spring Framework 框架引入 Mybatis 的 jar 包後,Spring Framework 是怎麼管理的?”
從前文得出每個 Mapper 介面都會轉換成一個 MapperFactoryBean,而 MapperFactoryBean 繼承了 SqlSessionDaoSupport(mybatis-spring-xx.jar),SqlSessionDaoSupport 繼承了 DaoSupport(spring-tx.jar),DaoSupport 實現了 InitializingBean介面,所以Spring容器初始化會執行DaoSupport#afterPropertiesSet方法,會執行裡面的checkDaoConfig方法,MapperFactoryBean 重寫了checkDaoConfig方法,所以最後會執行MapperFactoryBean#checkDaoConfig方法:
public abstract class DaoSupport implements InitializingBean { protected final Log logger = LogFactory.getLog(this.getClass()); public DaoSupport() {
} public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { this.checkDaoConfig(); try { this.initDao();
} catch (Exception var2) { throw new BeanInitializationException("Initialization of DAO failed", var2);
}
} protected abstract void checkDaoConfig() throws IllegalArgumentException; protected void initDao() throws Exception {
}
}
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean { private Class mapperInterface; private boolean addToConfig = true;
@Override protected void checkDaoConfig() { super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();//得到配置資訊 if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try {
configuration.addMapper(this.mapperInterface);//對介面進行xml解析
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e); throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
}
Configuration#addMapper public void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
MapperRegistry#addMapper public void addMapper(Class type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
} boolean loadCompleted = false; try { //為每個介面配置一個MapperProxyFactory,供後續Mapper介面進行JDK代理
knownMappers.put(type, new MapperProxyFactory(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. //解析類
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();//XML解析
loadCompleted = true;
} finally { if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
@MapperScan 註解是匯入了一個 MapperScannerRegistrar(是實現了 ImportBeanDefinitionRegistrar 介面的,作用是動態往 BeanDefinitionMap 新增 BeanDefinition),的作用是對包路徑的 Mapper 介面和對應的XML配置資訊進行掃描解析,將所有 Mapper 介面的class資訊掃描成對應的 BeanDefition, MapperFactoryBean 型別的 BeanDefition,同時為這個BeanDefition提供一個有參構造方法,引數是 class,在後面Mapper介面進行例項化的 JDK 代理時可以根據這個 class 返回對應的代理物件;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class) public @interface MapperScan {
}
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@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 basePackages = new ArrayList(); 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));//包掃描解析
}
}
下面是掃描解析包路徑的主要邏輯:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { /** * Calls the parent search that will search and register all the candidates.
* Then the registered objects are post processed to set them as
* MapperFactoryBeans */ @Override public Set doScan(String... basePackages) { //將每個Mapper解析成BeanDefinitionHolder存放到set集合中
Set beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else { //對BeanDefinitionHolder集合進行處理
processBeanDefinitions(beanDefinitions);
} return beanDefinitions;
} private void processBeanDefinitions(Set beanDefinitions) {
GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
} // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean //給每個BeanDefition提供一個有參構造方法,在後面Mapper介面進行例項化的JDK代理時可以根據這個class返回對應的代理物件;
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 //Mapper介面轉換成MapperFactoryBean
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) { if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
}
綜上所述:在 Spring 容器初始化過程中,在對Mapper介面進行例項化的過程中,@MapperScan 註解主要是解析包路徑將 Mapper 介面解析成 MapperFactoryBean 的 BeanDefition,這是 Mapper 介面在例項化之前做的事情,在例項化中和之後的過程中,主要利用 Spring的InitializingBean 介面的特性(每個實現了 InitializingBean 介面的子類有一個 afterPropertiesSet 方法,在例項化過程中會執行)來實現對 Mapper 介面資訊的初始化,比如 sql 語句的初始化,將這些資訊快取起來放到一個 Map 中:
上面的是 Mybatis 和 Spring Framework 結合後的初始化流程,那麼單獨的 Mybatis 初始化流程是怎樣的呢?透過下面的案例(有詳細案例)進行探究:
String resource = "mybatis-config2.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
mapper.list();
mapper.list();
透過 debug 出來的方法呼叫棧可以看出:透過 SqlSessionFactoryBuilder 對 Mapper 介面和 XML 配置進行解析的資訊也是儲存到 Configuration 的 mappedStatements 中;
sqlSession.getMapper() 直接從 DefaultSqlSession 中獲取,還是跟上面一樣在解析完存放到 Configuration 的 MapperRegistry 變數中,代理還是走 JDK 動態代理:
綜上所述: Mybatis 和 Spring Framework 結合後對 Mapper 介面的初始化過程中會轉換成 MapperFactoryBean 型別,然後利用 Spring的InitializingBean 介面的特性來實現對 Mapper 介面資訊的初始化,再將這些資訊快取起來放到 Configuration 的 mappedStatements 中;而Mybatis直接解析完放到 Configuration 的 mappedStatements 中;
最後剩餘的一個問題:Mapper 介面使用和不使用 @Mapper 註解有什麼區別?
透過檢視@Mapper註解的註釋,發現這個註解只是一個標識功能,使用與不使用沒什麼區別,不會影響系統的功能;
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/75/viewspace-2797579/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Mybatis的初始化和結合Spring Framework後初始化的原始碼探究MyBatisSpringFramework原始碼
- Mybatis一級快取和結合Spring Framework後失效的原始碼探究MyBatis快取SpringFramework原始碼
- MyBatis是如何初始化的?MyBatis
- 理解 MyBatis 是如何在 Spring 容器中初始化的MyBatisSpring
- 神經網路的初始化方法總結 | 又名“如何選擇合適的初始化方法”神經網路
- 精盡MyBatis原始碼分析 - MyBatis初始化(三)之 SQL 初始化(上)MyBatis原始碼SQL
- 精盡MyBatis原始碼分析 - MyBatis初始化(四)之 SQL 初始化(下)MyBatis原始碼SQL
- Spring IoC bean 的初始化SpringBean
- Spring Bean如何初始化的SpringBean
- 4_Spring Bean的初始化和銷燬SpringBean
- Spring原始碼分析(一)Spring的初始化和XML解析Spring原始碼XML
- 物件的初始化和清理物件
- Spring Boot + Mybatis + Spring MVC環境配置(一) :Spring Boot初始化,依賴新增Spring BootMyBatisMVC
- Spring Ioc之初始化Spring
- Spring Boot 2.2中的延遲初始化Spring Boot
- 【spring原始碼系列】之【Bean的初始化】Spring原始碼Bean
- spring ioc原理-容器初始化的大致流程Spring
- 關於SpringBoot結合mybatis後遇到的坑Spring BootMyBatis
- 資料結構:連結串列的初始化插入和刪除2.3.1資料結構
- 最全面的C結構體的初始化和賦值結構體賦值
- SpringMvc後臺初始化SpringMVC
- Spring知識點回顧(05)bean的初始化和銷燬SpringBean
- Java變數的宣告和初始化Java變數
- 初始化ArrayList的簡單方法總結
- springboot和mybatis結合Spring BootMyBatis
- C++ 結構體例項和類例項的初始化C++結構體
- Mybatis原始碼系列1-Mybaits初始化MyBatis原始碼AI
- 從底層原始碼淺析Mybatis的SqlSessionFactory初始化過程原始碼MyBatisSQLSession
- Innodb undo之 undo物理結構的初始化
- 一個簡單的filamentphp後臺初始化模板PHP
- Spring Boot如何初始化資料Spring Boot
- Spring配置初始化ApplicationContextSpringAPPContext
- spring原始碼深度解析— IOC 之 bean 的初始化Spring原始碼Bean
- Spring容器 —— 深入 bean 的載入(五、初始化 bean)SpringBean
- Spring Boot中初始化資源的幾種方式Spring Boot
- Spring 原始碼(17)Spring Bean的建立過程(8)Bean的初始化Spring原始碼Bean
- 【面經】面試官:講講類的載入、連結和初始化?面試
- Spring原始碼分析:Spring IOC容器初始化Spring原始碼