Mybatis-Spring
博主技術有限,本文難免有錯誤的地方,如果您發現了歡迎評論私信指出,謝謝
JAVA技術交流群:737698533
當我們使用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();
.........
//給這個BeanDefinition設置通用的構造參數
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
//設置這個BeanDefinition的BeanCalss類型為MapperFactoryBean類型
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中情況
-
當前的bean是一個普通的bean
第一個條件false 取反 true 第二個條件false 結果true,直接返回bean實例
-
當前是一個FactoryBean,想通過工廠獲取Bean
第一個條件 true 取反false 第二個條件false 結果false,進行下面的操作
-
當前是一個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方法