Browse Source

Ignore child context events in DeferredRepositoryInitializationListener.

Compare the event's ApplicationContext.getBeanFactory() with the listener's
BeanFactory to filter out child context events, preventing premature
repository initialization.

Closes spring-projects/spring-data-commons#3459

Signed-off-by: seongjun-rpls <seongjun.ha@rapportlabs.kr>
pull/3460/head
seongjun.ha 1 month ago
parent
commit
385d587483
  1. 11
      src/main/java/org/springframework/data/repository/config/DeferredRepositoryInitializationListener.java
  2. 136
      src/test/java/org/springframework/data/repository/config/DeferredRepositoryInitializationListenerUnitTests.java

11
src/main/java/org/springframework/data/repository/config/DeferredRepositoryInitializationListener.java

@ -19,7 +19,9 @@ import org.apache.commons.logging.Log; @@ -19,7 +19,9 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.data.repository.Repository;
@ -45,6 +47,15 @@ class DeferredRepositoryInitializationListener implements ApplicationListener<Co @@ -45,6 +47,15 @@ class DeferredRepositoryInitializationListener implements ApplicationListener<Co
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
// Ignore events from child contexts (e.g., Spring Cloud OpenFeign, LoadBalancer)
// to avoid premature repository initialization.
// See https://github.com/spring-projects/spring-boot/commit/708cbd72942e65906ea32ab3204af6c4e58a7314
if (context instanceof ConfigurableApplicationContext cac && cac.getBeanFactory() != beanFactory) {
return;
}
logger.info("Triggering deferred initialization of Spring Data repositories…");
beanFactory.getBeansOfType(Repository.class);

136
src/test/java/org/springframework/data/repository/config/DeferredRepositoryInitializationListenerUnitTests.java

@ -0,0 +1,136 @@ @@ -0,0 +1,136 @@
/*
* Copyright 2018-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.config;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.repository.Repository;
/**
* Unit tests for {@link DeferredRepositoryInitializationListener}.
*
* @author Oliver Drotbohm
* @author seongjun-rpls
*/
@ExtendWith(MockitoExtension.class)
class DeferredRepositoryInitializationListenerUnitTests {
@Mock ListableBeanFactory beanFactory;
@Test // GH-3459
void triggersInitializationOnMatchingContextEvent() {
var context = new GenericApplicationContext();
context.refresh();
// Create listener with the real context's BeanFactory
var listener = new DeferredRepositoryInitializationListener(context.getBeanFactory());
// Fire event from the same context
listener.onApplicationEvent(new ContextRefreshedEvent(context));
// The test verifies no exception is thrown and listener completes normally
// Since we're using real context, we can't easily mock getBeansOfType
context.close();
}
@Test // GH-3459
void ignoresEventsFromChildContext() {
// Mock beanFactory that should NOT be called
ListableBeanFactory mockBeanFactory = mock(ListableBeanFactory.class);
var listener = new DeferredRepositoryInitializationListener(mockBeanFactory);
// Create a real context (will have different beanFactory)
var context = new GenericApplicationContext();
context.refresh();
// Fire event - should be ignored because context.getBeanFactory() != mockBeanFactory
listener.onApplicationEvent(new ContextRefreshedEvent(context));
// Verify the mock was never called
verify(mockBeanFactory, never()).getBeansOfType(Repository.class);
context.close();
}
@Test // GH-3459
void triggersInitializationOnParentContextEvent() {
// Create a mock beanFactory
DefaultListableBeanFactory mockBeanFactory = mock(DefaultListableBeanFactory.class);
// Create a mock ConfigurableApplicationContext
ConfigurableApplicationContext mockContext = mock(ConfigurableApplicationContext.class);
when(mockContext.getBeanFactory()).thenReturn(mockBeanFactory);
// Create listener with the same mock beanFactory
var listener = new DeferredRepositoryInitializationListener(mockBeanFactory);
// Fire event from the mock context
listener.onApplicationEvent(new ContextRefreshedEvent(mockContext));
// Verify getBeansOfType WAS called
verify(mockBeanFactory).getBeansOfType(Repository.class);
}
@Test // GH-3459
void ignoresEventsFromSiblingChildContexts() {
// Create a mock beanFactory for the "parent" listener
DefaultListableBeanFactory mockParentBeanFactory = mock(DefaultListableBeanFactory.class);
// Create listener as if registered in a parent context
var listener = new DeferredRepositoryInitializationListener(mockParentBeanFactory);
// Create real child contexts (simulates Feign-like scenario)
var childContext1 = new GenericApplicationContext();
childContext1.refresh();
var childContext2 = new GenericApplicationContext();
childContext2.refresh();
// Fire events from child contexts - should be ignored
listener.onApplicationEvent(new ContextRefreshedEvent(childContext1));
listener.onApplicationEvent(new ContextRefreshedEvent(childContext2));
// Verify that getBeansOfType was NOT called for child context events
verify(mockParentBeanFactory, never()).getBeansOfType(Repository.class);
// Now create a mock parent context that returns our mock beanFactory
ConfigurableApplicationContext mockParentContext = mock(ConfigurableApplicationContext.class);
when(mockParentContext.getBeanFactory()).thenReturn(mockParentBeanFactory);
// Fire event from "parent" context
listener.onApplicationEvent(new ContextRefreshedEvent(mockParentContext));
// Verify that getBeansOfType WAS called exactly once for parent context
verify(mockParentBeanFactory, times(1)).getBeansOfType(Repository.class);
childContext1.close();
childContext2.close();
}
}
Loading…
Cancel
Save