以点到面的谈一下Spring的lazy-init

背景:源自一个朋友问我为什么在beanFactory里,lazy-init看不到效果。发现这个问题我也不大清楚,是我知识体系中的死角,另外也想顺便探究一下Spring的lazy-init到底是怎样的实现,因为靠表面的API调用和调试无从入手,所以才深入到源码内部探个究竟,把经验总结出来,难免有错误的地方请指正。

所谓延迟加载lazy-init,起初个人理解为什么时候调用到了,什么时候再加载,既然有这项作用说明如果在lazy-init不生效的情况下,Spring是会将bean预先加载的。
进而推论出lazy-init=“false”的情况下, 所有的bean,是被实例化后进行缓存了的。

下面我就根据猜测,分别使用XmlBeanFactory和ClassPathApplicationContext进行了调试分析。

虽然lazy-init只是一个小配置,但是要了解内部原理,从中必须先要知道3点:
1.bean实例在何时创建
2.Spring是如何创建的bean实例
3.Bean实例缓存到了哪里
要想知道配置的lazy-init是否起作用,必须知道bean实例的缓存对象是否为空,如果不为空,说明lazy-init没有起作用。但是想知道缓存对象就必须要先跟踪到1、2两点。

先看看BeanFactory

String url = "org\\zrx\\test\\ioc\\applicationContext.xml";
Resource resource = new ClassPathResource(url);
XmlBeanFactory bf = new XmlBeanFactory(resource);

ApplicationContext代码

<bean id="student" class="org.zrx.test.ioc.beans.Student" lazy-init="false"></bean>

仅仅这样测试无法得知,于是跟踪到XmlBeanFactory内部
内只有一个引用

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

beanFactory好比是一个大型的专门煲汤用的容器,里面存放了一碗一碗的煲好的汤beanDefinition,而煲汤用的原材料就是来自applicationContext.xml等Spring的Bean配置文档,加工的大厨是XmlBeanDefinitionReader,每一碗汤,上面都贴上标签上面标明它的名字,如“排骨汤”、“甲鱼汤”等等。。。这就是DefaultListableBeanFactory中的beanDefinitionMap。XmlBeanFactory继承自DefaultListableBeanFactory所以同样将beanDefinition存放到beanDefinitionMap里
XmlBeanDefinitionReader 既然是大厨,他就有很多小弟,各自分工不同听它指挥,比如说
BeanDefinitionParserDelegate负责把菜加工成汤,它把xml文档解析并set到一个个的beanDefinition对象里。
BeanDefinitionHolder负责把汤放到碗里,写好名称放到容器内,是个跑腿的。 它的作用就是把BeanDefinitionParserDelegate已经封装好的值对象beanDefinition放到BeanFactory的beanDefinitionMap里去。

下面看一下具体过程的代码片段吧:

beanFactory初始化

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}


loadBeanDefinitions:

try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}


doLoadBeanDefinitions:

int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);


registerBeanDefinitions:
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));


这里有两个关注的点:
createReaderContext:
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, this.namespaceHandlerResolver);


可以看出,XmlReaderContext存放了XmlBeanDefinitionReader的引用,而XmlBeanDefinitionReader存放了Beanfactory的引用,故而XmlReaderContext同样存放了beanFactory的引用。这个类起到了上下文的作用

下面关注一下registerBeanDefinitions的实现:
Element root = doc.getDocumentElement();

BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);

preProcessXml(root);
parseBeanDefinitions(root, delegate);
postProcessXml(root);

-------------------------------------------------
protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
delegate.initDefaults(root);
return delegate;
}

可以看出好戏开始了,这里,使用BeanDefinitionParserDelegate 真正的完成了切菜、加工等过程。在createHelper里,将上下文对象传入,由此可以联想到将会把加工的BeanDefinition对象传入上下文中。

重点关注加工过程在parseBeanDefinitions方法内:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(delegate.getNamespaceURI(root))) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
String namespaceUri = delegate.getNamespaceURI(ele);
if (delegate.isDefaultNamespace(namespaceUri)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}


越来越清晰了,在这里很明显是在对传入的Document对象做解析,本人对XML解析的API不熟,不过没关系,看还是可以看明白滴,Spring超长的方法命名就是为了能够看懂,这里解析文档进入了parseDefaultElement方法:

if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}


三种情况:采用import、alias、和普通的bean
我采用的是普通的bean定义,因此进入processBeanDefinition走着:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}


此处又见一位刚才提到的熟人:跑腿师傅BeanDefinitionHolder ,BeanDefinitionHolder 一旦出现,说明菜已经加工好,具体加工过程一定在delegate.parseBeanDefinitionElement调用。
parseBeanDefinitionElement:
由于代码太长,细节的东西就不全贴上了,源代码中都有,此处贴上一些我自认为比较关键的东西

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
。。。
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
。。。
。。。
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}

此段代码有两处:
第一处正如刚才所说,将Element对象解析并存入 AbstractBeanDefinition 内,好比是大厨把加工过的汤,盛放到碗里。
那么此时,跑腿的师傅就过来了,贴上标签,准备把碗端到容器里去。此处实例化了BeanDefinitionHolder对象,传入beanDefinition、beanName。
至此,得到了拿着汤碗的BeanDefinitionHolder。

跳到上一段代码:接下来看BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());这段代码
既然得到了BeanDefinitionHolder,那么如何放到容器内呢,传入Holder,传入上下文中的"Registry",这个Registry是啥呢,进入方法体看变量得知在实例化ReaderContext时,我们传入的beanFactory对象,就是这个方法get到的。
传入了BeanDefinitionHolder和beanFafctory意味着最后一步:要将加工好的汤放入容器中了!

registerBeanDefinition代码:

public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {

// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
.....
}
code]

此处,终于见到beanFactory的初始化最后一步了。此处beanFactory调用了registerBeanDefinition
传入beanName和BeanDefinition:
[code="java"]
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
。。。。。。。。。。。。。。。
this.beanDefinitionMap.put(beanName, beanDefinition);
。。。。。。。。。
}


至此加工好的数据对象贴上标签存入了容器内了!
等等。。。还没结束
bean的实例化那里去了??费了好大周折却把开始想要的忘了。
没关系我们再缕一缕思路:
从后往前推导,整个加工的过程,中最后得到的结果是beanDefinitionMap, 而map是key和value,key是beanName,value存放beanDefinition对象。
而beanDefinition是从xml文档中分析出来的数据,它盛放了所有关于文档的信息,看一下其内部到底存放了哪些变量就明了了,具体就不往上贴了,只需要关注一个属性:
private volatile Object beanClass;


看上去貌似很像了,到底是不是呢,线索有了,这就需要我们去验证了。。
在new beanFactory时,进入源代码DefaultBeanDefinitionDocumentReader中的processBeanDefinition方法内第一行:BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
加断点。 该行代码是实例化BeanDefinitionHolder 过程,前文中提到过。
由于BeanDefinitionHolder 是暂存Beandefinition的地方,因此此处可以debug出Beandefinition内的beanClass的值是否为实例化的bean的class对象。
或者还有一个办法,修改main函数中的测试代码:
String url = "org\\zrx\\test\\ioc\\applicationContext.xml";
Resource resource = new ClassPathResource(url);
XmlBeanFactory bf = new XmlBeanFactory(resource);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
reader.loadBeanDefinitions(resource);
AbstractBeanDefinition bd = (AbstractBeanDefinition)bf.getBeanDefinition("student");
System.out.println(bd.getBeanClass());


当采用这个方法查看时发现报异常:
java.lang.IllegalStateException: Bean class name [org.zrx.test.ioc.beans.Student] has not been resolved into an actual Class

没关系,咱有源代码,查一下原因:
原来异常抛自如下源代码:
if (!(beanClassObject instanceof Class)) {
throw new IllegalStateException(
"Bean class name [" + beanClassObject + "] has not been resolved into an actual Class");
}

说明,beanClassObject 并不是实例化的class对象。
而采用第一种方式debug后,发现beanClass是String类型。既然beanClass属性的定义为Object,说明其中一定有存放对象的情况, 但是为什么是String呢,还是回到实例化BeanDefinitionHolder 那段代码中仔细寻找BeanDefinition 实例化过程中对BeanClass的操作吧。。

看BeanDefinitionParserDelegate的parseBeanDefinitionElement方法,当中有如下代码:
AbstractBeanDefinition bd = createBeanDefinition(className, parent);

接着看createBeanDefinition:

protected AbstractBeanDefinition createBeanDefinition(String className, String parentName)
throws ClassNotFoundException {

return BeanDefinitionReaderUtils.createBeanDefinition(
parentName, className, this.readerContext.getBeanClassLoader());
}


重点在create的过程,继续跟进BeanDefinitionReaderUtils.createBeanDefinition,由于代码不是很长,所以全都粘贴下来:
public static AbstractBeanDefinition createBeanDefinition(
String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException {

GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setParentName(parentName);
if (className != null) {
if (classLoader != null) {
bd.setBeanClass(ClassUtils.forName(className, classLoader));
}
else {
bd.setBeanClassName(className);
}
}
return bd;
}


在这里,条理已经看得很清晰了,当传入的classloader为null,set的是字符串,不为null就set对象,ClassUtils.forName代码就不贴了,具体作用就是封装了一下反射,通过类名实例化对象。
由此可知,传入的classloader是空值。
为了验证这个结论,我们可以修改一下测试类:
String url = "org\\zrx\\test\\ioc\\applicationContext.xml";
Resource resource = new ClassPathResource(url);
XmlBeanFactory bf = new XmlBeanFactory(resource);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
[color=red]reader.setBeanClassLoader(Thread.currentThread().getContextClassLoader());[/color] reader.loadBeanDefinitions(resource);
AbstractBeanDefinition bd = (AbstractBeanDefinition)bf.getBeanDefinition("student");
System.out.println(bd.getBeanClass());


运行可得class org.zrx.test.ioc.beans.Student

下面看一下,究竟为何classLoader传入为null:
回到实例化AbstractBeanDefinition的代码片段:
return BeanDefinitionReaderUtils.createBeanDefinition(
parentName, className, this.readerContext.getBeanClassLoader());

绕得我团团转的源头似乎从这里开始,没错,在上下文这个类中的classLoader一定就是空了!
上下文类的getBeanClassloader方法源代码:
public final ClassLoader getBeanClassLoader() {
return this.reader.getBeanClassLoader();
}

根据初始化上下文方法可得,BeanClassLoader是AbstractBeanDefinitionReader的属性,而beanClassLoader的确未被初始化。
那么结论貌似出来了。。折腾了半天,原来不论lazy-init如何设置,XmlBeanFactory bf = new XmlBeanFactory(resource);这段代码,根本就不会进行对象的实例化,所以lazy-init根本就不起作用了。
重新回到那个朋友问的问题,为什么lazy-init不论设置为true和false,对于beanFactory都不加载对象呢,那是因为 beanFactory不具备预加载这个功能,lazy-init存在的意义是为applicationcontext容器提供服务的。

为了验证以上结论,还是看一下beanFactory真正实例化对象是什么样子吧。此处针对性较强,为了验证Beandefinition的beanClass是否为bean实例化对象的存放处,因此就不详细贴无关的代码了。
测试代码加入:
bf.getBean("student")

对getBean进行跟踪
return doGetBean(name, null, null, false);

进入doGetBean
由于采用的默认singleton方式创建对象,进入下列代码片段
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}


进入getSingleton方法
try {
singletonObject = singletonFactory.getObject();
}

此处回调的getObject方法,进入createBean:
Object beanInstance = doCreateBean(beanName, mbd, args);

此时传入的mbd内的beanclass仍然为String

进入doCreateBean,该方法存在于父类AbstractAutowireCapableBeanFactory:
protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {

....
resolveBeanClass(mbd, beanName);

... Object bean = resolveBeforeInstantiation(beanName, mbd);
Object beanInstance = doCreateBean(beanName, mbd, args);
... return beanInstance;
}

下面看一下如何让mbd内的beancl由String变为Class的方法:resolveBeanClass

protected Class resolveBeanClass(final RootBeanDefinition mbd, String beanName, final Class... typesToMatch)
throws CannotLoadBeanClassException {
... if (mbd.hasBeanClass()) {
return mbd.getBeanClass();
}
...
return doResolveBeanClass(mbd, typesToMatch);
...
}
public boolean hasBeanClass() {
return (this.beanClass instanceof Class);
}

此时mbd当然hasBeanClass不成立
进入AbstractBeanFactory的doResolveBeanClass方法:

private Class doResolveBeanClass(RootBeanDefinition mbd, Class... typesToMatch) throws ClassNotFoundException {
....
return mbd.resolveBeanClass(getBeanClassLoader());
}


此处getBeanClassLoader比较关键,它在AbstractBeanFactory中定义,我们看一下得到的是什么对象:
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

得到了当前线程默认的ClassLoader
进入resolveBeanClass方法,可见该方法存在于AbstractBeanDefinition内:

public Class resolveBeanClass(ClassLoader classLoader) throws ClassNotFoundException {
String className = getBeanClassName();
if (className == null) {
return null;
}
Class resolvedClass = ClassUtils.forName(className, classLoader);
this.beanClass = resolvedClass;
return resolvedClass;
}

联系到前文,似乎一切明朗了,由于此处classLoader已经实例化,因此 ClassUtils.forName得到的将会是一个实实在在的Class类对象,那么BeanDefinition的beanclass就由String被转换为了Class。
但是似乎还没结束,究竟对象是否是从这里获取的呢,我们回到刚才的“createBean”方法内,继续往下看。
真正的返回值为Object beanInstance = doCreateBean(beanName, mbd, args);

BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
......

此处采用了包装类BeanWrapper ,省略的部分对实例化的bean做了一系列初始化工作,比如initalizingBean等等
进入createBeanInstance方法
Class beanClass = resolveBeanClass(mbd, beanName);

我们只关注这段代码实现吧,代码太多了,贴重点
protected Class resolveBeanClass(final RootBeanDefinition mbd, String beanName, final Class... typesToMatch)
throws CannotLoadBeanClassException {
if (mbd.hasBeanClass()) {
return mbd.getBeanClass();
}
。。。


由于mdb中的beancla于刚才成功实例化,因此此处获得的对象正是刚才实例化的,看了分析的是正确的,此段代码得以印证。

成功得到对象,回到DefaultSingletonBeanRegistry的getSingleton方法查看,似乎还没有结束,返回对象前调用了如下方法:
addSingleton(beanName, singletonObject);

protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
[color=red]this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));[/color] 。。。
}


注意红字部分,由于是singleton对象,因此缓存该实例,在第二次加载的时候,直接
Object singletonObject = this.singletonObjects.get(beanName);

返回该对象。

综上所述,默认的条件下,beanFactory只有在getBean的时候,才实例化对象,并进行缓存。因此init-lazy只适用于ApplicationContext。

下面对ApplicationContext初始化的时候,实例化所有bean这个特性简单分析。

测试代码修改为
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(url);

进入构造方法

super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}

在refresh里进行初始化bean,refresh代码有好多初始化方法包括初始化beanFactory等等,不一一列举了,只要了解bean生命周期就知道大体方法作用了,个人认为只要基本的beanFactory原理了解了,ApplicationContext其实就相当于一个加强的类,核心调用的还是beanFactory。
我要说的是里面有一个方法跟初始化bean有关。
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);


强势进入该方法:
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}

// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);

// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();

[color=red]// Instantiate all remaining (non-lazy-init) singletons.[/color] [color=red]beanFactory.preInstantiateSingletons();[/color] }


想必大家看到红字的注释了吧。既然只有非lazy-init才调用,那么一定是预加载的方法咯,强势进入preInstantiateSingletons方法查看。

public void preInstantiateSingletons() throws BeansException {
if (this.logger.isInfoEnabled()) {
this.logger.info("Pre-instantiating singletons in " + this);
}

synchronized (this.beanDefinitionMap) {
for (String beanName : this.beanDefinitionNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && ![color=red]bd.isLazyInit[/color]()) {
if (isFactoryBean(beanName)) {
final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() {
return ((SmartFactoryBean) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean) factory).isEagerInit();
}
if (isEagerInit) {
[color=red]getBean(beanName);[/color] }
}
else {
[color=red]getBean(beanName);[/color] }
}
}
}
}


很明了了,当bean不是init-lazy的情况下,getBean操作。
而getBean恰好刚才分析出,将xml解析装载到beanDefinition缓存到beanDefinitionMap中,bean的对象也因此实例化并存入beanDefinition的beanclass属性中。

分析结束。
-------------------------------------------------

一字一字敲进来,太累了,个人感觉对于Spring源码的分析,还是比较入门,里面还有很多点省略掉了,但是通过lazy-init这个“点”来进行了一个“面”的分析,我觉得这是一种学习方式,有时候一个小知识点会牵扯很多更为复杂的问题,程序员热爱开源的原因恐怕也是这样吧,有了源代码,即使不看文档,一样能解决问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值