Drop using FACTORY_BEAN_OBJECT_TYPE attribute entirely.

We now constantly use RootBeanDefinition.setBeanClass(…) (for the raw factory type) and ….setTargetType(…) to declare the full repository factory type including the user defined repository. Any other generics are simply filled with Object.class.

Ticket: #2894.
This commit is contained in:
Oliver Drotbohm
2023-08-02 13:51:06 +02:00
parent d98eeef745
commit 98f20a4457
3 changed files with 30 additions and 53 deletions

View File

@@ -25,19 +25,19 @@ import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
@@ -70,7 +70,6 @@ public class RepositoryConfigurationDelegate {
private static final String REPOSITORY_REGISTRATION = "Spring Data %s - Registering repository: %s - Interface: %s - Factory: %s";
private static final String MULTIPLE_MODULES = "Multiple Spring Data modules found, entering strict repository configuration mode";
private static final String NON_DEFAULT_AUTOWIRE_CANDIDATE_RESOLVER = "Non-default AutowireCandidateResolver (%s) detected. Skipping the registration of LazyRepositoryInjectionPointResolver. Lazy repository injection will not be working";
private static final String FACTORY_BEAN_OBJECT_TYPE = FactoryBean.OBJECT_TYPE_ATTRIBUTE;
private static final Log logger = LogFactory.getLog(RepositoryConfigurationDelegate.class);
@@ -183,9 +182,9 @@ public class RepositoryConfigurationDelegate {
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
RootBeanDefinition beanDefinition = (RootBeanDefinition) definitionBuilder.getBeanDefinition();
beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, getRepositoryInterface(configuration));
beanDefinition.setTargetType(getRepositoryInterface(configuration));
beanDefinition.setResourceDescription(configuration.getResourceDescription());
String beanName = configurationSource.generateBeanName(beanDefinition);
@@ -316,14 +315,31 @@ public class RepositoryConfigurationDelegate {
* @return can be {@literal null}.
*/
@Nullable
private Class<?> getRepositoryInterface(RepositoryConfiguration<?> configuration) {
private ResolvableType getRepositoryInterface(RepositoryConfiguration<?> configuration) {
String interfaceName = configuration.getRepositoryInterface();
ClassLoader classLoader = resourceLoader.getClassLoader() == null
? ClassUtils.getDefaultClassLoader()
: resourceLoader.getClassLoader();
return ReflectionUtils.loadIfPresent(interfaceName, classLoader);
classLoader = classLoader != null ? classLoader : getClass().getClassLoader();
Class<?> repositoryInterface = ReflectionUtils.loadIfPresent(interfaceName, classLoader);
Class<?> factoryBean = ReflectionUtils.loadIfPresent(configuration.getRepositoryFactoryBeanClassName(),
classLoader);
int numberOfGenerics = factoryBean.getTypeParameters().length;
Class<?>[] generics = new Class<?>[numberOfGenerics];
generics[0] = repositoryInterface;
if (numberOfGenerics > 1) {
for (int i = 1; i < numberOfGenerics; i++) {
generics[i] = Object.class;
}
}
return ResolvableType.forClassWithGenerics(factoryBean, generics);
}
/**

View File

@@ -32,21 +32,17 @@ import org.springframework.aop.framework.Advised;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.aot.AotContext;
import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.TargetAware;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.util.QTypeContributor;
import org.springframework.data.util.TypeContributor;
@@ -152,8 +148,6 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
this.repositoryContext = buildAotRepositoryContext(repositoryBean, repositoryMetadata);
enhanceRepositoryBeanDefinition(repositoryBean, repositoryMetadata, this.repositoryContext);
return this;
}
@@ -203,44 +197,12 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
return RepositoryBeanDefinitionReader.readRepositoryInformation(repositoryMetadata, getBeanFactory());
}
/**
* Helps the AOT processing render the {@link FactoryBean} type correctly that is used to tell the outcome of the
* {@link FactoryBean}. We just need to set the target {@link Repository} {@link Class type} of the
* {@link RepositoryFactoryBeanSupport} while keeping the actual ID and DomainType set to {@link Object}. If the
* generic type signature does not match, then we do not try to resolve and remap the types, but rather set the
* {@literal factoryBeanObjectType} attribute on the {@link RootBeanDefinition}.
*/
protected void enhanceRepositoryBeanDefinition(RegisteredBean repositoryBean,
RepositoryConfiguration<?> repositoryMetadata, AotRepositoryContext repositoryContext) {
logTrace(String.format("Enhancing repository factory bean definition [%s]", repositoryBean.getBeanName()));
Class<?> repositoryFactoryBeanType = repositoryContext
.introspectType(repositoryMetadata.getRepositoryFactoryBeanClassName()).resolveType()
.orElse(RepositoryFactoryBeanSupport.class);
ResolvableType resolvedRepositoryFactoryBeanType = ResolvableType.forClass(repositoryFactoryBeanType);
RootBeanDefinition repositoryBeanDefinition = repositoryBean.getMergedBeanDefinition();
if (isRepositoryWithTypeParameters(resolvedRepositoryFactoryBeanType)) {
repositoryBeanDefinition.setTargetType(ResolvableType.forClassWithGenerics(repositoryFactoryBeanType,
repositoryContext.getRepositoryInformation().getRepositoryInterface(), Object.class, Object.class));
} else {
repositoryBeanDefinition.setTargetType(resolvedRepositoryFactoryBeanType);
repositoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
repositoryContext.getRepositoryInformation().getRepositoryInterface());
}
}
/**
* {@link BiConsumer Callback} for data module specific contributions.
*
* @param moduleContribution {@link BiConsumer} used by data modules to submit contributions; can be {@literal null}.
* @return this.
*/
@SuppressWarnings("unused")
public RepositoryRegistrationAotContribution withModuleContribution(
@Nullable BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution) {
this.moduleContribution = moduleContribution;
@@ -385,8 +347,4 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
public Predicate<Class<?>> typeFilter() { // like only document ones. // TODO: As in MongoDB?
return it -> true;
}
private static boolean isRepositoryWithTypeParameters(ResolvableType type) {
return type.getGenerics().length == 3;
}
}

View File

@@ -25,8 +25,8 @@ import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@@ -76,10 +76,13 @@ class RepositoryConfigurationDelegateUnitTests {
for (var definition : delegate.registerRepositoriesIn(context, extension)) {
var beanDefinition = definition.getBeanDefinition();
var attribute = beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
assertThat(attribute).isInstanceOfSatisfying(Class.class, it -> {
assertThat(it.getName()).endsWith("Repository");
assertThat(beanDefinition).isInstanceOfSatisfying(RootBeanDefinition.class, it -> {
var type = it.getTargetType();
assertThat(type).isNotNull();
assertThat(type.getName()).endsWith("FactoryBean");
});
}
}