SprignMVC+myBatis整合+mybatis原始碼分析+動態代理實現流程+如何根據mapper介面生成其實現類
首先熟悉三個概念:
SqlSessionFactoryBean
–為整合應用提供SqlSession物件資源
MapperFactoryBean
–根據指定的Mapper介面生成Bean例項
MapperScannerConfigurer
–根據指定包批量掃描Mapper介面並生成例項
SqlSessionFactoryBean:
在單獨使用MyBatis時,所有操作都是圍繞SqlSession展開的,SqlSession是通過SqlSessionFactory獲取的,SqlSessionFactory又是通過SqlSessionFactoryBuilder建立生成的。
在SpringMvc+MyBatis整合時,同樣需要SqlSession。SqlSessionFactoryBean這個元件通過原來的SqlSessionFactoryBuilder生成SqlSessionFactory物件,為整合應用提供SqlSession物件。
Java程式碼
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />
<property name="username" value="jsd1403" />
<property name="password" value="root" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="mapperLocations" value="classpath:com/lydia/entity/*.xml" />
</bean>
MapperFactoryBean:
其作用是根據Mapper介面獲取我們想要的Mapper物件,它封裝了原有的session.getMapper()功能的實現。
在定義MapperFactoryBean時,需要注入一下兩個屬性:
–SqlSessionFactoryBean物件,用於提供SqlSession
–要返回Mapper物件的Mapper介面
MapperFactoryBean配置如下:
Java程式碼
<!-- 方法一:定義mapper -->
<bean id="deptMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.lydia.entity.DeptMapper"></property>
<!-- 指定SqlSessionFactoryBean物件 -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
MapperScannerConfigurer配置使用:
注意:使用MapperFactoryBean時,當有一個Mapper(可以理解為表對應的對映檔案)就MapperFactoryBean,當mapper少數可以通過applicationContext配置檔案,通過id獲取。
如果大量的mapper,需要使用mybatis-spring.jar通過的MapperScannerConfigurer元件,通過這個元件可以自動掃描指定包下的各個Mapper介面,並註冊對應的MapperFactoryBean物件。
把之前的MapperFactoryBean的配置註釋掉,換成如下配置依然執行通過:
Java程式碼
<!--方法2:
可以把掃描到的Mapper介面變成Mapper物件-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定要掃描包: 多個包用逗號隔開 -->
<property name="basePackage" value="com.lydia,com.tarena" />
<!--指定sqlSessionFactory -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
注意:上面sqlSessionFactory屬性也可以不用指定,預設會以Autowired方式注入。
如果指定的某個包下並不完全是我們定義的Mapper介面,我們也可以通過自定義註解的方式指定生成MapperFactoryBean物件。
配置如下:
<!--方法3:
只要Mapper類前面加上@MyBatisRepository 這個自己指定的註解就OK-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.lydia" />
<property name="annotationClass" value="com.lydia.annotation.MyBatisRepository" />
</bean>
自定義註解:MyBatisRepository.java
Java程式碼
public @interface MyBatisRepository {
}
在DeptMapper介面中使用:
//@Repository("deptMapper")
@MyBatisRepository
public interface DeptMapper {
void addDept(Dept dept);
void deleteDept(Dept dept);
void updateDept(Dept dept);
......
}
測試:
public class TestCase {
@Test
public void testFindAll() throws Exception {
String conf = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(conf);
//獲取對應的mapper物件,並呼叫mapper介面中對應的方法
DeptMapper mapper = ac.getBean("deptMapper", DeptMapper.class);
List<Dept> lists = mapper.findAllDept();
for (Dept dept : lists) {
System.out.println(dept);
}
}
}
MapperScannerConfigurer處理過程原始碼分析
本文將分析mybatis與spring整合的MapperScannerConfigurer的底層原理,之前已經分析過java中實現動態,可以使用jdk自帶api和cglib第三方庫生成動態代理。本文分析的mybatis版本3.2.7,mybatis-spring版本1.2.2。
MapperScannerConfigurer介紹
MapperScannerConfigurer是spring和mybatis整合的mybatis-spring jar包中提供的一個類。
想要了解該類的作用,就得先了解MapperFactoryBean。
MapperFactoryBean的出現為了代替手工使用SqlSessionDaoSupport或SqlSessionTemplate編寫資料訪問物件(DAO)的程式碼,使用動態代理實現。
比如下面這個官方文件中的配置:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
org.mybatis.spring.sample.mapper.UserMapper是一個介面,我們建立一個MapperFactoryBean例項,然後注入這個介面和sqlSessionFactory(mybatis中提供的SqlSessionFactory介面,MapperFactoryBean會使用SqlSessionFactory建立SqlSession)這兩個屬性。
之後想使用這個UserMapper介面的話,直接通過spring注入這個bean,然後就可以直接使用了,spring內部會建立一個這個介面的動態代理。
當發現要使用多個MapperFactoryBean的時候,一個一個定義肯定非常麻煩,於是mybatis-spring提供了MapperScannerConfigurer這個類,它將會查詢類路徑下的對映器並自動將它們建立成MapperFactoryBean。
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
</bean>
這段配置會掃描org.mybatis.spring.sample.mapper下的所有介面,然後建立各自介面的動態代理類。
MapperScannerConfigurer底層程式碼分析
以以下程式碼為示例進行講解(部分程式碼,其他程式碼及配置省略):
package org.format.dynamicproxy.mybatis.dao;
public interface UserDao {
public User getById(int id);
public int add(User user);
public int update(User user);
public int delete(User user);
public List<User> getAll();
}
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.format.dynamicproxy.mybatis.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
我們先通過測試用例debug檢視userDao的實現類到底是什麼。
我們可以看到,userDao是1個MapperProxy類的例項。
看下MapperProxy的原始碼,沒錯,實現了InvocationHandler,說明使用了jdk自帶的動態代理。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
下面開始分析MapperScannerConfigurer的原始碼
MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor介面,BeanDefinitionRegistryPostProcessor介面是一個可以修改spring工長中已定義的bean的介面,該介面有個postProcessBeanDefinitionRegistry方法。
然後我們看下ClassPathMapperScanner中的關鍵是如何掃描對應package下的介面的。
其實MapperScannerConfigurer的作用也就是將對應的介面的型別改造為MapperFactoryBean,而這個MapperFactoryBean的屬性mapperInterface是原型別。MapperFactoryBean本文開頭已分析過。
所以最終我們還是要分析MapperFactoryBean的實現原理!
MapperFactoryBean繼承了SqlSessionDaoSupport類,SqlSessionDaoSupport類繼承DaoSupport抽象類,DaoSupport抽象類實現了InitializingBean介面,因此例項個MapperFactoryBean的時候,都會呼叫InitializingBean介面的afterPropertiesSet方法。
DaoSupport的afterPropertiesSet方法:
MapperFactoryBean重寫了checkDaoConfig方法:
然後通過spring工廠拿對應的bean的時候:
這裡的SqlSession是SqlSessionTemplate,SqlSessionTemplate的getMapper方法:
Configuration的getMapper方法,會使用MapperRegistry的getMapper方法:
MapperRegistry的getMapper方法:
MapperProxyFactory構造MapperProxy:
沒錯! MapperProxyFactory就是使用了jdk組帶的Proxy完成動態代理。
MapperProxy本來一開始已經提到。MapperProxy內部使用了MapperMethod類完成方法的呼叫:
下面,我們以UserDao的getById方法來debug看看MapperMethod的execute方法是如何走的。
@Test
public void testGet() {
int id = 1;
System.out.println(userDao.getById(id));
}
<select id="getById" parameterType="int" resultType="org.format.dynamicproxy.mybatis.bean.User">
SELECT * FROM users WHERE id = #{id}
</select>
java動態代理淺析
最近在公司看到了mybatis與spring整合中MapperScannerConfigurer的使用,該類通過反向代理自動生成基於介面的動態代理類。
於是想起了java的動態代理,然後就有了這篇文章。
本文使用動態代理模擬處理事務的攔截器。
介面:
public interface UserService {
public void addUser();
public void removeUser();
public void searchUser();
}
實現類:
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("add user");
}
public void removeUser() {
System.out.println("remove user");
}
public void searchUser() {
System.out.println("search user");
}
}
java動態代理的實現有2種方式
1.jdk自帶的動態代理
使用jdk自帶的動態代理需要了解InvocationHandler介面和Proxy類,他們都是在java.lang.reflect包下。
InvocationHandler介紹:
InvocationHandler是代理例項的呼叫處理程式實現的介面。
每個代理例項都具有一個關聯的InvocationHandler。對代理例項呼叫方法時,這個方法會呼叫InvocationHandler的invoke方法。
Proxy介紹:
Proxy 提供靜態方法用於建立動態代理類和例項。
例項(模擬AOP處理事務):
public class TransactionInterceptor implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start Transaction");
method.invoke(target, args);
System.out.println("end Transaction");
return null;
}
}
測試程式碼:
public class TestDynamicProxy {
@Test
public void testJDK() {
TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
UserService userService = new UserServiceImpl();
transactionInterceptor.setTarget(userService);
UserService userServiceProxy =
(UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
transactionInterceptor);
userServiceProxy.addUser();
}
}
測試結果:
start Transaction
add user
end Transaction
很明顯,我們通過userServiceProxy這個代理類進行方法呼叫的時候,會在方法呼叫前後進行事務的開啟和關閉。
2. 第三方庫cglib
CGLIB是一個功能強大的,高效能、高質量的程式碼生成庫,用於在執行期擴充套件Java類和實現Java介面。
它與JDK的動態代理的之間最大的區別就是:
JDK動態代理是針對介面的,而cglib是針對類來實現代理的,cglib的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為採用的是繼承,所以不能對final修飾的類進行代理。
例項:
public class UserServiceCallBack implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("start Transaction by cglib");
methodProxy.invokeSuper(o, args);
System.out.println("end Transaction by cglib");
return null;
}
}
測試程式碼:
public class TestDynamicProxy {
@Test
public void testCGLIB() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new UserServiceCallBack());
UserServiceImpl proxy = (UserServiceImpl)enhancer.create();
proxy.addUser();
}
}
測試結果:
start Transaction by cglib
add user
end Transaction by cglib
mybatis如何根據mapper介面生成其實現類
mybatis裡頭給sqlSession指定執行哪條sql的時候,有兩種方式,一種是寫mapper的xml的namespace+statementId,如下:
public Student findStudentById(Integer studId) {
logger.debug(“Select Student By ID :{}”, studId);
SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
try {
return sqlSession.selectOne(“com.mybatis3.StudentMapper.findStudentById”, studId);
} finally {
sqlSession.close();
}
}
另外一種方法是指定mapper的介面:
public Student findStudentById(Integer studId) {
logger.debug(“Select Student By ID :{}”, studId);
SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.findStudentById(studId);
} finally {
sqlSession.close();
}
}
一般的話,比較推薦第二種方法,因為手工寫namespace和statementId極大增加了犯錯誤的概率,而且也降低了開發的效率。
問題
mapper的實現類如何生成
如果使用mapper介面的方式,問題來了,這個是個介面,通過sqlSession物件get出來的一定是個實現類,問題是,我們並沒有手工去寫 實現類,那麼誰去幹了這件事情呢?答案是mybatis通過JDK的動態代理方式,在啟動載入配置檔案時,根據配置mapper的xml去生成。
mybatis-spring幫忙做了什麼
自動open和close session
一、mapper代理類是如何生成的
啟動時載入解析mapper的xml
如果不是整合spring的,會去讀取節點,去載入mapper的xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="2"/>
</settings>
<typeAliases>
<typeAlias alias="CommentInfo" type="com.xixicat.domain.CommentInfo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/demo"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/xixicat/dao/CommentMapper.xml"/>
</mappers>
</configuration>
如果是整合spring的,會去讀spring的sqlSessionFactory的xml配置中的mapperLocations,然後去解析mapper的xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 配置mybatis配置檔案的位置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="typeAliasesPackage" value="com.xixicat.domain"/>
<!-- 配置掃描Mapper XML的位置 -->
<property name="mapperLocations" value="classpath:com/xixicat/dao/*.xml"/>
</bean>
然後繫結namespace(XMLMapperBuilder)
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
這裡先去判斷該namespace能不能找到對應的class,若可以則呼叫
configuration.addMapper(boundType);
configuration委託給MapperRegistry:
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
生成該mapper的代理工廠(MapperRegistry)
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(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();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
這裡的重點就是MapperProxyFactory類:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
getMapper的時候生成mapper代理類
@SuppressWarnings("unchecked")
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);
}
}
new出來MapperProxy
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
這裡給代理類注入了sqlSession
MapperProxy實現InvocationHandler介面進行攔截代理
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
這裡的代理攔截,主要是尋找到MapperMethod,通過它去執行SQL。
MapperMethod委託給SqlSession去執行sql
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
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;
}
其實這裡就回到了第一種模式,該模式是直接指定了statement的Id(這裡是command.getName()),而通過mapper的介面方式,則多了這麼步驟,最後通過MapperMethod,給sqlSession傳入statement的id。
sqlSession其實自己也不執行sql,它只是mybatis對外公佈的一個api入口,具體它委託給了executor去執行sql。
什麼時候去getMapper
手工get,比如
public void createStudent(Student student) {
SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
studentMapper.insertStudent(student);
sqlSession.commit();
} finally {
sqlSession.close();
}
}- 整合spring的話
在spring容器給指定的bean注入mapper的時候get出來(見MapperFactoryBean的getObject方法)
二、mybatis-spring幫忙做了什麼
通過MapperScannerConfigurer將mapper適配成spring bean
<!-- 配置掃描Mapper介面的包路徑 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.xixicat.dao"/>
</bean>
這裡使用 MapperFactoryBean將Mapper介面配置成 Spring bean 實體同時注入sqlSessionFactory。
MapperScannerConfigurer給每個mapper生成對應的MapperFactoryBean
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
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.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
委託給ClassPathMapperScanner去scan
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 {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition 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
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
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);
}
}
}
return beanDefi