io.microsphere.util.ClassLoaderUtils Maven / Gradle / Ivy
/**
*
*/
package io.microsphere.util;
import io.microsphere.classloading.URLClassPathHandle;
import io.microsphere.lang.ClassDataRepository;
import io.microsphere.logging.Logger;
import io.microsphere.net.URLUtils;
import io.microsphere.reflect.ReflectionUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.lang.management.ClassLoadingMXBean;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.SecureClassLoader;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.jar.JarFile;
import static io.microsphere.collection.CollectionUtils.isNotEmpty;
import static io.microsphere.collection.SetUtils.ofSet;
import static io.microsphere.constants.FileConstants.CLASS_EXTENSION;
import static io.microsphere.constants.PathConstants.BACK_SLASH;
import static io.microsphere.constants.PathConstants.SLASH;
import static io.microsphere.constants.SymbolConstants.DOT;
import static io.microsphere.lang.function.ThrowableSupplier.execute;
import static io.microsphere.logging.LoggerFactory.getLogger;
import static io.microsphere.reflect.FieldUtils.findField;
import static io.microsphere.reflect.FieldUtils.getFieldValue;
import static io.microsphere.reflect.MethodUtils.findMethod;
import static io.microsphere.reflect.MethodUtils.invokeMethod;
import static io.microsphere.util.ServiceLoaderUtils.loadServicesList;
import static io.microsphere.util.StringUtils.EMPTY;
import static io.microsphere.util.StringUtils.contains;
import static io.microsphere.util.StringUtils.endsWith;
import static io.microsphere.util.StringUtils.isBlank;
import static io.microsphere.util.StringUtils.replace;
import static io.microsphere.util.SystemUtils.JAVA_VENDOR;
import static io.microsphere.util.SystemUtils.JAVA_VERSION;
import static java.lang.management.ManagementFactory.getClassLoadingMXBean;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
/**
* {@link ClassLoader} Utility
*
* @author Mercy
* @version 1.0.0
* @see ClassLoader
* @since 1.0.0
*/
public abstract class ClassLoaderUtils extends BaseUtils {
private static final Logger logger = getLogger(ClassLoaderUtils.class);
private static final Class classLoaderClass = ClassLoader.class;
private static final String findLoadedClassMethodName = "findLoadedClass";
private static final String classesFieldName = "classes";
protected static final ClassLoadingMXBean classLoadingMXBean = getClassLoadingMXBean();
private static final ConcurrentMap> loadedClassesCache = new ConcurrentHashMap<>(256);
private static final URLClassPathHandle urlClassPathHandle = initURLClassPathHandle();
private static URLClassPathHandle initURLClassPathHandle() {
List urlClassPathHandles = loadServicesList(URLClassPathHandle.class);
for (URLClassPathHandle urlClassPathHandle : urlClassPathHandles) {
if (urlClassPathHandle.supports()) {
return urlClassPathHandle;
}
}
throw new IllegalStateException("No URLClassPathHandle found, Please check the META-INF/services/io.microsphere.classloading.URLClassPathHandle");
}
/**
* Returns the number of classes that are currently loaded in the Java virtual machine.
*
* @return the number of currently loaded classes.
*/
public static int getLoadedClassCount() {
return classLoadingMXBean.getLoadedClassCount();
}
/**
* Returns the total number of classes unloaded since the Java virtual machine has started execution.
*
* @return the total number of unloaded classes.
*/
public static long getUnloadedClassCount() {
return classLoadingMXBean.getUnloadedClassCount();
}
/**
* Tests if the verbose output for the class loading system is enabled.
*
* @return true if the verbose output for the class loading system is enabled; false otherwise.
*/
public static boolean isVerbose() {
return classLoadingMXBean.isVerbose();
}
/**
* Enables or disables the verbose output for the class loading system. The verbose output information and the
* output stream to which the verbose information is emitted are implementation dependent. Typically, a Java
* virtual machine implementation prints a message each time a class file is loaded.
*
* This method can be called by multiple threads concurrently. Each invocation of this method enables or disables
* the verbose output globally.
*
* @param value true to enable the verbose output; false to disable.
* @throws SecurityException if a security manager exists and the caller does not have ManagementPermission("control").
*/
public static void setVerbose(boolean value) {
classLoadingMXBean.setVerbose(value);
}
/**
* Returns the total number of classes that have been loaded since the Java virtual machine has started execution.
*
* @return the total number of classes loaded.
*/
public static long getTotalLoadedClassCount() {
return classLoadingMXBean.getTotalLoadedClassCount();
}
/**
* Get the default the ClassLoader to use.
*
* @return the ClassLoader (only {@code null} if even the system ClassLoader isn't accessible)
* @see Thread#getContextClassLoader()
* @see Class#getClassLoader()
* @see ClassLoader#getSystemClassLoader()
* @see ReflectionUtils#getCallerClass()
*/
@Nullable
public static ClassLoader getDefaultClassLoader() {
ClassLoader classLoader = null;
try {
classLoader = Thread.currentThread().getContextClassLoader();
} catch (Throwable ignored) {
}
if (classLoader == null) { // If the ClassLoader is also not found,
// try to get the ClassLoader from the Caller class
Class> callerClass = ReflectionUtils.getCallerClass(3);
if (callerClass != null) {
classLoader = callerClass.getClassLoader();
}
}
if (classLoader == null) {
classLoader = ClassLoaderUtils.class.getClassLoader();
}
if (classLoader == null) {
// classLoader is null indicates the bootstrap ClassLoader
try {
classLoader = ClassLoader.getSystemClassLoader();
} catch (Throwable ignored) {
}
}
return classLoader;
}
/**
* Get the ClassLoader from the loaded class if present.
*
* @param loadedClass the optional class was loaded by some {@link ClassLoader}
* @return the ClassLoader (only {@code null} if even the system ClassLoader isn't accessible)
* @see #getDefaultClassLoader()
*/
@Nullable
public static ClassLoader getClassLoader(@Nullable Class> loadedClass) {
ClassLoader classLoader = null;
try {
if (loadedClass == null) {
classLoader = getCallerClassLoader(4);
} else {
classLoader = loadedClass.getClassLoader();
}
} catch (SecurityException ignored) {
}
return classLoader == null ? getDefaultClassLoader() : classLoader;
}
/**
* Return the ClassLoader from the caller class
*
* @return the ClassLoader (only {@code null} if the caller class was absent
* @see ReflectionUtils#getCallerClass()
*/
public static ClassLoader getCallerClassLoader() {
return getCallerClassLoader(4);
}
/**
* Return the ClassLoader from the caller class
*
* @return the ClassLoader (only {@code null} if the caller class was absent
* @see ReflectionUtils#getCallerClass()
*/
private static ClassLoader getCallerClassLoader(int invocationFrame) {
ClassLoader classLoader = null;
Class> callerClass = ReflectionUtils.getCallerClass(invocationFrame);
if (callerClass != null) {
classLoader = callerClass.getClassLoader();
}
return classLoader;
}
/**
* Find Loaded {@link Class} under specified inheritable {@link ClassLoader} and class names
*
* @param classLoader {@link ClassLoader}
* @param classNames class names set
* @return {@link Class} if loaded , or null
*/
public static Set> findLoadedClasses(ClassLoader classLoader, Set classNames) {
Set> loadedClasses = new LinkedHashSet();
for (String className : classNames) {
Class> class_ = findLoadedClass(classLoader, className);
if (class_ != null) {
loadedClasses.add(class_);
}
}
return unmodifiableSet(loadedClasses);
}
/**
* Check specified {@link Class} is loaded on specified inheritable {@link ClassLoader}
*
* @param classLoader {@link ClassLoader}
* @param type {@link Class}
* @return If Loaded , return true
, or false
*/
public static boolean isLoadedClass(ClassLoader classLoader, Class> type) {
return isLoadedClass(classLoader, type.getName());
}
/**
* Check specified {@link Class#getName() class name} is loaded on specified inheritable {@link ClassLoader}
*
* @param classLoader {@link ClassLoader}
* @param className {@link Class#getName() class name}
* @return If Loaded , return true
, or false
*/
public static boolean isLoadedClass(ClassLoader classLoader, String className) {
return findLoadedClass(classLoader, className) != null;
}
/**
* Find Loaded {@link Class} under specified inheritable {@link ClassLoader}
*
* @param classLoader {@link ClassLoader}
* @param className class name
* @return {@link Class} if loaded , or null
*/
public static Class> findLoadedClass(ClassLoader classLoader, String className) {
Class> loadedClass = invokeFindLoadedClassMethod(classLoader, className);
if (loadedClass == null) {
Set classLoaders = getInheritableClassLoaders(classLoader);
for (ClassLoader loader : classLoaders) {
loadedClass = (Class>) invokeFindLoadedClassMethod(loader, className);
if (loadedClass != null) {
break;
}
}
}
return loadedClass;
}
/**
* Invoke the {@link Method} of {@link ClassLoader#findLoadedClass(String)}
*
* @param classLoader {@link ClassLoader}
* @param className the class name
* @return null
if not loaded or can't be loaded
*/
private static Class> invokeFindLoadedClassMethod(ClassLoader classLoader, String className) {
Class> loadedClass = null;
try {
Method findLoadedClassMethod = findMethod(classLoaderClass, findLoadedClassMethodName, String.class);
loadedClass = invokeMethod(classLoader, findLoadedClassMethod, className);
} catch (Throwable e) {
logger.error("The java.lang.ClassLoader#findLoadedClasss(String) method can't be invoked in the current JVM[vendor : {} , version : {}]",
JAVA_VENDOR, JAVA_VERSION, e.getCause());
}
return loadedClass;
}
/**
* Loaded specified class name under {@link ClassLoader}
*
* @param className the name of {@link Class}
* @param classLoader {@link ClassLoader}
* @return {@link Class} if can be loaded
*/
@Nullable
public static Class> loadClass(@Nonnull String className, @Nonnull ClassLoader classLoader) {
try {
return classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
if (logger.isTraceEnabled()) {
logger.trace("The Class[name : '{}'] can't be loaded from the ClassLoader : {}", className, classLoader);
}
} catch (Throwable ignored) {
}
return null;
}
/**
* Loaded specified class name under {@link ClassLoader}
*
* @param className the name of {@link Class}
* @param classLoader {@link ClassLoader}
* @param cached the resolved class is required to be cached or not
* @return {@link Class} if can be loaded
*/
public static Class> loadClass(String className, ClassLoader classLoader, boolean cached) {
Class loadedClass = null;
if (cached) {
String cacheKey = buildCacheKey(className, classLoader);
loadedClass = loadedClassesCache.computeIfAbsent(cacheKey, k -> execute(() -> loadClass(className, classLoader)));
} else {
loadedClass = loadClass(className, classLoader);
}
return loadedClass;
}
/**
* Get the resource URLs Set under specified resource name and type
*
* @param classLoader ClassLoader
* @param resourceType {@link ResourceType} Enum
* @param resourceName resource name ,e.g :
- Resource Name :
"/com/abc/def.log"
- Class Name :
*
"java.lang.String"
* @return the resource URL under specified resource name and type
* @throws NullPointerException If any argument is null
* @throws IOException
*/
public static Set getResources(ClassLoader classLoader, ResourceType resourceType, String resourceName) throws NullPointerException, IOException {
String normalizedResourceName = resourceType.resolve(resourceName);
Enumeration resources = classLoader.getResources(normalizedResourceName);
return resources != null && resources.hasMoreElements() ? ofSet(resources) : emptySet();
}
/**
* Get the resource URLs list under specified resource name
*
* @param classLoader ClassLoader
* @param resourceName resource name ,e.g :
- Resource Name :
"/com/abc/def.log"
- Class Name :
*
"java.lang.String"
* @return the resource URL under specified resource name and type
* @throws NullPointerException If any argument is null
* @throws IOException
*/
public static Set getResources(ClassLoader classLoader, String resourceName) throws NullPointerException, IOException {
Set resourceURLs = emptySet();
for (ResourceType resourceType : ResourceType.values()) {
resourceURLs = getResources(classLoader, resourceType, resourceName);
if (isNotEmpty(resourceURLs)) {
break;
}
}
return resourceURLs;
}
/**
* Get the resource URL under specified resource name
*
* @param classLoader ClassLoader
* @param resourceName resource name ,e.g :
- Resource Name :
"/com/abc/def.log"
- Class Name :
*
"java.lang.String"
* @return the resource URL under specified resource name and type
* @throws NullPointerException If any argument is null
*/
public static URL getResource(ClassLoader classLoader, String resourceName) throws NullPointerException {
URL resourceURL = null;
for (ResourceType resourceType : ResourceType.values()) {
resourceURL = getResource(classLoader, resourceType, resourceName);
if (resourceURL != null) {
break;
}
}
return resourceURL;
}
/**
* Get the resource URL under specified resource name and type
*
* @param classLoader ClassLoader
* @param resourceType {@link ResourceType} Enum
* @param resourceName resource name ,e.g :
- Resource Name :
"/com/abc/def.log"
- Class Name :
*
"java.lang.String"
* @return the resource URL under specified resource name and type
* @throws NullPointerException If any argument is null
*/
public static URL getResource(ClassLoader classLoader, ResourceType resourceType, String resourceName) throws NullPointerException {
String normalizedResourceName = resourceType.resolve(resourceName);
return classLoader.getResource(normalizedResourceName);
}
/**
* Get the {@link Class} resource URL under specified {@link Class#getName() Class name}
*
* @param classLoader ClassLoader
* @param className class name
* @return the resource URL under specified resource name and type
* @throws NullPointerException If any argument is null
*/
public static URL getClassResource(ClassLoader classLoader, String className) {
final String resourceName = className + CLASS_EXTENSION;
return getResource(classLoader, ResourceType.CLASS, resourceName);
}
/**
* Get the {@link Class} resource URL under specified {@link Class}
*
* @param classLoader ClassLoader
* @param type {@link Class type}
* @return the resource URL under specified resource name and type
* @throws NullPointerException If any argument is null
* @version 1.0.0
* @since 1.0.0
*/
public static URL getClassResource(ClassLoader classLoader, Class> type) {
String resourceName = type.getName();
return getClassResource(classLoader, resourceName);
}
/**
* Get all Inheritable {@link ClassLoader ClassLoaders} {@link Set} (including {@link ClassLoader} argument)
*
* @param classLoader {@link ClassLoader}
* @return Read-only {@link Set}
* @throws NullPointerException If classLoader
argument is null
*/
@Nonnull
public static Set getInheritableClassLoaders(ClassLoader classLoader) throws NullPointerException {
Set classLoadersSet = new LinkedHashSet();
classLoadersSet.add(classLoader);
ClassLoader parentClassLoader = classLoader.getParent();
while (parentClassLoader != null) {
classLoadersSet.add(parentClassLoader);
parentClassLoader = parentClassLoader.getParent();
}
return unmodifiableSet(classLoadersSet);
}
/**
* Get all loaded classes {@link Map} under specified inheritable {@link ClassLoader} , {@link ClassLoader} as key ,
* its loaded classes {@link Set} as value.
*
* @param classLoader {@link ClassLoader}
* @return Read-only Map
* @throws UnsupportedOperationException
* @throws NullPointerException If classLoader
argument is null
*/
@Nonnull
public static Map>> getAllLoadedClassesMap(ClassLoader classLoader) throws UnsupportedOperationException {
Map>> allLoadedClassesMap = new LinkedHashMap();
Set classLoadersSet = getInheritableClassLoaders(classLoader);
for (ClassLoader loader : classLoadersSet) {
allLoadedClassesMap.put(loader, getLoadedClasses(loader));
}
return unmodifiableMap(allLoadedClassesMap);
}
/**
* Get all loaded classes {@link Set} under specified inheritable {@link ClassLoader}
*
* @param classLoader {@link ClassLoader}
* @return Read-only {@link Set}
* @throws UnsupportedOperationException If JVM does not support
* @throws NullPointerException If classLoader
argument is null
*/
@Nonnull
public static Set> getAllLoadedClasses(ClassLoader classLoader) throws UnsupportedOperationException {
Set> allLoadedClassesSet = new LinkedHashSet();
Map>> allLoadedClassesMap = getAllLoadedClassesMap(classLoader);
for (Set> loadedClassesSet : allLoadedClassesMap.values()) {
allLoadedClassesSet.addAll(loadedClassesSet);
}
return unmodifiableSet(allLoadedClassesSet);
}
/**
* Get loaded classes {@link Set} under specified {@link ClassLoader}( not all inheritable {@link ClassLoader
* ClassLoaders})
*
* @param classLoader {@link ClassLoader}
* @return Read-only {@link Set}
* @throws UnsupportedOperationException If JVM does not support
* @throws NullPointerException If classLoader
argument is null
* @see #getAllLoadedClasses(ClassLoader)
*/
@Nonnull
public static Set> getLoadedClasses(ClassLoader classLoader) throws UnsupportedOperationException {
Field field = findField(classLoaderClass, classesFieldName);
List> classes = getFieldValue(classLoader, field);
return classes == null ? emptySet() : ofSet(classes);
}
/**
* Find loaded classes {@link Set} in class path
*
* @param classLoader {@link ClassLoader}
* @return Read-only {@link Set}
* @throws UnsupportedOperationException If JVM does not support
*/
public static Set> findLoadedClassesInClassPath(ClassLoader classLoader) throws UnsupportedOperationException {
Set classNames = ClassDataRepository.INSTANCE.getAllClassNamesInClassPaths();
return findLoadedClasses(classLoader, classNames);
}
/**
* Find loaded classes {@link Set} in class paths {@link Set}
*
* @param classLoader {@link ClassLoader}
* @param classPaths the class paths for the {@link Set} of {@link JarFile} or classes directory
* @return Read-only {@link Set}
* @throws UnsupportedOperationException If JVM does not support
* @see #findLoadedClass(ClassLoader, String)
*/
public static Set> findLoadedClassesInClassPaths(ClassLoader classLoader, Set classPaths) throws UnsupportedOperationException {
Set> loadedClasses = new LinkedHashSet();
for (String classPath : classPaths) {
loadedClasses.addAll(findLoadedClassesInClassPath(classLoader, classPath));
}
return loadedClasses;
}
/**
* Find loaded classes {@link Set} in class path
*
* @param classLoader {@link ClassLoader}
* @param classPath the class path for one {@link JarFile} or classes directory
* @return Read-only {@link Set}
* @throws UnsupportedOperationException If JVM does not support
* @see #findLoadedClass(ClassLoader, String)
*/
public static Set> findLoadedClassesInClassPath(ClassLoader classLoader, String classPath) throws UnsupportedOperationException {
Set classNames = ClassDataRepository.INSTANCE.getClassNamesInClassPath(classPath, true);
return findLoadedClasses(classLoader, classNames);
}
/**
* Test the specified class name is present in the {@link #getDefaultClassLoader() default ClassLoader}
*
* @param className the name of {@link Class}
* @return If found, return true
*/
public static boolean isPresent(@Nullable String className) {
return resolveClass(className) != null;
}
/**
* Test the specified class name is present in the {@link ClassLoader}
*
* @param className the name of {@link Class}
* @param classLoader {@link ClassLoader}
* @return If found, return true
*/
public static boolean isPresent(@Nullable String className, @Nullable ClassLoader classLoader) {
return resolveClass(className, classLoader) != null;
}
/**
* Resolve the {@link Class} by the specified name in the {@link #getDefaultClassLoader() default ClassLoader}
*
* @param className the name of {@link Class}
* @return If can't be resolved , return null
*/
public static Class> resolveClass(@Nullable String className) {
return resolveClass(className, getDefaultClassLoader());
}
/**
* Resolve the {@link Class} by the specified name and {@link ClassLoader}
*
* @param className the name of {@link Class}
* @param classLoader {@link ClassLoader}
* @return If can't be resolved , return null
*/
public static Class> resolveClass(@Nullable String className, @Nullable ClassLoader classLoader) {
return resolveClass(className, classLoader, false);
}
/**
* Resolve the {@link Class} by the specified name and {@link ClassLoader}
*
* @param className the name of {@link Class}
* @param classLoader {@link ClassLoader}
* @param cached the resolved class is required to be cached or not
* @return If can't be resolved , return null
*/
public static Class> resolveClass(@Nullable String className, @Nullable ClassLoader classLoader, boolean cached) {
if (isBlank(className)) {
return null;
}
Class> targetClass = null;
try {
ClassLoader targetClassLoader = classLoader == null ? getDefaultClassLoader() : classLoader;
targetClass = loadClass(className, targetClassLoader, cached);
} catch (Throwable ignored) { // Ignored
}
return targetClass;
}
public static Set findAllClassPathURLs(ClassLoader classLoader) {
Set allClassPathURLs = new LinkedHashSet<>();
URL[] classPathURLs = urlClassPathHandle.getURLs(classLoader);
int length = classPathURLs.length;
allClassPathURLs.addAll(Arrays.asList(classPathURLs).subList(0, length));
ClassLoader parentClassLoader = classLoader.getParent();
if (parentClassLoader != null) {
allClassPathURLs.addAll(findAllClassPathURLs(parentClassLoader));
}
return allClassPathURLs;
}
public static boolean removeClassPathURL(ClassLoader classLoader, URL url) {
if (!(classLoader instanceof SecureClassLoader)) {
return false;
}
return urlClassPathHandle.removeURL(classLoader, url);
}
public static URLClassLoader findURLClassLoader(ClassLoader classLoader) {
if (classLoader == null) {
return null;
}
URLClassLoader urlClassLoader = null;
if (classLoader instanceof URLClassLoader) {
urlClassLoader = (URLClassLoader) classLoader;
} else {
urlClassLoader = findURLClassLoader(classLoader.getParent());
}
return urlClassLoader;
}
private static URL toURL(String path) {
URL url = null;
try {
URI uri = new File(path).toURI();
url = uri.toURL();
} catch (Exception ignored) {
}
return url;
}
private static String buildCacheKey(String className, ClassLoader classLoader) {
String cacheKey = className + classLoader.hashCode();
return cacheKey;
}
/**
* Resource Type
*/
public enum ResourceType {
DEFAULT {
@Override
boolean supported(String name) {
return true;
}
@Override
public String normalize(String name) {
return name;
}
}, CLASS {
@Override
boolean supported(String name) {
return endsWith(name, CLASS_EXTENSION);
}
@Override
public String normalize(String name) {
String className = replace(name, CLASS_EXTENSION, EMPTY);
return replace(className, DOT, SLASH) + CLASS_EXTENSION;
}
}, PACKAGE {
@Override
boolean supported(String name) {
//TODO: use regexp to match more precise
return !CLASS.supported(name) && !contains(name, SLASH) && !contains(name, BACK_SLASH);
}
@Override
String normalize(String name) {
return replace(name, DOT, SLASH) + SLASH;
}
};
/**
* resolves resource name
*
* @param name resource name
* @return a newly resolved resource name
*/
public String resolve(String name) {
String normalizedName = supported(name) ? normalize(name) : null;
if (normalizedName == null) return normalizedName;
normalizedName = URLUtils.normalizePath(normalizedName);
// 除去开头的"/"
while (normalizedName.startsWith("/")) {
normalizedName = normalizedName.substring(1);
}
return normalizedName;
}
/**
* Is supported specified resource name in current resource type
*
* @param name resource name
* @return If supported , return true
, or return false
*/
abstract boolean supported(String name);
/**
* Normalizes resource name
*
* @param name resource name
* @return normalized resource name
*/
abstract String normalize(String name);
}
}