文章目錄
- 前言
- 1.概述
- 1.1重點類介紹
- 1.1.1 MapperProxyFactory
- 1.1.2 MapperFactoryBean
- 1.1.3 MapperScannerRegistrar
- 1.1.4 MapperScannerConfigurer
- 1.1.5 ClassPathMapperScanner
- 2.mybatis中如何將介面變成物件
- 3.mybatis如何將代理物件交給spring管理
- 3.1將物件交給Spring管理的方式
- 3.1.1 api手動添加
- 3.1.2 @Bean
- 3.1.3 factoryBean
- 3.2 mybatis對于spring擴展實作
- 3.2.1 @MapperScan注解
- 3.2.2 MapperScannerConfigurer
- 4.模仿mybatis實作一個自定義掃描器
- 4.1 @MyBeanScan
- 4.2 MyScannerRegistrar
- 4.3 MyPostRegistryPostProcessor
- 4.4 MyScanner
- 4.5 MyFilter
- 4.6 實驗結果
前言
上一篇博客介紹了Spring中的invokeBeanFactoryPostProcessors方法,其中涉及到了兩個介面,一是BeanFactoryPostProcessor介面,二是BeanDefinitionRegistryPostProcessor介面,詳細的內容可以參考上一篇博客《Spring IOC—invokeBeanFactoryPostProcessors原始碼分析》
而這兩個介面是spring提供的擴展點,本文將介紹關于BeanDefinitionRegistryPostProcessor擴展點的應用,
1.概述
對于BeanDefinitionRegistryPostProcessor擴展點應用的學習,可以參考mybatis是如何擴展這個介面的,這種站在巨人的肩膀上學習效率更高,
mybatis基于這個擴展點,將mapper介面交給了Spring管理,眾所周知Spring管理的是物件,那么是如何將mapper介面變成物件呢,又是通過怎樣的方式將這個物件交給spring管理的呢?如果有疑問的讀者不妨帶著問題繼續閱讀,
1.1重點類介紹
1.1.1 MapperProxyFactory
mapper代理工廠,基于介面和mapperProxy來構建一個mapper代理物件,實作了將介面轉變成一個物件,
1.1.2 MapperFactoryBean
MapperFactoryBean實作了FactoryBean,他是一個特殊的Bean物件,可以呼叫getObject()方法根據介面的不同,回傳不同的介面代理物件,這個物件也就是通過MapperProxyFactory產生的,
1.1.3 MapperScannerRegistrar
用來將MapperScannerConfigurer注冊到BeanDefinitionMap中,具體的執行時機后續會介紹,
1.1.4 MapperScannerConfigurer
實作了BeanDefinitionRegistryPostProcessor介面,這是基于spring的擴展,在Spring的內置掃描器掃描結束后觸發這個類的邏輯,執行postProcessBeanDefinitionRegistry方法,構建一個mybatis的掃描器來執行掃描mapper介面,
1.1.5 ClassPathMapperScanner
mybatis實作的一個掃描器,繼承了spring內置的掃描器ClassPathBeanDefinitionScanner,實作了將mapper.class到BeanDefinition的轉換,
2.mybatis中如何將介面變成物件
先來看一段代碼
Environment environment = new Environment("development", null, null);
Configuration configuration = new Configuration(environment);
configuration.addMapper(TestMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
mapper.query();
這段代碼大家肯定很熟悉了,而重點就在于sqlSession.getMapper(TestMapper.class);這一句上,debug除錯,一直執行下去,可以看到是使用了JDK的動態代理,基于傳入的介面,生成一個代理物件,代理物件執行的方法都會去執行mapperProxy中的invoke方法,具體做了什么這里不討論了,
//MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
第一個疑問就這樣解決了,mybatis使用基于介面的動態代理生成代理物件,
3.mybatis如何將代理物件交給spring管理
不妨先來看看有哪幾種方式可以將一個物件交給spring來管理
3.1將物件交給Spring管理的方式
3.1.1 api手動添加
ac.getBeanFactory().registerSingleton("beanName",new Object());
3.1.2 @Bean
@Bean
public Object object(){
return new Object();
}
3.1.3 factoryBean
factoryBean 他是一個特殊Bean,必須實作介面FactoryBean,當前這個Bean還能產生一個Bean,將這個MyFactoryBean類交給spring管理,也就可以將一個物件A交給了Spring來進行管理了,
mybatis就是使用的這種方式,
class MyFactoryBean implements FactoryBean{
@Override
public Object getObject() throws Exception {
return new A();
}
@Override
public Class<?> getObjectType() {
return A.class;
}
}
3.2 mybatis對于spring擴展實作
需要做哪些事情呢?
首先需要掃描指定包下面的介面,將這些介面生成代理物件,再通過FactoryBean將這些代理物件交給Spring管理,
3.2.1 @MapperScan注解

這個注解的主要作用是將MapperScannerConfigurer放到BeanDefinitionMap當中,
那么他是如何作業的呢?再上一篇博客中提到了spring中的內置類ConfigurationClassPostProcessor,這個類的作用是掃描帶spring注解的類添加到bdmap中,并且決議配置類上的@Import標簽,若這個標簽中的類實作了ImportBeanDefinitionRegistrar介面,會將這這個類實體化后放入到 importBeanDefinitionRegistrars 快取當中,掃描完成后會執行這個集合當中的物件的registerBeanDefinitions方法,
mybatis中MapperScannerRegistrar這個方法的作用就是將MapperScannerConfigurer注冊到BeanDefinitionMap當中,
//MapperScannerRegistrar
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// ,,,,忽略不重要的代碼
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
3.2.2 MapperScannerConfigurer
MapperScannerConfigurer實作了BeanDefinitionRegistryPostProcessor介面,即對spring進行擴展
還記得上一篇博客中這些后置處理器的執行順序嘛?
根據3.2.1MapperScannerConfigurer是在spring內置的后置處理器執行完畢后就被注冊到了bdMap中,而之后會去執行實作了BeanDefinitionRegistryPostProcessor介面和Ordered介面的后置處理器,然后會執行實作了BeanDefinitionRegistryPostProcessor的后置處理器,
所以會去執行MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法,如下
主要的邏輯就是初始化一個mybatis中實作的掃描器ClassPathMapperScanner,然后用這個掃描器執行掃描作業,即執行scan方法,
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);//mybatis中的掃描器
//下面的一系列set方法是對這個掃描器進行初始化
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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();//注冊一些掃描用的過濾器
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));//掃描basePackage包
}
ClassPathMapperScanner并沒有重寫父類的scan方法,所以這里呼叫的是父類的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);
}
重點在執行doScan方法,該方法由ClassPathMapperScanner重寫了,
@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);//將這些介面變成FactoryBean
}
return beanDefinitions;
}
首先會去呼叫父類的doScan方法來執行掃描,
遍歷包名
<1>處執行findCandidateComponents方法,根據包名去掃描符合條件的類,其中會呼叫到scanCandidateComponents(basePackage)方法,這個方法會根據包名得到這個包下的所有的檔案資源,然后會遍歷這些資源將所有的介面添加到候選集合中回傳,
<2>處遍歷找到的候選bd
<3>處設定一些默認的屬性值
<4>處看是否有注解,處理這些注解,例如@Lazy,@Primary、@DependsOn等
<5>檢查beanName是否沖突,沒有沖突的話構建成bdHolder,保存在一個set集合中并注冊bd ,
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {//遍歷包名
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);//<1>尋找符合條件的候選BeanDefinition
for (BeanDefinition candidate : candidates) {//<2>遍歷找到的候選bd
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());//設定Scope
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);//<3>設定一些默認的屬性值
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);//<4>看是否有注解,處理這些注解,例如@Lazy,@Primary、@DependsOn等
}
if (checkCandidate(beanName, candidate)) {//<5>檢查beanName是否沖突,沒有沖突的話構建成bdHolder,保存在一個set集合中并注冊bd ,
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
方法結束后回傳結果到ClassPathMapperScanner中的doScan繼續執行
此時得到的bd并不是所要的,不能對其進行實體化,因為他所包含的類還是個介面,介面是不能被實體化的,需要將其轉變成一個FactoryBean,
執行 processBeanDefinitions(beanDefinitions)
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
這個方法會設定建構式的引數為這個介面,然后將beanClass修改為MapperFactoryBean,之后實體化的時候會呼叫這個FactoryBean的getObject()方法來構建一個代理Mapper物件,這就是如何將map介面轉變成一個物件交給Spring管理的關鍵,
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 添加建構式需要的引數
// 上面這一行代碼可以使用下面的代碼替代
// MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
// 第一個引數 是set方法去掉set 后首字母小寫的名字, setMapperInterface 去掉set M改成小寫 得 mapperInterface
// propertyValues.add("mapperInterface",aClass);
definition.setBeanClass(this.mapperFactoryBeanClass);//設定型別為mapperFactoryBeanClass,之后的實體化會呼叫MapperFactoryBean中的getObject()來構建出一個基于介面的代理物件交給spring管理,
//省略非重要代碼
}
}
4.模仿mybatis實作一個自定義掃描器
通過對mybatis中對spring的擴展的學習,試著模仿一下實作使用自定義的注解,使得添加了該注解的類可以交給Spring來管理,
首先先來總結一下mybatis的如何實作的
-
@MapperScan
繼承了Import注解,MapperScannerRegistrar實作了ImportBeanDefinitionRegistrar介面,將MapperScannerConfigurer后置處理器注冊到BeanDefinitionMap中,執行時機詳見3.2.1,
-
MapperScannerConfigurer
這是一個Spring的擴展,實作了BeanDefinitionRegistryPostProcessor介面,主要的作用就是構建一個掃描器執行掃描,
-
ClassPathMapperScanner
mybatis實作的掃描器,繼承了Spring中的ClassPathBeanDefinitionScanner,實作屬于自己掃描的邏輯,
根據以上的總結可以模仿著來實作一套屬于自己的掃描規則,
4.1 @MyBeanScan
模仿@MapperScan,繼承了@Import標簽

該注解用于添加在配置類上,Spring內置的掃描器在完成了掃描之后會決議配置類上的Import標簽,將實作了ImportBeanDefinitionRegistrar的類實體化并添加到importBeanDefinitionRegistrars 這個map當中,之后會執行這個map中物件的registerBeanDefinitions方法,

4.2 MyScannerRegistrar
模仿MapperScannerRegistrar,實作了ImportBeanDefinitionRegistrar介面

主要邏輯就是將一個實作了BeanDefinitionRegistryPostProcessor介面的后置處理器注冊到BeanDefinitionMap當中,
4.3 MyPostRegistryPostProcessor
模仿MapperScannerConfigurer,實作了BeanDefinitionRegistryPostProcessor介面,

Spring會執行這個類中的postProcessBeanDefinitionRegistry方法,這個方法主要邏輯是構建一個自定義的掃描器,配置一些過濾規則,然后執行掃描,這里掃描的是com.gongsenlin.custom包下的類,
4.4 MyScanner
自定義的掃描器,繼承了Spring的掃描器,可以對里面的一些方法進行重寫,

4.5 MyFilter
在掃描器執行掃描的時候,會根據過濾器來過濾掉一些不符合條件的類,
自定義的過濾器,說明一些過濾規則,這里實作的是,類上注解包含了@MyBean的回傳true,就是說留下包含注解@MyBean的類,過濾掉不符合的,

4.6 實驗結果
com.gongsenlin.custom包下現在有兩個類,一個X和一個Y,Y上添加了MyBean注解,


撰寫測驗類,啟動Spring容器,

可以看到添加了MyBean注解的類被添加到了BeanDefinitionMap當中,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/60348.html
標籤:其他
上一篇:深度學習系列(0)——TensorFlow2.0極簡安裝(親測有效)
下一篇:使用IDEA搭建ssm框架
