@ -17,17 +17,20 @@ package org.springframework.data.repository.config;
@@ -17,17 +17,20 @@ package org.springframework.data.repository.config;
import java.lang.annotation.Annotation ;
import java.util.Collections ;
import java.util.List ;
import java.util.Map ;
import java.util.function.BiConsumer ;
import java.util.function.Predicate ;
import java.util.Optional ;
import java.util.Set ;
import java.util.stream.Stream ;
import org.apache.commons.logging.Log ;
import org.apache.commons.logging.LogFactory ;
import org.jspecify.annotations.Nullable ;
import org.springframework.aot.generate.GenerationContext ;
import org.springframework.aot.hint.MemberCategory ;
import org.springframework.aot.hint.RuntimeHints ;
import org.springframework.aot.hint.annotation.Reflectiv e ;
import org.springframework.aot.hint.TypeReferenc e ;
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar ;
import org.springframework.beans.BeansException ;
import org.springframework.beans.factory.BeanFactory ;
@ -43,12 +46,18 @@ import org.springframework.core.annotation.MergedAnnotation;
@@ -43,12 +46,18 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.Environment ;
import org.springframework.core.env.EnvironmentCapable ;
import org.springframework.core.env.StandardEnvironment ;
import org.springframework.data.aot.AotContext ;
import org.springframework.data.aot.AotTypeConfiguration ;
import org.springframework.data.projection.EntityProjectionIntrospector ;
import org.springframework.data.repository.Repository ;
import org.springframework.data.repository.aot.generate.RepositoryContributor ;
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.TypeContributor ;
import org.springframework.data.util.TypeUtils ;
import org.springframework.util.Assert ;
import org.springframework.util.ClassUtils ;
/ * *
* { @link BeanRegistrationAotProcessor } responsible processing and providing AOT configuration for repositories .
@ -57,13 +66,13 @@ import org.springframework.util.Assert;
@@ -57,13 +66,13 @@ import org.springframework.util.Assert;
* AOT tooling to allow deriving target type from the { @link RootBeanDefinition bean definition } . If generic types do
* not match due to customization of the factory bean by the user , at least the target repository type is provided via
* the { @link FactoryBean # OBJECT_TYPE_ATTRIBUTE } .
* < / p >
* < p >
* With { @link RepositoryRegistrationAotProcessor # contribute ( AotRepositoryContext , GenerationContext ) } , stores can
* provide custom logic for contributing additional ( eg . reflection ) configuration . By default , reflection configuration
* will be added for types reachable from the repository declaration and query methods as well as all used
* { @link Annotation annotations } from the { @literal org . springframework . data } namespace .
* < / p >
* With { @link RepositoryRegistrationAotProcessor # contributeRepositoryHints ( AotRepositoryContext , GenerationContext ) }
* and { @link RepositoryRegistrationAotProcessor # contributeAotRepository ( AotRepositoryContext ) } , stores can provide
* custom logic for contributing additional ( e . g . reflection ) configuration . By default , reflection configuration will
* be added for types reachable from the repository declaration and query methods as well as all used { @link Annotation
* annotations } from the { @literal org . springframework . data } namespace .
* < p >
* The processor is typically configured via { @link RepositoryConfigurationExtension # getRepositoryAotProcessor ( ) } and
* gets added by the { @link org . springframework . data . repository . config . RepositoryConfigurationDelegate } .
*
@ -75,6 +84,18 @@ import org.springframework.util.Assert;
@@ -75,6 +84,18 @@ import org.springframework.util.Assert;
public class RepositoryRegistrationAotProcessor
implements BeanRegistrationAotProcessor , BeanFactoryAware , EnvironmentAware , EnvironmentCapable {
private static final String KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME = "org.springframework.data.repository.kotlin.CoroutineCrudRepository" ;
private static final List < TypeReference > KOTLIN_REFLECTION_TYPE_REFERENCES = List . of (
TypeReference . of ( "org.springframework.data.repository.kotlin.CoroutineCrudRepository" ) ,
TypeReference . of ( Repository . class ) , //
TypeReference . of ( Iterable . class ) , //
TypeReference . of ( "kotlinx.coroutines.flow.Flow" ) , //
TypeReference . of ( "kotlin.collections.Iterable" ) , //
TypeReference . of ( "kotlin.Unit" ) , //
TypeReference . of ( "kotlin.Long" ) , //
TypeReference . of ( "kotlin.Boolean" ) ) ;
private final Log logger = LogFactory . getLog ( getClass ( ) ) ;
private @Nullable ConfigurableListableBeanFactory beanFactory ;
@ -83,140 +104,265 @@ public class RepositoryRegistrationAotProcessor
@@ -83,140 +104,265 @@ public class RepositoryRegistrationAotProcessor
private Map < String , RepositoryConfiguration < ? > > configMap = Collections . emptyMap ( ) ;
@Override
public void setBeanFactory ( BeanFactory beanFactory ) throws BeansException {
Assert . isInstanceOf ( ConfigurableListableBeanFactory . class , beanFactory ,
( ) - > "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory ) ;
this . beanFactory = ( ConfigurableListableBeanFactory ) beanFactory ;
}
@Override
public void setEnvironment ( Environment environment ) {
this . environment = environment ;
}
@Override
public Environment getEnvironment ( ) {
return this . environment ;
}
/ * *
* Setter for the config map . See { @code RepositoryConfigurationDelegate # registerAotComponents } .
*
* @param configMap
* /
@SuppressWarnings ( "unused" )
public void setConfigMap ( Map < String , RepositoryConfiguration < ? > > configMap ) {
this . configMap = configMap ;
}
public Map < String , RepositoryConfiguration < ? > > getConfigMap ( ) {
return this . configMap ;
}
protected ConfigurableListableBeanFactory getBeanFactory ( ) {
if ( this . beanFactory = = null ) {
throw new IllegalStateException (
"No BeanFactory available. Make sure to set the BeanFactory before using this processor." ) ;
}
return this . beanFactory ;
}
@Override
public @Nullable BeanRegistrationAotContribution processAheadOfTime ( RegisteredBean bean ) {
return isRepositoryBean ( bean ) ? newRepositoryRegistrationAotContribution ( bean ) : null ;
if ( ! isRepositoryBean ( bean ) ) {
return null ;
}
RepositoryConfiguration < ? > repositoryMetadata = getRepositoryMetadata ( bean ) ;
AotRepositoryContext repositoryContext = potentiallyCreateContext ( environment , bean ) ;
if ( repositoryMetadata = = null | | repositoryContext = = null ) {
return null ;
}
BeanRegistrationAotContribution contribution = ( generationContext , beanRegistrationCode ) - > {
contributeRepositoryHints ( repositoryContext , generationContext ) ;
contributeTypes ( repositoryContext , generationContext ) ;
repositoryContext . contributeTypeConfigurations ( generationContext ) ;
} ;
return new RepositoryRegistrationAotContribution ( repositoryContext , contribution ,
contributeAotRepository ( repositoryContext ) ) ;
}
@Nullable
protected RepositoryContributor contribute ( AotRepositoryContext repositoryContext ,
/ * *
* Contribute repository - specific hints , e . g . for repository proxy , base implementation , fragments . Customization hook
* for subclasses that wish to customize repository hint contribution .
*
* @param repositoryContext the repository context .
* @param generationContext the generation context .
* @since 4 . 0
* /
protected void contributeRepositoryHints ( AotRepositoryContext repositoryContext ,
GenerationContext generationContext ) {
repositoryContext . getResolvedTypes ( ) . stream ( )
. filter ( it - > ! RepositoryRegistrationAotContribution . isJavaOrPrimitiveType ( it ) )
. forEach ( it - > contributeType ( it , generationContext ) ) ;
RepositoryInformation repositoryInformation = repositoryContext . getRepositoryInformation ( ) ;
if ( logger . isTraceEnabled ( ) ) {
logger . trace (
"Contributing repository information for [%s]" . formatted ( repositoryInformation . getRepositoryInterface ( ) ) ) ;
}
// Native hints for repository proxy
repositoryContext . typeConfiguration ( repositoryInformation . getRepositoryInterface ( ) ,
config - > config . forReflectiveAccess ( MemberCategory . INVOKE_PUBLIC_METHODS ) . repositoryProxy ( ) ) ;
// Native hints for reflective base implementation access
repositoryContext . typeConfiguration ( repositoryInformation . getRepositoryBaseClass ( ) , config - > config
. forReflectiveAccess ( MemberCategory . INVOKE_DECLARED_CONSTRUCTORS , MemberCategory . INVOKE_PUBLIC_METHODS ) ) ;
// Repository Fragments
contributeFragments ( repositoryInformation . getFragments ( ) , generationContext ) ;
// Kotlin
if ( isKotlinCoroutineRepository ( repositoryInformation ) ) {
generationContext . getRuntimeHints ( ) . reflection ( ) . registerTypes ( KOTLIN_REFLECTION_TYPE_REFERENCES , hint - > { } ) ;
}
}
/ * *
* Contribute types for reflection , proxies , etc . Customization hook for subclasses that wish to customize type
* contribution hints .
*
* @param repositoryContext the repository context .
* @param generationContext the generation context .
* @since 4 . 0
* /
protected void contributeTypes ( AotRepositoryContext repositoryContext , GenerationContext generationContext ) {
contributeDomainTypes ( repositoryContext , generationContext ) ;
contributeResolvedTypes ( repositoryContext , generationContext ) ;
RepositoryInformation information = repositoryContext . getRepositoryInformation ( ) ;
// Repository query methods
information . getQueryMethods ( ) . stream ( ) . map ( information : : getReturnedDomainClass ) . filter ( Class : : isInterface )
. forEach ( type - > {
if ( EntityProjectionIntrospector . ProjectionPredicate . typeHierarchy ( ) . test ( type ,
information . getDomainType ( ) ) ) {
repositoryContext . typeConfiguration ( type , AotTypeConfiguration : : usedAsProjectionInterface ) ;
}
} ) ;
repositoryContext . getResolvedAnnotations ( ) . stream ( )
. filter ( RepositoryRegistrationAotProcessor : : isSpringDataManagedAnnotation ) . map ( MergedAnnotation : : getType )
. forEach ( it - > contributeType ( it , generationContext ) ) ;
return null ;
}
/ * *
* Processes the repository ' s domain and alternative domain types to consider { @link Reflective } annotations used on
* it .
* Customization hook for subclasses that wish to customize domain type contribution hints .
*
* @param repositoryContext must not be { @literal null } .
* @param generationContext must not be { @literal null } .
* @param repositoryContext the repository context .
* @param generationContext the generation context .
* @since 4 . 0
* /
// TODO: Can we merge #contribute, #registerReflectiveForAggregateRoot into RepositoryRegistrationAotContribution?
// hints and types are contributed from everywhere.
private void registerReflectiveForAggregateRoot ( AotRepositoryContext repositoryContext ,
GenerationContext generationContext ) {
protected void contributeDomainTypes ( AotRepositoryContext repositoryContext , GenerationContext generationContext ) {
RepositoryInformation information = repositoryContext . getRepositoryInformation ( ) ;
ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar ( ) ;
RuntimeHints hints = generationContext . getRuntimeHints ( ) ;
// Domain types, related types, projections
repositoryContext . typeConfiguration ( information . getDomainType ( ) , config - > config . forDataBinding ( ) . forQuerydsl ( ) ) ;
ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar ( ) ;
Stream . concat ( Stream . of ( information . getDomainType ( ) ) , information . getAlternativeDomainTypes ( ) . stream ( ) )
. forEach ( it - > {
// arent we already registering the types in RepositoryRegistrationAotContribution#contributeRepositoryInfo?
// TODO cross check with #contributeResolvedTypes
registrar . registerRuntimeHints ( hints , it ) ;
repositoryContext . typeConfiguration ( it , AotTypeConfiguration : : contributeAccessors ) ;
} ) ;
}
private boolean isRepositoryBean ( RegisteredBean bean ) {
return getConfigMap ( ) . containsKey ( bean . getBeanName ( ) ) ;
}
private void contributeResolvedTypes ( AotRepositoryContext repositoryContext , GenerationContext generationContext ) {
protected @Nullable RepositoryRegistrationAotContribution newRepositoryRegistrationAotContribution (
RegisteredBean repositoryBean ) {
RepositoryInformation information = repositoryContext . getRepositoryInformation ( ) ;
RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution . load ( this ,
repositoryBean ) ;
// TODO: These are twice.
repositoryContext . getResolvedTypes ( ) . stream ( )
. filter ( it - > TypeContributor . isPartOf ( it , Set . of ( information . getDomainType ( ) . getPackageName ( ) ) ) )
. forEach ( it - > repositoryContext . typeConfiguration ( it , AotTypeConfiguration : : contributeAccessors ) ) ;
// cannot contribute a repository bean.
if ( contribution = = null ) {
return null ;
}
repositoryContext . getResolvedTypes ( ) . stream ( ) . filter ( it - > ! isJavaOrPrimitiveType ( it ) )
. forEach ( it - > contributeType ( it , generationContext ) ) ;
}
// TODO: add the hook for customizing bean initialization code here!
/ * *
* This method allows for the creation to be overridden by subclasses .
*
* @param repositoryContext the context for the repository being processed .
* @return a { @link RepositoryContributor } to contribute store - specific AOT artifacts or { @literal null } to skip
* store - specific AOT contributions .
* @since 4 . 0
* /
@Nullable
protected RepositoryContributor contributeAotRepository ( AotRepositoryContext repositoryContext ) {
return null ;
}
return contribution . withModuleContribution ( ( repositoryContext , generationContext ) - > {
registerReflectiveForAggregateRoot ( repositoryContext , generationContext ) ;
return contribute ( repositoryContext , generationContext ) ;
} ) ;
private boolean isRepositoryBean ( RegisteredBean bean ) {
return getConfigMap ( ) . containsKey ( bean . getBeanName ( ) ) ;
}
@Override
public void setBeanFactory ( BeanFactory beanFactory ) throws BeansException {
private RepositoryConfiguration < ? > getRepositoryMetadata ( RegisteredBean bean ) {
Assert . isInstanceOf ( ConfigurableListableBeanFactory . class , beanFactory ,
( ) - > "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory ) ;
RepositoryConfiguration < ? > configuration = getConfigMap ( ) . get ( bean . getBeanName ( ) ) ;
this . beanFactory = ( ConfigurableListableBeanFactory ) beanFactory ;
}
if ( configuration = = null ) {
throw new IllegalArgumentException ( "No configuration for bean [%s]" . formatted ( bean . getBeanName ( ) ) ) ;
}
@Override
public void setEnvironment ( Environment environment ) {
this . environment = environment ;
return configuration ;
}
@Override
public Environment getEnvironment ( ) {
return this . environment ;
private void contributeType ( Class < ? > type , GenerationContext context ) {
TypeContributor . contribute ( type , it - > true , context ) ;
}
public void setConfigMap ( Map < String , RepositoryConfiguration < ? > > configMap ) {
this . configMap = configMap ;
private void contributeFragments ( Iterable < RepositoryFragment < ? > > fragments , GenerationContext contribution ) {
fragments . forEach ( it - > contributeFragment ( it , contribution ) ) ;
}
public Map < String , RepositoryConfiguration < ? > > getConfigMap ( ) {
return this . configMap ;
}
private static void contributeFragment ( RepositoryFragment < ? > fragment , GenerationContext context ) {
protected ConfigurableListableBeanFactory getBeanFactory ( ) {
Class < ? > repositoryFragmentType = fragment . getSignatureContributor ( ) ;
Optional < Class < ? > > implementation = fragment . getImplementationClass ( ) ;
if ( this . beanFactory = = null ) {
throw new IllegalStateException (
"No BeanFactory available. Make sure to set the BeanFactory before using this processor." ) ;
}
registerReflectiveHints ( repositoryFragmentType , context ) ;
return this . beanFactory ;
implementation . ifPresent ( typeToRegister - > registerReflectiveHints ( typeToRegister , context ) ) ;
}
protected @Nullable RepositoryConfiguration < ? > getRepositoryMetadata ( RegisteredBean bean ) {
return getConfigMap ( ) . get ( bean . getBeanName ( ) ) ;
}
private static void registerReflectiveHints ( Class < ? > typeToRegister , GenerationContext context ) {
protected void contributeType ( Class < ? > type , GenerationContext generationContext ) {
TypeContributor . contribute ( type , it - > true , generationContext ) ;
}
context . getRuntimeHints ( ) . reflection ( ) . registerType ( typeToRegister , hint - > {
protected Log getLogger ( ) {
return this . logger ;
}
hint . withMembers ( MemberCategory . INVOKE_PUBLIC_METHODS ) ;
protected void logDebug ( String message , Object . . . arguments ) {
logAt ( Log : : isDebugEnabled , Log : : debug , message , arguments ) ;
if ( ! typeToRegister . isInterface ( ) ) {
hint . withMembers ( MemberCategory . INVOKE_DECLARED_CONSTRUCTORS ) ;
}
} ) ;
}
protected void logTrace ( String message , Object . . . arguments ) {
logAt ( Log : : isTraceEnabled , Log : : trace , message , arguments ) ;
private @Nullable AotRepositoryContext potentiallyCreateContext ( Environment environment , RegisteredBean bean ) {
RepositoryBeanDefinitionReader reader = new RepositoryBeanDefinitionReader ( bean ) ;
RepositoryConfiguration < ? > configuration = reader . getConfiguration ( ) ;
RepositoryConfigurationExtensionSupport extension = reader . getConfigurationExtension ( ) ;
if ( configuration = = null | | extension = = null ) {
logger . warn (
"Cannot create AotRepositoryContext for bean [%s]. No RepositoryConfiguration/RepositoryConfigurationExtension. Please make sure to register the repository bean through @Enable…Repositories."
. formatted ( bean . getBeanName ( ) ) ) ;
return null ;
}
RepositoryInformation repositoryInformation = reader . getRepositoryInformation ( ) ;
DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext ( bean , repositoryInformation ,
extension . getModuleName ( ) , AotContext . from ( bean . getBeanFactory ( ) , environment ) ,
configuration . getConfigurationSource ( ) ) ;
repositoryContext . setIdentifyingAnnotations ( extension . getIdentifyingAnnotations ( ) ) ;
return repositoryContext ;
}
private void logAt ( Predicate < Log > logLevelPredicate , BiConsumer < Log , String > logOperation , String message ,
Object . . . arguments ) {
private static boolean isKotlinCoroutineRepository ( RepositoryInformation repositoryInformation ) {
Log logger = getLogger ( ) ;
Class < ? > coroutineRepository = org . springframework . data . util . ClassUtils . loadIfPresent (
KOTLIN_COROUTINE_REPOSITORY_TYPE_NAME , repositoryInformation . getRepositoryInterface ( ) . getClassLoader ( ) ) ;
if ( logLevelPredicate . test ( logger ) ) {
logOperation . accept ( logger , String . format ( message , arguments ) ) ;
}
return coroutineRepository ! = null
& & ClassUtils . isAssignable ( coroutineRepository , repositoryInformation . getRepositoryInterface ( ) ) ;
}
private static boolean isSpringDataManagedAnnotation ( MergedAnnotation < ? > annotation ) {
@ -229,4 +375,10 @@ public class RepositoryRegistrationAotProcessor
@@ -229,4 +375,10 @@ public class RepositoryRegistrationAotProcessor
return type . getPackageName ( ) . startsWith ( TypeContributor . DATA_NAMESPACE ) ;
}
private static boolean isJavaOrPrimitiveType ( Class < ? > type ) {
return ClassUtils . isPrimitiveOrWrapper ( type ) //
| | ClassUtils . isPrimitiveArray ( type ) //
| | TypeUtils . type ( type ) . isPartOf ( "java" ) ;
}
}