/*
* Copyright 2014 - 2020 Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.dynamic;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
This nexus is a global dispatcher for initializing classes with LoadedTypeInitializer
s. To do so, this class is to be loaded by the system class loader in an explicit manner. Any instrumented class is then injected a code block into its static type initializer that makes a call to this very same nexus which had the loaded type initializer registered before hand.
Note: Availability of the Nexus
class and its injection into the system class loader can be disabled entirely by setting the PROPERTY
system property to false
.
Important: The nexus must never be accessed directly but only by the NexusAccessor
which makes sure that the nexus is loaded by the system class loader. Otherwise, a class might not be able to initialize itself if it is loaded by different class loader that does not have the system class loader in its hierarchy.
/**
* <p>
* This nexus is a global dispatcher for initializing classes with
* {@link net.bytebuddy.implementation.LoadedTypeInitializer}s. To do so, this class is to be loaded
* by the system class loader in an explicit manner. Any instrumented class is then injected a code
* block into its static type initializer that makes a call to this very same nexus which had the
* loaded type initializer registered before hand.
* </p>
* <p>
* <b>Note</b>: Availability of the {@link Nexus} class and its injection into the system class loader
* can be disabled entirely by setting the {@link Nexus#PROPERTY} system property to {@code false}.
* </p>
* <p>
* <b>Important</b>: The nexus must never be accessed directly but only by the {@link NexusAccessor}
* which makes sure that the nexus is loaded by the system class loader. Otherwise, a class might not
* be able to initialize itself if it is loaded by different class loader that does not have the
* system class loader in its hierarchy.
* </p>
*/
public class Nexus extends WeakReference<ClassLoader> {
A system property that allows to disable the use of the Nexus
class which is normally injected into the system class loader. /**
* A system property that allows to disable the use of the {@link Nexus} class which is normally injected into the system class loader.
*/
public static final String PROPERTY = "net.bytebuddy.nexus.disabled";
An type-safe constant for a non-operational reference queue.
/**
* An type-safe constant for a non-operational reference queue.
*/
private static final ReferenceQueue<ClassLoader> NO_QUEUE = null;
A map of keys identifying a loaded type by its name and class loader mapping their potential LoadedTypeInitializer
where the class loader of these initializers is however irrelevant. /**
* A map of keys identifying a loaded type by its name and class loader mapping their
* potential {@link net.bytebuddy.implementation.LoadedTypeInitializer} where the class
* loader of these initializers is however irrelevant.
*/
private static final ConcurrentMap<Nexus, Object> TYPE_INITIALIZERS = new ConcurrentHashMap<Nexus, Object>();
The name of a type for which a loaded type initializer is registered.
/**
* The name of a type for which a loaded type initializer is registered.
*/
private final String name;
The class loader's hash code upon registration.
/**
* The class loader's hash code upon registration.
*/
private final int classLoaderHashCode;
A random value that uniquely identifies a Nexus entry in order to avoid conflicts when
applying the self-initialization strategy in multiple transformations.
/**
* A random value that uniquely identifies a Nexus entry in order to avoid conflicts when
* applying the self-initialization strategy in multiple transformations.
*/
private final int identification;
Creates a key for identifying a loaded type initializer.
Params: - type – The loaded type for which a key is to be created.
- identification – An identification for the initializer to run.
/**
* Creates a key for identifying a loaded type initializer.
*
* @param type The loaded type for which a key is to be created.
* @param identification An identification for the initializer to run.
*/
private Nexus(Class<?> type, int identification) {
this(nonAnonymous(type.getName()), type.getClassLoader(), NO_QUEUE, identification);
}
Creates a key for identifying a loaded type initializer.
Params: - name – The name of a type for which a loaded type initializer is registered.
- classLoader – The class loader for which a loaded type initializer is registered.
- referenceQueue – The reference queue to notify upon the class loader's collection or
null
if no queue should be notified. - identification – An identification for the initializer to run.
/**
* Creates a key for identifying a loaded type initializer.
*
* @param name The name of a type for which a loaded type initializer is registered.
* @param classLoader The class loader for which a loaded type initializer is registered.
* @param referenceQueue The reference queue to notify upon the class loader's collection or {@code null} if no queue should be notified.
* @param identification An identification for the initializer to run.
*/
private Nexus(String name, ClassLoader classLoader, ReferenceQueue<? super ClassLoader> referenceQueue, int identification) {
super(classLoader, classLoader == null
? null
: referenceQueue);
this.name = name;
classLoaderHashCode = System.identityHashCode(classLoader);
this.identification = identification;
}
Normalizes a type name if it is loaded by an anonymous class loader.
Params: - typeName – The name as returned by
Class.getName()
.
Returns: The non-anonymous name of the given class.
/**
* Normalizes a type name if it is loaded by an anonymous class loader.
*
* @param typeName The name as returned by {@link Class#getName()}.
* @return The non-anonymous name of the given class.
*/
private static String nonAnonymous(String typeName) {
int anonymousLoaderIndex = typeName.indexOf('/');
return anonymousLoaderIndex == -1
? typeName
: typeName.substring(0, anonymousLoaderIndex);
}
Initializes a loaded type. This method must only be invoked via the system class loader.
Important: This method must never be called directly but only by using a InitializationAppender
which enforces to access this class for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded by different class loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class loaders, too. Any access of the instance is done using Java reflection instead.
Params: - type – The loaded type to initialize.
- identification – An identification for the initializer to run.
Throws: - Exception – If an exception occurs.
/**
* <p>
* Initializes a loaded type. This method must only be invoked via the system class loader.
* </p>
* <p>
* <b>Important</b>: This method must never be called directly but only by using a {@link NexusAccessor.InitializationAppender} which enforces to
* access this class for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded
* by different class loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by
* different class loaders, too. Any access of the instance is done using Java reflection instead.
* </p>
*
* @param type The loaded type to initialize.
* @param identification An identification for the initializer to run.
* @throws Exception If an exception occurs.
*/
@SuppressWarnings("unused")
public static void initialize(Class<?> type, int identification) throws Exception {
Object typeInitializer = TYPE_INITIALIZERS.remove(new Nexus(type, identification));
if (typeInitializer != null) {
typeInitializer.getClass().getMethod("onLoad", Class.class).invoke(typeInitializer, type);
}
}
Registers a new loaded type initializer.
Important: This method must never be called directly but only by using a NexusAccessor
which enforces to access this class for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded by different class loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class loaders, too. Any access of the instance is done using Java reflection instead.
Params: - name – The name of the type for the loaded type initializer.
- classLoader – The class loader of the type for the loaded type initializer.
- referenceQueue – The reference queue to notify upon the class loader's collection which will be enqueued a reference which can be handed to
clean(Reference<? super ClassLoader>)
or null
if no reference queue should be notified. - identification – An identification for the initializer to run.
- typeInitializer – The type initializer to register. The initializer must be an instance of
LoadedTypeInitializer
where it does however not matter which class loader loaded this latter type.
/**
* <p>
* Registers a new loaded type initializer.
* </p>
* <p>
* <b>Important</b>: This method must never be called directly but only by using a {@link NexusAccessor} which enforces to access this class
* for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded by different class
* loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class loaders,
* too. Any access of the instance is done using Java reflection instead.
* </p>
*
* @param name The name of the type for the loaded type initializer.
* @param classLoader The class loader of the type for the loaded type initializer.
* @param referenceQueue The reference queue to notify upon the class loader's collection which will be enqueued a reference which can be
* handed to {@link Nexus#clean(Reference)} or {@code null} if no reference queue should be notified.
* @param identification An identification for the initializer to run.
* @param typeInitializer The type initializer to register. The initializer must be an instance
* of {@link net.bytebuddy.implementation.LoadedTypeInitializer} where
* it does however not matter which class loader loaded this latter type.
*/
public static void register(String name, ClassLoader classLoader, ReferenceQueue<? super ClassLoader> referenceQueue, int identification, Object typeInitializer) {
TYPE_INITIALIZERS.put(new Nexus(name, classLoader, referenceQueue, identification), typeInitializer);
}
Cleans any stale entries from this nexus. Entries are considered stale if their class loader was collected before a class was initialized.
Important: This method must never be called directly but only by using a NexusAccessor
which enforces to access this class for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded by different class loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class loaders, too. Any access of the instance is done using Java reflection instead.
Params: - reference – The stale reference to clean.
/**
* <p>
* Cleans any stale entries from this nexus. Entries are considered stale if their class loader was collected before a class was initialized.
* </p>
* <p>
* <b>Important</b>: This method must never be called directly but only by using a {@link NexusAccessor} which enforces to access this class
* for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded by different class
* loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class loaders,
* too. Any access of the instance is done using Java reflection instead.
* </p>
*
* @param reference The stale reference to clean.
*/
public static void clean(Reference<? super ClassLoader> reference) {
TYPE_INITIALIZERS.remove(reference);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + classLoaderHashCode;
result = 31 * result + identification;
return result;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (other == null || getClass() != other.getClass()) {
return false;
}
Nexus nexus = (Nexus) other;
return classLoaderHashCode == nexus.classLoaderHashCode
&& identification == nexus.identification
&& name.equals(nexus.name)
&& get() == nexus.get();
}
@Override
public String toString() {
return "Nexus{" +
"name='" + name + '\'' +
", classLoaderHashCode=" + classLoaderHashCode +
", identification=" + identification +
", classLoader=" + get() +
'}';
}
}