This is the 23rd day of my participation in the August More Text Challenge.More challenges in August

One, foreword

I recently ran into a curious issue with SpringBoot’s @conditionalonbean. ConditionalOnBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation @conditionalonBean annotation ConditionalOnBean (types: ***; SearchStrategy: All) did not find any beans of type ***. Let’s analyze this problem together.

Ii. Brief introduction of the problem

First, let’s take a closer look at the problem. We have introduced RedisClientConfiguration in the JAR via AutoConfiguration, where a Bean is annotated by way of @conditionalonBean (figure below). RedicClient is initialized to the Spring container only when a Cluster instance exists.

We then defined a Cluster instance in the Spring container using XML in the business code project, but this instance is implemented using FactoryBean. Namely in the image below ReloadableJimClientFactoryBean is a FactoryBean, its actually received is a Cluster instance.

We get an error during startup that the RedisClient instance cannot be loaded because the Cluster instance does not exist.

In code engineering, we defined the Cluster instance in XML, but why can’t SpringBoot find the instance in OnCondition during startup? Next, we will divide it from the source code level.

Third, problem analysis

1. Cause analysis

First we find ConditionalOnBean source. It’s in the collectBeanNamesForType of the OnBeanCondition. This is where SpringBoot looks for the corresponding Bean instance through the Cluster type, and does not instantiate it if it cannot find the corresponding Bean. We can see through the debug at this point it is unable to pass the beanFactory. The getBeanNamesForType get Bean instance to the Cluster.

Then we trace the source code to obtain the specific code address:

DefaultListableBeanFactory.getBeanNamesForType 

-> DefaultListableBeanFactory.doGetBeanNamesForType 

-> AbstractBeanFactory.isTypeMatch 

-> AbstractAutowireCapableBeanFactory.getTypeForFactoryBean

Then we jump the source location, found through ReloadableJimClientFactoryBean to obtain the actual type, access to a? That is, it cannot match the required type Cluster. ResolvableType.NONE is returned, indicating that no Cluster type Bean was found.

So the question comes, why from ReloadableJimClientFactoryBean instance to get is a specific type? What about clusters that are not implemented? The problem with our view ReloadableJimClientFactoryBean source. It implements a FactoryBean that does not show the type of the specified generic, and Spring cannot get the type of its implementation.

So we try to achieve a ReloadableJimClientFactoryBean class, and let it show specify the type of the corresponding generic. The code is as follows:

We then register the class in XML and start the project. No error is reported after startup, so we debug again to check the key part of the parameter. First AbstractAutowireCapableBeanFactory. GetTypeForFactoryBean, it could obtain the real to the FactoryBean corresponding type, and the type of Cluster.

Then we can see in the OnBeanCondition collectBeanNamesForType, can through the beanFactory getBeanNamesForType obtain corresponding Cluster instance. So you can instantiate RedisClient normally.

2. Problem summary

The reason for this problem is that the code is not written according to the specification. Normally all generics need to be undefined if the type is known.

You can see that if the class of a FactoryBean does not specify a generic type, Spring cannot get the type of the actual instance of that FactoryBean. ConditionalOnBean cannot find the corresponding Bean instance by type, and ConditionalOnBean annotated Bean instances cannot be instantiated.

Fourth, the extension problem

In the process of locating this issue, I came across another issue that caused ConditionalOnBean not to work. We annotated a Bean in the business code (non-JAR) by way of @conditionalonBean (figure below). RedicClient is initialized to the Spring container only when a Cluster instance exists. At the same time, we inject a class like JimClientFactoryBean through XML in the project (this class does not have the FactoryBean generic type problems described above).

I then start the code and find that I can’t instantiate the Person class properly.

By analyzing the loading sequence of Configuration from the source code level (as shown in the figure below), it can be seen that BeanMethod is loaded before XML in Configuration, so the instance defined in XML cannot be read when BeanMethod is loaded. Source path: ConfigurationClassBeanDefinitionReader loadBeanDefinitionsForConfigurationClass

Instead of defining JimClientFactoryBean in XML, we define JimClientFactoryBean in the Application class (below), and there are no errors and the project starts normally.

Here’s a question for you to think about. If we write the code as follows, will the project start normally?

Five, the convention

If you have any questions or comments about this article, please add lifeofCoder.