HHH-13565 Ensure all events from EventListenerGroup can be fired without allocations
This commit is contained in:
parent
646a8756a9
commit
9bfffd85d7
|
@ -6,15 +6,19 @@
|
|||
*/
|
||||
package org.hibernate.event.service.internal;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.event.service.spi.DuplicationStrategy;
|
||||
import org.hibernate.event.service.spi.EventActionWithParameter;
|
||||
import org.hibernate.event.service.spi.EventListenerGroup;
|
||||
import org.hibernate.event.service.spi.EventListenerRegistrationException;
|
||||
import org.hibernate.event.service.spi.JpaBootstrapSensitive;
|
||||
|
@ -23,6 +27,7 @@ import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
|
|||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
* @author Sanne Grinovero
|
||||
*/
|
||||
class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
||||
private EventType<T> eventType;
|
||||
|
@ -30,9 +35,14 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
|||
|
||||
private final Set<DuplicationStrategy> duplicationStrategies = new LinkedHashSet<>();
|
||||
|
||||
// Performance: make sure a forEach iteration on this type is efficient; in particular we choose ArrayList
|
||||
// so to avoid allocating iterators.
|
||||
private ArrayList<T> listeners;
|
||||
// Performance: make sure iteration on this type is efficient; in particular we do not want to allocate iterators,
|
||||
// not having to capture state in lambdas.
|
||||
// So we keep the listeners in both a List (for convenience) and in an array (for iteration). Make sure
|
||||
// their content stays in synch!
|
||||
private T[] listeners = null;
|
||||
|
||||
//Update both fields when making changes!
|
||||
private List<T> listenersAsList;
|
||||
|
||||
public EventListenerGroupImpl(EventType<T> eventType, EventListenerRegistryImpl listenerRegistry) {
|
||||
this.eventType = eventType;
|
||||
|
@ -66,7 +76,8 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
|||
|
||||
@Override
|
||||
public int count() {
|
||||
return listeners == null ? 0 : listeners.size();
|
||||
final T[] ls = listeners;
|
||||
return ls == null ? 0 : ls.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -74,16 +85,16 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
|||
if ( duplicationStrategies != null ) {
|
||||
duplicationStrategies.clear();
|
||||
}
|
||||
if ( listeners != null ) {
|
||||
listeners.clear();
|
||||
}
|
||||
listeners = null;
|
||||
listenersAsList = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final <U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent) {
|
||||
if ( listeners != null && listeners.size() != 0 ) {
|
||||
final T[] ls = listeners;
|
||||
if ( ls != null && ls.length != 0 ) {
|
||||
final U event = eventSupplier.get();
|
||||
for ( T listener : this.listeners ) {
|
||||
for ( T listener : ls ) {
|
||||
actionOnEvent.accept( listener, event );
|
||||
}
|
||||
}
|
||||
|
@ -91,13 +102,24 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
|||
|
||||
@Override
|
||||
public final <U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent) {
|
||||
if ( listeners != null ) {
|
||||
for ( T listener : this.listeners ) {
|
||||
final T[] ls = listeners;
|
||||
if ( ls != null ) {
|
||||
for ( T listener : ls ) {
|
||||
actionOnEvent.accept( listener, event );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U,X> void fireEventOnEachListener(final U event, final X parameter, final EventActionWithParameter<T, U, X> actionOnEvent) {
|
||||
final T[] ls = listeners;
|
||||
if ( ls != null ) {
|
||||
for ( T listener : ls ) {
|
||||
actionOnEvent.applyEventToListener( listener, event, parameter );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDuplicationStrategy(DuplicationStrategy strategy) {
|
||||
duplicationStrategies.add( strategy );
|
||||
|
@ -105,22 +127,44 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
|||
|
||||
/**
|
||||
* Implementation note: should be final for performance reasons.
|
||||
* @deprecated this is not the most efficient way for iterating the event listeners.
|
||||
* See {@link #fireEventOnEachListener(Object, BiConsumer)} and co. for better alternatives.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public final Iterable<T> listeners() {
|
||||
return listeners == null ? Collections.EMPTY_LIST : listeners;
|
||||
final List<T> ls = listenersAsList;
|
||||
return ls == null ? Collections.EMPTY_LIST : ls;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SafeVarargs
|
||||
public final void appendListeners(T... listeners) {
|
||||
internalAppendListeners( listeners );
|
||||
checkForArrayRefresh();
|
||||
}
|
||||
|
||||
private void checkForArrayRefresh() {
|
||||
final List<T> list = listenersAsList;
|
||||
if ( this.listeners == null ) {
|
||||
T[] a = (T[]) Array.newInstance( eventType.baseListenerInterface(), list.size() );
|
||||
listeners = list.<T>toArray( a );
|
||||
}
|
||||
}
|
||||
|
||||
private void internalAppendListeners(T[] listeners) {
|
||||
for ( T listener : listeners ) {
|
||||
appendListener( listener );
|
||||
internalAppendListener( listener );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendListener(T listener) {
|
||||
internalAppendListener( listener );
|
||||
checkForArrayRefresh();
|
||||
}
|
||||
|
||||
private void internalAppendListener(T listener) {
|
||||
if ( listenerShouldGetAdded( listener ) ) {
|
||||
internalAppend( listener );
|
||||
}
|
||||
|
@ -129,28 +173,39 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
|||
@Override
|
||||
@SafeVarargs
|
||||
public final void prependListeners(T... listeners) {
|
||||
internalPrependListeners( listeners );
|
||||
checkForArrayRefresh();
|
||||
}
|
||||
|
||||
private void internalPrependListeners(T[] listeners) {
|
||||
for ( T listener : listeners ) {
|
||||
prependListener( listener );
|
||||
internalPreprendListener( listener );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prependListener(T listener) {
|
||||
internalPreprendListener( listener );
|
||||
checkForArrayRefresh();
|
||||
}
|
||||
|
||||
private void internalPreprendListener(T listener) {
|
||||
if ( listenerShouldGetAdded( listener ) ) {
|
||||
internalPrepend( listener );
|
||||
}
|
||||
}
|
||||
|
||||
private boolean listenerShouldGetAdded(T listener) {
|
||||
if ( listeners == null ) {
|
||||
listeners = new ArrayList<>();
|
||||
final List<T> ts = listenersAsList;
|
||||
if ( ts == null ) {
|
||||
listenersAsList = new ArrayList<>();
|
||||
return true;
|
||||
// no need to do de-dup checks
|
||||
}
|
||||
|
||||
boolean doAdd = true;
|
||||
strategy_loop: for ( DuplicationStrategy strategy : duplicationStrategies ) {
|
||||
final ListIterator<T> itr = listeners.listIterator();
|
||||
final ListIterator<T> itr = ts.listIterator();
|
||||
while ( itr.hasNext() ) {
|
||||
final T existingListener = itr.next();
|
||||
if ( strategy.areMatch( listener, existingListener ) ) {
|
||||
|
@ -180,7 +235,8 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
|||
private void internalPrepend(T listener) {
|
||||
checkAgainstBaseInterface( listener );
|
||||
performInjections( listener );
|
||||
listeners.add( 0, listener );
|
||||
listenersAsList.add( 0, listener );
|
||||
listeners = null; //Marks it for refreshing
|
||||
}
|
||||
|
||||
private void performInjections(T listener) {
|
||||
|
@ -206,6 +262,7 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
|
|||
private void internalAppend(T listener) {
|
||||
checkAgainstBaseInterface( listener );
|
||||
performInjections( listener );
|
||||
listeners.add( listener );
|
||||
listenersAsList.add( listener );
|
||||
listeners = null; //Marks it for refreshing
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.event.service.spi;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
|
||||
@Incubating
|
||||
@FunctionalInterface
|
||||
public interface EventActionWithParameter<T, U, X> {
|
||||
|
||||
void applyEventToListener(T eventListener, U action, X param);
|
||||
|
||||
}
|
|
@ -7,9 +7,11 @@
|
|||
package org.hibernate.event.service.spi;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.event.spi.EventType;
|
||||
|
||||
/**
|
||||
|
@ -35,6 +37,12 @@ public interface EventListenerGroup<T> extends Serializable {
|
|||
|
||||
public int count();
|
||||
|
||||
/**
|
||||
* @deprecated this is not the most efficient way for iterating the event listeners.
|
||||
* See {@link #fireEventOnEachListener(Object, BiConsumer)} and its overloaded variants for better alternatives.
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public Iterable<T> listeners();
|
||||
|
||||
/**
|
||||
|
@ -67,6 +75,7 @@ public interface EventListenerGroup<T> extends Serializable {
|
|||
* @param actionOnEvent
|
||||
* @param <U> the kind of event
|
||||
*/
|
||||
@Incubating
|
||||
<U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent);
|
||||
|
||||
/**
|
||||
|
@ -76,6 +85,10 @@ public interface EventListenerGroup<T> extends Serializable {
|
|||
* @param actionOnEvent
|
||||
* @param <U> the kind of event
|
||||
*/
|
||||
@Incubating
|
||||
<U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent);
|
||||
|
||||
@Incubating
|
||||
<U,X> void fireEventOnEachListener(final U event, X param, final EventActionWithParameter<T,U,X> actionOnEvent);
|
||||
|
||||
}
|
||||
|
|
|
@ -724,7 +724,7 @@ public final class SessionImpl
|
|||
try {
|
||||
//Uses a capturing lambda in this case as we need to carry the additional Map parameter:
|
||||
fastSessionServices.eventListenerGroup_PERSIST
|
||||
.fireEventOnEachListener( event, (l, e) -> l.onPersist( e, copiedAlready ) );
|
||||
.fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist );
|
||||
}
|
||||
catch ( MappingException e ) {
|
||||
throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ) ;
|
||||
|
@ -745,7 +745,7 @@ public final class SessionImpl
|
|||
checkOpenOrWaitingForAutoClose();
|
||||
pulseTransactionCoordinator();
|
||||
PersistEvent event = new PersistEvent( entityName, object, this );
|
||||
fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, (l,e) -> l.onPersist( e, copiedAlready ) );
|
||||
fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist );
|
||||
delayedAfterCompletion();
|
||||
}
|
||||
|
||||
|
@ -793,7 +793,7 @@ public final class SessionImpl
|
|||
private void fireMerge(final Map copiedAlready, final MergeEvent event) {
|
||||
try {
|
||||
pulseTransactionCoordinator();
|
||||
fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, (l,e) -> l.onMerge( e, copiedAlready ) );
|
||||
fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, copiedAlready, MergeEventListener::onMerge );
|
||||
}
|
||||
catch ( ObjectDeletedException sse ) {
|
||||
throw getExceptionConverter().convert( new IllegalArgumentException( sse ) );
|
||||
|
@ -904,7 +904,7 @@ public final class SessionImpl
|
|||
private void fireDelete(final DeleteEvent event, final Set transientEntities) {
|
||||
try{
|
||||
pulseTransactionCoordinator();
|
||||
fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, (l,e) -> l.onDelete( e, transientEntities ) );
|
||||
fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, transientEntities, DeleteEventListener::onDelete );
|
||||
}
|
||||
catch ( ObjectDeletedException sse ) {
|
||||
throw getExceptionConverter().convert( new IllegalArgumentException( sse ) );
|
||||
|
@ -1179,7 +1179,7 @@ public final class SessionImpl
|
|||
// it seems they prevent these hot methods from being inlined.
|
||||
private void fireLoadNoChecks(final LoadEvent event, final LoadType loadType) {
|
||||
pulseTransactionCoordinator();
|
||||
fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, (l,e) -> l.onLoad( e, loadType ) );
|
||||
fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, loadType, LoadEventListener::onLoad );
|
||||
}
|
||||
|
||||
private void fireResolveNaturalId(final ResolveNaturalIdEvent event) {
|
||||
|
@ -1262,7 +1262,7 @@ public final class SessionImpl
|
|||
private void fireRefresh(final Map refreshedAlready, final RefreshEvent event) {
|
||||
try {
|
||||
pulseTransactionCoordinator();
|
||||
fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, (l,e) -> l.onRefresh( e, refreshedAlready ) );
|
||||
fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, refreshedAlready, RefreshEventListener::onRefresh );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw getExceptionConverter().convert( e );
|
||||
|
|
Loading…
Reference in New Issue