服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|JavaScript|易语言|

服务器之家 - 编程语言 - Java教程 - 浅谈BeanPostProcessor加载次序及其对Bean造成的影响分析

浅谈BeanPostProcessor加载次序及其对Bean造成的影响分析

2021-07-29 11:40不动明王1984 Java教程

这篇文章主要介绍了浅谈BeanPostProcessor加载次序及其对Bean造成的影响分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

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方法中调用

 

复制代码 代码如下:
postprocessorregistrationdelegate.invokebeanfactorypostprocessors(beanfactory, getbeanfactorypostprocessors());

 

其中对已经注册的beanfactorypostprocessors挨个调用其postprocessbeanfactory方法,其中有一个configurationclasspostprocessor,其postprocessbeanfactory方法中注册了一个importawarebeanpostprocessor:

?
1
beanfactory.addbeanpostprocessor(new importawarebeanpostprocessor(beanfactory));

最后在registerbeanpostprocessors方法中调用

?
1
postprocessorregistrationdelegate.registerbeanpostprocessors(beanfactory, this);

在该方法中,首先注册beanpostprocessorchecker:

 

复制代码 代码如下:
beanfactory.addbeanpostprocessor(new beanpostprocessorchecker(beanfactory, beanprocessortargetcount));

 

该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

延伸 · 阅读

精彩推荐