mybatis-spring:@MapperScan注解的使用
mybatis-spring:@MapperScan注解
在demo: springboot+mybatis的示例中,dao層接口使用了注解@MapperScan:指定掃描com.xuxd.demo.dao.UserDao所在包路徑下的所有接口類。
本文分析下@MapperScan注解做了哪些動(dòng)作。
@MapperScan源碼
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
/**
*缺省屬性(==basePackages),basePackages的別名
*/
String[] value() default {};
/**
* 哪些包路徑下的接口被掃描注冊(cè)(接口至少有一個(gè)方法),具體實(shí)現(xiàn)類(非接口)忽略
*/
String[] basePackages() default {};
/**
* 指定類所在包下所有接口被掃描注冊(cè)(接口至少有一個(gè)方法),具體實(shí)現(xiàn)類(非接口)忽略
*/
Class<?>[] basePackageClasses() default {};
/**
* 掃描到的滿足條件的接口,首先要把它們相關(guān)bean定義注冊(cè)到spring容器中吧,注冊(cè)bean定義
* 的時(shí)候,需要定義bean名稱,這個(gè)是用來(lái)自定方生成bean名稱的策略組件,個(gè)人覺(jué)得很少用
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* 這個(gè)注解指定的接口也要被掃描
*/
Class<? extends Annotation> annotationClass() default Annotation.class;
/**
* 繼承這個(gè)接口的接口也要被掃描
*/
Class<?> markerInterface() default Class.class;
/**
* 多數(shù)據(jù)源的時(shí)候可能用到這個(gè),后面單獨(dú)說(shuō)明這個(gè)
*/
String sqlSessionTemplateRef() default "";
/**
* 多數(shù)據(jù)源的時(shí)候可能用到這個(gè),后面單獨(dú)說(shuō)明這個(gè)
*/
String sqlSessionFactoryRef() default "";
/**
* 多數(shù)據(jù)源的時(shí)候可能用到這個(gè),后面單獨(dú)說(shuō)明這個(gè)
*/
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
這個(gè)注解的重點(diǎn)是@Import(MapperScannerRegistrar.class)
使用這個(gè)注解導(dǎo)入MapperScannerRegistrar主要完成兩件事:
1. 掃描指定接口
2. 注冊(cè)這些接口的bean定義到spring容器
接下來(lái)進(jìn)入MapperScannerRegistrar類看下是如何完成這兩動(dòng)作:
MapperScannerRegistrar.class
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}
這個(gè)類實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口:
public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
@MapperScan注解類上使用了@Import注解導(dǎo)入了這個(gè)接口的實(shí)現(xiàn)類(MapperScannerRegistrar.class),因此spring解析MybatisConfig(源碼:demo: springboot+mybatis)這個(gè)類的時(shí)候,解析到這個(gè)類上使用了注解@MapperScan,從MapperScan注解類上(注解都是一個(gè)接口,java會(huì)創(chuàng)建代理類)發(fā)現(xiàn)了@Import注解及MapperScannerRegistrar類(因?yàn)镮mport注解是導(dǎo)入配置類的)。
在加載MybatisConfig配置類的bean定義時(shí)候,找到了ImportBeanDefinitionRegistrar 的實(shí)現(xiàn)類MapperScannerRegistrar,便會(huì)回調(diào)這個(gè)MapperScannerRegistrar的registerBeanDefinitions方法。
總之一句話:
在加載配置類MybatisConfig的bean定義的時(shí)候,會(huì)調(diào)用與之看起來(lái)有點(diǎn)關(guān)系的MapperScannerRegistrar的registerBeanDefinitions方法。
MapperScannerRegistrar的registerBeanDefinitions方法第一個(gè)參數(shù)importingClassMetadata指的是MybatisConfig這個(gè)類的。

可以debug,看這個(gè)參數(shù)的信息。
@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));
}
看這個(gè)方法的源碼,主要完成2件事:
1. 解析MapperScan注解的各個(gè)字段的值 ,用以初始化類路徑掃描器
2. 確定掃描類路徑下哪些接口,如指定的包路徑、指定的類所在包路徑。上面倒數(shù)第2行代碼,注冊(cè)過(guò)濾器,用來(lái)指定包含哪些注解或接口的掃描(@MapperScan的annotationClass的markerInterface屬性,如果設(shè)置的話)
因此,重點(diǎn)是最后一行代碼doScan的調(diào)用。
這里不貼源碼了,前文提到,MapperScannerRegistrar主要完成兩件事,都會(huì)在這里完成,解析包路徑,掃描指定接口并注冊(cè)bean定義到spring容器。
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
在ClassPathMapperScanner類的processBeanDefinitions方法內(nèi)看到這里注冊(cè)的一個(gè)spring的工廠bean:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
...
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
...
}
大部分代碼刪除了, 只留下這幾個(gè)說(shuō)明。
不了解 spring的FactoryBean的建議查看下相關(guān)文檔。
這里用直白的話說(shuō),就是:
我在service層需要注入這個(gè)Dao層接口的bean(比如demo: springboot+mybatis中UserServiceImpl類的UserDao字段的自動(dòng)注入),依據(jù)類型注入。
spring在自己的容器里翻呀翻,如果是普通bean,一看和這個(gè)接口類型(UserDao)都不匹配就換一個(gè),找到了這個(gè)工廠bean,一看是工廠bean,就不能直接做類型匹配了,而是調(diào)用getObjectType方法,把返回的類型和需要被注入字段的類型一比較,正好匹配(都是UserDao類型),就調(diào)用這個(gè)工廠bean的getObject方法返回這個(gè)對(duì)象,然后通過(guò)反射等操作,把這個(gè)值注入到這個(gè)字段中。而調(diào)用getObject方法,其實(shí)就是我們平常直接用mybatis的接口返回的一個(gè)MapperProxy的代理對(duì)象的操作了。
demo: springboot+mybatis
最近因工作原因,需要研究下spring的事務(wù)部分和mybatis的多數(shù)據(jù)源的源碼實(shí)現(xiàn),這樣才能更容易的在代碼層面通過(guò)擴(kuò)展/重寫等方式去定制自己的實(shí)現(xiàn)。
以前雖然用過(guò)幾次mybatis,但是卻一直沒(méi)抽出時(shí)間認(rèn)真翻看下源碼,趁這次機(jī)會(huì),花點(diǎn)時(shí)間研究下,順便做個(gè)筆記。
關(guān)于看源碼,我向來(lái)是覺(jué)得只有一步步去debug整個(gè)流程,查看每一步的數(shù)據(jù)流向和數(shù)據(jù)狀態(tài),才會(huì)有個(gè)更清晰的深知。如果只是看的話,有些源碼中各種繼承、適配、代理、裝飾等,會(huì)分不清當(dāng)前使用的到底是哪個(gè)類。
于是乎,所謂工欲善其事,必先利其器。先搭建個(gè)極簡(jiǎn)單的mybatis的工程環(huán)境,用來(lái)調(diào)試源碼。
這個(gè)工程用了spring boot+mybatis。mybatis采用java config的形式(是真心不喜歡xml配置,所以源碼研究上也會(huì)避開xml的加載)
后面博文關(guān)于分析描述就會(huì)針對(duì)這個(gè)工程的配置來(lái)說(shuō)了。
另外,代碼中關(guān)于spring事務(wù)的注解先注釋了。
說(shuō)了這么多,是希望緩解自己又寫了篇這么沒(méi)技術(shù)含量的博客的尷尬,哎,最近這段時(shí)間寫的博客確實(shí)有些湊數(shù)了。
工程代碼
數(shù)據(jù)庫(kù)腳本:
CREATE DATABASE `testdb` /*!40100 DEFAULT CHARACTER SET utf8 */ -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `age` int(11) NOT NULL, `username` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
工程結(jié)構(gòu)

按工程結(jié)構(gòu),列一下文件代碼:
User.java
public class User {
int id;
int age;
String username;
public User() {
}
public User(int id, int age, String username) {
this.id = id;
this.age = age;
this.username = username;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", username='" + username + '\'' +
'}';
}
}
MybatisConfig.java
@Configuration
@MapperScan(basePackageClasses = {UserDao.class})
//@EnableTransactionManagement //啟用spring事務(wù)
public class MybatisConfig {
@Autowired
private Environment environment;
// 數(shù)據(jù)源配置
@Bean
public DataSource dataSource() {
// mybatis自帶的一個(gè)簡(jiǎn)易數(shù)據(jù)庫(kù)連接池,只是為了debug代碼,這個(gè)就不關(guān)心了
PooledDataSource pooledDataSource = new PooledDataSource();
pooledDataSource.setDriver(environment.getProperty("mysql.driver"));
pooledDataSource.setUsername(environment.getProperty("mysql.username"));
pooledDataSource.setPassword(environment.getProperty("mysql.passwd"));
pooledDataSource.setUrl(environment.getProperty("mysql.url"));
return pooledDataSource;
}
// spring事務(wù)管理的基礎(chǔ)bean,事務(wù)部分會(huì)用到
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("classpath:/mapper/*.xml"));
return sqlSessionFactoryBean;
}
}
UserController.java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
IUserService userService;
@GetMapping("/save")
public String saveUser() {
User user = new User(10, 100, "test user");
try {
return userService.saveUser(user) ? "save success" : "save fail";
} catch (Exception ignore) {
ignore.printStackTrace();// 不打印日志了,堆棧信息直接打到控制臺(tái)看
return "save error: " + ignore.getMessage();
}
}
@GetMapping("/list")
public String getUsers() throws Exception {
return userService.getUsers().toString();
}
@GetMapping("/delete")
public String deleteUser() throws Exception {
return userService.deleteUser() ? "delete success" : "delete fail";
}
}
UserDao.java
@Repository
public interface UserDao {
boolean saveUser(User user);
List<User> getUsers();
boolean deleteUser();
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements IUserService {
@Autowired
UserDao userDao;
//@Transactional //指定這個(gè)方法的事務(wù)屬性
@Override
public boolean saveUser(User user) throws Exception {
boolean success = userDao.saveUser(user);
// spring事務(wù)能力測(cè)試的時(shí)候,使用下面這段代碼
/*if (true) {
throw new RuntimeException();
}*/
return success;
}
@Override
public List<User> getUsers() throws Exception {
return userDao.getUsers();
}
@Override
public boolean deleteUser() throws Exception {
return userDao.deleteUser();
}
}
IUserService.java
public interface IUserService {
boolean saveUser(User user) throws Exception;
List<User> getUsers() throws Exception;
boolean deleteUser() throws Exception;
}
WebApplication.java
@SpringBootApplication
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuxd.demo.dao.UserDao">
<sql id="user_column">
id,age,username
</sql>
<select id="getUsers" resultType="com.xuxd.demo.beans.User">
select * from user
</select>
<!-- 增加用戶 -->
<insert id="saveUser" parameterType="com.xuxd.demo.beans.User">
insert into user
(<include refid="user_column"/>)
values
(#{id},#{age},#{username})
</insert>
<delete id="deleteUser">
DELETE from USER where id = 10
</delete>
</mapper>
application.properties
#data source config mysql.driver=com.mysql.jdbc.Driver mysql.username=root mysql.passwd=123456 mysql.url=jdbc:mysql://localhost:3306/testdb?useSSL=false
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xuxd</groupId>
<artifactId>spring-mybatis.demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.43</version>
</dependency>
</dependencies>
</project>
后續(xù)就用這個(gè)工程debug源碼了。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
圖文詳解mybatis+postgresql平臺(tái)搭建步驟
從頭開始搭建一個(gè)mybatis+postgresql平臺(tái),這篇文章主要介紹了圖文詳解mybatis+postgresql平臺(tái)搭建步驟,感興趣的小伙伴們可以參考一下2016-07-07
解決idea每次打開新的項(xiàng)目都需要重新配置maven問(wèn)題
這篇文章主要介紹了解決idea每次打開新的項(xiàng)目都需要重新配置maven問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
Java中的形式參數(shù)和實(shí)際參數(shù)案例詳解
這篇文章主要介紹了Java中的形式參數(shù)和實(shí)際參數(shù),形參和實(shí)參間的關(guān)系,兩者是在調(diào)用的時(shí)候進(jìn)行結(jié)合的,通常實(shí)參會(huì)將取值傳遞給形參,形參去之后進(jìn)行函數(shù)過(guò)程運(yùn)算,然后可能將某些值經(jīng)過(guò)參數(shù)或函數(shù)符號(hào)返回給調(diào)用者,需要的朋友可以參考下2023-10-10
Spring Boot集成Java DSL的實(shí)現(xiàn)代碼
這篇文章主要介紹了Spring Boot集成Java DSL的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
MyBatis自定義TypeHandler如何解決字段映射問(wèn)題
這篇文章主要介紹了MyBatis自定義TypeHandler如何解決字段映射問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12

