Skip to content

Commit 1b9d4a3

Browse files
committed
HHH-17643 Load BytecodeProvider as a java service
Also allow `SerializableProxy` deserialization even when no session factory is available.
1 parent 2811e24 commit 1b9d4a3

File tree

6 files changed

+210
-14
lines changed

6 files changed

+210
-14
lines changed

hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeProviderInitiator.java

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,40 @@
66
*/
77
package org.hibernate.bytecode.internal;
88

9+
import java.util.Collection;
10+
import java.util.Iterator;
911
import java.util.Map;
12+
import java.util.ServiceLoader;
1013

1114
import org.hibernate.Internal;
1215
import org.hibernate.boot.registry.StandardServiceInitiator;
16+
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
1317
import org.hibernate.bytecode.spi.BytecodeProvider;
1418
import org.hibernate.internal.CoreMessageLogger;
15-
import org.hibernate.internal.util.config.ConfigurationHelper;
1619
import org.hibernate.service.spi.ServiceRegistryImplementor;
1720

1821
import org.jboss.logging.Logger;
1922

20-
import static org.hibernate.cfg.BytecodeSettings.BYTECODE_PROVIDER;
23+
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
2124

2225
public final class BytecodeProviderInitiator implements StandardServiceInitiator<BytecodeProvider> {
2326

27+
/**
28+
* @deprecated Register a {@link BytecodeProvider} through Java {@linkplain java.util.ServiceLoader services}.
29+
*/
30+
@Deprecated( forRemoval = true )
2431
public static final String BYTECODE_PROVIDER_NAME_BYTEBUDDY = "bytebuddy";
32+
33+
/**
34+
* @deprecated Register a {@link BytecodeProvider} through Java {@linkplain java.util.ServiceLoader services}.
35+
*/
36+
@Deprecated( forRemoval = true )
2537
public static final String BYTECODE_PROVIDER_NAME_NONE = "none";
38+
39+
/**
40+
* @deprecated Deprecated with no replacement
41+
*/
42+
@Deprecated( forRemoval = true )
2643
public static final String BYTECODE_PROVIDER_NAME_DEFAULT = BYTECODE_PROVIDER_NAME_BYTEBUDDY;
2744

2845
/**
@@ -32,8 +49,9 @@ public final class BytecodeProviderInitiator implements StandardServiceInitiator
3249

3350
@Override
3451
public BytecodeProvider initiateService(Map<String, Object> configurationValues, ServiceRegistryImplementor registry) {
35-
String provider = ConfigurationHelper.getString( BYTECODE_PROVIDER, configurationValues, BYTECODE_PROVIDER_NAME_DEFAULT );
36-
return buildBytecodeProvider( provider );
52+
final ClassLoaderService classLoaderService = castNonNull( registry.getService( ClassLoaderService.class ) );
53+
final Collection<BytecodeProvider> bytecodeProviders = classLoaderService.loadJavaServices( BytecodeProvider.class );
54+
return getBytecodeProvider( bytecodeProviders );
3755
}
3856

3957
@Override
@@ -43,12 +61,26 @@ public Class<BytecodeProvider> getServiceInitiated() {
4361

4462
@Internal
4563
public static BytecodeProvider buildDefaultBytecodeProvider() {
46-
return buildBytecodeProvider( BYTECODE_PROVIDER_NAME_BYTEBUDDY );
64+
return getBytecodeProvider( ServiceLoader.load( BytecodeProvider.class ) );
4765
}
4866

4967
@Internal
50-
public static BytecodeProvider buildBytecodeProvider(String providerName) {
68+
public static BytecodeProvider getBytecodeProvider(Iterable<BytecodeProvider> bytecodeProviders) {
69+
final Iterator<BytecodeProvider> iterator = bytecodeProviders.iterator();
70+
if ( !iterator.hasNext() ) {
71+
// If no BytecodeProvider service is available, default to the "no-op" enhancer
72+
return new org.hibernate.bytecode.internal.none.BytecodeProviderImpl();
73+
}
74+
75+
final BytecodeProvider provider = iterator.next();
76+
if ( iterator.hasNext() ) {
77+
throw new IllegalStateException( "Found multiple BytecodeProvider service registrations, cannot determine which one to use" );
78+
}
79+
return provider;
80+
}
5181

82+
@Internal
83+
public static BytecodeProvider buildBytecodeProvider(String providerName) {
5284
CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, BytecodeProviderInitiator.class.getName() );
5385
LOG.bytecodeProvider( providerName );
5486

hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
1212
import org.hibernate.bytecode.enhance.spi.Enhancer;
1313
import org.hibernate.property.access.spi.PropertyAccess;
14+
import org.hibernate.service.JavaServiceLoadable;
1415
import org.hibernate.service.Service;
1516

1617
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -25,6 +26,7 @@
2526
*
2627
* @author Steve Ebersole
2728
*/
29+
@JavaServiceLoadable
2830
public interface BytecodeProvider extends Service {
2931
/**
3032
* Retrieve the specific factory for this provider capable of

hibernate-core/src/main/java/org/hibernate/cfg/BytecodeSettings.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ public interface BytecodeSettings {
2020
* At present only bytebuddy is supported, bytebuddy being the default since version 5.3.
2121
*
2222
* @settingDefault {@code "bytebuddy"}
23+
* @deprecated Will be removed, Hibernate ORM will use the BytecodeProvider implementation it finds on the
24+
* classpath loading it via the standard ServiceLoader mechanism. Currently, there is only a single
25+
* implementation which is included in Hibernate ORM, so it's not possible to override this.
26+
* See HHH-17643
2327
*/
28+
@Deprecated( forRemoval = true )
2429
String BYTECODE_PROVIDER = "hibernate.bytecode.provider";
2530

2631
/**

hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import org.hibernate.proxy.HibernateProxy;
1818
import org.hibernate.type.CompositeType;
1919

20+
import static org.hibernate.bytecode.internal.BytecodeProviderInitiator.buildDefaultBytecodeProvider;
21+
2022
public final class SerializableProxy extends AbstractSerializableProxy {
2123
private final Class<?> persistentClass;
2224
private final Class<?>[] interfaces;
@@ -30,6 +32,8 @@ public final class SerializableProxy extends AbstractSerializableProxy {
3032

3133
private final CompositeType componentIdType;
3234

35+
private static volatile BytecodeProviderImpl fallbackBytecodeProvider;
36+
3337
public SerializableProxy(
3438
String entityName,
3539
Class<?> persistentClass,
@@ -120,23 +124,32 @@ private Object readResolve() {
120124

121125
private static SessionFactoryImplementor retrieveMatchingSessionFactory(final String sessionFactoryUuid, final String sessionFactoryName) {
122126
Objects.requireNonNull( sessionFactoryUuid );
123-
final SessionFactoryImplementor sessionFactory = SessionFactoryRegistry.INSTANCE.findSessionFactory( sessionFactoryUuid, sessionFactoryName );
124-
if ( sessionFactory != null ) {
125-
return sessionFactory;
127+
return SessionFactoryRegistry.INSTANCE.findSessionFactory( sessionFactoryUuid, sessionFactoryName );
128+
}
129+
130+
private static BytecodeProviderImpl retrieveByteBuddyBytecodeProvider(final SessionFactoryImplementor sessionFactory) {
131+
if ( sessionFactory == null ) {
132+
// When the session factory is not available fallback to local bytecode provider
133+
return getFallbackBytecodeProvider();
126134
}
127-
else {
128-
throw new IllegalStateException( "Could not identify any active SessionFactory having UUID " + sessionFactoryUuid );
135+
136+
return castBytecodeProvider( sessionFactory.getServiceRegistry().getService( BytecodeProvider.class ) );
137+
}
138+
139+
private static BytecodeProviderImpl getFallbackBytecodeProvider() {
140+
BytecodeProviderImpl provider = fallbackBytecodeProvider;
141+
if ( provider == null ) {
142+
provider = fallbackBytecodeProvider = castBytecodeProvider( buildDefaultBytecodeProvider() );
129143
}
144+
return provider;
130145
}
131146

132-
private static BytecodeProviderImpl retrieveByteBuddyBytecodeProvider(final SessionFactoryImplementor sessionFactory) {
133-
final BytecodeProvider bytecodeProvider = sessionFactory.getServiceRegistry().getService( BytecodeProvider.class );
147+
private static BytecodeProviderImpl castBytecodeProvider(BytecodeProvider bytecodeProvider) {
134148
if ( bytecodeProvider instanceof BytecodeProviderImpl ) {
135149
return (BytecodeProviderImpl) bytecodeProvider;
136150
}
137151
else {
138152
throw new IllegalStateException( "Unable to deserialize a SerializableProxy proxy: the bytecode provider is not ByteBuddy." );
139153
}
140154
}
141-
142155
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.orm.test.serialization;
8+
9+
import java.io.Serializable;
10+
11+
import org.hibernate.Hibernate;
12+
import org.hibernate.SessionFactory;
13+
import org.hibernate.cfg.AvailableSettings;
14+
import org.hibernate.cfg.Configuration;
15+
import org.hibernate.internal.SessionFactoryRegistry;
16+
import org.hibernate.internal.util.SerializationHelper;
17+
import org.hibernate.proxy.HibernateProxy;
18+
19+
import org.hibernate.testing.junit4.BaseUnitTestCase;
20+
import org.hibernate.testing.util.ServiceRegistryUtil;
21+
import org.junit.Test;
22+
23+
import jakarta.persistence.Entity;
24+
import jakarta.persistence.FetchType;
25+
import jakarta.persistence.Id;
26+
import jakarta.persistence.JoinColumn;
27+
import jakarta.persistence.ManyToOne;
28+
29+
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
30+
import static org.junit.Assert.assertEquals;
31+
import static org.junit.Assert.assertFalse;
32+
import static org.junit.Assert.assertNotNull;
33+
import static org.junit.Assert.assertTrue;
34+
35+
/**
36+
* @author Marco Belladelli
37+
*/
38+
public class ProxySerializationNoSessionFactoryTest extends BaseUnitTestCase {
39+
@Test
40+
public void testUninitializedProxy() {
41+
executeTest( false );
42+
}
43+
44+
@Test
45+
public void testInitializedProxy() {
46+
executeTest( true );
47+
}
48+
49+
private void executeTest(boolean initializeProxy) {
50+
final Configuration cfg = new Configuration()
51+
.setProperty( AvailableSettings.HBM2DDL_AUTO, "create-drop" )
52+
.addAnnotatedClass( SimpleEntity.class )
53+
.addAnnotatedClass( ChildEntity.class );
54+
ServiceRegistryUtil.applySettings( cfg.getStandardServiceRegistryBuilder() );
55+
final SimpleEntity parent;
56+
try (final SessionFactory factory = cfg.buildSessionFactory()) {
57+
doInHibernate( () -> factory, session -> {
58+
final SimpleEntity entity = new SimpleEntity();
59+
entity.setId( 1L );
60+
entity.setName( "TheParent" );
61+
session.persist( entity );
62+
63+
final ChildEntity child = new ChildEntity();
64+
child.setId( 1L );
65+
child.setParent( entity );
66+
session.persist( child );
67+
} );
68+
69+
parent = doInHibernate( () -> factory, session -> {
70+
final ChildEntity childEntity = session.find( ChildEntity.class, 1L );
71+
final SimpleEntity entity = childEntity.getParent();
72+
if ( initializeProxy ) {
73+
assertEquals( "TheParent",entity.getName() );
74+
}
75+
return entity;
76+
} );
77+
}
78+
79+
// The session factory is not available anymore
80+
assertFalse( SessionFactoryRegistry.INSTANCE.hasRegistrations() );
81+
82+
assertTrue( parent instanceof HibernateProxy );
83+
assertEquals( initializeProxy, Hibernate.isInitialized( parent ) );
84+
85+
// Serialization and deserialization should still work
86+
final SimpleEntity clone = (SimpleEntity) SerializationHelper.clone( parent );
87+
assertNotNull( clone );
88+
assertEquals( parent.getId(), clone.getId() );
89+
if ( initializeProxy ) {
90+
assertEquals( parent.getName(), clone.getName() );
91+
}
92+
}
93+
94+
@Entity( name = "SimpleEntity" )
95+
static class SimpleEntity implements Serializable {
96+
@Id
97+
private Long id;
98+
99+
private String name;
100+
101+
public Long getId() {
102+
return id;
103+
}
104+
105+
public void setId(final Long id) {
106+
this.id = id;
107+
}
108+
109+
public String getName() {
110+
return name;
111+
}
112+
113+
public void setName(final String name) {
114+
this.name = name;
115+
}
116+
}
117+
118+
@Entity( name = "ChildEntity" )
119+
static class ChildEntity {
120+
@Id
121+
private Long id;
122+
123+
@ManyToOne( fetch = FetchType.LAZY )
124+
@JoinColumn
125+
private SimpleEntity parent;
126+
127+
public Long getId() {
128+
return id;
129+
}
130+
131+
public void setId(final Long id) {
132+
this.id = id;
133+
}
134+
135+
public SimpleEntity getParent() {
136+
return parent;
137+
}
138+
139+
public void setParent(SimpleEntity parent) {
140+
this.parent = parent;
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)