Affects: \6.1.4
We have encountered an issue where RepositoryFactoryInformation is no longer autowired correctly.
E.g.
@Service
public class MyService {
private final List<RepositoryFactoryInformation<?, ?>> repositoryFactoryInformations;
private final List<JpaRepositoryFactoryBean<?, ?, ?>> jpaRepositoryFactoryBeans;
JpaRepositoryFactoryBean is found, but RepositoryFactoryInformation is not, although JpaRepositoryFactoryBean also implements RepositoryFactoryInformation.
A small project with a more complete description and reproduction is available here: https://github.com/Pinusar/RepositoryFactoryInformationBug
The issue might be related to this commit: https://github.com/spring-projects/spring-framework/commit/0e9eab55ceff1a7b4f8720f2c3725aaac2bedb48#diff-bc3adc4d2ff654e867247e2887b0f6e738b2d07bc55f6ff45328d406be53c74f
Comment From: jhoeller
Could you indicate more specifically between which versions you see this regression: from 5.3.x to 6.1.4, I suppose?
To be on the safe side, you can also try against 6.1.5 even though I do not expect a difference there. The current behavior is present since 6.0 already and has not changed recently.
Comment From: Pinusar
I just tried this also with Spring Framework 6.1.5 but behavior stayed the same.
And the regression seems to have appeared between versions 6.0.17 and 6.1.1
Comment From: quaff
I confirm it works with Spring Boot 3.1.9/Spring Framework 6.0.17 but failed with Spring Boot 3.2.3/Spring Framework 6.1.4. Here is a minimal reproducer:
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
import org.springframework.test.context.ContextConfiguration;
import jakarta.persistence.Entity;
@DataJpaTest
@EnableJpaRepositories(basePackageClasses = InjectTests.TestEntityRepository.class, considerNestedRepositories = true)
@EntityScan(basePackageClasses = InjectTests.TestEntity.class)
@ContextConfiguration(classes = InjectTests.class)
class InjectTests {
@Autowired
private List<RepositoryFactoryInformation<?, ?>> repositoryFactoryInformations;
@Autowired
private List<JpaRepositoryFactoryBean<?, ?, ?>> jpaRepositoryFactoryBeans;
@Test
void test() {
assertThat(repositoryFactoryInformations).isEqualTo(jpaRepositoryFactoryBeans);
}
interface TestEntityRepository extends JpaRepository<TestEntity, Long> {
}
@Entity
static class TestEntity extends AbstractPersistable<Long> {
}
}
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.3'
id 'io.spring.dependency-management' version 'latest.release'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'com.h2database:h2'
}
tasks.named('test') {
useJUnitPlatform()
}
Comment From: murkaje
Created another reproducer using spring-context and replicating the behavior of spring-data-commons. The issue is present in spring-beans 6.0.0 until latest (6.1.5) and works fine in spring-beans 5.x versions e.g. 5.3.33
This issue started happening because spring-data-commons is generating bean definitions for FactoryBeans with targetType being set. This seems to be the commit that introduced the change: https://github.com/spring-projects/spring-data-commons/commit/98f20a4457ace08348a8e568eaed2c451e127699
Note the line
beanDefinition.setTargetType(getRepositoryInterface(configuration));
The reproducer:
@Configuration
public class Main {
public interface GenericIntf<T> {}
// Default bean definition doesn't have targetType set so won't reproduce
// @Component
public static class MyFactoryBean<T extends CharSequence> implements FactoryBean<T>, GenericIntf<T> {
@Override
public T getObject() { return (T) "bean"; }
@Override
public Class<?> getObjectType() { return String.class; }
}
@Component
public record FactoryHolder(List<GenericIntf<?>> factoryBeans) { // Inject site is not FactoryBean
public FactoryHolder {
System.out.println(factoryBeans);
}
}
@Component // Uncomment and use @Component on MyFactoryBean to see expected behavior
public static class BFPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
RootBeanDefinition rbd = new RootBeanDefinition();
rbd.setBeanClass(MyFactoryBean.class);
// Important: Replicate org.springframework.data.repository.config.RepositoryConfigurationDelegate#registerRepositoriesIn
// behavior of setting targetType, required to hit other branch in org.springframework.beans.factory.support.GenericTypeAwareAutowireCandidateResolver.checkGenericTypeMatch
rbd.setTargetType(ResolvableType.forClassWithGenerics(MyFactoryBean.class, String.class));
((BeanDefinitionRegistry) beanFactory).registerBeanDefinition("myBean", rbd);
System.out.println("Registered FactoryBean def: " + rbd);
}
}
public static void main(String[] args) {
new AnnotationConfigApplicationContext(Main.class).start();
}
}
The issue seems to happen due to the change https://github.com/spring-projects/spring-framework/commit/0e9eab55ceff1a7b4f8720f2c3725aaac2bedb48#diff-bc3adc4d2ff654e867247e2887b0f6e738b2d07bc55f6ff45328d406be53c74f where the following check is done
if (resolvedClass != null && FactoryBean.class.isAssignableFrom(resolvedClass)) {
Class<?> typeToBeMatched = dependencyType.resolve();
if (typeToBeMatched != null && !FactoryBean.class.isAssignableFrom(typeToBeMatched)) {
targetType = targetType.getGeneric();
The bean candidate MyFactoryBean is a FactoryBean so it continues.
The injection site expects the interface GenericIntf which doesn't extend FactoryBean.
Now it sets targetType to the generic parameter type of MyFactoryBean<String>, so targetType = String.class. Afterwards the check dependencyType.isAssignableFrom(targetType) no longer passes as GenericIntf is not assignable from String.
However without the assignment the check would read (GenericIntf).isAssignableFrom(MyFactoryBean) which would be true and the injection happens as in 5.x versions.
Comment From: snicoll
@murkaje I haven't tried it yet (it would be better to share something we can clone or download rather than having to copy paste code in text from a comment), but what the OP shared is working with Spring Framework 6.0.x. Yours might be a different incarnation or a totally different problem if it's broken since 6.0.0.
Comment From: snicoll
I can see that there is a change in Spring Data between Spring Boot 3.1.x and Spring Boot 3.2.x which explains my confusion as I didn't see any change in framework. We'll have a look with them and see if we want to handle that additional tricky case.