Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Example Analysis of BeanPostProcessor loading sequence and its influence on Bean

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article shares with you the content of a sample analysis of the BeanPostProcessor load order and its impact on Bean. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.

Preface

BeanPostProcessor is a factory hook that allows the Spring framework to make custom changes to a new Bean instance when it is created. For example, it is wrapped by checking its labeled interface or using a proxy. The application context automatically detects BeanPostProcessor from the Bean definition and applies them to any Bean that is subsequently created.

The factory of the normal Bean object allows you to register the post-processors in the program and apply it to all Bean subsequently created in this factory. Typical scenarios such as post-processors use the postProcessBeforeInitialization method to populate the Bean; through feature interfaces or other similar ways, while the postProcessAfterInitialization method is generally used to create proxies for the created Bean.

BeanPostProcessor itself is also a Bean, and generally speaking, its instantiation time is earlier than ordinary Bean, but BeanPostProcessor also relies on some Bean, which leads to some Bean instantiation earlier than BeanPostProcessor, which can lead to some problems. I encountered it recently when dealing with the integration of shiro and spring cache, and the result was that spring cache didn't work. Now show the problem scenario, the search process and the solution.

1 problem scenario

We intend to integrate shiro and spring cache in the project, use spring cache to manage the cache, and also include the query of user information during shiro authentication. The service is layered in the project, and the out layer is responsible for the permissions and session,inner layer to focus on transactions and caching and interact with DAO. The two layers can also be easily extended to RPC or micro-service mode. Therefore, innerUserService is relied on in the authRealm of shiro, and the annotation of spring cache is configured in innerUserService, using cache for caching. The configuration is as follows (excerpt important parts):

Bean (name= "shiroFilter") public ShiroFilterFactoryBean shiroFilter (@ Qualifier ("securityManager") SecurityManager manager) {ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean (); bean.setSecurityManager (manager);. Return bean;} / / configure core security transaction manager @ 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 has been loaded -") DefaultWebSecurityManager manager=new DefaultWebSecurityManager (); manager.setRealm (authRealm); manager.setSessionManager (sessionManager); manager.setRememberMeManager (rememberMeManager); manager.setCacheManager (cacheManager); return manager;} / / configure custom permission logger @ 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 is a custom shiro AuthorizingRealm used to perform authentication and authorization. Its implementation relies on innerUserService to find user information from the library. The sample code is as follows:

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 roleNames = new HashSet () Set permissions = new HashSet (); 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 () / / get user name String password = new String ((char []) token.getCredentials ()); / / get password 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 ());}

The annotation of spring cache is configured in innerUserService. The sample code is as follows:

@ Servicepublic 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) is marked on the configuration file to start spring cache. Here is not too much to explain the specific use of shiro and spring cache, interested students, please search the relevant information.

In theory, such a configuration should be able to use the spring cache cache configured in innerUserService directly during authentication.

However, the problem arises, when the authRealm relies on innerUserService, the spring cache defined on innerUserService is miraculously invalid. When authRealm does not rely on innerUserService, cache runs well.

Next is the path to find the problem.

2 problem-solving trip

2.1 apparent causes of spring cache failure

First of all, to find the apparent / direct cause of spring cache failure, we know that spring cache uses Spring AOP and interceptors to intercept methods that define specific annotations, and then executes specific logic. Therefore, its implementation depends on the dynamic proxy mechanism auto-proxy, and after preliminary debugging, it is found that when dependent by authRealm, innerUserService will not be proxied, so there is no way to enter the pointcut of AOP, that is to say, the AOP aspect is invalid!

2.2 analyze the deep-seated reasons from the integration mechanism of spring cache

Why not be proxied? let's first confirm when proxy encapsulation is normally carried out. Then the definition of BeanPostProcessor comes to mind. It is documented that BeanPostProcessor allows some obscene things, such as proxies, to be done to Bean before and after instantiation. We found InstantiationAwareBeanPostProcessor, SmartInstantiationAwareBeanPostProcessor, AbstractAutoProxyCreator, InfrastructureAdvisorAutoProxyCreator in the implementation class of BeanPostProcessor. The @ enableCache tag, by contrast, starts with @ import CachingConfigurationSelector, and its selectImports method returns the full class names of AutoProxyRegistrar and ProxyCachingConfiguration (we defined mode=AdviceMode.PROXY), that is, loading these two classes. The first function is to register InfrastructureAdvisorAutoProxyCreator into BeanDefinitionRegistry. The second function is to register BeanFactoryCacheOperationSourceAdvisor and CacheInterceptor.

Therefore, under normal circumstances, a bean with spring cache-related annotations will be enhanced by the InfrastructureAdvisorAutoProxyCreator proxy based on advisor after creation, and the proxy can then intercept its methods in the interceptor CacheInterceptor, and then execute the cache-related logic. The specific processing logic is omitted here. If you are interested, please refer to the relevant documentation.

So the first suspicion is that innerUserService is not enhanced by InfrastructureAdvisorAutoProxyCreator's agent. Sure enough, debugging found that in the case of authRealm dependency, when the Bean of InnerUserService is instantiated, the PostBeanProcessor used to process the Bean is significantly less than when it is not dependent on authRealm, and does not contain InfrastructureAdvisorAutoProxyCreator.

Also, an extra line of information will be typed when you are dependent:

.

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)

.

Inferred from this, it may be that the timing of innerUserService startup is too early, resulting in those BeanPostProcessor people who do not have time to instantiate and register.

2.3 impact of the startup phase of BeanPostProcessor on the Bean on which it depends

First of all, it is confirmed that authRealm is also a victim, because the dependency of shiroFilter- > SecurityManager- > authRealm causes it to be instantiated in advance. The apparent culprit is shiroFilter, but who caused the unexpected early start of shiroFilter? When exactly will shiroFilter and InfrastructureAdvisorAutoProxyCreator be launched?

After a lot of debugging in the dark, I finally understand the starting time of BeanPostProcessor. The list of BeanPostProcessor is maintained in AbstractBeanFactory:

Private final List beanPostProcessors = new ArrayList ()

And implement the method defined by ConfigurableBeanFactory:

Void addBeanPostProcessor (BeanPostProcessor beanPostProcessor)

So we first monitor AbstractBeanFactory.addBeanPostProcessor () to see who called this method to register BeanPostProcessor during startup. There are four stages of discovery instantiation and registration of PostBeanFactory:

The first stage is that the calling procedure calls AbstractApplicationContext.refresh () at startup, where the prepareBeanFactory method registers the

ApplicationContextAwareProcessor 、 ApplicationListenerDetector:

.

BeanFactory.addBeanPostProcessor (new ApplicationContextAwareProcessor (this))

.

BeanFactory.addBeanPostProcessor (new ApplicationListenerDetector (this))

.

Then register WebApplicationContextServletContextAwareProcessor in the postProcessBeanFactory method:

BeanFactory.addBeanPostProcessor (new WebApplicationContextServletContextAwareProcessor (this))

Then call it in the invokeBeanFactoryPostProcessors method

The copy code is as follows:

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors (beanFactory, getBeanFactoryPostProcessors ())

The postProcessBeanFactory method is called one by one on the registered BeanFactoryPostProcessors, including a ConfigurationClassPostProcessor, and an ImportAwareBeanPostProcessor is registered in its postProcessBeanFactory method:

BeanFactory.addBeanPostProcessor (new ImportAwareBeanPostProcessor (beanFactory))

Finally, call in the registerBeanPostProcessors method

PostProcessorRegistrationDelegate.registerBeanPostProcessors (beanFactory, this)

In this method, register BeanPostProcessorChecker first:

The copy code is as follows:

BeanFactory.addBeanPostProcessor (new BeanPostProcessorChecker (beanFactory, beanProcessorTargetCount))

The BeanPostProcessorChecker is the real killer of the above line of information. After the Bean is created, it will check the number of BeanPostProcessor and the total number of BeanPostProcessor that can work on the current Bean, and report the above message if the number is less than the total.

Then instantiate and register the BeanPostProcessor that implements PriorityOrdered, the BeanPostProcessor that implements Ordered, and the BeanPostProcessor that does not implement Ordered in three phases. The code is as follows:

/ / Separate between BeanPostProcessors that implement PriorityOrdered, / / Ordered, and the rest. List priorityOrderedPostProcessors = new ArrayList (); List internalPostProcessors = new ArrayList (); List orderedPostProcessorNames = new ArrayList (); List nonOrderedPostProcessorNames = new ArrayList (); 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 orderedPostProcessors = new ArrayList (); 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 nonOrderedPostProcessors = new ArrayList (); 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))

It should be noted that, except for the first stage, the BeanPostProcessor of the same stage in other stages will not be registered with beanFactory until all instantiations are completed. Therefore, the BeanPostProcessor of the same stage and its dependent Bean cannot enjoy the "services" of the same stage but instantiated BeanPostProcessor at the time of instantiation, because they are not yet registered.

From the above debugging and source code analysis, the instantiation and registration of BeanPostProcessor is divided into four stages: the first stage applicationContext built-in stage, the second stage priorityOrdered stage, the third stage Ordered stage, the fourth stage nonOrdered stage. While BeanPostProcessor is also a Bean, it must be instantiated before it is registered. And it is batch instantiation and registration, that is, all instantiations of BeanPostProcesser belonging to the same batch are completed, and then all are registered, so there is no problem of instantiating first and registering first. When instantiating, the Bean on which it depends is also instantiated first.

As a result, Bean, which is dependent on PriorityOrderedBeanPostProcessor, cannot be initialized with the services of PriorityOrdered, Ordered, and nonOrdered's BeanPostProcessor. Bean, which is relied on by OrderedBeanPostProcessor, cannot enjoy the services of Ordered and nonOrdered's BeanPostProcessor. In the end, Bean, which is relied on by nonOrderedBeanPostProcessor, can not enjoy the service of nonOrderedBeanPostProcessor.

Since the startup phase of InfrastructureAdvisorAutoProxyCreator is Ordered, we need to make sure that there is no priorityOrdered or Ordered BeanPostProcessor that directly or indirectly depends on shiroFilter, that is, our innerUserService.

At the same time, this situation is also mentioned in the comments on the PriorityOrdered interface:

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 "accidental injury" caused by type checking based on the Bean name when BeanPostProcessor performs dependent Bean injection

OK, the problem seems to have been identified, modify the Bean configuration of all PriorityOrdered and Ordered types of PostBeanProcessor in Configuration so that it is no longer dependent on shiroFilter. Start again, only to find that shiroFilter- > SecurityManager- > authRealm- > innerUserService is still started ahead of time.

Puzzled, it is another round of dark debugging to find out the specific start-up time of shiroFilter. It is found that when a BeanPostProcessor called dataSourceInitializerPostProcessor is instantiated, the shiroFilter is initialized when its dependent parameters are obtained based on the type. This causes all subsequent SecurityManager- > authRealm- > innerUserService to be initialized in advance. But the BeanPostProcessor before dataSourceInitializerPostProcessor did not. The difference in whether or not they cause shiroFilter initialization occurs when the AbstractBeanFactory.isTypeMatch method is called:

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) determines whether the Bean corresponding to the name is a FactoryBean. If FactoryBean executes the sentence 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;}}. }

Then enter the AbstractAutowireCapableBeanFactory.getTypeForFactoryBean method:

@ 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) determines whether the factoryBeanName corresponding to the bean has been initialized, and if not, returns. If so, continue with return null;} / / Let's obtain a shortcut instance for an early getObjectType () call... FactoryBean fb = (mbd.isSingleton ()? GetSingletonFactoryBeanForTypeCheck (beanName, mbd): getNonSingletonFactoryBeanForTypeCheck (beanName, mbd); }

Among them, there is an important judgment:

/ / 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;}

The note makes it clear that if the factoryBean factory where the factoryBean for the name is located has not been resolved and instantiated, it will exit directly and will not force the creation of the facotryBean factory, that is, the Bean for Configuration. Debug again, and sure enough, there is a lifecycleBeanPostProcessor between the previous BeanPostProcessor and dataSourceInitializerPostProcessor, and lifecycleBeanPostProcessor shows the definition in our Configuration, so when lifecycleBeanPostProcessor starts, it causes Configuration instantiation.

The difference in shiroFilter behavior between dataSourceInitializerPostProcessor and BeanPostProcessor before it is perfectly explained here. In essence, dataSourceInitializerPostProcessor is not important, the important thing is that lifecycleBeanPostProcessor initializes Configuration. Even if it is not dataSourceInitializerPostProcessor, the shiroFilter is also initialized when another BeanPostProcessor is instantiated.

Finally hide the big BOSS and find out that the solution is simple, just move the lifecycleBeanPostProcessor out to a separate Configuration.

3. Summary

3.1Boot sequence of BeanPostProcessor and its effect on dependent Bean

The timing of starting BeanPostProcessor. It is divided into four stages: the first stage context built-in stage, the second stage priorityOrdered stage, the third stage Ordered stage, the fourth stage nonOrdered stage.

While BeanPostProcessor is also a Bean, it must be instantiated before it is registered. And it is batch instantiation and registration, that is, all instantiations of BeanPostProcesser belonging to the same batch are completed, and then all are registered, so there is no problem of instantiating first and registering first. When instantiating, the Bean on which it depends is also instantiated first.

As a result, Bean, which is dependent on PriorityOrderedBeanPostProcessor, cannot enjoy the services of PriorityOrdered, Ordered, and nonOrdered's BeanPostProcessor after initialization. Bean, which is relied on by OrderedBeanPostProcessor, cannot enjoy the services of Ordered and nonOrdered's BeanPostProcessor. In the end, Bean, which is relied on by nonOrderedBeanPostProcessor, can not enjoy the service of nonOrderedBeanPostProcessor.

3.2 pay attention to avoid the trap of "accidental injury" during BeanPostProcessor startup

When BeanPostProcessor is instantiated, when automatic dependency injection obtains the Bean that needs to be injected according to the type, it will instantiate some eligible Bean (FactoryBean and its FactoryBeanFactory has been instantiated) first. If this FacotryBean relies on other ordinary Bean, it will cause the Bean to start ahead of time, resulting in false injury (unable to enjoy the post-processing of some BeanPostProcessor, such as typical auto-proxy).

Thank you for reading! On the "BeanPostProcessor loading order and its impact on Bean example analysis" this article is shared here, I hope the above content can be of some help to you, so that you can learn more knowledge, if you think the article is good, you can share it out for more people to see it!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report