Spring Framework 组件注册 之 FactoryBean
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
如果将bikefactorybean
的issingleton()
方法返回了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”为例。
- spring容器启动过程中,在执行完所有的
beanfactorypostprocessor
,beanpostprocessor
以及注册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
- 根据第一步的结果,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
。
- 从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无限的代码所淹没。
学习永远都不是一件简单的事情,可以有迷茫,可以懒惰,但是前进的脚步永远都不能停止。
不积跬步,无以至千里;不积小流,无以成江海;
推荐阅读
-
Vue源码解读之Component组件注册的实现
-
向Spring容器中注册组件的方法汇总小结
-
Vue.js 2.x之组件的定义和注册图文详解
-
JSP 开发之Spring BeanUtils组件使用
-
SpringCloud之服务注册与发现Spring Cloud Eureka实例代码
-
20.DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件
-
向Spring容器中注册组件的方法汇总小结
-
Django REST Framework之权限组件
-
Spring Framework 组件注册 之 FactoryBean
-
Vue源码解读之Component组件注册的实现