HHH-17643 Load `BytecodeProvider` as a java service

Also allow `SerializableProxy` deserialization even when no session factory is available.
This commit is contained in:
Marco Belladelli 2024-01-11 16:41:20 +01:00
parent 776c05cad7
commit 5587badf3f
6 changed files with 211 additions and 15 deletions

View File

@ -6,23 +6,40 @@
*/ */
package org.hibernate.bytecode.internal; package org.hibernate.bytecode.internal;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.ServiceLoader;
import org.hibernate.Internal; import org.hibernate.Internal;
import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.boot.registry.StandardServiceInitiator;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.bytecode.spi.BytecodeProvider;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.hibernate.cfg.BytecodeSettings.BYTECODE_PROVIDER; import static org.hibernate.internal.util.NullnessUtil.castNonNull;
public final class BytecodeProviderInitiator implements StandardServiceInitiator<BytecodeProvider> { public final class BytecodeProviderInitiator implements StandardServiceInitiator<BytecodeProvider> {
/**
* @deprecated Register a {@link BytecodeProvider} through Java {@linkplain java.util.ServiceLoader services}.
*/
@Deprecated( forRemoval = true )
public static final String BYTECODE_PROVIDER_NAME_BYTEBUDDY = "bytebuddy"; public static final String BYTECODE_PROVIDER_NAME_BYTEBUDDY = "bytebuddy";
/**
* @deprecated Register a {@link BytecodeProvider} through Java {@linkplain java.util.ServiceLoader services}.
*/
@Deprecated( forRemoval = true )
public static final String BYTECODE_PROVIDER_NAME_NONE = "none"; public static final String BYTECODE_PROVIDER_NAME_NONE = "none";
/**
* @deprecated Deprecated with no replacement
*/
@Deprecated( forRemoval = true )
public static final String BYTECODE_PROVIDER_NAME_DEFAULT = BYTECODE_PROVIDER_NAME_BYTEBUDDY; public static final String BYTECODE_PROVIDER_NAME_DEFAULT = BYTECODE_PROVIDER_NAME_BYTEBUDDY;
/** /**
@ -32,8 +49,9 @@ public final class BytecodeProviderInitiator implements StandardServiceInitiator
@Override @Override
public BytecodeProvider initiateService(Map<String, Object> configurationValues, ServiceRegistryImplementor registry) { public BytecodeProvider initiateService(Map<String, Object> configurationValues, ServiceRegistryImplementor registry) {
String provider = ConfigurationHelper.getString( BYTECODE_PROVIDER, configurationValues, BYTECODE_PROVIDER_NAME_DEFAULT ); final ClassLoaderService classLoaderService = castNonNull( registry.getService( ClassLoaderService.class ) );
return buildBytecodeProvider( provider ); final Collection<BytecodeProvider> bytecodeProviders = classLoaderService.loadJavaServices( BytecodeProvider.class );
return getBytecodeProvider( bytecodeProviders );
} }
@Override @Override
@ -43,12 +61,26 @@ public final class BytecodeProviderInitiator implements StandardServiceInitiator
@Internal @Internal
public static BytecodeProvider buildDefaultBytecodeProvider() { public static BytecodeProvider buildDefaultBytecodeProvider() {
return buildBytecodeProvider( BYTECODE_PROVIDER_NAME_BYTEBUDDY ); return getBytecodeProvider( ServiceLoader.load( BytecodeProvider.class ) );
}
@Internal
public static BytecodeProvider getBytecodeProvider(Iterable<BytecodeProvider> bytecodeProviders) {
final Iterator<BytecodeProvider> iterator = bytecodeProviders.iterator();
if ( !iterator.hasNext() ) {
// If no BytecodeProvider service is available, default to the "no-op" enhancer
return new org.hibernate.bytecode.internal.none.BytecodeProviderImpl();
}
final BytecodeProvider provider = iterator.next();
if ( iterator.hasNext() ) {
throw new IllegalStateException( "Found multiple BytecodeProvider service registrations, cannot determine which one to use" );
}
return provider;
} }
@Internal @Internal
public static BytecodeProvider buildBytecodeProvider(String providerName) { public static BytecodeProvider buildBytecodeProvider(String providerName) {
CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, BytecodeProviderInitiator.class.getName() ); CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, BytecodeProviderInitiator.class.getName() );
LOG.bytecodeProvider( providerName ); LOG.bytecodeProvider( providerName );

View File

@ -11,6 +11,7 @@ import java.util.Map;
import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer; import org.hibernate.bytecode.enhance.spi.Enhancer;
import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.service.JavaServiceLoadable;
import org.hibernate.service.Service; import org.hibernate.service.Service;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -25,6 +26,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@JavaServiceLoadable
public interface BytecodeProvider extends Service { public interface BytecodeProvider extends Service {
/** /**
* Retrieve the specific factory for this provider capable of * Retrieve the specific factory for this provider capable of

View File

@ -20,7 +20,12 @@ public interface BytecodeSettings {
* At present only bytebuddy is supported, bytebuddy being the default since version 5.3. * At present only bytebuddy is supported, bytebuddy being the default since version 5.3.
* *
* @settingDefault {@code "bytebuddy"} * @settingDefault {@code "bytebuddy"}
* @deprecated Will be removed, Hibernate ORM will use the BytecodeProvider implementation it finds on the
* classpath loading it via the standard ServiceLoader mechanism. Currently, there is only a single
* implementation which is included in Hibernate ORM, so it's not possible to override this.
* See HHH-17643
*/ */
@Deprecated( forRemoval = true )
String BYTECODE_PROVIDER = "hibernate.bytecode.provider"; String BYTECODE_PROVIDER = "hibernate.bytecode.provider";
/** /**

View File

@ -17,6 +17,8 @@ import org.hibernate.proxy.AbstractSerializableProxy;
import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxy;
import org.hibernate.type.CompositeType; import org.hibernate.type.CompositeType;
import static org.hibernate.bytecode.internal.BytecodeProviderInitiator.buildDefaultBytecodeProvider;
public final class SerializableProxy extends AbstractSerializableProxy { public final class SerializableProxy extends AbstractSerializableProxy {
private final Class<?> persistentClass; private final Class<?> persistentClass;
private final Class<?>[] interfaces; private final Class<?>[] interfaces;
@ -30,6 +32,8 @@ public final class SerializableProxy extends AbstractSerializableProxy {
private final CompositeType componentIdType; private final CompositeType componentIdType;
private static volatile BytecodeProviderImpl fallbackBytecodeProvider;
public SerializableProxy( public SerializableProxy(
String entityName, String entityName,
Class<?> persistentClass, Class<?> persistentClass,
@ -120,17 +124,27 @@ public final class SerializableProxy extends AbstractSerializableProxy {
private static SessionFactoryImplementor retrieveMatchingSessionFactory(final String sessionFactoryUuid, final String sessionFactoryName) { private static SessionFactoryImplementor retrieveMatchingSessionFactory(final String sessionFactoryUuid, final String sessionFactoryName) {
Objects.requireNonNull( sessionFactoryUuid ); Objects.requireNonNull( sessionFactoryUuid );
final SessionFactoryImplementor sessionFactory = SessionFactoryRegistry.INSTANCE.findSessionFactory( sessionFactoryUuid, sessionFactoryName ); return SessionFactoryRegistry.INSTANCE.findSessionFactory( sessionFactoryUuid, sessionFactoryName );
if ( sessionFactory != null ) {
return sessionFactory;
}
else {
throw new IllegalStateException( "Could not identify any active SessionFactory having UUID " + sessionFactoryUuid );
}
} }
private static BytecodeProviderImpl retrieveByteBuddyBytecodeProvider(final SessionFactoryImplementor sessionFactory) { private static BytecodeProviderImpl retrieveByteBuddyBytecodeProvider(final SessionFactoryImplementor sessionFactory) {
final BytecodeProvider bytecodeProvider = sessionFactory.getServiceRegistry().getService( BytecodeProvider.class ); if ( sessionFactory == null ) {
// When the session factory is not available fallback to local bytecode provider
return getFallbackBytecodeProvider();
}
return castBytecodeProvider( sessionFactory.getServiceRegistry().getService( BytecodeProvider.class ) );
}
private static BytecodeProviderImpl getFallbackBytecodeProvider() {
BytecodeProviderImpl provider = fallbackBytecodeProvider;
if ( provider == null ) {
provider = fallbackBytecodeProvider = castBytecodeProvider( buildDefaultBytecodeProvider() );
}
return provider;
}
private static BytecodeProviderImpl castBytecodeProvider(BytecodeProvider bytecodeProvider) {
if ( bytecodeProvider instanceof BytecodeProviderImpl ) { if ( bytecodeProvider instanceof BytecodeProviderImpl ) {
return (BytecodeProviderImpl) bytecodeProvider; return (BytecodeProviderImpl) bytecodeProvider;
} }
@ -138,5 +152,4 @@ public final class SerializableProxy extends AbstractSerializableProxy {
throw new IllegalStateException( "Unable to deserialize a SerializableProxy proxy: the bytecode provider is not ByteBuddy." ); throw new IllegalStateException( "Unable to deserialize a SerializableProxy proxy: the bytecode provider is not ByteBuddy." );
} }
} }
} }

View File

@ -0,0 +1 @@
org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl

View File

@ -0,0 +1,143 @@
/*
* 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.orm.test.serialization;
import java.io.Serializable;
import org.hibernate.Hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.internal.SessionFactoryRegistry;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.hibernate.testing.util.ServiceRegistryUtil;
import org.junit.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author Marco Belladelli
*/
public class ProxySerializationNoSessionFactoryTest extends BaseUnitTestCase {
@Test
public void testUninitializedProxy() {
executeTest( false );
}
@Test
public void testInitializedProxy() {
executeTest( true );
}
private void executeTest(boolean initializeProxy) {
final Configuration cfg = new Configuration()
.setProperty( AvailableSettings.HBM2DDL_AUTO, "create-drop" )
.addAnnotatedClass( SimpleEntity.class )
.addAnnotatedClass( ChildEntity.class );
ServiceRegistryUtil.applySettings( cfg.getStandardServiceRegistryBuilder() );
final SimpleEntity parent;
try (final SessionFactory factory = cfg.buildSessionFactory()) {
doInHibernate( () -> factory, session -> {
final SimpleEntity entity = new SimpleEntity();
entity.setId( 1L );
entity.setName( "TheParent" );
session.persist( entity );
final ChildEntity child = new ChildEntity();
child.setId( 1L );
child.setParent( entity );
session.persist( child );
} );
parent = doInHibernate( () -> factory, session -> {
final ChildEntity childEntity = session.find( ChildEntity.class, 1L );
final SimpleEntity entity = childEntity.getParent();
if ( initializeProxy ) {
assertEquals( "TheParent",entity.getName() );
}
return entity;
} );
}
// The session factory is not available anymore
assertFalse( SessionFactoryRegistry.INSTANCE.hasRegistrations() );
assertTrue( parent instanceof HibernateProxy );
assertEquals( initializeProxy, Hibernate.isInitialized( parent ) );
// Serialization and deserialization should still work
final SimpleEntity clone = (SimpleEntity) SerializationHelper.clone( parent );
assertNotNull( clone );
assertEquals( parent.getId(), clone.getId() );
if ( initializeProxy ) {
assertEquals( parent.getName(), clone.getName() );
}
}
@Entity( name = "SimpleEntity" )
static class SimpleEntity implements Serializable {
@Id
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
@Entity( name = "ChildEntity" )
static class ChildEntity {
@Id
private Long id;
@ManyToOne( fetch = FetchType.LAZY )
@JoinColumn
private SimpleEntity parent;
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public SimpleEntity getParent() {
return parent;
}
public void setParent(SimpleEntity parent) {
this.parent = parent;
}
}
}