前言
beanpostprocessor是一個工廠鉤子,允許spring框架在新創建bean實例時對其進行定制化修改。例如:通過檢查其標注的接口或者使用代理對其進行包裹。應用上下文會從bean定義中自動檢測出beanpostprocessor并將它們應用到隨后創建的任何bean上。
普通bean對象的工廠允許在程序中注冊post-processors,應用到隨后在本工廠中創建的所有bean上。典型的場景如:post-processors使用postprocessbeforeinitialization方法通過特征接口或其他類似的方式來填充bean;而為創建好的bean創建代理則一般使用postprocessafterinitialization方法。
beanpostprocessor本身也是一個bean,一般而言其實例化時機要早過普通的bean,但是beanpostprocessor也會依賴一些bean,這就導致了一些bean的實例化早于beanpostprocessor,由此會導致一些問題。最近在處理shiro和spring cache整合時就碰到了,導致的結果就是spring cache不起作用。現將問題場景、查找歷程及解決方法展現一下。
1 問題場景
打算在項目中將shiro與spring cache整合,使用spring cache統一管理緩存,也包括shiro認證時的用戶信息查詢。項目中將service分層,outter層負責權限和session,inner層主打事務和緩存并與dao交互,兩層之間也可以較容易的擴展為rpc或微服務模式。因此在shiro的authrealm中依賴了inneruserservice,并在inneruserservice中配置了spring cache的標注,使用cache進行緩存。配置如下(摘錄重要部分):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
@bean (name= "shirofilter" ) public shirofilterfactorybean shirofilter( @qualifier ( "securitymanager" ) securitymanager manager ) { shirofilterfactorybean bean= new shirofilterfactorybean(); bean.setsecuritymanager(manager); .............. return bean; } //配置核心安全事務管理器 @bean (name= "securitymanager" ) public securitymanager securitymanager( @qualifier ( "authrealm" ) authorizingrealm authrealm, @qualifier ( "sessionmanager" ) sessionmanager sessionmanager, @qualifier ( "cookieremembermemanager" ) remembermemanager remembermemanager, @qualifier ( "cachemanager" ) cachemanager cachemanager) { system.err.println( "--------------shiro已經加載----------------" ); defaultwebsecuritymanager manager= new defaultwebsecuritymanager(); manager.setrealm(authrealm); manager.setsessionmanager(sessionmanager); manager.setremembermemanager(remembermemanager); manager.setcachemanager(cachemanager); return manager; } //配置自定義權限登錄器 @bean (name= "authrealm" ) public authorizingrealm authrealm(iinneruserservice userservice) { myrealm myrealm = new myrealm(iinneruserservice); logger.info( "authrealm myrealm initiated!" ); return myrealm; } @bean public lifecyclebeanpostprocessor lifecyclebeanpostprocessor(){ return new lifecyclebeanpostprocessor(ordered.lowest_precedence); } |
其中myrealm是自定義的shiro authorizingrealm,用于執行認證與授權,其實現依賴inneruserservice從庫中查找用戶信息,示例代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public class myrealm extends authorizingrealm { iinneruserservice userservice; public myrealm(){ super (); } public myrealm(iinneruserservice userservice){ this .userservice = userservice; } public iinneruserservice getuserservice() { return userservice; } public void setuserservice(iinneruserservice userservice) { this .userservice = userservice; } @override protected authorizationinfo dogetauthorizationinfo( principalcollection principals) { //null usernames are invalid if (principals == null ) { throw new authorizationexception( "principalcollection method argument cannot be null." ); } set<string> rolenames = new hashset<string>(); set<string> permissions = new hashset<string>(); user user = (user)getavailableprincipal(principals); rolenames.add( "role1" ); rolenames.add( "role2" ); permissions.add( "user:create" ); permissions.add( "user:update" ); permissions.add( "user:delete" ); simpleauthorizationinfo info = new simpleauthorizationinfo(rolenames); info.setstringpermissions(permissions); return info; } @override protected authenticationinfo dogetauthenticationinfo( authenticationtoken token) throws authenticationexception { string username = (string)token.getprincipal(); //得到用戶名 string password = new string(( char [])token.getcredentials()); //得到密碼 user user = userservice.findbyusernameinner(username); if (user== null ){ throw new unknownaccountexception(); } else if (!password.equals(user.getpassword())) { throw new incorrectcredentialsexception(); } else { return new simpleauthenticationinfo(user, password, getname()); } } } |
而在inneruserservice中配置了spring cache的標注,示例代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@service public class iinneruserserviceimpl implements iinneruserservice { logger logger = loggerfactory.getlogger(iinneruserserviceimpl. class ); @autowired iuserdao userdao; @override @cacheable (value = "mycache" , key = "#username" ) public user findbyusernameinner(string username) { user user = userdao.findbyusername(username); logger.info( "real execute find from database, username:{}" , username); return user; } } |
并在配置文件上標注了@enablecaching(mode=advicemode.proxy)以啟動spring cache。這里不過多解釋具體shiro和spring cache的使用,有興趣的同學請自行搜索相關資料。
按理說這樣的配置在認證的時候應該可以直接使用到inneruserservice中配置的spring cache緩存。
但,問題出現了,當authrealm中依賴了inneruserservice以后,定義在inneruserservice上的spring cache就神奇的失效了。而authrealm不依賴inneruserservice的時候,cache卻運行的好好的。
接下來是問題查找的路徑。
2 解決問題之旅
2.1 spring cache失效的表象原因
首先要找到spring cache失效的表象/直接原因,我們知道spring cache使用spring aop和攔截器的方式攔截定義了特定標注的方法,然后執行特定邏輯。因此其實現依賴于動態代理機制auto-proxy,而經過初步調試發現,當被authrealm依賴以后,inneruserservice就不會被代理了,因此無從進入aop的pointcut,也就是說aop切面失效了!
2.2 從spring cache的集成機制分析深層次原因
為何沒有被代理呢,我們先來確認一下正常情況下什么時候進行代理封裝,這時關于beanpostprocessor的定義浮現腦海,據文檔記載beanpostprocessor允許在bean實例化的前后對其做一些猥瑣的事情,比如代理。我們在beanpostprocessor的實現類中發現了instantiationawarebeanpostprocessor、smartinstantiationawarebeanpostprocessor、abstractautoproxycreator、infrastructureadvisorautoproxycreator這一脈。而反觀@enablecache標注在啟動的時候會@import cachingconfigurationselector,其selectimports方法會返回autoproxyregistrar和proxycachingconfiguration的全類名(我們定義了mode=advicemode.proxy),也就是加載這兩個類。第一個的作用就是注冊infrastructureadvisorautoproxycreator到beandefinitionregistry中。第二個的作用就是注冊了beanfactorycacheoperationsourceadvisor和cacheinterceptor。
因此,當正常情況下,一個添加了spring cache相關標注的bean會在創建后被infrastructureadvisorautoproxycreator基于advisor進行代理增強,代理后便可在攔截器cacheinterceptor中對其方法進行攔截,然后執行cache相關邏輯。此處省略具體處理邏輯,有興趣請參考相關文檔。
所以第一懷疑就是inneruserservice沒有經過infrastructureadvisorautoproxycreator的代理增強。果然調試發現,被authrealm依賴的情況下在inneruserservice的bean實例化時,用于處理該bean的postbeanprocessor明顯比沒被authrealm依賴時少,并且不含有infrastructureadvisorautoproxycreator。
而且,被依賴時會多打出來一行信息:
...................
bean 'iinneruserserviceimpl' of type [shiro.web.inner.service.impl.iinneruserserviceimpl] is not eligible for getting processed by all beanpostprocessors (for example: not eligible for auto-proxying)
...................
據此推斷,可能是inneruserservice啟動時機過早,導致的后面那些beanpostprocessor們來沒來得及實例化及注冊呢。
2.3 beanpostprocessor啟動階段對其依賴的bean造成的影響
首先確認了authrealm也是受害者,因為shirofilter->securitymanager->authrealm的依賴關系導致其不得不提前實例化。表面上的罪魁禍首是shirofilter,但是到底是誰導致的shirofilter預料之外的提前啟動呢。shirofilter與infrastructureadvisorautoproxycreator的具體啟動時機到底是什么時候呢。
又經過一番混天暗地的調試,終于了解了beanpostprocessor的啟動時機。在abstractbeanfactory中維護了beanpostprocessor的列表:
1
|
private final list<beanpostprocessor> beanpostprocessors = new arraylist<beanpostprocessor>(); |
并實現了configurablebeanfactory定義的方法:
1
|
void addbeanpostprocessor(beanpostprocessor beanpostprocessor); |
因此我們首先監控abstractbeanfactory.addbeanpostprocessor(),看看啟動過程中誰調用了該方法來注冊beanpostprocessor。發現實例化及注冊postbeanfactory的階段分為四個:
第一階段是在啟動時調用過程會調用abstractapplicationcontext.refresh(),其中的preparebeanfactory方法中注冊了
applicationcontextawareprocessor、applicationlistenerdetector:
........
beanfactory.addbeanpostprocessor(new applicationcontextawareprocessor(this));
........
beanfactory.addbeanpostprocessor(new applicationlistenerdetector(this));
........
然后在postprocessbeanfactory方法中注冊了webapplicationcontextservletcontextawareprocessor:
1
2
|
beanfactory.addbeanpostprocessor( new webapplicationcontextservletcontextawareprocessor( this )); |
然后在invokebeanfactorypostprocessors方法中調用
其中對已經注冊的beanfactorypostprocessors挨個調用其postprocessbeanfactory方法,其中有一個configurationclasspostprocessor,其postprocessbeanfactory方法中注冊了一個importawarebeanpostprocessor:
1
|
beanfactory.addbeanpostprocessor( new importawarebeanpostprocessor(beanfactory)); |
最后在registerbeanpostprocessors方法中調用
1
|
postprocessorregistrationdelegate.registerbeanpostprocessors(beanfactory, this ); |
在該方法中,首先注冊beanpostprocessorchecker:
該beanpostprocessorchecker就是輸出上面那行信息的真兇,它會在bean創建完后檢查可在當前bean上起作用的beanpostprocessor個數與總的beanpostprocessor個數,如果起作用的個數少于總數,則報出上面那句信息。
然后分成三個階段依次實例化并注冊實現了priorityordered的beanpostprocessor、實現了ordered的beanpostprocessor、沒實現ordered的beanpostprocessor,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
// separate between beanpostprocessors that implement priorityordered, // ordered, and the rest. list<beanpostprocessor> priorityorderedpostprocessors = new arraylist<beanpostprocessor>(); list<beanpostprocessor> internalpostprocessors = new arraylist<beanpostprocessor>(); list<string> orderedpostprocessornames = new arraylist<string>(); list<string> nonorderedpostprocessornames = new arraylist<string>(); for (string ppname : postprocessornames) { if (beanfactory.istypematch(ppname, priorityordered. class )) { beanpostprocessor pp = beanfactory.getbean(ppname, beanpostprocessor. class ); priorityorderedpostprocessors.add(pp); if (pp instanceof mergedbeandefinitionpostprocessor) { internalpostprocessors.add(pp); } } else if (beanfactory.istypematch(ppname, ordered. class )) { orderedpostprocessornames.add(ppname); } else { nonorderedpostprocessornames.add(ppname); } } // first, register the beanpostprocessors that implement priorityordered. sortpostprocessors(priorityorderedpostprocessors, beanfactory); registerbeanpostprocessors(beanfactory, priorityorderedpostprocessors); // next, register the beanpostprocessors that implement ordered. list<beanpostprocessor> orderedpostprocessors = new arraylist<beanpostprocessor>(); for (string ppname : orderedpostprocessornames) { beanpostprocessor pp = beanfactory.getbean(ppname, beanpostprocessor. class ); orderedpostprocessors.add(pp); if (pp instanceof mergedbeandefinitionpostprocessor) { internalpostprocessors.add(pp); } } sortpostprocessors(orderedpostprocessors, beanfactory); registerbeanpostprocessors(beanfactory, orderedpostprocessors); // now, register all regular beanpostprocessors. list<beanpostprocessor> nonorderedpostprocessors = new arraylist<beanpostprocessor>(); for (string ppname : nonorderedpostprocessornames) { beanpostprocessor pp = beanfactory.getbean(ppname, beanpostprocessor. class ); nonorderedpostprocessors.add(pp); if (pp instanceof mergedbeandefinitionpostprocessor) { internalpostprocessors.add(pp); } } registerbeanpostprocessors(beanfactory, nonorderedpostprocessors); // finally, re-register all internal beanpostprocessors. sortpostprocessors(internalpostprocessors, beanfactory); registerbeanpostprocessors(beanfactory, internalpostprocessors); // re-register post-processor for detecting inner beans as applicationlisteners, // moving it to the end of the processor chain (for picking up proxies etc). beanfactory.addbeanpostprocessor( new applicationlistenerdetector(applicationcontext)); |
需要注意的是,除了第一個階段,其他階段同一個階段的beanpostprocessor是在全部實例化完成以后才會統一注冊到beanfactory的,因此,同一個階段的beanpostprocessor及其依賴的bean在實例化的時候是無法享受到相同階段但是先實例化的beanpostprocessor的“服務”的,因為它們還沒有注冊。
從上面調試與源代碼分析,beanpostprocessor的實例化與注冊分為四個階段,第一階段applicationcontext內置階段、第二階段priorityordered階段、第三階段ordered階段、第四階段nonordered階段。而beanpostprocessor同時也是bean,其注冊之前一定先實例化。而且是分批實例化和注冊,也就是屬于同一批的beanpostprocesser全部實例化完成后,再全部注冊,不存在先實例化先注冊的問題。而在實例化的時候其依賴的bean同樣要先實例化。
因此導致一個結果就是,被priorityorderedbeanpostprocessor所依賴的bean其初始化時無法享受到priorityordered、ordered、和nonordered的beanpostprocessor的服務。而被orderedbeanpostprocessor所依賴的bean無法享受ordered、和nonordered的beanpostprocessor的服務。最后被nonorderedbeanpostprocessor所依賴的bean無法享受到nonorderedbeanpostprocessor的服務。
由于infrastructureadvisorautoproxycreator的啟動階段是ordered,因此我們需要確保沒有任何priorityordered和ordered的beanpostprocessor直接或間接的依賴到shirofilter,也就是依賴到我們的inneruserservice。
同時,在priorityordered接口的注解中也提到了該情況:
note: {@code priorityordered} post-processor beans are initialized in
* a special phase, ahead of other post-processor beans. this subtly
* affects their autowiring behavior: they will only be autowired against
* beans which do not require eager initialization for type matching.
2.4 beanpostprocessor在進行依賴的bean注入時,根據bean名稱進行類型檢查時導致的“誤傷”
ok,問題貌似已查明,修改configuration中所有priorityordered和ordered類型的postbeanprocessor的bean配置,使其不再依賴shirofilter。再次啟動,卻發現仍然提前啟動了shirofilter->securitymanager->authrealm->inneruserservice。
百思不得其解,又是一輪昏天暗地的調試,查找shirofilter具體的啟動時機。發現在一個叫做datasourceinitializerpostprocessor的beanpostprocessor實例化的時候,在根據類型獲得其依賴的參數時,對shirofilter執行了初始化。導致后續securitymanager->authrealm->inneruserservice統統提前初始化。但是在datasourceinitializerpostprocessor之前的beanpostprocessor卻沒有。經調試它們是否會導致shirofilter初始化的區別在調用abstractbeanfactory.istypematch方法時出現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public boolean istypematch(string name, resolvabletype typetomatch) throws nosuchbeandefinitionexception{ ..................... // check bean class whether we're dealing with a factorybean. if (factorybean. class .isassignablefrom(beantype)) { //(1)判斷名稱對應的bean是否是一個factorybean,若是factorybean才執行本句 if (!beanfactoryutils.isfactorydereference(name)) { // if it's a factorybean, we want to look at what it creates, not the factory class. beantype = gettypeforfactorybean(beanname, mbd); if (beantype == null ) { return false ; } } } ..................... } |
然后進入abstractautowirecapablebeanfactory.gettypeforfactorybean方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
@override protected class <?> gettypeforfactorybean(string beanname, rootbeandefinition mbd) { string factorybeanname = mbd.getfactorybeanname(); string factorymethodname = mbd.getfactorymethodname(); if (factorybeanname != null ) { if (factorymethodname != null ) { // try to obtain the factorybean's object type from its factory method declaration // without instantiating the containing bean at all. beandefinition fbdef = getbeandefinition(factorybeanname); if (fbdef instanceof abstractbeandefinition) { abstractbeandefinition afbdef = (abstractbeandefinition) fbdef; if (afbdef.hasbeanclass()) { class <?> result = gettypeforfactorybeanfrommethod(afbdef.getbeanclass(), factorymethodname); if (result != null ) { return result; } } } } // if not resolvable above and the referenced factory bean doesn't exist yet, // exit here - we don't want to force the creation of another bean just to // obtain a factorybean's object type... if (!isbeaneligibleformetadatacaching(factorybeanname)) { //(2)判斷該bean對應的factorybeanname是否已經初始化了,如果沒有,就返回。如果有,則繼續 return null ; } } // let's obtain a shortcut instance for an early getobjecttype() call... factorybean<?> fb = (mbd.issingleton() ? getsingletonfactorybeanfortypecheck(beanname, mbd) : getnonsingletonfactorybeanfortypecheck(beanname, mbd)); ...................... } |
其中,有一個重要的判斷:
1
2
3
4
5
6
|
// if not resolvable above and the referenced factory bean doesn't exist yet, // exit here - we don't want to force the creation of another bean just to // obtain a factorybean's object type... if (!isbeaneligibleformetadatacaching(factorybeanname)) { return null ; } |
注解說的很明確,如果名字對應的factorybean所在的factorybean工廠尚未解析并實例化,那就直接退出,不會強制創建該facotrybean工廠,也就是configuration對應的bean。再次調試,果然發現,在先前的beanpostprocessor和datasourceinitializerpostprocessor之間,存在一個lifecyclebeanpostprocessor,而lifecyclebeanpostprocessor是在我們的configuration中顯示定義的,因此,當lifecyclebeanpostprocessor啟動時會導致configuration實例化。
datasourceinitializerpostprocessor和在它之前的beanpostprocessor對shirofilter行為的不同在這里得到了完美的解釋。本質上說datasourceinitializerpostprocessor并不重要,重要的是lifecyclebeanpostprocessor將configuration初始化了。就算不是datasourceinitializerpostprocessor,那另一個beanpostprocessor實例化時同樣會將shirofilter初始化。
最終隱藏大boss查明,解決方案就簡單了,將lifecyclebeanpostprocessor移出到一個單獨的configuration就好了。
3. 總結
3.1 beanpostprocessor啟動順序,以及其對于依賴的bean的影響
beanpostprocessor的啟動時機。分為四個階段,第一階段context內置階段、第二階段priorityordered階段、第三階段ordered階段、第四階段nonordered階段。
而beanpostprocessor同時也是bean,其注冊之前一定先實例化。而且是分批實例化和注冊,也就是屬于同一批的beanpostprocesser全部實例化完成后,再全部注冊,不存在先實例化先注冊的問題。而在實例化的時候其依賴的bean同樣要先實例化。
因此導致一個結果就是,被priorityorderedbeanpostprocessor所依賴的bean其初始化以后無法享受到priorityordered、ordered、和nonordered的beanpostprocessor的服務。而被orderedbeanpostprocessor所依賴的bean無法享受ordered、和nonordered的beanpostprocessor的服務。最后被nonorderedbeanpostprocessor所依賴的bean無法享受到nonorderedbeanpostprocessor的服務。
3.2 注意避免beanpostprocessor啟動時的“誤傷”陷阱
beanpostprocessor實例化時,自動依賴注入根據類型獲得需要注入的bean時,會將某些符合條件的bean(factorybean并且其factorybeanfactory已經實例化的)先實例化,如果此facotrybean又依賴其他普通bean,會導致該bean提前啟動,造成誤傷(無法享受部分beanpostprocessor的后處理,例如典型的auto-proxy)。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/m0_37962779/article/details/78605478