当前位置: 移动技术网 > IT编程>开发语言>Java > Spring Framework 组件注册 之 FactoryBean

Spring Framework 组件注册 之 FactoryBean

2019年07月04日  | 移动技术网IT编程  | 我要评论

spring framework 组件注册 之 factorybean

前言

前两篇文章介绍了如何使用@component@import注解来向spring容器中注册组件(javabean),本文将介绍通过factorybean接口继续向spring容器中注册组件。可能第一印象是spring中beanfactory接口,但是这里确实说的是factorybean

推荐阅读

factorybean 与 beanfactory

根据接口名称,我们也可以简单看出两者的区别

  • factorybean:它是spring中的一个bean,只不过它是一个特殊的bean(工厂bean),我们可以通过它来自定义生产需要的普通javabean
  • beanfactory:它是spring的bean工厂,是spring最为重要的接口之一,spring通过此接口获取,管理容器中的各个bean

接下来将进入本文正题,如何通过factorybean接口向spring容器中注册组件

factorybean简单使用

正如前面说的,factorybean也是spring中的一个bean,但是它又是一个特殊的bean,它的存在是为了生产其他的javabean。首先我们看看factorybean自身的接口定义

public interface factorybean<t> {
    /**
     * 从spring容器中获取bean时会调用此方法,返回一个t对象
     */
    @nullable
    t getobject() throws exception;

    /**
     * 此工厂bean返回对象的类型
     */
    @nullable
    class<?> getobjecttype();

    /**
     * 工厂bean创建的对象是否为单例,
         * 如果返回false,说明getobject方法的实例对象不是单例的,
     * spring每次从容器中获取t对象时,都调用getobject方法创建一个对象
     */
    default boolean issingleton() {
        //spring 5 接口默认返回true(单例)
        return true;
    }
}

factorybean接口定义简单明了,就是用来获取一个bean的基本信息,下面我们自己实现该接口,来生产一个javabean

/**
 * 产生 bike 对象的工厂bean
 */
@component
public class bikefactorybean implements factorybean<bike> {

    public bike getobject() throws exception {
        system.out.println("......开始创建bike对象......");
        return new bike();
    }

    public class<?> getobjecttype() {
        return bike.class;
    }

    public boolean issingleton() {
        return true;
    }
}

自定义的一个javabean类

/**
 * 待注册的自定义组件
 */
@data
public class bike {
    private string id = "by factorybean";
}

添加spring容器启动的引导类

/**
 * spring 容器启动引导类,测试 factorybean 功能
 */
@componentscan("com.spring.study.ioc.factorybean")
public class testfactorybeanbootstrap {

    public static void main(string[] args) {
        annotationconfigapplicationcontext applicationcontext =
                new annotationconfigapplicationcontext(testfactorybeanbootstrap.class);
        //获取工厂bean本身的id
        string[] beannames = applicationcontext.getbeannamesfortype(bikefactorybean.class);
        system.out.println("bikefactorybean names:" + arrays.aslist(beannames));
        //获取工厂bean产生的bean的id
        beannames = applicationcontext.getbeannamesfortype(bike.class);
        system.out.println("bike bean names:" + arrays.aslist(beannames));
        object bean = applicationcontext.getbean("bikefactorybean");
        system.out.println(bean);
        bean = applicationcontext.getbean(bike.class);
        system.out.println(bean);
        // 获取工厂bean 本身的实例对象
        bean = applicationcontext.getbean(beanfactory.factory_bean_prefix + "bikefactorybean");
        system.out.println(bean);
        applicationcontext.close();
    }
}

启动spring容器,控制台打印结果:

bikefactorybean names:[&bikefactorybean]
bike bean names:[bikefactorybean]
......开始创建bike对象......
bike(id=by factorybean)
bike(id=by factorybean)
com.spring.study.ioc.factorybean.bikefactorybean@4eb7f003

由结果可以看出

  • 虽然代码中只在bikefactorybean类上加了@component注解,但是从spring容器仍然可以获取到bike类的信息
  • 工厂bean的id与实际产生的bean的id仅差了一个&符,也就是说,工厂bean定义的id实际为getobject()方法返回bean的id,而工厂bean本身的id被添加了一个前缀&
  • 工厂bean的issingleton()方法返回了true,所以通过spring容器多次获取实际的bean时,getobject()方法也是执行一次
  • 根据工厂bean的id可以看出,要想从spring容器中获取工厂bean本身,则需要在注册的id前面添加一个&符,而此前缀在beanfactory接口中已经定义了factory_bean_prefix

如果将bikefactorybeanissingleton()方法返回了false

 public boolean issingleton() {
        return false;
 }

重新启动spring容器,可以看如下结果:

bikefactorybean names:[&bikefactorybean]
bike bean names:[bikefactorybean]
......开始创建bike对象......
bike(id=by factorybean)
......开始创建bike对象......
bike(id=by factorybean)
com.spring.study.ioc.factorybean.bikefactorybean@4eb7f003

由结果可以看出,唯一的变化在于从spring容器中多次获取实际bean时,工厂bean的getobject()方法被多次进行了调用。这与spring容器中被标识为原型的普通bean相同,每次从spring中获取bean时都会被实例化。

factorybean 执行过程

要想了解factorybean的执行过程,就需要结合spring容器启动的过程来进行分析。而spring容器的启动过程经过了纷繁复杂的步骤。为了尽可能少的入坑和挖坑,下面仅结合factorybean相关的源码进行说明。

在开始入坑之旅之前,结合前面的例子做几点说明,方便后续讲解

前面定义的 bikefactorybean 类上面直接添加了@component注解,这样spring会默认以类名首字母小写(bikefactorybean)作为beanname;如果使用@bean进行注册时,spring默认会以方法名作为beanname,下面继续以“bikefactorybean”为例。

  1. spring容器启动过程中,在执行完所有的beanfactorypostprocessorbeanpostprocessor以及注册listener后会执行org.springframework.context.support.abstractapplicationcontext#finishbeanfactoryinitialization()方法,此方法会对剩下所有的非abstract非lazyinit的单实例bean进行实例化,以下为部分代码片段。
@override
public void preinstantiatesingletons() throws beansexception {
        ...省略代码...
    // 1. 拷贝一份副本:spring容器中的所有的bean名称
    list<string> beannames = new arraylist<>(this.beandefinitionnames);
        // 2. 遍历每一个beanname,尝试通过getbean()方法进行实例化
        // 在getbean()方法内部会先尝试从容器singletonobjects中获取bean,如果没有才会进行实例化操作
        for (string beanname : beannames) {
            // 3. 通过beanname获取bean定义信息 beandefinition
            rootbeandefinition bd = getmergedlocalbeandefinition(beanname);
        // 4. 根据beandefinition判断该bean是否不是抽象的,单例的,非懒加载的
            if (!bd.isabstract() && bd.issingleton() && !bd.islazyinit()) {
        // 5. 满足上面的条件后,在根据beanname判断此bean是否是一个工厂bean(实现了factorybean接口)
                if (isfactorybean(beanname)) {
            // 6. 如果是一个工厂bean,则在此处进行工厂bean本身的实例化
                    object bean = getbean(factory_bean_prefix + beanname);
            ...省略代码...
                }
                else {
            // 如果不是工厂bean,也是调用getbean()方法进行实例化
                    getbean(beanname);
                }
            }
        }
        ...省略代码...
    }

preinstantiatesingletons()是在finishbeanfactoryinitialization()方法内部调用的

根据上面的代码流程,在第6步时,会对bikefactorybean类本身进行实例化,并且可以看出传递的beanname为初始注册的name前添加了&符前缀即&bikefactorybean,用于在genbean()方法内部标识它是一个工厂bean。但是在跟踪源码后发现,在getbean()方法内部,会先将传入的beanname(&bikefactorybean)开头的&符去除,并且最终实例化bean后,在容器中保存的beanname还是不带&符前缀的名称即bikefactorybean

  1. 根据第一步的结果,spring容器在启动后,工厂bean会像普通bean一样在spring容器中会保留一条自身的单实例bean(spring容器中保存的数据为:<bikefactorybean, bikefactorybean>),既然spring容器中只保存了bikefactorybean本身,那么后续获取bike类的beanname和bean实例时,又是怎么获取到的呢?带着疑问,我们继续看后面的代码。首先,上面的例子中调用了applicationcontext.getbeannamesfortype(bike.class)方法来获取bike类的beanname。所以继续跟踪此方法看看到底发生了什么。
    // getbeannamesfortype()方法内部最终调用了此方法,可断点跟踪至此
    private string[] dogetbeannamesfortype(resolvabletype type, boolean includenonsingletons, boolean alloweagerinit) {
        // 用来保存符合条件的结果(beanname集合)
        list<string> result = new arraylist<>();
        // 与上面的代码相似,遍历spring容器中注册的所有的beannames
        for (string beanname : this.beandefinitionnames) {
            if (!isalias(beanname)) {
                try {
                    // 根据beanname获取bean的定义信息 beandefinition
                    rootbeandefinition mbd = getmergedlocalbeandefinition(beanname);
                    // 根据beandefinition 进行检查
                    if (!mbd.isabstract() && (alloweagerinit ||
                            (mbd.hasbeanclass() || !mbd.islazyinit() || isalloweagerclassloading()) &&
                                    !requireseagerinitfortype(mbd.getfactorybeanname()))) {
                        // 根据beanname和bean的定义信息判断是否是工厂bean
                        boolean isfactorybean = isfactorybean(beanname, mbd);
                        beandefinitionholder dbd = mbd.getdecorateddefinition();
                        boolean matchfound =
                                (alloweagerinit || !isfactorybean ||
                                        (dbd != null && !mbd.islazyinit()) || containssingleton(beanname)) &&
                                        (includenonsingletons ||
                                                (dbd != null ? mbd.issingleton() : issingleton(beanname))) &&
                                        // 根据bean的定义信息判断完后,在此方法中判断此beanname对应的bean实例是否与传入的类型相匹配
                                        istypematch(beanname, type);
                        // 如果根据beanname获得的是一个工厂bean,并且与传入的类型不匹配,则满足条件,将beanname添加 & 符前缀
                        if (!matchfound && isfactorybean) {
                            // 对于工厂bean,接下来尝试匹配工厂bean实例本身
                            beanname = factory_bean_prefix + beanname;
                            matchfound = (includenonsingletons || mbd.issingleton()) && istypematch(beanname, type);
                        }
                        // 如果获取的bean实例与传入的类型匹配,将beanname添加到结果集合中
                        if (matchfound) {
                            result.add(beanname);
                        }
                    }
                }
                catch (cannotloadbeanclassexception ex) {
                    // ...省略代码...
                }
                catch (beandefinitionstoreexception ex) {
                    // ...省略代码...
                }
            }
        }
        // ...省略代码...

        return stringutils.tostringarray(result);
    }

istypematch()方法中的部分代码

public boolean istypematch(string name, resolvabletype typetomatch) throws nosuchbeandefinitionexception {
        // 对beanname进行处理,将开头的 & 符过滤
        string beanname = transformedbeanname(name);
        // 从spring容器中获取单实例bean,由于spring容器启动时已经将单实例bean进行了实例化,
        // 所以此时可以直接在容器中得到bean实例
        object beaninstance = getsingleton(beanname, false);
        if (beaninstance != null && beaninstance.getclass() != nullbean.class) {
            // 获取到bean实例后,判断是否为工厂bean
            if (beaninstance instanceof factorybean) {
                // 如果是工厂bean,并且获取的beanname不是以&符开头
                if (!beanfactoryutils.isfactorydereference(name)) {
                    // 将实例强转为 factorybean 并调用 factorybean接口的getobjecttype()方法,
                    // 获取工厂bean所生产的实例类型
                    class<?> type = gettypeforfactorybean((factorybean<?>) beaninstance);
                    // 判断工厂bean生产的实例类型与传入的类型是否匹配
                    return (type != null && typetomatch.isassignablefrom(type));
                }
                else {
                    return typetomatch.isinstance(beaninstance);
                }
            }
            // ...省略代码...
        }
        // ...省略代码...
    }

gettypeforfactorybean()方法中的代码

protected class<?> gettypeforfactorybean(final factorybean<?> factorybean) {
        try {
            if (system.getsecuritymanager() != null) {
                return accesscontroller.doprivileged((privilegedaction<class<?>>)
                        factorybean::getobjecttype, getaccesscontrolcontext());
            }
            else {
                // 直接调用 factorybean 接口的 getobjecttype()方法,获取生产的类型
                return factorybean.getobjecttype();
            }
        }
        catch (throwable ex) {
            // thrown from the factorybean's getobjecttype implementation.
            logger.info("factorybean threw exception from getobjecttype, despite the contract saying " +
                    "that it should return null if the type of its object cannot be determined yet", ex);
            return null;
        }
    }

以上便是getbeannamesfortype()方法经过的部分重要代码

由此可以看出,当我们想要获取bikefactorybean本身的beanname时,dogetbeannamesfortype方法内部将bikefactorybean前添加了&符前缀,于是便获取到了&bikefactorybean
当我们想要获取bike类型的beanname时,spring会通过容器遍历已经注册的所有的beannames,然后根据beanname及对应的bean定义信息beandefinition进行判断过滤,并且对于所有的工厂bean,会获取spring容器中已经实例化的bean对象,调用 factorybean 接口的 getobjecttype()方法,得到工厂bean所生产的实例类型,然后与bike.class相比较,如果匹配,则将此beanname保存到结果集中,最后返回。所以,当我们想要获取bike类型的beanname时,从spring容器中便可以找到bikefactorybean

  1. 从spring容器中获取到beanname后,我们继续获取bike实例
    从前文中引导类的代码可以看出,获取bike实例有两种方式,跟踪源码可以发现,根据bike类型获取实例时,spring实际是通过第二步获取到beanname后再最终调用dogetbean方法获取实例对象。下面看看部分源码
protected <t> t dogetbean(final string name, @nullable final class<t> requiredtype,
                              @nullable final object[] args, boolean typecheckonly) throws beansexception {
        // 对传入的beanname进行过滤,去除&符前缀
        final string beanname = transformedbeanname(name);
        object bean;

        // 从spring容器中获取实例,由于spring容器启动时已经将单实例bean进行实例化,所以此时可以直接获得
        object sharedinstance = getsingleton(beanname);
        if (sharedinstance != null && args == null) {
            if (logger.istraceenabled()) {
                if (issingletoncurrentlyincreation(beanname)) {
                    logger.trace("returning eagerly cached instance of singleton bean '" + beanname +
                            "' that is not fully initialized yet - a consequence of a circular reference");
                }
                else {
                    logger.trace("returning cached instance of singleton bean '" + beanname + "'");
                }
            }
            // 获取指定的bean实例,如果是工厂bean,则为bean实例本身或其创建的对象。
            bean = getobjectforbeaninstance(sharedinstance, name, beanname, null);
        }
        // ...省略代码...
        return (t) bean;
    }

从上面的代码可以看出,获取bike实例的具体代码还在getobjectforbeaninstanc()方法内部,我们继续查看

protected object getobjectforbeaninstance(
            object beaninstance, string name, string beanname, @nullable rootbeandefinition mbd) {

        // 判断beanname是否是以&符开头的
        if (beanfactoryutils.isfactorydereference(name)) {
            if (beaninstance instanceof nullbean) {
                return beaninstance;
            }
            if (!(beaninstance instanceof factorybean)) {
                throw new beanisnotafactoryexception(transformedbeanname(name), beaninstance.getclass());
            }
        }

        // 根据beanname从spring容器中获取的bean实例如果不是工厂bean,或者beanname是以&符开头,就直接返回这个bean实例
        // 当我们获取bike类型的实例时,beanname为“bikefactorybean”,
        // beaninstance为“bikefactorybean”类型,是一个工厂bean,所以条件不满足,继续向下走
        if (!(beaninstance instanceof factorybean) || beanfactoryutils.isfactorydereference(name)) {
            return beaninstance;
        }

        object object = null;
        if (mbd == null) {
            // 根据beanname从缓存中获取bean实例,第一次来获取bike实例时为空,
        // factorybeanobjectcache.get(beanname);
            // 后续再获取时,便可以在此获得到,然后返回
            object = getcachedobjectforfactorybean(beanname);
        }
        if (object == null) {
            // 将获取的工厂bean强转为 factorybean 类型,以便下面调用其getobject()方法获取对象
            factorybean<?> factory = (factorybean<?>) beaninstance;
            // 获取bean定义信息
            if (mbd == null && containsbeandefinition(beanname)) {
                mbd = getmergedlocalbeandefinition(beanname);
            }
            boolean synthetic = (mbd != null && mbd.issynthetic());
            //在此方法内部调用 factorybean 接口的 getobject()方法获取对象
            object = getobjectfromfactorybean(factory, beanname, !synthetic);
        }
        return object;
    }

距离真相还差两步了,坚持就是胜利,我们继续看getobjectfromfactorybean()的源码

protected object getobjectfromfactorybean(factorybean<?> factory, string beanname, boolean shouldpostprocess) {
        // 此处调用 factorybean 的issingleton()方法,判断是否是一个单列
        // 如果是单例的,走if内部,获取到对象后,会保存到factorybeanobjectcache缓存中,以便后续使用
        if (factory.issingleton() && containssingleton(beanname)) {
            synchronized (getsingletonmutex()) {
                // 检查缓存中是否已经存在
                object object = this.factorybeanobjectcache.get(beanname);
                if (object == null) {
                    // 调用最后一个方法,执行factorybean 的 getobject()方法获取对象
                    object = dogetobjectfromfactorybean(factory, beanname);
                    // 再次检查缓存
                    object alreadythere = this.factorybeanobjectcache.get(beanname);
                    if (alreadythere != null) {
                        object = alreadythere;
                    }
                    else {
                        // ... 省略代码 ...
                        if (containssingleton(beanname)) {
                            // 将获取的对象放入factorybeanobjectcache缓存中,以便后续使用
                            this.factorybeanobjectcache.put(beanname, object);
                        }
                    }
                }
                return object;
            }
        }
        // 如果不是单例的,每次获取的对象直接返回,不会放入缓存中,所以每次都会调用getobject()方法
        else {
            object object = dogetobjectfromfactorybean(factory, beanname);
            if (shouldpostprocess) {
                try {
                    object = postprocessobjectfromfactorybean(object, beanname);
                }
                catch (throwable ex) {
                    throw new beancreationexception(beanname, "post-processing of factorybean's object failed", ex);
                }
            }
            return object;
        }
    }

根据上面的流程,终于来到了最后一步

private object dogetobjectfromfactorybean(final factorybean<?> factory, final string beanname)
            throws beancreationexception {
        object object;
        try {
            if (system.getsecuritymanager() != null) {
                accesscontrolcontext acc = getaccesscontrolcontext();
                try {
                    object = accesscontroller.doprivileged((privilegedexceptionaction<object>) factory::getobject, acc);
                } catch (privilegedactionexception pae) {
                    throw pae.getexception();
                }
            }
            else {
                // 直接调用 factorybean 接口的 getobject()方法获取实例对象
                object = factory.getobject();
            }
        }
        catch (factorybeannotinitializedexception ex) {
            throw new beancurrentlyincreationexception(beanname, ex.tostring());
        }
        // ... 省略代码 ...
        return object;
    }

经过如此多的代码,spring终于帮我们获取到bike对象实例

通过bikefactorybean来获取bike类的实例时,spring先获取bike类型对应的beanname(bikefactorybean),然后根据beanname获取到工厂bean实例本身(bikefactorybean),最终spring会调用bikefactorybean 的 getobject()方法来获取bike对象实例。并且根据 bikefactorybean 实例的 issingleton() 方法来判断bike类型的实例是否时单例的,依此来决定要不要将获取的bike对象放入到缓存中,以便后续使用。

总结

本文主要讲解了如何通过 factorybean接口向spring容器中注入组件,通过简单的案例进行模拟,并根据案例对源码的执行过程进行跟踪,分析了factorybean接口的执行过程。
另外,在每一次跟踪spring源码时,都会有新的收获。在spring庞大的体系下,只有定位好自己的目标,明确自己的需求,才不会被spring无限的代码所淹没。

学习永远都不是一件简单的事情,可以有迷茫,可以懒惰,但是前进的脚步永远都不能停止。

不积跬步,无以至千里;不积小流,无以成江海;

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网