Define packages in exporter class loader (#409)
This commit is contained in:
parent
f6e16c8f90
commit
f50a410fb4
|
@ -22,15 +22,19 @@ import java.io.InputStream;
|
|||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.bytebuddy.jar.asm.ClassReader;
|
||||
import net.bytebuddy.jar.asm.ClassWriter;
|
||||
import net.bytebuddy.jar.asm.commons.ClassRemapper;
|
||||
|
||||
@Slf4j
|
||||
public class ExporterClassLoader extends URLClassLoader {
|
||||
// We need to prefix the names to prevent the gradle shadowJar relocation rules from touching
|
||||
// them. It's possible to do this by excluding this class from shading, but it may cause issue
|
||||
// with transitive dependencies down the line.
|
||||
private final ShadingRemapper remapper =
|
||||
private static final ShadingRemapper remapper =
|
||||
new ShadingRemapper(
|
||||
rule(
|
||||
"#io.opentelemetry.OpenTelemetry",
|
||||
|
@ -52,8 +56,11 @@ public class ExporterClassLoader extends URLClassLoader {
|
|||
rule("#java.util.logging.Logger", "#io.opentelemetry.auto.bootstrap.PatchLogger"),
|
||||
rule("#org.slf4j", "#io.opentelemetry.auto.slf4j"));
|
||||
|
||||
public ExporterClassLoader(final URL[] urls, final ClassLoader parent) {
|
||||
super(urls, parent);
|
||||
private final Manifest manifest;
|
||||
|
||||
public ExporterClassLoader(final URL url, final ClassLoader parent) {
|
||||
super(new URL[] {url}, parent);
|
||||
this.manifest = getManifest(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -69,16 +76,79 @@ public class ExporterClassLoader extends URLClassLoader {
|
|||
|
||||
@Override
|
||||
protected Class<?> findClass(final String name) throws ClassNotFoundException {
|
||||
|
||||
// Use resource loading to get the class as a stream of bytes, then use ASM to transform it.
|
||||
try (final InputStream in = getResourceAsStream(name.replace('.', '/') + ".class")) {
|
||||
final ClassWriter cw = new ClassWriter(0);
|
||||
final ClassReader cr = new ClassReader(in);
|
||||
cr.accept(new ClassRemapper(cw, remapper), ClassReader.EXPAND_FRAMES);
|
||||
final byte[] bytes = cw.toByteArray();
|
||||
return defineClass(name, bytes, 0, bytes.length);
|
||||
} catch (final IOException e) {
|
||||
InputStream in = getResourceAsStream(name.replace('.', '/') + ".class");
|
||||
if (in == null) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
try {
|
||||
final byte[] bytes = remapClassBytes(in);
|
||||
definePackageIfNeeded(name);
|
||||
return defineClass(name, bytes, 0, bytes.length);
|
||||
} catch (final IOException e) {
|
||||
throw new ClassNotFoundException(name, e);
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
log.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void definePackageIfNeeded(String className) {
|
||||
String packageName = getPackageName(className);
|
||||
if (packageName == null) {
|
||||
// default package
|
||||
return;
|
||||
}
|
||||
if (isPackageDefined(packageName)) {
|
||||
// package has already been defined
|
||||
return;
|
||||
}
|
||||
try {
|
||||
definePackage(packageName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// this exception is thrown when the package has already been defined, which is possible due
|
||||
// to race condition with the check above
|
||||
if (!isPackageDefined(packageName)) {
|
||||
// this shouldn't happen however
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPackageDefined(String packageName) {
|
||||
return getPackage(packageName) != null;
|
||||
}
|
||||
|
||||
private void definePackage(String packageName) {
|
||||
if (manifest == null) {
|
||||
definePackage(packageName, null, null, null, null, null, null, null);
|
||||
} else {
|
||||
definePackage(packageName, manifest, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] remapClassBytes(InputStream in) throws IOException {
|
||||
final ClassWriter cw = new ClassWriter(0);
|
||||
final ClassReader cr = new ClassReader(in);
|
||||
cr.accept(new ClassRemapper(cw, remapper), ClassReader.EXPAND_FRAMES);
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
private static String getPackageName(String className) {
|
||||
int index = className.lastIndexOf('.');
|
||||
return index == -1 ? null : className.substring(0, index);
|
||||
}
|
||||
|
||||
private static Manifest getManifest(URL url) {
|
||||
try {
|
||||
JarFile jarFile = new JarFile(url.getFile());
|
||||
return jarFile.getManifest();
|
||||
} catch (IOException e) {
|
||||
log.warn(e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ public class TracerInstaller {
|
|||
}
|
||||
final DefaultExporterConfig config = new DefaultExporterConfig("exporter");
|
||||
final ExporterClassLoader exporterLoader =
|
||||
new ExporterClassLoader(new URL[] {url}, TracerInstaller.class.getClassLoader());
|
||||
new ExporterClassLoader(url, TracerInstaller.class.getClassLoader());
|
||||
|
||||
final SpanExporterFactory spanExporterFactory =
|
||||
getExporterFactory(SpanExporterFactory.class, exporterLoader);
|
||||
|
|
|
@ -32,7 +32,8 @@ class ClassLoaderMatcherTest extends AgentSpecification {
|
|||
|
||||
def "skips exporter classloader"() {
|
||||
setup:
|
||||
final URLClassLoader exporterLoader = new ExporterClassLoader(new URL[0], null)
|
||||
URL url = new URL("file://")
|
||||
final URLClassLoader exporterLoader = new ExporterClassLoader(url, null)
|
||||
expect:
|
||||
ClassLoaderMatcher.skipClassLoader().matches(exporterLoader)
|
||||
}
|
||||
|
|
|
@ -53,8 +53,7 @@ class ExporterAdaptersTest extends Specification {
|
|||
def file = new File(exporter)
|
||||
println "Attempting to load ${file.toString()} for ${classname}"
|
||||
assert file.exists(): "${file.toString()} does not exist"
|
||||
URL[] urls = [file.toURI().toURL()]
|
||||
def classLoader = new ExporterClassLoader(urls, this.getClass().getClassLoader())
|
||||
def classLoader = new ExporterClassLoader(file.toURI().toURL(), this.getClass().getClassLoader())
|
||||
def serviceLoader = ServiceLoader.load(SpanExporterFactory, classLoader)
|
||||
|
||||
when:
|
||||
|
|
Loading…
Reference in New Issue