了解Spring之BeanDefinition对象

BeanDefinition

  • 首先我们需要了解BeanDefinition到底是个什么东西?
  • 了解Spring基于BeanDifination对象做了哪些实现?
  • 基于Spring是如何使用Beandifination对象来操作的?基于Mybatismapper分析。

首先我们需要了解BeanDefinition到底是个什么东西?

image.png

从IDEA的关系图上来看Beandefinition对象具有如下特点:

  • 拥有属性存储的功能[AttributeAccessor]
  • 拥有资源获取的能力,也就是读取配置资源的能力【BeanMatadataElement】
  • 对Bean对象的描述能力[BeanDefinition]

了解Spring基于BeanDifination对象做了哪些实现?

通过了解Spring的实现,能够知道这个东西的实现方式以及作者对实现模型的定位。

image.png

图中红线框柱的部分则是针对Beandifination的具体实现:

我们先从AbstractBeanDefinition对象开始了解

image.png

从上述实现方法来看,该类就是实现了BeanDefinition的所有方法。

而其他的子类:

GenericBeanDefinition : 通用的bean实现,自2.5以后新加入的bean文件配置属性定义类,是ChildBeanDefinitionRootBeanDefinition更好的替代者,

ScannedGenericBeanDefinition : 被包扫描到的bean定义

AnnotatedGenericBeanDefinition : 查找类注解初始化的定义

RootBeanDefinition : 代表一个从配置源(XMLJava Config等)中生成的BeanDefinition

ChildBeanDefinition : 可以从父BeanDefinition中集成构造方法,属性等。

基于Spring是如何使用Beandifination对象来操作的?

我们从最常用的三个类取看:

GenericBeanDefinition : 针对配置Bean的处理

ScannedGenericBeanDefinition

它的类是在ClassPathScanningCandidateComponentProvider.findCandidateComponents方法中被调用

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
    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

我们先在实例化处打个断点看看它是如何被执行的。

image.png

分析链路(只选举关键链路):

invokeBeanDefinitionRegistryPostProcessors: 执行Bean中实现了BeanDefinitionRegistryPostProcessor的类的postProcessBeanDefinitionRegistry方法,目标类可以看到是:ConfigurationClassPostProcessor

processConfigBeanDefinitions: 断点在循环遍历Bean的时候,会去判断Bean上面的注解。

如果包含有@Import@Configuration等注解,则会采用

这里可以大概知道GenericBeanDefinitionAnnotatedGenericBeanDefinitionScannedGenericBeanDefinition等类都是针对配置类型的Bean定义。

那么常用的Bean呢?

RootBeanDefinition: 普通Bean的定义

它的作用也是封装对象的信息,不过不同于配置对象,他是更为普通的Bean类型。

在创建bean的实例的同时,都是以RootBeanDefinition来做处理

AbstractBeanFactory.java

// 创建Bean的时候触发
protected <T> T doGetBean(
            final String name, final Class<T> 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

大家都知道MybatisMapper是不需要实现类的,只需要定义一个接口就行了,但是它是如何通过Spring拿到对应的实现的呢?

在Spring容器启动的时候,在解析完了配置文件之后。开始执行Spring内部拓展接口的调用其中包括了

BeanPostProcessBeanDefinitionRegistryPostProcessor.

BeanPostProcess: 针对每个bean的实例化之前和之后会触发。

BeanDefinitionRegistryPostProcessor : 发生的比上面要早,在bean还处于BeanDefinition加载完毕之后的阶段。

Mybatis就是基于这个时机通过MapperScannerConfigurer触发了BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry方法的调用。

// 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你的basePackagesqlSessionFactorysqlSessionTemplate等等属性,

  • 要扫描的包
  • 配置文件解析对象
  • SQL执行对象

有了这三个功能基本上已经具备了执行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。

以上是仅从个人观点,希望会给大家带来一些帮助。有问题请及时指正。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容