BeanDefinition
到底是个什么东西?BeanDifination
对象做了哪些实现?Beandifination
对象来操作的?基于Mybatis
的mapper
分析。BeanDefinition
到底是个什么东西?从IDEA的关系图上来看Beandefinition
对象具有如下特点:
BeanDifination
对象做了哪些实现?通过了解Spring的实现,能够知道这个东西的实现方式以及作者对实现模型的定位。
图中红线框柱的部分则是针对Beandifination
的具体实现:
我们先从AbstractBeanDefinition
对象开始了解
从上述实现方法来看,该类就是实现了BeanDefinition
的所有方法。
而其他的子类:
GenericBeanDefinition
: 通用的bean实现,自2.5以后新加入的bean文件配置属性定义类,是ChildBeanDefinition
和RootBeanDefinition
更好的替代者,
ScannedGenericBeanDefinition
: 被包扫描到的bean定义
AnnotatedGenericBeanDefinition
: 查找类注解初始化的定义
RootBeanDefinition
: 代表一个从配置源(XML
,Java Config
等)中生成的BeanDefinition
ChildBeanDefinition
: 可以从父BeanDefinition
中集成构造方法,属性等。
Beandifination
对象来操作的?我们从最常用的三个类取看:
GenericBeanDefinition
: 针对配置Bean的处理
ScannedGenericBeanDefinition
它的类是在ClassPathScanningCandidateComponentProvider.findCandidateComponents
方法中被调用
public Set findCandidateComponents(String basePackage) {
Set candidates = new LinkedHashSet();
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
}
}
// 部分代码省略
return candidates;
}
这是统一的入口,看这个方法名就可以知道,通过定义填写了basePackage
的相关注解,最终都会经过方法去查找,并且会包装成ScannedGenericBeanDefinition
对象。
AnnotatedGenericBeanDefinition
我们先在实例化处打个断点看看它是如何被执行的。
分析链路(只选举关键链路):
invokeBeanDefinitionRegistryPostProcessors: 执行Bean中实现了BeanDefinitionRegistryPostProcessor
的类的postProcessBeanDefinitionRegistry
方法,目标类可以看到是:ConfigurationClassPostProcessor
processConfigBeanDefinitions: 断点在循环遍历Bean的时候,会去判断Bean上面的注解。
如果包含有@Import
、@Configuration
等注解,则会采用
这里可以大概知道GenericBeanDefinition
、AnnotatedGenericBeanDefinition
、ScannedGenericBeanDefinition
等类都是针对配置类型的Bean定义。
那么常用的Bean呢?
RootBeanDefinition
: 普通Bean的定义
它的作用也是封装对象的信息,不过不同于配置对象,他是更为普通的Bean类型。
在创建bean的实例的同时,都是以RootBeanDefinition
来做处理
AbstractBeanFactory.java
// 创建Bean的时候触发
protected T doGetBean(
final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// 部分省略
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
}
// Create bean instance.
if (mbd.isSingleton()) {
//创建单例工厂
}
else if (mbd.isPrototype()) {
// 是否原型
} else {
// 获取scope
String scopeName = mbd.getScope();
}
return (T) bean;
}
其实我的理解就是描述了Bean的对象,方便在实例化的时候做一些特殊操作。比如@Autowired等注解。
BeanDefinition
也有对应的拓展点: BeanDefinitionRegistryPostProcessor
举个常用的案例:Mybatis
大家都知道Mybatis
的Mapper
是不需要实现类的,只需要定义一个接口就行了,但是它是如何通过Spring拿到对应的实现的呢?
在Spring容器启动的时候,在解析完了配置文件之后。开始执行Spring内部拓展接口的调用其中包括了
BeanPostProcess
、BeanDefinitionRegistryPostProcessor
.
BeanPostProcess: 针对每个bean的实例化之前和之后会触发。
BeanDefinitionRegistryPostProcessor : 发生的比上面要早,在bean还处于BeanDefinition
加载完毕之后的阶段。
Mybatis就是基于这个时机通过MapperScannerConfigurer
触发了BeanDefinitionRegistryPostProcessor
的postProcessBeanDefinitionRegistry
方法的调用。
// MapperScannerConfigurer
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();
// 扫描包 , 会触发下面的doScan方法
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
这个时候需要告诉Mybatis你的basePackage
、sqlSessionFactory
、sqlSessionTemplate
等等属性,
有了这三个功能基本上已经具备了执行SQL的条件。
这时候MapperScannerConfigurer需要为扫描包下面的接口指定一个具体实现MapperFactoryBean
。不然这个构建的BeanDefinition是没有用的。
这里在ClassPathMapperScanner的doScan中体现
// 这里会在上面的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();
// 为对象属性赋值
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));
return beanDefinitions;
}
指定了具体实现之后将上面三个关键属性交给MapperFactoryBean
管理。
这个时候大概明白了,所有的Mapper的具体实现都是MapperFactoryBean。
而MapperFactoryBean
也具备了执行SQL的条件。
后面都是些细节的执行过程就不细究了,比如通过接口的包+类名+方法名和xml中的对象相对应得到具体的执行SQL。
以上是仅从个人观点,希望会给大家带来一些帮助。有问题请及时指正。