丨spring bean生命周期常见错误_第1页
丨spring bean生命周期常见错误_第2页
丨spring bean生命周期常见错误_第3页
丨spring bean生命周期常见错误_第4页
丨spring bean生命周期常见错误_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1LightMgrServiceLightService,从而控制宿舍灯的开启和关闭。我们希望在LightMgrService初始化时能够自动调用LightService的check方法来检查所有宿舍灯的电路是否正常,代码如下:代代123456789importimportorg.springframewpublicclassLightMgrService{privateLightServicelightService;publicLightMgrService(){}}LightServicecheck代代publicclassLightServicepublicvoidstart()System.out.println("turnonall publicvoidshutdown()System.out.println("turnoffall publicvoidcheck()System.out.println("checkall 12以上代码定义了LightService的构造器执行中,LightServiceshutdowncheckall然而事与愿违,我们得到的只会是NullPointerException显然这是新手最常犯的错误,但是问题的根源,是我们Spring的了解。下面这张时序图描述了Spring启动时的一些关键结点:第一部分,将一些必要的系统类,比如Bean的后置处理器类,到Spring容器,其中就包括我们这节课关注的CommonAnnotationBeanPostProcessor类;第二部分,将这些后置处理器实例化,并到Spring的容器中很多必要的系统类,尤其是Bean后置处理器(比如AutowiredAnnotationBeanPostProcessor)Spring并在Spring中扮演了非常重要的角色;BeanSpringPostConstruct处理逻辑就需要用到CommonAnnotationBeanPostProcessor(继承自现在我们重点看下第三部分,即Spring>doGetBean()->getSingleton(),如果发现Bean不存在,则调用>doCreateBean(查看doCreateBean(代代123456789protectedObjectdoCreateBean(finalStringbeanName,finalRootBeanDefinitionthrowsBeanCreationException{if(instanceWrapper==null)instanceWrapper=createBeanInstance(beanName,mbd,}finalObjectbean=ObjectexposedObject=bean;try{populateBean(beanName,mbd,exposedObject=initializeBean(beanName,exposedObject,}catch(Throwableex)}上述代码完整地展示了Bean初始化的三个关键步骤,按执行顺序分别是第5行的createBeanInstance,第12行的populateBean,以及第13行的initializeBean,分别对应实例化Bean,注入Bean依赖,以及初始化Bean(例如执行@PostConstruct标记的方法)这三个功能,这也和上述时序图的流程相符。BeancreateBeanInstance1123456789代publicstatic<T>TinstantiateClass(Constructor<T>ctor,Object...args)throAssert.notNull(ctor,"Constructormustnotbenull");try{return(KotlinDetector.isKotlinReflectPresent()&&KotlinDetector.isKotlKotlinDelegate.instantiateClass(ctor,args):}catch(InstantiationExceptionex)thrownewBeanInstantiationException(ctor,"Isitclass?",}}这里因为当前的语言并非Kotlin,所以最终将调用ctor.newInstance()方法实例化用户定制类LightMgrService,而默认构造器显然是在类实例化的时候被自动调用的,Spring也无法控制。而此时负责自动装配的populateBean方法还没有被执行,LightMgrService的属性LightService还是null,因而得到空指针异常也在情理之中。通过源码分析,现在我们知道了问题的根源,就是在于使用@Autowired直接标记在成员属性上而的装配行为是发生在构造器执行之后的。所以这里我们可以通过下面这种代代publicclassLightMgrService3345678privateLightServicepublicLightMgrService(LightService{this.lightService=lightService;}9 第02课的案例2中,我们就提到了构造器参数的隐式注入。当使用上面的代码时,构造器参数LightService会被自动注入LightService的Bean,从而在构造器执行时,不会出现空指针。可以说,使用构造器参数来隐式注入是一种Spring最佳实践,因为它成功地规避了案例1中的问题。代代protectedObjectinitializeBean(finalStringbeanName,finalObjectbean,if(mbd==null||!mbd.isSynthetic())wrappedBean=applyBeanPostProcessorsBeforeInitialization(wrappedBean, tryinvokeInitMethods(beanName,wrappedBean, 10这里你可以看到applyBeanPostProcessorsBeforeInitialization和invokeInitMethods这两个关键方法的执行,它们分别处理了@PostConstruct注解和InitializingBean接口applyBeanPostProcessorsBeforeInitialization与InitDestroyAnnotationBeanPostProcessor的buildLifecycleMetadata方法(CommonAnnotationBeanPostProcessor的父类dofinalList<LifecycleElement>currDestroyMethods=newReflectionUtils.doWithLocalMethods(targetClass,method->//此处的this.initAnnotationType值,即为if(this.initAnnotationType!=null&&LifecycleElementelement=new12在这个方法里,SpringPostConstruct.classinvokeInitMethods与InitializingBeaninvokeInitMethods方判断当前Bean是否实现了InitializingBean接口,只有在实现了该接口的情况下,Spring才会调用该Bean的接口实现方法afterPropertiesSet()。1123456789代protectedvoidinvokeInitMethods(StringbeanName,finalObjectbean,@NullablethrowsThrowable{booleanisInitializingBean=(beaninstanceofif(isInitializingBean&&(mbd==null||//else((InitializingBean)}}//}添加init方法,并且使用PostConstruct代代123456789importimportorg.springframewpublicclass{privateLightServicepublicvoidinit(){}}实现InitializingBean接口,在其afterPropertiesSet代代123456789importimportimportpublicclassLightMgrServiceimplements{privateLightServicepublicvoidafterPropertiesSet()throws{}}对比最开始解决方案,很明显,针对本案例而言,后续的两种方案并不是最优的。但是在一些场景下,这两种方案各有,不然g!案例2:意外触发shutdown importpublicclassLightServicepublicvoidSystem.out.println("shuttingdownall 9在之前的案例中,如果我们的宿舍管理系统在重启时,灯是不会被关闭的。但是随着业务的需求变化,我们可能会去掉ie注解,而是使用另外一种产生Bn建一个配置类Bguratnnfguratn)来创建一堆B包含了创建te类型的B,并将其到pring代代importimportpublicclassBeanConfigurationpublicLightServicereturnnew 9复用案例1的启动程序,稍作修改,让Spring启动完成后立马关闭当前Spring上下文。代代publicclassApplicationpublicstaticvoidmain(String[]args)ConfigurableApplicationContextcontext= 7器,完成后再关闭当前的Spring容器。按照预期,这段代码运行后不会有任何的log输出,毕竟我们只是改变了Bean的产生方式。但实际运行这段代码后,我们可以看到控制台上打印了shuttingdownalllights。显然shutdownbug:Bean通过调试,我们发现只有通过使用Bean注解到Spring容器的对象,才会在Spring容器被关闭的时候自动调用shutdown方法,而使用@Component(Service也是一种Component)将当前类自动注入到Spring容器时,shutdown方法则不会被自动执行。使用Bean注解的方法所的Bean对象,如果用户不设置destroyMethod属性,则 BeanDefinition.INFER_METHOD。此时Spring会检查当前Bean对象的原始类中是否有名为shutdown或者close的方法,如果有,此方被Spring下面我们继续查看Spring首先我们可以查找INFER_METHOD枚举值的,很容易就找到了使用该枚举值的方12123代privateStringinferDestroyMethodIfNecessary(Objectbean,RootBeanDefinitionStringdestroyMethodName=ifBeanDefinition.INFER_METHOD.equals(destroyMethodName)4(!(beaninstanceofDisposableBean))5try6//尝试查找close7return8}9catch ethodExceptionex)try//尝试查找shutdownreturn}catch ethodExceptionex2)//nocandidatedestroymethod}}我们可以看到,代码逻辑和Bean注解类中对于destroyMethod属性的注释完全一致destroyMethodName如果等于INFER_METHOD,且当前类没有实现DisposableBean接口,那么首先查找类的close方法,如果找不到,就在抛出异常后继续查找shutdown方法;如果找到了,则返回其方法名(close或者shutdown)。接着,继续逐级查找,最终得到的调用链从上到下为然后,我们追溯到了顶层的doCreateBean代代protectedObjectdoCreateBean(finalStringbeanName,finalthrowsBeanCreationExceptionif(instanceWrapper==null)instanceWrapper=createBeanInstance(beanName,mbd, //InitializethebeanObjectexposedObject=trypopulateBean(beanName,mbd,exposedObject=initializeBean(beanName,exposedObject, //RegisterbeanastryregisterDisposableBeanIfNecessary(beanName,bean,}catch(BeanDefinitionValidationException{thrownewmbd.getResourceDescription(),beanName,"Invaliddestruction}return管理了Bean的整个生命周期中几乎所有的关键节点,直接负责了Bean对象的生老病死,BeanBean对象依赖的注入;Disposable方法的接着,继续查看registerDisposableBean方法:代代publicvoidregisterDisposableBean(StringbeanName,DisposableBeanbean)synchronized(this.disposableBeans)this.disposableBeans.put(beanName, 7在registerDisposableBean方法内,DisposableBeanAdapter类(其属性destroyMethodName记录了使用哪种destory方法)被实例化并添加到DefaultSingletonBeanRegistry#disposableBeansdisposableBeans些DisposableBeanAdapter实例,直到AnnotationConfigApplicationContext的close方法被调用。而当AnnotationConfigApplicationContext的close方法被调用时,即当Spring容器被销毁时,最终会调用到DefaultSingletonBeanRegistry#destroySingletondisposableBeansDisposableBean,closeshutdown代代publicvoiddestroySingleton(StringbeanName)//Removearegisteredsingletonofthegivenname,if//DestroythecorrespondingDisposableBeanDisposableBeansynchronized(this.disposableBeans)disposableBean=(DisposableBean) destroyBean(beanName,10很明显,最终我们的案例调用了LightService#shutdown我们可以通过避免在Java类中定义一些带有特殊意义动词的方法来解决,当然如果一定要定义名为close或者shutdown方法,也可以通过将Bean注解内destroyMethod属性代代importimportpublicclassBeanConfigurationpublicLightServicereturnnew 9另外,针对这个问题再多提示一点。如果我们能养成良好的编码习惯熟悉的注解之前,认真研读一下该注解的注释,也可以大概率规避这个问题。@ServiceLightService,其shutdown方法不能被执行?这里补充说明下。想要执行,则必须要添加DisposableBeanAdapter,}else}}15!mbd.isPrototype()&&requiresDestruction(bean,requiresDestruction条件。翻阅它的代码,最终的关键调用参考11234567代publicstaticbooleanhasDestroyMethod(Objectbean,RootBeanDefinitionbeanDefif(beaninstanceofDisposableBean||beaninstanceofAutoCloseable){return}StringdestroyMethodName=ifBea

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论