示例
- 首先引入Mybatis的使用示例,其中兩個關鍵變量:SqlSessionFactory ,SqlSession
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Mapper1 mapper = sqlSession.getMapper(Mapper1.class);
String result = mapper.select1();
- 問題引入,以下代碼中(系統中存在Mapper1,Mapper2,Mapper3接口),如果直接運作main函數,背景會報錯,mapper注入錯誤,此時可以引入FactoryBean機制,生成mapper的代理對象:
@Service
public class MybatisAndSpringService {
@Autowired
private Mapper1 mapper1;
// ......多個Mapper接口(省略)
@Autowired
private Mappern mappern;
public void test() {
String result1 = mapper1.select1();
System.out.println("result1 = " + result1);
String result2 = mapper2.select2();
System.out.println("result2 = " + result2);
//……
}
}
// 主函數
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
// 注冊主配置類
ac.register(MyConfig.class);
// 重新整理容器
ac.refresh();
MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
service.test();
}
- 引入FactoryBean接口,此時如果直接運作main函數依然會報錯,因為自定義FactoryBean不在Spring容器中,是以需要采用聲明式程式設計的方式,修改main函數:
public class ArtistFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
// JDK動态代理
Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{Mapper1.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("目前調用的方法名:" + method.getName());
return null;
}
});
return obj;
// Mybatis的代理邏輯
// return sqlSession.getMapper(mapperClazz);
}
@Override
public Class<?> getObjectType() {
return Mapper1.class;
}
}
// main函數
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
// 注冊主配置類
ac.register(MyConfig.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(ArtistFactoryBean.class);
ac.registerBeanDefinition("facotrybean", beanDefinition);
// 重新整理容器
ac.refresh();
MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
service.test();
}
- 到了這一步,直接運作是沒有問題的,繼續思考:FactoryBean的getObject方法,生成代理對象的接口是在代碼中寫死的,是以還需要通過構造函數傳參的方式修改
// 修改FactoryBean
public class ArtistFactoryBean implements FactoryBean {
private Class<?> mapperClazz;
public ArtistFactoryBean(Class<?> mapperClazz) {
this.mapperClazz = mapperClazz;
}
@Override
public Object getObject() throws Exception {
// JDK動态代理
Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{mapperClazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("目前調用的方法名:" + method.getName());
return null;
}
});
return obj;
// Mybatis的代理邏輯
// return sqlSession.getMapper(mapperClazz);
}
@Override
public Class<?> getObjectType() {
return mapperClazz;
}
}
// 同時在main函數中也要修改
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
// 注冊主配置類
ac.register(MyConfig.class);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(ArtistFactoryBean.class);
// 新增邏輯
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
ac.registerBeanDefinition("facotrybean", beanDefinition);
// 重新整理容器
ac.refresh();
MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
service.test();
}
- 經過上述修改之後,FactoryBean中mapper接口的傳入方式得到了解決,但是,如果此時要添加其他的mapper,比如:Mapper2,Mapper3……,就會出現下面的編碼:
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
// 注冊主配置類
ac.register(MyConfig.class);
// Mapper1
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(ArtistFactoryBean.class);
// 新增邏輯
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
ac.registerBeanDefinition("facotrybean", beanDefinition);
// ……很多Mapper(省略)
// Mappern
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(ArtistFactoryBean.class);
// 新增邏輯
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mappern.class);
ac.registerBeanDefinition("facotrybean", beanDefinition);
// 重新整理容器
ac.refresh();
MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
service.test();
}
- 顯然這種編碼方式非常不優雅,那麼接下來可以考慮包掃描的方式,把Mapper接口掃描出來儲存到一個集合中,然後周遊集合,就可以替換掉這麼一大片備援的代碼,可以利用Spring提供的ImportBeanDefinitionRegistrar接口,實作它的registerBeanDefinitions方法
- 同時結合Spring的類掃描器ClassPathBeanDefinitionScanner,但是Spring隻掃描類,而這裡我們隻掃描接口,是以需要自定義一個類掃描器,需要修改isCandidateComponent的邏輯,隻傳回接口,否則Mapper接口無法掃描到
- 重寫doScan邏輯,因為我們最終需要的是Mapper接口的代理對象,是以需要把之前定義的FactoryBean設定進去
- 需要注意,在配置類上需要加入注解:@Import(ArtistImportBeanDefinitionRegistrar.class),使Bean定義注冊器成為Spring容器中的Bean
//自定義類掃描器
public class ArtistScanner extends ClassPathBeanDefinitionScanner {
public ArtistScanner(BeanDefinitionRegistry registry) {
super(registry);
}
// 需要掃描到接口
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> holders = super.doScan(basePackages);
for (BeanDefinitionHolder holder : holders) {
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) holder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(holder.getBeanDefinition().getBeanClassName());
// 為了擷取被掃描到的Mapper接口的代理對象
beanDefinition.setBeanClass(ArtistFactoryBean.class);
}
return holders;
}
}
// 批量注冊Bean定義
public class ArtistImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String scanPath = "com.xxxx.mapper";
// 此時要自定義包掃描器,因為Spring的包掃描器隻掃描類,不掃描接口;而這裡要掃描接口
ArtistScanner scanner = new ArtistScanner(registry);
// match方法傳回true,保證接口能被掃描到
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
scanner.scan(scanPath);
}
}
- 經過上述的修改,就可以把main方法中備援的代碼替換掉,到此還有一個最大的問題,getObject中生成代理對象是手工操作的,而代理對象應該由Mybatis架構生成,對應到文章開頭提到的SqlSessionFactory
// 修改後的FactoryBean
public class ArtistFactoryBean implements FactoryBean {
private Class<?> mapperClazz;
public ArtistFactoryBean(Class<?> mapperClazz) {
this.mapperClazz = mapperClazz;
}
@Autowired
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
this.sqlSession = sqlSessionFactory.openSession();
}
@Override
public Object getObject() throws Exception {
// Mybatis的代理邏輯
return sqlSession.getMapper(mapperClazz);
}
@Override
public Class<?> getObjectType() {
return mapperClazz;
}
}
- 可以看到,通過setter注入SqlSessionFactory對象,擷取到SqlSession
- 在配置類中也要添加SqlSessionFactory的Bean對象
@Configuration
@ComponentScan("com.mybatisandspring")
public class MyConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
}
- 繼續看,我們的掃描路徑是在代碼中寫死的,可以進一步作修改
- 自定義一個注解,把mapper接口所在的包路徑作為value,傳入注解,然後在registerBeanDefinitions方法中,通過參數AnnotationMetadata擷取該注解的value值,這樣就可以靈活地配置了。
- 首先自定義注解(這裡把配置類中的@Import移到此處,為了代碼的可讀性,這裡不影響邏輯的描述)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
String value();
}
@Configuration
@ComponentScan("com.mybatisandspring")
@ArtistMapperScan("com.mybatisandspring.mapper")
public class MyConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
}
- 到此為止,基本上mybatis-spring架構的核心原理基本都展現出來了,概括地說:就是把Mybatis生成的代理對象注入到Spring容器中,mybatis-spring.jar包起到了整合的橋梁的作用
- 下面對比自己寫的類和mybatis-spring.jar中的核心類
mybatis-spring包的整合源碼分析
// @MapperScan注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}
// 自定義掃描注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
String value();
}
- MapperScannerRegistrar的registerBeanDefinitions方法會先解析MapperScan注解,然後再注冊Bean定義
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
// MapperScannerConfigurer執行掃描邏輯
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
String defaultScope = annoAttrs.getString("defaultScope");
if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
builder.addPropertyValue("defaultScope", defaultScope);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
// 注冊Bean定義
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
- 在MapperScannerConfigurer中有一個屬性mapperFactoryBeanClass,類型是MapperFactoryBean,它的getObject方法就是傳回Mapper接口的代理對象,mapperInterface也是作為被掃描的屬性傳入
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
- 總結:
- MapperFactoryBean完成的功能是生成Mapper接口的代理對象
- MapperScannerConfigurer掃描Mapper接口
- MapperScannerRegistrar完成的功能是把掃描到的Mapper接口的代理對象轉為Bean定義,存入Spring容器
- SqlSessionFactory負責解析配置的mybatis配置檔案,包括資料源、類型别名、映射檔案的掃描路徑等等資訊