-
Notifications
You must be signed in to change notification settings - Fork 414
Move the creation of bootstrap beans into create function #327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move the creation of bootstrap beans into create function #327
Conversation
@@ -67,54 +71,6 @@ public void initialize(BootstrapRegistry registry) { | |||
ClassUtils.isPresent("org.springframework.cloud.bootstrap.marker.Marker", null)) { | |||
return; | |||
} | |||
// create curator | |||
CuratorFactory.registerCurator(registry, null, true, bootstrapContext -> isEnabled(bootstrapContext.get(Binder.class))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isEnabled
check in the code below always resulted in false
because Boot had not fully loaded configuration properties at this point. None of these registered beans actually returned anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may be missing something here, but didn't the isEnabled
calls actually get executed later, when the beans were instantiated for real, as opposed to when they were registered into the bootstrap context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isEnabled call would be executed as soon as the bean was needed. In most cases that would have happened within ZookeeperFunction.apply
. However the Binder
from the BootstrapContext
in the registerIfAbsent
function is not the same as the one that is used in theZookeeper.apply
method. In the apply
method we are getting the Binder while Boot is in the process of gathering all property sources.
See https://github.com/spring-cloud/spring-cloud-config/blob/main/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java#L240
and
https://github.com/spring-cloud/spring-cloud-config/blob/main/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java#L231
This Binder
contains all resolved property sources at this point that may have not been applied to the BootstrapContext yet. It is very confusing I know but this is the crux as to why I changed this code in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I've been looking at what's going on here and I think I'm starting to understand this problem with the Binder
. It is indeed very complex and confusing, unfortunately. :(
I was wondering, why do we have a "less knowledgeable" Binder
in the BootstrapContext
at that point in time, when a "more knowledgeable" already exists? The Binder
in the bootstrap context seems to get re-registered a number of times by the calls to ConfigDataEnvironment.registerBootstrapBinder
, could it not get updated once more before all the ZooKeeper & Curator beans need to be instantiated? Or is this resolverContext.getBinder()
some specialized one-off instance that would not be usable as the generic bootstrap context Binder
? I'm still trying to understand what the intended lifecycle of all the things involved are. (If you have any relevant documentation you can share on these low-level implementation details, that would be much appreciated. 🙂) I noticed this part of the code is not Spring Cloud anymore, but Spring Boot. Is it possible to make some changes there too, if that would lead to a more elegant solution here, or is that out of the question?
The other idea I had was that we could perhaps register another bean into the BootstrapContext
, just before we register ConfigServerInstanceMonitor
, something from where one could retrieve this "more knowledgeable" Binder
. This could maybe be the ConfigDataLocationResolverContext resolverContext
itself, maybe some new class' instance that is specific to Spring Cloud Config and this use-case. This would allow for the original code to be modified only slightly, e.g.:
registry.registerIfAbsent(ConfigServerInstanceProvider.Function.class, context -> {
if (!isEnabled(context.get(ConfigDataLocationResolverContext.class).getBinder())) {
return (id) -> Collections.emptyList();
}
return context.get(ZookeeperDiscoveryClient.class)::getInstances;
});
This way all the other beans could again be registered into the bootstrap context, like before, and much of the confusion would also be avoided (at least the out-of-the ordinary passing around of the "more knowledgeable" Binder
). Unfortunately this is just an idea at this point, I didn't have time to (dis)prove it yet. Maybe you've considered this already and can save me some time if you know it won't work. 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to make some changes there too, if that would lead to a more elegant solution here, or is that out of the question?
Possibly, but you would need to open an issue in Spring Boot and discuss it with them
The other idea I had was that we could perhaps register another bean into the BootstrapContext, just before we register ConfigServerInstanceMonitor, something from where one could retrieve this "more knowledgeable" Binder. This could maybe be the ConfigDataLocationResolverContext resolverContext itself, maybe some new class' instance that is specific to Spring Cloud Config and this use-case.
I can try this an see if it would work, but it might take a few days
@@ -123,11 +79,19 @@ public void initialize(BootstrapRegistry registry) { | |||
|
|||
// promote beans to context | |||
registry.addCloseListener(event -> { | |||
ZookeeperDiscoveryClient discoveryClient = event.getBootstrapContext().get(ZookeeperDiscoveryClient.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the beans aren't registered in the bootstrap context we need to get them from ZookeeperFunction
to promote them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it an intentional decision not to add these beans into the bootstrap context anymore, or was it done out of necessity only? I.e. was this change done because this is semantically "the right thing" to do, and the old behavior of registering them into the context is considered incorrect, or because there seemed to be no better way to fix the bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was done out of necessity. As I described above the Binder
does not contain the property sources to correctly resolve the isEnabled
call when Boot is trying to load all its configuration, so this code no longer worked.
if (discoveryClient != null) { | ||
event.getApplicationContext().getBeanFactory().registerSingleton("zookeeperServiceDiscovery", | ||
discoveryClient); | ||
} | ||
|
||
CuratorFramework curatorFramework = zookeeperFunction.getCuratorFramework(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to be done by CuratorFactory.registerCurator
but we don't call that anymore so we need to promote it here
@pantherdd would appreciate your eyes on this as well. My last fix didn't end up working, I think this ultimately solves #324 |
@ryanjbaxter Sure, I'll try to find some time for it this week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming a 👍🏻 from @pantherdd
@@ -67,54 +71,6 @@ public void initialize(BootstrapRegistry registry) { | |||
ClassUtils.isPresent("org.springframework.cloud.bootstrap.marker.Marker", null)) { | |||
return; | |||
} | |||
// create curator | |||
CuratorFactory.registerCurator(registry, null, true, bootstrapContext -> isEnabled(bootstrapContext.get(Binder.class))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may be missing something here, but didn't the isEnabled
calls actually get executed later, when the beans were instantiated for real, as opposed to when they were registered into the bootstrap context?
@@ -123,11 +79,19 @@ public void initialize(BootstrapRegistry registry) { | |||
|
|||
// promote beans to context | |||
registry.addCloseListener(event -> { | |||
ZookeeperDiscoveryClient discoveryClient = event.getBootstrapContext().get(ZookeeperDiscoveryClient.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it an intentional decision not to add these beans into the bootstrap context anymore, or was it done out of necessity only? I.e. was this change done because this is semantically "the right thing" to do, and the old behavior of registering them into the context is considered incorrect, or because there seemed to be no better way to fix the bug?
@@ -123,11 +79,19 @@ public void initialize(BootstrapRegistry registry) { | |||
|
|||
// promote beans to context | |||
registry.addCloseListener(event -> { | |||
ZookeeperDiscoveryClient discoveryClient = event.getBootstrapContext().get(ZookeeperDiscoveryClient.class); | |||
ZookeeperFunction zookeeperFunction = ((ZookeeperFunction) event.getBootstrapContext().get(ConfigServerInstanceProvider.Function.class)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cast will throw if someone tries to register their own ConfigServerInstanceProvider.Function
, for example as a workaround. And ZookeeperFunction
is non-public as well as final
, so there's no way to subclass it to work around the ClassCastException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can make it public and non-final then. That would resolve this, agree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be much better if we could completely avoid this "mini-context" class somehow (hence my other ideas in the other comments), but yes, I think doing so would help if ZookeeperFunction
has to stay.
log.warn("Error fetching config server instance from Zookeeper", e); | ||
return Collections.emptyList(); | ||
if (this.discoveryClient == null) { | ||
ZookeeperProperties properties = context.getOrElseSupply(ZookeeperProperties.class, () -> binder.bind(ZookeeperProperties.PREFIX, Bindable.of(ZookeeperProperties.class)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried applying this ZookeeperFunction
fix in my test project, but it didn't quite help in the end. The problem was that my customization code, that registers my ZookeeperProperties
instance, had the same isEnabled
condition as Spring itself, because I wanted to have the same activation behavior. But this creates the same problem with the Binder
; I would need access to the "more knowledgeable" one when I register my custom beans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can safely assume at the point where the beans are being requested from the bootstrap context that we have already done the isEnabled check (or we wouldn't be requesting them) so you shouldn't have to repeat that check for your custom beans
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I guess that sounds reasonable. However I was simply copying that approach from ZookeeperConfigServerBootstrapper.initialize
and CuratorFactory.registerCurator
– should the isEnabled
guard be removed from all of the bootstrap context bean registrations except for a few (e.g. CuratorFramework
)? I'm trying to be as consistent with the framework's internals as possible.
The change I made in #317 which registered a new
ConfigSeverInstanceProvider.Function
only partially worked. All the beans that were registered in theinitialize
method really didn't do anything anymore. They all check if the right properties are enabled but that always resulted infalse
because the properties are not set in the environment when Boot initially load configuration.Further more the new
ConfigServerInstanceProvider.Function
did not check if existing beans were already registered. I attempted to fix that in #325 but that fix did not work as I thought it did.This fix should truly fix #324