2
2
3
3
import java .lang .reflect .Method ;
4
4
import java .nio .charset .Charset ;
5
+ import java .util .concurrent .ConcurrentHashMap ;
6
+ import java .util .logging .Level ;
7
+ import java .util .logging .Logger ;
5
8
6
9
/**
7
10
* Class loader that is needed to load generated classes.
@@ -10,6 +13,11 @@ public class MyClassLoader extends ClassLoader
10
13
{
11
14
private final static Charset UTF8 = Charset .forName ("UTF-8" );
12
15
16
+ // Maps parent classloader instance and class name to the corresponding lock object.
17
+ // N.B. this must be static because multiple instances of MyClassLoader must all use the same lock
18
+ // when loading classes directly on the same parent.
19
+ private final static ConcurrentHashMap <String , Object > parentParallelLockMap = new ConcurrentHashMap <>();
20
+
13
21
/**
14
22
* Flag that determines if we should first try to load new class
15
23
* using parent class loader or not; this may be done to try to
@@ -56,52 +64,156 @@ public static boolean canAddClassInPackageOf(Class<?> cls)
56
64
* implement
57
65
*/
58
66
public Class <?> loadAndResolve (ClassName className , byte [] byteCode )
59
- throws IllegalArgumentException
67
+ throws IllegalArgumentException
60
68
{
61
- // First things first: just to be sure; maybe we have already loaded it?
62
- Class <?> old = findLoadedClass (className . getDottedName () );
63
- if (old != null ) {
64
- return old ;
69
+ // first, try to loadAndResolve via the parent classloader, if configured to do so
70
+ Class <?> classFromParent = loadAndResolveUsingParentClassloader (className , byteCode );
71
+ if (classFromParent != null ) {
72
+ return classFromParent ;
65
73
}
66
-
67
- Class <?> impl ;
68
-
69
- // Important: bytecode is generated with a template name (since bytecode itself
70
- // is used for checksum calculation) -- must be replaced now, however
71
- replaceName ( byteCode , className . getSlashedTemplate (), className . getSlashedName ());
72
-
73
- // First: let's try calling it directly on parent, to be able to access protected/package-access stuff:
74
- if ( _cfgUseParentLoader ) {
75
- ClassLoader cl = getParent ();
76
- // if we have parent, that is
77
- if ( cl != null ) {
78
- try {
79
- Method method = ClassLoader . class . getDeclaredMethod ( "defineClass" ,
80
- new Class [] { String . class , byte []. class , int . class ,
81
- int . class });
82
- method . setAccessible ( true );
83
- return ( Class <?>) method . invoke ( getParent (),
84
- className . getDottedName (), byteCode , 0 , byteCode . length ) ;
85
- } catch ( Exception e ) {
86
- // Should we handle this somehow?
74
+
75
+ // fall back to loading and resolving ourselves
76
+ synchronized ( getClassLoadingLock ( className . getDottedName ())) {
77
+ // First: check to see if we have loaded it ourselves
78
+ Class <?> existingClass = findLoadedClass ( className . getDottedName ());
79
+ if ( existingClass != null ) {
80
+ return existingClass ;
81
+ }
82
+
83
+ // Important: bytecode is generated with a template name (since bytecode itself
84
+ // is used for checksum calculation) -- must be replaced now, however
85
+ replaceName ( byteCode , className . getSlashedTemplate (), className . getSlashedName ());
86
+
87
+ // Second: define a new class instance using the bytecode
88
+ Class <?> newClass ;
89
+ try {
90
+ newClass = defineClass ( className . getDottedName (), byteCode , 0 , byteCode . length );
91
+ } catch ( LinkageError e ) {
92
+ Throwable t = e ;
93
+ while ( t . getCause () != null ) {
94
+ t = t . getCause ();
87
95
}
96
+ throw new IllegalArgumentException ("Failed to load class '" + className + "': " + t .getMessage (), t );
88
97
}
98
+ // important: must also resolve the newly-created class.
99
+ resolveClass (newClass );
100
+ return newClass ;
89
101
}
102
+ }
90
103
91
- // but if that doesn't fly, try to do it from our own class loader
92
-
93
- try {
94
- impl = defineClass (className .getDottedName (), byteCode , 0 , byteCode .length );
95
- } catch (LinkageError e ) {
96
- Throwable t = e ;
97
- while (t .getCause () != null ) {
98
- t = t .getCause ();
104
+ /**
105
+ * Attempt to load (and resolve) the class using the parent class loader (if it is configured and present).
106
+ * This method will return {@code null} if the parent classloader is not configured or cannot be retrieved.
107
+ *
108
+ * @param className Interface or abstract class that class to load should extend or implement
109
+ * @param byteCode the generated bytecode for the class to load
110
+ * @return the loaded class, or {@code null} if the class could not be loaded on the parent classloader.
111
+ */
112
+ private Class <?> loadAndResolveUsingParentClassloader (ClassName className , byte [] byteCode )
113
+ {
114
+ ClassLoader parentClassLoader ;
115
+ if (!_cfgUseParentLoader || (parentClassLoader = getParent ()) == null ) {
116
+ return null ;
117
+ }
118
+ // N.B. The parent-class-loading locks are shared between all instances of MyClassLoader.
119
+ // We can be confident that no attempt will be made to re-acquire *any* parent-class-loading lock instance
120
+ // inside the synchronized region (eliminating the risk of deadlock), even if the parent class loader is also
121
+ // an instance of MyClassLoader, because:
122
+ // a) this method is the only place that attempts to acquire a parent class loading lock,
123
+ // b) the only non-private method which calls this method and thus acquires this lock is
124
+ // MyClassLoader#loadAndResolve,
125
+ // c) nothing in the synchronized region can have the effect of calling #loadAndResolve on this
126
+ // or any other instance of MyClassLoader.
127
+ synchronized (getParentClassLoadingLock (parentClassLoader , className .getDottedName ())) {
128
+ // First: check to see if the parent classloader has loaded it already
129
+ Class <?> impl = findLoadedClassOnParent (parentClassLoader , className .getDottedName ());
130
+ if (impl != null ) {
131
+ return impl ;
99
132
}
100
- throw new IllegalArgumentException ("Failed to load class '" +className +"': " +t .getMessage (), t );
133
+
134
+ // Important: bytecode is generated with a template name (since bytecode itself
135
+ // is used for checksum calculation) -- must be replaced now, however
136
+ replaceName (byteCode , className .getSlashedTemplate (), className .getSlashedName ());
137
+
138
+ // Second: define a new class instance on the parent classloder using the bytecode
139
+ impl = defineClassOnParent (parentClassLoader , className .getDottedName (), byteCode , 0 , byteCode .length );
140
+ // important: must also resolve the newly-created class.
141
+ resolveClassOnParent (parentClassLoader , impl );
142
+ return impl ;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Get the class loading lock for the parent class loader for loading the named class.
148
+ *
149
+ * This is effectively the same implementation as ClassLoader#getClassLoadingLock, but using
150
+ * our static parentParallelLockMap and keying off of the parent ClassLoader instance as well as
151
+ * the class name to load.
152
+ *
153
+ * @param parentClassLoader The parent ClassLoader
154
+ * @param className The name of the to-be-loaded class
155
+ */
156
+ private Object getParentClassLoadingLock (ClassLoader parentClassLoader , String className ) {
157
+ // N.B. using the canonical name and identity hash code to represent the parent class loader in the key
158
+ // in case that ClassLoader instance (which could be anything) implements #hashCode or #toString poorly.
159
+ // In the event of a collision here (same key, different parent class loader), we will end up using the
160
+ // same lock only to synchronize loads of the same class on two different class loaders,
161
+ // which shouldn't ever deadlock (see proof in #loadAndResolveUsingParentClassloader);
162
+ // worst case is unnecessary contention for the lock.
163
+ String key = parentClassLoader .getClass ().getCanonicalName ()
164
+ + ":" + System .identityHashCode (parentClassLoader )
165
+ + ":" + className ;
166
+ Object newLock = new Object ();
167
+ Object lock = parentParallelLockMap .putIfAbsent (key , newLock );
168
+ if (lock == null ) {
169
+ lock = newLock ;
170
+ }
171
+ return lock ;
172
+ }
173
+
174
+ private Class <?> findLoadedClassOnParent (ClassLoader parentClassLoader , String className ) {
175
+ try {
176
+ Method method = ClassLoader .class .getDeclaredMethod ("findLoadedClass" , String .class );
177
+ method .setAccessible (true );
178
+ return (Class <?>) method .invoke (parentClassLoader , className );
179
+ } catch (Exception e ) {
180
+ String msg = String .format ("Exception trying 'findLoadedClass(%s)' on parent ClassLoader '%s'" ,
181
+ className , parentClassLoader );
182
+ Logger .getLogger (MyClassLoader .class .getName ()).log (Level .FINE , msg , e );
183
+ return null ;
184
+ }
185
+ }
186
+
187
+ // visible for testing
188
+ Class <?> defineClassOnParent (ClassLoader parentClassLoader ,
189
+ String className ,
190
+ byte [] byteCode ,
191
+ int offset ,
192
+ int length ) {
193
+ try {
194
+ Method method = ClassLoader .class .getDeclaredMethod ("defineClass" ,
195
+ new Class []{String .class , byte [].class , int .class , int .class });
196
+ method .setAccessible (true );
197
+ return (Class <?>) method .invoke (parentClassLoader ,
198
+ className , byteCode , offset , length );
199
+ } catch (Exception e ) {
200
+ String msg = String .format ("Exception trying 'defineClass(%s, <bytecode>)' on parent ClassLoader '%s'" ,
201
+ className , parentClassLoader );
202
+ Logger .getLogger (MyClassLoader .class .getName ()).log (Level .FINE , msg , e );
203
+ return null ;
204
+ }
205
+ }
206
+
207
+ private void resolveClassOnParent (ClassLoader parentClassLoader , Class <?> clazz ) {
208
+ try {
209
+ Method method = ClassLoader .class .getDeclaredMethod ("resolveClass" , Class .class );
210
+ method .setAccessible (true );
211
+ method .invoke (parentClassLoader , clazz );
212
+ } catch (Exception e ) {
213
+ String msg = String .format ("Exception trying 'resolveClass(%s)' on parent ClassLoader '%s'" ,
214
+ clazz , parentClassLoader );
215
+ Logger .getLogger (MyClassLoader .class .getName ()).log (Level .FINE , msg , e );
101
216
}
102
- // important: must also resolve the class...
103
- resolveClass (impl );
104
- return impl ;
105
217
}
106
218
107
219
public static int replaceName (byte [] byteCode ,
0 commit comments