说说Spring中为何要引入Lookup注解
前言
我们先探一探官方文档关于MethodInjection的章节是怎么说的:
Inmostapplicationscenarios,mostbeansinthecontaineraresingletons.Whenasingletonbeanneedstocollaboratewithanothersingletonbeanoranon-singletonbeanneedstocollaboratewithanothernon-singletonbean,youtypicallyhandlethedependencybydefiningonebeanasapropertyoftheother.Aproblemariseswhenthebeanlifecyclesaredifferent.SupposesingletonbeanAneedstousenon-singleton(prototype)beanB,perhapsoneachmethodinvocationonA.ThecontainercreatesthesingletonbeanAonlyonce,andthusonlygetsoneopportunitytosettheproperties.ThecontainercannotprovidebeanAwithanewinstanceofbeanBeverytimeoneisneeded.
用一句话概括就是一个单例BeanA每次获取另外一个BeanB的时候怎么保证这个BeanB是一个新的实例?
正文
ApplicationContextAware接口
官方文档首先也提到了一个解决方案就是把A弄成容器的Aware(makebeanAawareofthecontainer),也就是实现ApplicationContextAware接口。
Asolutionistoforegosomeinversionofcontrol.YoucanmakebeanAawareofthecontainerbyimplementingtheApplicationContextAwareinterface,andbymakingagetBean("B")calltothecontaineraskfor(atypicallynew)beanBinstanceeverytimebeanAneedsit.Thefollowingexampleshowsthisapproach
文档里随后就提供了一个示例来说明这个解决方案如何做。
//aclassthatusesastatefulCommand-styleclasstoperformsomeprocessing packagefiona.apple; //Spring-APIimports importorg.springframework.beans.BeansException; importorg.springframework.context.ApplicationContext; importorg.springframework.context.ApplicationContextAware; publicclassCommandManagerimplementsApplicationContextAware{ privateApplicationContextapplicationContext; publicObjectprocess(MapcommandState){ //grabanewinstanceoftheappropriateCommand Commandcommand=createCommand(); //setthestateonthe(hopefullybrandnew)Commandinstance command.setState(commandState); returncommand.execute(); } protectedCommandcreateCommand(){ //noticetheSpringAPIdependency! returnthis.applicationContext.getBean("command",Command.class); } publicvoidsetApplicationContext( ApplicationContextapplicationContext)throwsBeansException{ this.applicationContext=applicationContext; } }
虽然解决了一开始提出的问题,但是Spring随后就说到:
Theprecedingisnotdesirable,becausethebusinesscodeisawareofandcoupledtotheSpringFramework.MethodInjection,asomewhatadvancedfeatureoftheSpringIoCcontainer,letsyouhandlethisusecasecleanly.
也就是说前面的方法是不可取的,因为业务代码知道并耦合到Spring框架。那怎么降低这个耦合度呢?后半句就给出了答案:方法注入是SpringIOC容器的一个稍微高级的特性,它允许您干净地处理这个用例。
LookupMethod方法注入
首先再次引入官方文档中的阐述:
Lookupmethodinjectionistheabilityofthecontainertooverridemethodsoncontainer-managedbeansandreturnthelookupresultforanothernamedbeaninthecontainer.Thelookuptypicallyinvolvesaprototypebean,asinthescenariodescribedintheprecedingsection.TheSpringFrameworkimplementsthismethodinjectionbyusingbytecodegenerationfromtheCGLIBlibrarytodynamicallygenerateasubclassthatoverridesthemethod.
简要概括下这段话有三个意思:
- LookupMethod注入可以让容器重写容器中bean上的方法并返回容器中另一个bean的查找结果。
- Lookup通常会是一个原型bean,如文章开头所说的。
- Spring框架通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类,从而实现这种方法注入。
使用Lookup方式需要注意以下几点:
- Forthisdynamicsubclassingtowork,theclassthattheSpringbeancontainersubclassescannotbefinal,andthemethodtobeoverriddencannotbefinal,either.
- Unit-testingaclassthathasanabstractmethodrequiresyoutosubclasstheclassyourselfandtosupplyastubimplementationoftheabstractmethod.
- Concretemethodsarealsonecessaryforcomponentscanning,whichrequiresconcreteclassestopickup.
- Afurtherkeylimitationisthatlookupmethodsdonotworkwithfactorymethodsandinparticularnotwith@Beanmethodsinconfigurationclasses,since,inthatcase,thecontainerisnotinchargeofcreatingtheinstanceandthereforecannotcreatearuntime-generatedsubclassonthefly.
这段话也可以概括为以下几点:
- 使这个动态子类可以用,这个类不能是final,要重写的方法也不能是final。
- 单元测试具有抽象方法的类需要您自己对该类进行子类化,并提供抽象方法的存根实现。
- 具体的方法对于组件扫描也是必要的,这需要具体的类来获取。
- 一个关键限制是Lookup方法不适用于工厂方法,尤其是配置类中的@Bean方法,因为在这种情况下,容器不负责创建实例,因此不能动态创建运行时生成的子类。
接下来Spring就拿上面本来基于ApplicationContextAware的方法来说,就可以用Lookup来替换了。对于前面代码段中的CommandManager类,Spring容器动态重写createCommand()方法的实现。CommandManager类没有任何Spring依赖项,如修改后的示例所示
InthecaseoftheCommandManagerclassinthepreviouscodesnippet,theSpringcontainerdynamicallyoverridestheimplementationofthecreateCommand()method.TheCommandManagerclassdoesnothaveanySpringdependencies,asthereworkedexampleshows.
Xml配置lookup-method
publicabstractclassCommandManager{ publicObjectprocess(ObjectcommandState){ //grabanewinstanceoftheappropriateCommandinterface Commandcommand=createCommand(); //setthestateonthe(hopefullybrandnew)Commandinstance command.setState(commandState); returncommand.execute(); } //okay...butwhereistheimplementationofthismethod? protectedabstractCommandcreateCommand(); }
在包含要注入的方法的CommandManager类中,要注入的方法需要以下形式的签名:
[abstract] theMethodName(no-arguments);
如果方法是抽象的,则动态生成的子类将实现该方法。否则,动态生成的子类将重写在原始类中定义的具体方法。
我们来看下Bean的配置:
当需要myCommand这个Bean的新实例时,被标识为commandManager的bean就会调用自己的createCommand()方法。如果需要的话,必须小心地将myCommand这个bean部署为原型。如果是单例,则每次都返回相同的myCommandbean实例.
@Lookup注解
在基于注解的组件模型中,可以通过@lookup注释声明查找方法,如下例所示
publicabstractclassCommandManager{ publicObjectprocess(ObjectcommandState){ Commandcommand=createCommand(); command.setState(commandState); returncommand.execute(); } @Lookup("myCommand") protectedabstractCommandcreateCommand(); }
或者,你也可以依靠目标bean根据lookup方法的声明返回类型进行解析
publicabstractclassCommandManager{ publicObjectprocess(ObjectcommandState){ MyCommandcommand=createCommand(); command.setState(commandState); returncommand.execute(); } @Lookup protectedabstractMyCommandcreateCommand(); }
需要注意的是你通常应该用一个具体的存根实现声明这种带注解的查找方法,以便它们与Spring的组件扫描规则兼容,默认情况下抽象类会被忽略。此限制不适用于显式注册或显式导入的bean类。也就是说如果用组件扫描Bean的话因为抽象类默认是被忽略的,但是你加上这个Lookup注解后就不会呗忽略。
Spring在最后也提供了其他两种解决思路:
AnotherwayofaccessingdifferentlyscopedtargetbeansisanObjectFactory/Providerinjectionpoint.SeeScopedBeansasDependencies。YoumayalsofindtheServiceLocatorFactoryBean(intheorg.springframework.beans.factory.configpackage)tobeuseful.
- 通过ObjectFactory或者ObjectProvider.
- 通过ServiceLocatorFactoryBean.
这两个方案我们之后会单独写文章来探讨,下篇文章我打算来具体的使用下这个Lookup方法注入并且从源码角度来看下Spring如何巧妙地实现它的。
总结
到此这篇关于Spring中为何要引入Lookup注解的文章就介绍到这了,更多相关Spring为何引入Lookup内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!