spring源码阅读2-2——bean的管理

我们在《spring源码阅读2-1——bean的管理》中,摸清了BeanFactory家族的整体框架和功能概览,本文将继续剖析spring如何将BeanFactory一点一点实现的。

首先是上次已经获得的BeanFactory家族类图,本人在这上方加了备注,希望能让大家看的更为清晰。


BeanFactory家族类图
SimpleAliasRegistry

这个类实现了AliasRegistry接口

alias注册管理

首先是registerAlias()方法的实现:
为了方便读者阅读,加入了中文注释

源码:
    private final Map<String, String> aliasMap = new ConcurrentHashMap<String, String>(16);

    @Override
    public void registerAlias(String name, String alias) {
        Assert.hasText(name, "'name' must not be empty");    //参数校验
        Assert.hasText(alias, "'alias' must not be empty");
        if (alias.equals(name)) {    //保障了不存在与正名相同的别名
            this.aliasMap.remove(alias);
        }
        else {
            if (!allowAliasOverriding()) {  //是否开启覆盖注册,默认为true
                String registeredName = this.aliasMap.get(alias);
                if (registeredName != null && !registeredName.equals(name)) {
                    throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
                            name + "': It is already registered for name '" + registeredName + "'.");
                }
            }
            checkForAliasCircle(name, alias);  //确保添加的没有name和alias值相反的数据且alias和name不相等
            this.aliasMap.put(alias, name);
        }
    }

---------------------------- 方法封装 ------------------------------------------------------

    public String canonicalName(String name) {
        String canonicalName = name;
        // Handle aliasing...
        String resolvedName;
        do {
            resolvedName = this.aliasMap.get(canonicalName);
            if (resolvedName != null) {
                canonicalName = resolvedName;
            }
        }
        while (resolvedName != null);
        return canonicalName;
    }

    protected void checkForAliasCircle(String name, String alias) {
        if (alias.equals(canonicalName(name))) {
            throw new IllegalStateException("Cannot register alias '" + alias +
                    "' for name '" + name + "': Circular reference - '" +
                    name + "' is a direct or indirect alias for '" + alias + "' already");
        }
    }

解读:
第一句是声明了一个线程安全的散列表对象(ConcurrentHashMap)来作为缓存(如果对线程安全和非线程安全的集合类不太清楚,建议可以系统地去学习)来存放注册的alias。
aliasMap以alias为键,name为值,其中alias为别名,而name是正名,如下图所示。

alias散列表

从代码中我们看到我们可以为把alias当做name来再次注册别名,举个例子,叫"张三"的人有两个外号:"小张"和"小三",那我可以为小三注册别名,叫"三哥",即name="小三", alias="三哥"(当然name="张三"也是可以的)。但是不能注册name和alias正好相反的数据,以及name和alias相等的数据。为什么这么设计呢?后面会给出解释。

源码:
    @Override
    public void removeAlias(String alias) {
        String name = this.aliasMap.remove(alias);    
        if (name == null) {
            throw new IllegalStateException("No alias '" + alias + "' registered");
        }
    }

    @Override
    public boolean isAlias(String name) {
        return this.aliasMap.containsKey(name);
    }

    @Override
    public String[] getAliases(String name) {
        List<String> result = new ArrayList<String>();
        synchronized (this.aliasMap) {
            retrieveAliases(name, result);
        }
        return StringUtils.toStringArray(result);
    }

    //遍历散列表,获取name为定值的所有alias
    private void retrieveAliases(String name, List<String> result) {
        for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
            String registeredName = entry.getValue();
            if (registeredName.equals(name)) {
                String alias = entry.getKey();
                result.add(alias);
                retrieveAliases(alias, result);  //注意是有回调的哦
            }
        }
    }

解读:
removeAlias()isAlias()就不说了,根据name来获取alias的方法getAliases(String name),由于散列表是以alias为键,而name为值,要想找出满足条件的所有alias值,就必须对散列表进行遍历,找出所有name和给定值相等的值。
我在注释中注明了是有回调函数存在的,当找到name值和给定值相等是,会把这个alias作为name,再次对散列表进行遍历,如下图所示

回调的效果

返回值中有"三哥",而三哥对应的name是"小三"。这也是在注册时限制注册的数据name和alias不能正好相反且name和alias不能相等的原因,否则回调函数将陷入无限循环中去。
因此,在设计阶段和编程过程中,如果涉及回调,就要规避无限循环的可能。
至此,我们已经阅读完了这个类的所有方法(还有一个resolveAliases()入参是一个较为复杂的对象,暂时不解释,等下回碰到了再细究)

DefautlSingletonBeanRegistry

该类继承了SimpleAliasRegistry,并且实现SingletonBeanRegistry接口。
同样的,来看下是如何实现registerSingleton(String beanName, Object singletonObject)方法:

源码:
    protected static final Object NULL_OBJECT = new Object();

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

    private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);

    @Override
    public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            Object oldObject = this.singletonObjects.get(beanName);
            if (oldObject != null) {
                throw new IllegalStateException("Could not register object [" + singletonObject +
                        "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
            }
            addSingleton(beanName, singletonObject);
        }
    }

    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }

解读:
同SimpleAliasRegistry,这里也是用散列表来做缓存。但是SingleBean的注册要来的复杂,因为bean还涉及到初始化的问题,因此这里有多个缓存用的对象:

  • singletonObjects是一个线程安全的散列表,用于存放已注册的SingleBean实例
  • singletonFactories用来存放初始化SingleBean实例的factory对象
  • earlySingletonObjects 用来存放已存在但是未注册的SingleBean实例
  • registeredSingletons是一个集合,存放的是已注册的SingleBean对象的名称

从代码中可以看到,当一个Bean被注册后,就会将这个Bean实例添加到缓存中去,如果这个实例存在对应的BeanFactory,则BeanFactory将被移除,或者这个Bean存在于earlySingletonObjects中,也将从earlySingletonObjects中移除。
这个过程可能不太好理解,举个例子来说明吧:
有a,b,c三个学生要在大学注册,a社会自考招生,没有学籍;b是高中FactoryB的学生;c是该大学的留级生,学籍已存在。b注册完成后,要将学籍档案转移到大学中,c也要将学籍档案转移到当前年级的档案库中去(如下图所示)

Bean注册图解
    private final Set<String> singletonsCurrentlyInCreation =
            Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
    @Override
    public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);
    }

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

    public boolean isSingletonCurrentlyInCreation(String beanName) {
        return this.singletonsCurrentlyInCreation.contains(beanName);
    }

解读:
这段逻辑比较清晰了,还是那上面那个例子来讲,要获得一个学生的学籍档案,首先直接从该年级的学籍档案中查看,如果没有,这个学生是否为该入学注册的学生(isSingletonCurrentlyIncreation()方法判断),如果是,就依次查找earlySingletonObjects和singletonFactories对象。
再看源码的过程中,会有很多疑惑,比如这里为什么要设置这么多的缓存对象,想alias那样注册不就好了,为什么要那么麻烦呢?但是,要沉住气,坚持下去,最终会让你豁然开朗。这也许是源码阅读的魅力所在吧

源码:
    @Override
    public boolean containsSingleton(String beanName) {
        return this.singletonObjects.containsKey(beanName);
    }

    @Override
    public String[] getSingletonNames() {
        synchronized (this.singletonObjects) {
            return StringUtils.toStringArray(this.registeredSingletons);
        }
    }

    @Override
    public int getSingletonCount() {
        synchronized (this.singletonObjects) {
            return this.registeredSingletons.size();
        }
    }

这几个方法的重写就不说了,和alias的注册管理是一样的。

源码:
    private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<String, Set<String>>(64);

    private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<String, Set<String>>(64);

    public void registerDependentBean(String beanName, String dependentBeanName) {
        // A quick check for an existing entry upfront, avoiding synchronization...
        String canonicalName = canonicalName(beanName);
        Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
        if (dependentBeans != null && dependentBeans.contains(dependentBeanName)) {
            return;
        }

        // No entry yet -> fully synchronized manipulation of the dependentBeans Set
        synchronized (this.dependentBeanMap) {
            dependentBeans = this.dependentBeanMap.get(canonicalName);
            if (dependentBeans == null) {
                dependentBeans = new LinkedHashSet<String>(8);
                this.dependentBeanMap.put(canonicalName, dependentBeans);
            }
            dependentBeans.add(dependentBeanName);
        }
        synchronized (this.dependenciesForBeanMap) {
            Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(dependentBeanName);
            if (dependenciesForBean == null) {
                dependenciesForBean = new LinkedHashSet<String>(8);
                this.dependenciesForBeanMap.put(dependentBeanName, dependenciesForBean);
            }
            dependenciesForBean.add(canonicalName);
        }
    }

    protected boolean isDependent(String beanName, String dependentBeanName) {
        String canonicalName = canonicalName(beanName);
        Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
        if (dependentBeans == null) {
            return false;
        }
        if (dependentBeans.contains(dependentBeanName)) {
            return true;
        }
        for (String transitiveDependency : dependentBeans) {
            if (isDependent(transitiveDependency, dependentBeanName)) {
                return true;
            }
        }
        return false;
    }

    protected boolean hasDependentBean(String beanName) {
        return this.dependentBeanMap.containsKey(beanName);
    }

    public String[] getDependentBeans(String beanName) {
        Set<String> dependentBeans = this.dependentBeanMap.get(beanName);
        if (dependentBeans == null) {
            return new String[0];
        }
        return StringUtils.toStringArray(dependentBeans);
    }

解读:
DefaultSingletonBeanRegistry不仅实现了SingleBeanRegistry中的对SingleBean注册管理的方法,还扩展了对于SingleBean的依赖关系进行管理。
缓存对象:

  • dependentBeanMap 存储bean所依赖的其他bean组件
  • dependenciesForBeanMap 存储当前bean被哪些其他bean组件依赖

方法:

  • registerDependentBean() ==> 注册bean所依赖的dependentBean
  • isDependent() ==> 返回denpendentBean是否为bean的依赖bean
  • hasDenpendentBean() ==> 返回bean是否有dependentBean
  • getDependentBeans() ==> 返回bean的所有dependentBean
  • destroyBean() ==> 删除一个bean,删除其依赖信息
总结
总结.png
本文到这就要结束了,回顾下
    本文主要对Factory家族中的SimpleAliasRegistry和DefaultSingletonBeanRegistry进行分析。
其主要通过线程安全的散列表做缓存,实现了alias管理、SingleBean管理以及Bean依赖的管理的功能。

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

推荐阅读更多精彩内容