@ -28,15 +28,18 @@ import java.util.function.BiFunction;
@@ -28,15 +28,18 @@ import java.util.function.BiFunction;
import org.springframework.aop.framework.AopProxyUtils ;
import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.NoSuchBeanDefinitionException ;
import org.springframework.beans.factory.ListableBeanFactory ;
import org.springframework.beans.factory.config.BeanDefinition ;
import org.springframework.beans.factory.config.ConfigurableBeanFactory ;
import org.springframework.beans.factory.support.BeanDefinitionRegistry ;
import org.springframework.core.ResolvableType ;
import org.springframework.core.ResolvableTypeProvider ;
import org.springframework.core.annotation.AnnotationAwareOrderComparator ;
import org.springframework.lang.NonNull ;
import org.springframework.lang.Nullable ;
import org.springframework.util.Assert ;
import org.springframework.util.ClassUtils ;
import org.springframework.util.ConcurrentReferenceHashMap ;
import org.springframework.util.ObjectUtils ;
import org.springframework.util.ReflectionUtils ;
import org.springframework.util.comparator.Comparators ;
@ -45,11 +48,12 @@ import org.springframework.util.comparator.Comparators;
@@ -45,11 +48,12 @@ import org.springframework.util.comparator.Comparators;
* @author Christoph Strobl
* @author Myeonghyeon Lee
* @author Johannes Englmeier
* @author Oliver Drotbohm
* @since 2 . 2
* /
class EntityCallbackDiscoverer {
private final CallbackRetriever defaultRetriever = new CallbackRetriever ( false ) ;
private final CallbackRetriever defaultRetriever = new CallbackRetriever ( ) ;
private final Map < CallbackCacheKey , CallbackRetriever > retrieverCache = new ConcurrentHashMap < > ( 64 ) ;
private final Map < Class < ? > , ResolvableType > entityTypeCache = new ConcurrentReferenceHashMap < > ( 64 ) ;
@ -64,9 +68,8 @@ class EntityCallbackDiscoverer {
@@ -64,9 +68,8 @@ class EntityCallbackDiscoverer {
EntityCallbackDiscoverer ( ) { }
/ * *
* Create a new { @link EntityCallback } instance .
* < p >
* Pre loads { @link EntityCallback } beans by scanning the { @link BeanFactory } .
* Create a new { @link EntityCallback } instance . < p Pre - loads { @link EntityCallback } beans by scanning the
* { @link BeanFactory } .
* /
EntityCallbackDiscoverer ( BeanFactory beanFactory ) {
setBeanFactory ( beanFactory ) ;
@ -81,18 +84,12 @@ class EntityCallbackDiscoverer {
@@ -81,18 +84,12 @@ class EntityCallbackDiscoverer {
// Explicitly remove target for a proxy, if registered already,
// in order to avoid double invocations of the same callback.
Object singletonTarget = AopProxyUtils . getSingletonTarget ( callback ) ;
if ( singletonTarget instanceof EntityCallback ) {
this . defaultRetriever . entityCallbacks . remove ( singletonTarget ) ;
}
this . defaultRetriever . entityCallbacks . add ( callback ) ;
this . retrieverCache . clear ( ) ;
}
}
void addEntityCallbackBean ( String callbackBeanName ) {
synchronized ( this . retrievalMutex ) {
this . defaultRetriever . entityCallbackBeans . add ( callbackBeanName ) ;
this . defaultRetriever . entityCallbacks . add ( callback ) ;
this . retrieverCache . clear ( ) ;
}
}
@ -105,29 +102,12 @@ class EntityCallbackDiscoverer {
@@ -105,29 +102,12 @@ class EntityCallbackDiscoverer {
}
}
void removeEntityCallbackBean ( String callbackBeanName ) {
synchronized ( this . retrievalMutex ) {
this . defaultRetriever . entityCallbackBeans . remove ( callbackBeanName ) ;
this . retrieverCache . clear ( ) ;
}
}
void clear ( ) {
synchronized ( this . retrievalMutex ) {
this . defaultRetriever . entityCallbacks . clear ( ) ;
this . defaultRetriever . entityCallbackBeans . clear ( ) ;
this . retrieverCache . clear ( ) ;
}
}
/ * *
* Return a { @link Collection } of all { @link EntityCallback } s matching the given entity type . Non - matching callbacks
* get excluded early .
*
* @param entity the entity to be called back for . Allows for excluding non - matching callbacks early , based on cached
* matching information .
* @param entity the entity to be called back for . Allows for excluding non - matching callbacks early , based on
* cached matching information .
* @param callbackType the source callback type .
* @return a { @link Collection } of { @link EntityCallback } s .
* @see EntityCallback
@ -140,27 +120,27 @@ class EntityCallbackDiscoverer {
@@ -140,27 +120,27 @@ class EntityCallbackDiscoverer {
// Quick check for existing entry on ConcurrentHashMap...
CallbackRetriever retriever = this . retrieverCache . get ( cacheKey ) ;
if ( retriever ! = null ) {
return ( Collection < EntityCallback < S > > ) ( Collection ) retriever . getEntityCallbacks ( ) ;
return ( Collection ) retriever . getEntityCallbacks ( ) ;
}
if ( this . beanClassLoader = = null | | ( ClassUtils . isCacheSafe ( entity . getClass ( ) , this . beanClassLoader )
& & ( sourceType = = null | | ClassUtils . isCacheSafe ( sourceType , this . beanClassLoader ) ) ) ) {
if ( this . beanClassLoader = = null | | ClassUtils . isCacheSafe ( entity . getClass ( ) , this . beanClassLoader )
& & ( sourceType = = null | | ClassUtils . isCacheSafe ( sourceType , this . beanClassLoader ) ) ) {
// Fully synchronized building and caching of a CallbackRetriever
synchronized ( this . retrievalMutex ) {
retriever = this . retrieverCache . get ( cacheKey ) ;
if ( retriever ! = null ) {
return ( Collection < EntityCallback < S > > ) ( Collection ) retriever . getEntityCallbacks ( ) ;
return ( Collection ) retriever . getEntityCallbacks ( ) ;
}
retriever = new CallbackRetriever ( true ) ;
retriever = new CallbackRetriever ( ) ;
Collection < EntityCallback < ? > > callbacks = retrieveEntityCallbacks ( ResolvableType . forClass ( sourceType ) ,
callbackType , retriever ) ;
this . retrieverCache . put ( cacheKey , retriever ) ;
return ( Collection < EntityCallback < S > > ) ( Collection ) callbacks ;
return ( Collection ) callbacks ;
}
} else {
// No CallbackRetriever caching -> no synchronization necessary
return ( Collection < EntityCallback < S > > ) ( Collection ) retrieveEntityCallbacks ( callbackType , callbackType , null ) ;
return ( Collection ) retrieveEntityCallbacks ( callbackType , callbackType , null ) ;
}
}
@ -174,7 +154,7 @@ class EntityCallbackDiscoverer {
@@ -174,7 +154,7 @@ class EntityCallbackDiscoverer {
entityTypeCache . put ( callbackType , eventType ) ;
}
return ( eventType ! = ResolvableType . NONE ? eventType : null ) ;
return eventType ! = ResolvableType . NONE ? eventType : null ;
}
/ * *
@ -185,55 +165,33 @@ class EntityCallbackDiscoverer {
@@ -185,55 +165,33 @@ class EntityCallbackDiscoverer {
* @param retriever the { @link CallbackRetriever } , if supposed to populate one ( for caching purposes )
* @return the pre - filtered list of entity callbacks for the given entity and callback type .
* /
private Collection < EntityCallback < ? > > retrieveEntityCallbacks ( ResolvableType entityType , ResolvableType callbackType ,
@Nullable CallbackRetriever retriever ) {
private Collection < EntityCallback < ? > > retrieveEntityCallbacks ( ResolvableType entityType ,
ResolvableType callbackType , @Nullable CallbackRetriever retriever ) {
List < EntityCallback < ? > > allCallbacks = new ArrayList < > ( ) ;
Set < EntityCallback < ? > > callbacks ;
Set < String > callbackBeans ;
synchronized ( this . retrievalMutex ) {
callbacks = new LinkedHashSet < > ( this . defaultRetriever . entityCallbacks ) ;
callbackBeans = new LinkedHashSet < > ( this . defaultRetriever . entityCallbackBeans ) ;
}
for ( EntityCallback < ? > callback : callbacks ) {
if ( supportsEvent ( callback , entityType , callbackType ) ) {
callback = callback instanceof EntityCallbackAdapter < ? > adapter ? adapter . delegate ( ) : callback ;
if ( retriever ! = null ) {
retriever . getEntityCallbacks ( ) . add ( callback ) ;
}
allCallbacks . add ( callback ) ;
}
}
if ( ! callbackBeans . isEmpty ( ) ) {
BeanFactory beanFactory = getRequiredBeanFactory ( ) ;
for ( String callbackBeanName : callbackBeans ) {
try {
Class < ? > callbackImplType = beanFactory . getType ( callbackBeanName ) ;
if ( callbackImplType = = null | | supportsEvent ( callbackImplType , entityType ) ) {
EntityCallback < ? > callback = beanFactory . getBean ( callbackBeanName , EntityCallback . class ) ;
if ( ! allCallbacks . contains ( callback ) & & supportsEvent ( callback , entityType , callbackType ) ) {
if ( retriever ! = null ) {
if ( beanFactory . isSingleton ( callbackBeanName ) ) {
retriever . entityCallbacks . add ( callback ) ;
} else {
retriever . entityCallbackBeans . add ( callbackBeanName ) ;
}
}
allCallbacks . add ( callback ) ;
}
}
} catch ( NoSuchBeanDefinitionException ex ) {
// Singleton callback instance (without backing bean definition) disappeared -
// probably in the middle of the destruction phase
}
allCallbacks . add ( callback ) ;
}
}
AnnotationAwareOrderComparator . sort ( allCallbacks ) ;
if ( retriever ! = null & & retriever . entityCallbackBeans . isEmpty ( ) ) {
if ( retriever ! = null ) {
retriever . entityCallbacks . clear ( ) ;
retriever . entityCallbacks . addAll ( allCallbacks ) ;
}
@ -242,43 +200,8 @@ class EntityCallbackDiscoverer {
@@ -242,43 +200,8 @@ class EntityCallbackDiscoverer {
}
/ * *
* Filter a callback early through checking its generically declared entity type before trying to instantiate it .
* < p >
* If this method returns { @literal true } for a given callback as a first pass , the callback instance will get
* retrieved and fully evaluated through a { @link # supportsEvent ( EntityCallback , ResolvableType , ResolvableType ) } call
* afterwards .
*
* @param callback the callback ' s type as determined by the BeanFactory .
* @param entityType the entity type to check .
* @return whether the given callback should be included in the candidates for the given callback type .
* /
protected boolean supportsEvent ( Class < ? > callback , ResolvableType entityType ) {
ResolvableType declaredEventType = resolveDeclaredEntityType ( callback ) ;
return ( declaredEventType = = null | | declaredEventType . isAssignableFrom ( entityType ) ) ;
}
/ * *
* Determine whether the given callback supports the given entity type and callback type .
*
* @param callback the target callback to check .
* @param entityType the entity type to check .
* @param callbackType the source type to check against .
* @return whether the given callback should be included in the candidates for the given callback type .
* /
protected boolean supportsEvent ( EntityCallback < ? > callback , ResolvableType entityType , ResolvableType callbackType ) {
return supportsEvent ( callback . getClass ( ) , entityType )
& & callbackType . isAssignableFrom ( ResolvableType . forInstance ( callback ) ) ;
}
public void setBeanClassLoader ( ClassLoader classLoader ) {
this . beanClassLoader = classLoader ;
}
/ * *
* Set the { @link BeanFactory } and optionally { @link # setBeanClassLoader ( ClassLoader ) class loader } if not set . Pre
* loads { @link EntityCallback } beans by scanning the { @link BeanFactory } .
* Set the { @link BeanFactory } and optionally { @link # setBeanClassLoader ( ClassLoader ) class loader } if not set .
* Pre - loads { @link EntityCallback } beans by scanning the { @link BeanFactory } .
*
* @param beanFactory must not be { @literal null } .
* @see org . springframework . beans . factory . BeanFactoryAware # setBeanFactory ( BeanFactory )
@ -288,13 +211,15 @@ class EntityCallbackDiscoverer {
@@ -288,13 +211,15 @@ class EntityCallbackDiscoverer {
this . beanFactory = beanFactory ;
if ( beanFactory instanceof ConfigurableBeanFactory cbf ) {
if ( this . beanClassLoader = = null ) {
this . beanClassLoader = cbf . getBeanClassLoader ( ) ;
}
this . retrievalMutex = cbf . getSingletonMutex ( ) ;
}
defaultRetriever . discoverEntityCallbacks ( this . beanFactory ) ;
defaultRetriever . discoverEntityCallbacks ( beanFactory ) ;
this . retrieverCache . clear ( ) ;
}
@ -305,8 +230,10 @@ class EntityCallbackDiscoverer {
@@ -305,8 +230,10 @@ class EntityCallbackDiscoverer {
ReflectionUtils . doWithMethods ( callbackType , methods : : add , method - > {
if ( ! Modifier . isPublic ( method . getModifiers ( ) ) | | method . getParameterCount ( ) ! = args . length + 1
| | method . isBridge ( ) | | ReflectionUtils . isObjectMethod ( method ) ) {
if ( ! Modifier . isPublic ( method . getModifiers ( ) )
| | method . getParameterCount ( ) ! = args . length + 1
| | method . isBridge ( )
| | ReflectionUtils . isObjectMethod ( method ) ) {
return false ;
}
@ -318,7 +245,7 @@ class EntityCallbackDiscoverer {
@@ -318,7 +245,7 @@ class EntityCallbackDiscoverer {
}
throw new IllegalStateException (
String . format ( "%s does not define a callback method accepting %s and %s additional arguments" ,
"%s does not define a callback method accepting %s and %s additional arguments" . formatted (
ClassUtils . getShortName ( callbackType ) , ClassUtils . getShortName ( entityType ) , args . length ) ) ;
}
@ -329,6 +256,7 @@ class EntityCallbackDiscoverer {
@@ -329,6 +256,7 @@ class EntityCallbackDiscoverer {
Object [ ] invocationArgs = new Object [ args . length + 1 ] ;
invocationArgs [ 0 ] = entity ;
if ( args . length > 0 ) {
System . arraycopy ( args , 0 , invocationArgs , 1 , args . length ) ;
}
@ -337,104 +265,108 @@ class EntityCallbackDiscoverer {
@@ -337,104 +265,108 @@ class EntityCallbackDiscoverer {
} ;
}
private BeanFactory getRequiredBeanFactory ( ) {
/ * *
* Filter a callback early through checking its generically declared entity type before trying to instantiate it .
* < p >
* If this method returns { @literal true } for a given callback as a first pass , the callback instance will get
* retrieved and fully evaluated through a { @link # supportsEvent ( EntityCallback , ResolvableType , ResolvableType ) }
* call afterwards .
*
* @param callback the callback ' s type as determined by the BeanFactory .
* @param entityType the entity type to check .
* @return whether the given callback should be included in the candidates for the given callback type .
* /
static boolean supportsEvent ( ResolvableType callbackType , ResolvableType entityType ) {
return callbackType . as ( EntityCallback . class ) . getGeneric ( 0 ) . isAssignableFrom ( entityType ) ;
}
/ * *
* Determine whether the given callback supports the given entity type and callback type .
*
* @param callback the target callback to check .
* @param entityType the entity type to check .
* @param callbackType the source type to check against .
* @return whether the given callback should be included in the candidates for the given callback type .
* /
static boolean supportsEvent ( EntityCallback < ? > callback , ResolvableType entityType ,
ResolvableType callbackType ) {
ResolvableType callbackInstanceType = callback instanceof EntityCallbackAdapter < ? > provider
? provider . getResolvableType ( )
: ResolvableType . forInstance ( callback ) ;
Assert . state ( beanFactory ! = null ,
"EntityCallbacks cannot retrieve callback beans because it is not associated with a BeanFactory" ) ;
return beanFactory ;
return supportsEvent ( callbackInstanceType , entityType )
& & callbackType . isAssignableFrom ( callbackInstanceType ) ;
}
/ * *
* Helper class that encapsulates a specific set of target { @link EntityCallback callbacks } , allowing for efficient
* retrieval of pre - filtered callbacks .
* /
class CallbackRetriever {
private static class CallbackRetriever {
private final Set < EntityCallback < ? > > entityCallbacks = new LinkedHashSet < > ( ) ;
private final Set < String > entityCallbackBeans = new LinkedHashSet < > ( ) ;
private final boolean preFiltered ;
CallbackRetriever ( boolean preFiltered ) {
this . preFiltered = preFiltered ;
}
Collection < EntityCallback < ? > > getEntityCallbacks ( ) {
List < EntityCallback < ? > > allCallbacks = new ArrayList < > (
this . entityCallbacks . size ( ) + this . entityCallbackBeans . size ( ) ) ;
allCallbacks . addAll ( this . entityCallbacks ) ;
if ( ! this . entityCallbackBeans . isEmpty ( ) ) {
BeanFactory beanFactory = getRequiredBeanFactory ( ) ;
for ( String callbackBeanName : this . entityCallbackBeans ) {
try {
EntityCallback < ? > callback = beanFactory . getBean ( callbackBeanName , EntityCallback . class ) ;
if ( this . preFiltered | | ! allCallbacks . contains ( callback ) ) {
allCallbacks . add ( callback ) ;
}
} catch ( NoSuchBeanDefinitionException ex ) {
// Singleton callback instance (without backing bean definition) disappeared -
// probably in the middle of the destruction phase
}
}
}
if ( ! this . preFiltered | | ! this . entityCallbackBeans . isEmpty ( ) ) {
AnnotationAwareOrderComparator . sort ( allCallbacks ) ;
}
return allCallbacks ;
return this . entityCallbacks ;
}
void discoverEntityCallbacks ( BeanFactory beanFactory ) {
beanFactory . getBeanProvider ( EntityCallback . class ) . stream ( ) . forEach ( entityCallbacks : : add ) ;
}
}
/ * *
* Cache key for { @link EntityCallback } , based on event type and source type .
* /
static final class CallbackCacheKey implements Comparable < CallbackCacheKey > {
private final ResolvableType callbackType ;
// We need both a ListableBeanFactory and BeanDefinitionRegistry here for advanced inspection.
// If we don't get that, use simple inspection.
if ( ! ( beanFactory instanceof ListableBeanFactory & & beanFactory instanceof BeanDefinitionRegistry ) ) {
beanFactory . getBeanProvider ( EntityCallback . class ) . stream ( ) . forEach ( entityCallbacks : : add ) ;
return ;
}
private final Class < ? > entityType ;
var bf = ( ListableBeanFactory & BeanDefinitionRegistry ) beanFactory ;
public CallbackCacheKey ( ResolvableType callbackType , @Nullable Class < ? > entityType ) {
for ( var beanName : bf . getBeanNamesForType ( EntityCallback . class ) ) {
Assert . notNull ( callbackType , "Callback type must not be null" ) ;
Assert . notNull ( entityType , "Entity type must not be null" ) ;
EntityCallback < ? > bean = EntityCallback . class . cast ( bf . getBean ( beanName ) ) ;
this . callbackType = callbackType ;
this . entityType = entityType ;
}
ResolvableType type = ResolvableType . forClass ( EntityCallback . class , bean . getClass ( ) ) ;
ResolvableType entityType = type . getGeneric ( 0 ) ;
@Override
public boolean equals ( Object other ) {
if ( entityType . resolve ( ) ! = null ) {
entityCallbacks . add ( bean ) ;
} else {
if ( this = = other ) {
return true ;
BeanDefinition definition = bf . getBeanDefinition ( beanName ) ;
entityCallbacks . add ( new EntityCallbackAdapter < > ( bean , definition . getResolvableType ( ) ) ) ;
}
}
CallbackCacheKey otherKey = ( CallbackCacheKey ) other ;
return ( this . callbackType . equals ( otherKey . callbackType )
& & ObjectUtils . nullSafeEquals ( this . entityType , otherKey . entityType ) ) ;
}
}
/ * *
* A combination of an { @link EntityCallback }
*
* @author Oliver Drotbohm
* /
private static record EntityCallbackAdapter < T > ( EntityCallback < T > delegate , ResolvableType type )
implements EntityCallback < T > , ResolvableTypeProvider {
@NonNull
@Override
public int hashCode ( ) {
return this . callbackType . hashCode ( ) * 17 + ObjectUtils . nullSafeHashCode ( this . entityType ) ;
public ResolvableType getResolvableTyp e( ) {
return type ;
}
}
/ * *
* Cache key for { @link EntityCallback } , based on event type and source type .
* /
private static record CallbackCacheKey ( ResolvableType callbackType , @Nullable Class < ? > entityType )
implements Comparable < CallbackCacheKey > {
@Override
public int compareTo ( CallbackCacheKey other ) {
return Comparators . < CallbackCacheKey > nullsHigh ( ) . thenComparing ( it - > callbackType . toString ( ) )
return Comparators . < CallbackCacheKey > nullsHigh ( )
. thenComparing ( it - > callbackType . toString ( ) )
. thenComparing ( it - > entityType . getName ( ) ) . compare ( this , other ) ;
}
}