Skip to content

Commit aff789c

Browse files
committed
HHH-18011 Extract DefaultEnhancerClassFileLocator and allow using a different implementation
1 parent 4b172ed commit aff789c

File tree

7 files changed

+337
-55
lines changed

7 files changed

+337
-55
lines changed

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.hibernate.bytecode.enhance.internal.bytebuddy;
88

99
import java.util.Map;
10+
import java.util.Objects;
1011
import java.util.Optional;
1112
import java.util.concurrent.ConcurrentHashMap;
1213
import java.util.function.Function;
@@ -35,10 +36,16 @@ class ByteBuddyEnhancementContext {
3536
private final ConcurrentHashMap<TypeDescription, Map<String, MethodDescription>> getterByTypeMap = new ConcurrentHashMap<>();
3637
private final ConcurrentHashMap<String, Object> locksMap = new ConcurrentHashMap<>();
3738

38-
ByteBuddyEnhancementContext(EnhancementContext enhancementContext) {
39-
this.enhancementContext = enhancementContext;
39+
ByteBuddyEnhancementContext(final EnhancementContext enhancementContext) {
40+
this.enhancementContext = Objects.requireNonNull( enhancementContext );
4041
}
4142

43+
/**
44+
* @deprecated as it's currently unused and we're not always actually sourcing the classes to be transformed
45+
* from a classloader, so this getter can't always be honoured correctly.
46+
* @return the ClassLoader provided by the underlying EnhancementContext. Might be otherwise ignored.
47+
*/
48+
@Deprecated(forRemoval = true)
4249
public ClassLoader getLoadingClassLoader() {
4350
return enhancementContext.getLoadingClassLoader();
4451
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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.bytecode.enhance.internal.bytebuddy;
8+
9+
import java.util.concurrent.ConcurrentHashMap;
10+
11+
import net.bytebuddy.description.type.TypeDescription;
12+
import net.bytebuddy.pool.TypePool;
13+
14+
/**
15+
* A TypePool which only loads, and caches, types whose package
16+
* name starts with certain chosen prefixes.
17+
* The default is to only load classes whose package names start with
18+
* either "jakarta." or "java.".
19+
* This allows to reuse these caches independently from application
20+
* code and classloader changes, as during enhancement we frequently
21+
* encounter such symbols as well, for example triggered by JPA annotations
22+
* or properties mapped via standard java types and collections.
23+
* Symbols resolved by this pool are backed by loaded classes from
24+
* ORM's classloader.
25+
*/
26+
public class CoreTypePool extends TypePool.AbstractBase implements TypePool {
27+
28+
private final ClassLoader hibernateClassLoader = CoreTypePool.class.getClassLoader();
29+
private final ConcurrentHashMap<String, Resolution> resolutions = new ConcurrentHashMap<>();
30+
private final String[] acceptedPrefixes;
31+
32+
/**
33+
* Construct a new {@link CoreTypePool} with its default configuration:
34+
* to only load classes whose package names start with either "jakarta."
35+
* or "java."
36+
*/
37+
public CoreTypePool() {
38+
//By default optimise for jakarta annotations, and java util collections
39+
this("jakarta.", "java.", "org.hibernate.annotations.");
40+
}
41+
42+
/**
43+
* Construct a new {@link CoreTypePool} with a choice of which prefixes
44+
* for fully qualified classnames will be loaded by this {@link TypePool}.
45+
*/
46+
public CoreTypePool(final String... acceptedPrefixes) {
47+
//While we implement a cache in this class we also want to enable
48+
//ByteBuddy's default caching mechanism as it will cache the more
49+
//useful output of the parsing and introspection of such types.
50+
super( new TypePool.CacheProvider.Simple() );
51+
this.acceptedPrefixes = acceptedPrefixes;
52+
}
53+
54+
private boolean isCoreClassName(final String name) {
55+
for ( String acceptedPrefix : this.acceptedPrefixes ) {
56+
if ( name.startsWith( acceptedPrefix ) ) {
57+
return true;
58+
}
59+
}
60+
return false;
61+
}
62+
63+
@Override
64+
protected Resolution doDescribe(final String name) {
65+
if ( isCoreClassName( name ) ) {
66+
final Resolution resolution = resolutions.get( name );
67+
if ( resolution != null ) {
68+
return resolution;
69+
}
70+
else {
71+
//We implement this additional layer of caching, which is on top of
72+
//ByteBuddy's default caching, so as to prevent resolving the same
73+
//types concurrently from the classloader.
74+
//This is merely an efficiency improvement and will NOT provide a
75+
//strict guarantee of symbols being resolved exactly once as there
76+
//is no SPI within ByteBuddy which would allow this: the point is to
77+
//make it exceptionally infrequent, which greatly helps with
78+
//processing of large models.
79+
return resolutions.computeIfAbsent( name, this::actualResolve );
80+
}
81+
}
82+
else {
83+
//These are not cached to not leak references to application code names
84+
return new Resolution.Illegal( name );
85+
}
86+
}
87+
88+
private Resolution actualResolve(final String name) {
89+
try {
90+
final Class<?> aClass = Class.forName( name, false, hibernateClassLoader );
91+
return new TypePool.Resolution.Simple( TypeDescription.ForLoadedType.of( aClass ) );
92+
}
93+
catch ( ClassNotFoundException e ) {
94+
return new Resolution.Illegal( name );
95+
}
96+
}
97+
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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.bytecode.enhance.internal.bytebuddy;
8+
9+
import net.bytebuddy.dynamic.ClassFileLocator;
10+
import net.bytebuddy.pool.TypePool;
11+
12+
/**
13+
* Extends the TypePool contract of ByteBuddy with our additional needs.
14+
*/
15+
public interface EnhancerClassLocator extends TypePool {
16+
17+
/**
18+
* Register a new class to the locator explicitly.
19+
* @param className
20+
* @param originalBytes
21+
*/
22+
void registerClassNameAndBytes(String className, byte[] originalBytes);
23+
24+
/**
25+
* This can optionally be used to remove an explicit mapping when it's no longer
26+
* essential to retain it.
27+
* The underlying implementation might ignore the operation.
28+
* @param className
29+
*/
30+
void deregisterClassNameAndBytes(String className);
31+
32+
/**
33+
* @return the underlying {@link ClassFileLocator}
34+
*/
35+
ClassFileLocator asClassFileLocator();
36+
}

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java

+20-53
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66
*/
77
package org.hibernate.bytecode.enhance.internal.bytebuddy;
88

9-
import java.io.IOException;
109
import java.lang.annotation.Annotation;
1110
import java.lang.reflect.Modifier;
1211
import java.util.ArrayList;
1312
import java.util.Collection;
1413
import java.util.Collections;
1514
import java.util.List;
1615
import java.util.Map;
16+
import java.util.Objects;
1717
import java.util.Optional;
18-
import java.util.concurrent.ConcurrentHashMap;
1918
import java.util.function.Supplier;
2019

2120
import org.hibernate.Version;
@@ -66,7 +65,6 @@
6665
import net.bytebuddy.implementation.FixedValue;
6766
import net.bytebuddy.implementation.Implementation;
6867
import net.bytebuddy.implementation.StubMethod;
69-
import net.bytebuddy.pool.TypePool;
7068

7169
import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer;
7270

@@ -93,9 +91,7 @@ public Class<? extends Annotation> annotationType() {
9391

9492
protected final ByteBuddyEnhancementContext enhancementContext;
9593
private final ByteBuddyState byteBuddyState;
96-
97-
private final EnhancerClassFileLocator classFileLocator;
98-
private final TypePool typePool;
94+
private final EnhancerClassLocator typePool;
9995

10096
/**
10197
* Extract the following constants so that enhancement on large projects
@@ -126,10 +122,20 @@ public Class<? extends Annotation> annotationType() {
126122
* @param byteBuddyState refers to the ByteBuddy instance to use
127123
*/
128124
public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState) {
125+
this( enhancementContext, byteBuddyState, ModelTypePool.buildModelTypePool( enhancementContext.getLoadingClassLoader() ) );
126+
}
127+
128+
/**
129+
* Expert level constructor, this allows for more control of state and bytecode loading,
130+
* which allows integrators to optimise for particular contexts of use.
131+
* @param enhancementContext
132+
* @param byteBuddyState
133+
* @param classLocator
134+
*/
135+
public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState, final EnhancerClassLocator classLocator) {
129136
this.enhancementContext = new ByteBuddyEnhancementContext( enhancementContext );
130-
this.byteBuddyState = byteBuddyState;
131-
this.classFileLocator = new EnhancerClassFileLocator( enhancementContext.getLoadingClassLoader() );
132-
this.typePool = buildTypePool( classFileLocator );
137+
this.byteBuddyState = Objects.requireNonNull( byteBuddyState );
138+
this.typePool = Objects.requireNonNull( classLocator );
133139
}
134140

135141
/**
@@ -147,13 +153,13 @@ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddy
147153
public byte[] enhance(String className, byte[] originalBytes) throws EnhancementException {
148154
//Classpool#describe does not accept '/' in the description name as it expects a class name. See HHH-12545
149155
final String safeClassName = className.replace( '/', '.' );
150-
classFileLocator.registerClassNameAndBytes( safeClassName, originalBytes );
156+
typePool.registerClassNameAndBytes( safeClassName, originalBytes );
151157
try {
152158
final TypeDescription typeDescription = typePool.describe( safeClassName ).resolve();
153159

154160
return byteBuddyState.rewrite( typePool, safeClassName, byteBuddy -> doEnhance(
155161
() -> byteBuddy.ignore( isDefaultFinalizer() )
156-
.redefine( typeDescription, ClassFileLocator.Simple.of( safeClassName, originalBytes ) )
162+
.redefine( typeDescription, typePool.asClassFileLocator() )
157163
.annotateType( HIBERNATE_VERSION_ANNOTATION ),
158164
typeDescription
159165
) );
@@ -165,14 +171,14 @@ public byte[] enhance(String className, byte[] originalBytes) throws Enhancement
165171
throw new EnhancementException( "Failed to enhance class " + className, e );
166172
}
167173
finally {
168-
classFileLocator.deregisterClassNameAndBytes( safeClassName );
174+
typePool.deregisterClassNameAndBytes( safeClassName );
169175
}
170176
}
171177

172178
@Override
173179
public void discoverTypes(String className, byte[] originalBytes) {
174180
if ( originalBytes != null ) {
175-
classFileLocator.registerClassNameAndBytes( className, originalBytes );
181+
typePool.registerClassNameAndBytes( className, originalBytes );
176182
}
177183
try {
178184
final TypeDescription typeDescription = typePool.describe( className ).resolve();
@@ -183,14 +189,10 @@ public void discoverTypes(String className, byte[] originalBytes) {
183189
throw new EnhancementException( "Failed to discover types for class " + className, e );
184190
}
185191
finally {
186-
classFileLocator.deregisterClassNameAndBytes( className );
192+
typePool.deregisterClassNameAndBytes( className );
187193
}
188194
}
189195

190-
private TypePool buildTypePool(final ClassFileLocator classFileLocator) {
191-
return TypePool.Default.WithLazyResolution.of( classFileLocator );
192-
}
193-
194196
private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builderSupplier, TypeDescription managedCtClass) {
195197
// can't effectively enhance interfaces
196198
if ( managedCtClass.isInterface() ) {
@@ -652,39 +654,4 @@ else if ( access != null && access.load().value() == AccessType.FIELD ) {
652654
}
653655
}
654656

655-
private static class EnhancerClassFileLocator extends ClassFileLocator.ForClassLoader {
656-
private final ConcurrentHashMap<String, Resolution> resolutions = new ConcurrentHashMap<>();
657-
658-
/**
659-
* Creates a new class file locator for the given class loader.
660-
*
661-
* @param classLoader The class loader to query which must not be the bootstrap class loader, i.e. {@code null}.
662-
*/
663-
protected EnhancerClassFileLocator(ClassLoader classLoader) {
664-
super( classLoader );
665-
}
666-
667-
@Override
668-
public Resolution locate(String className) throws IOException {
669-
assert className != null;
670-
final Resolution resolution = resolutions.get( className );
671-
if ( resolution != null ) {
672-
return resolution;
673-
}
674-
else {
675-
return super.locate( className );
676-
}
677-
}
678-
679-
void registerClassNameAndBytes(String className, byte[] bytes) {
680-
assert className != null;
681-
assert bytes != null;
682-
resolutions.put( className, new Resolution.Explicit( bytes ) );
683-
}
684-
685-
void deregisterClassNameAndBytes(String className) {
686-
resolutions.remove( className );
687-
}
688-
}
689-
690657
}

0 commit comments

Comments
 (0)