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.