Remove CachedContentServiceCondition to prevent early bean instantiation - #6725
Open
zxcv9203 wants to merge 1 commit into
Open
Remove CachedContentServiceCondition to prevent early bean instantiation#6725zxcv9203 wants to merge 1 commit into
CachedContentServiceCondition to prevent early bean instantiation#6725zxcv9203 wants to merge 1 commit into
Conversation
The condition called getBean(GoogleGenAiChatModel.class) during REGISTER_BEAN condition evaluation, eagerly instantiating the chat model dependency chain inside the BeanFactoryPostProcessor phase. A user-defined Client bean created that early receives unresolved `@Value` placeholders because PropertySourcesPlaceholderConfigurer has not registered its embedded value resolver yet. The nullability check the condition attempted is runtime information and is already handled by the `@Nullable` bean method returning null, which registers a NullBean that behaves as an absent bean wherever the value is consumed. See spring-projects#4447 Signed-off-by: zxcv9203 <zxcv9203@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug description
Defining a custom
com.google.genai.Clientbean whose@Beanmethod takes a@Value("${spring.ai.google.genai.api-key}")parameter injects the raw placeholder string instead of the property value.Nothing throws and the application starts normally, so it only surfaces later when Gemini rejects the literal
${spring.ai.google.genai.api-key}as an invalid API key. Reading the same property throughEnvironmentin that method works, so the problem is placeholder resolution rather than the property itself.It reproduces on 1.1.8, 2.0.0 and 2.0.1-SNAPSHOT.
Root cause
The cause is
CachedContentServiceCondition#getMatchOutcomecallingcontext.getBeanFactory().getBean(GoogleGenAiChatModel.class).Bean-method conditions are evaluated in the
REGISTER_BEANphase insideConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry, which runs beforePropertySourcesPlaceholderConfigurerhas registered its embedded value resolver. ThatgetBeancall eagerly instantiates the chat model dependency chain, so the user'sClientbean is created there, its@Valueparameter goes throughAbstractBeanFactory#resolveEmbeddedValuewith no resolver present, and the placeholder comes back unchanged.The eager chain then fails because a later constructor argument is not registered yet,
ToolCallingManageron 2.0.x andRetryTemplateon 1.1.x, but the condition swallows that incatch (Exception)and reports a no-match. The poisoned singleton stays in the context, andgoogleGenAiCachedContentServiceis never registered in a real application, so the condition never achieves what it was written for.Stack captured at the user's
@BeanmethodFix
This PR removes it. What the condition tries to check, whether
chatModel.getCachedContentService()is non-null, is runtime information derived from the injectedClientinstance, so noConditionimplementation can answer it without instantiating beans.The
@Beanmethod already returns@Nullableand handles that case at the right time. Returning null registers aNullBean, which behaves as an absent bean wherever the value is consumed, sogetBeansOfTypefilters it out,ObjectProvider#getIfAvailable()returns null and required injection still fails as before.Two things change observably. The bean is now actually registered when the chat model exists and
spring.ai.google.genai.chat.enable-cached-contentis not set tofalse, which never happened before. The chat model itself is unaffected since it only exposes the service through a getter and never reads the field internally.And when the service is null, as with a mocked
Clientwithout cache surfaces,containsBeanandgetBeanNamesForTypenow report the bean while by-namegetBeanreturns null instead of throwing. Value-based lookups behave exactly as they did.Nothing inside spring-ai injects that bean or references it in a
@ConditionalOnBean, so no internal wiring is affected. The bean exists for applications to autowire, which is what the reference documentation shows for programmatic cache management, and that has silently failed until now because the condition meant the bean was never registered.Testing
./mvnw -Dmaven.build.cache.enabled=false clean packagepasses with 5,555 tests and no failures, and the google-genai autoconfigure module runs 41 tests clean.The regression test added here,
userDefinedClientBeanReceivesResolvedValuePlaceholders, fails onmainwithexpected: "test-key" but was: "${spring.ai.google.genai.api-key}"and passes with the change. Reverting only the source changes while keeping the tests also makescachedContentServiceBeanIsCreatedWhenChatModelExistsfail on the bean it now asserts, so both assertions do exercise this fix../mvnw process-sourcesleaves the tree clean.