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.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
import java.util.Enumeration;
|
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.ClassReader;
|
||||||
import net.bytebuddy.jar.asm.ClassWriter;
|
import net.bytebuddy.jar.asm.ClassWriter;
|
||||||
import net.bytebuddy.jar.asm.commons.ClassRemapper;
|
import net.bytebuddy.jar.asm.commons.ClassRemapper;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class ExporterClassLoader extends URLClassLoader {
|
public class ExporterClassLoader extends URLClassLoader {
|
||||||
// We need to prefix the names to prevent the gradle shadowJar relocation rules from touching
|
// 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
|
// them. It's possible to do this by excluding this class from shading, but it may cause issue
|
||||||
// with transitive dependencies down the line.
|
// with transitive dependencies down the line.
|
||||||
private final ShadingRemapper remapper =
|
private static final ShadingRemapper remapper =
|
||||||
new ShadingRemapper(
|
new ShadingRemapper(
|
||||||
rule(
|
rule(
|
||||||
"#io.opentelemetry.OpenTelemetry",
|
"#io.opentelemetry.OpenTelemetry",
|
||||||
|
@ -52,8 +56,11 @@ public class ExporterClassLoader extends URLClassLoader {
|
||||||
rule("#java.util.logging.Logger", "#io.opentelemetry.auto.bootstrap.PatchLogger"),
|
rule("#java.util.logging.Logger", "#io.opentelemetry.auto.bootstrap.PatchLogger"),
|
||||||
rule("#org.slf4j", "#io.opentelemetry.auto.slf4j"));
|
rule("#org.slf4j", "#io.opentelemetry.auto.slf4j"));
|
||||||
|
|
||||||
public ExporterClassLoader(final URL[] urls, final ClassLoader parent) {
|
private final Manifest manifest;
|
||||||
super(urls, parent);
|
|
||||||
|
public ExporterClassLoader(final URL url, final ClassLoader parent) {
|
||||||
|
super(new URL[] {url}, parent);
|
||||||
|
this.manifest = getManifest(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -69,16 +76,79 @@ public class ExporterClassLoader extends URLClassLoader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Class<?> findClass(final String name) throws ClassNotFoundException {
|
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.
|
// 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")) {
|
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 ClassWriter cw = new ClassWriter(0);
|
||||||
final ClassReader cr = new ClassReader(in);
|
final ClassReader cr = new ClassReader(in);
|
||||||
cr.accept(new ClassRemapper(cw, remapper), ClassReader.EXPAND_FRAMES);
|
cr.accept(new ClassRemapper(cw, remapper), ClassReader.EXPAND_FRAMES);
|
||||||
final byte[] bytes = cw.toByteArray();
|
return cw.toByteArray();
|
||||||
return defineClass(name, bytes, 0, bytes.length);
|
}
|
||||||
} catch (final IOException e) {
|
|
||||||
throw new ClassNotFoundException(name);
|
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 DefaultExporterConfig config = new DefaultExporterConfig("exporter");
|
||||||
final ExporterClassLoader exporterLoader =
|
final ExporterClassLoader exporterLoader =
|
||||||
new ExporterClassLoader(new URL[] {url}, TracerInstaller.class.getClassLoader());
|
new ExporterClassLoader(url, TracerInstaller.class.getClassLoader());
|
||||||
|
|
||||||
final SpanExporterFactory spanExporterFactory =
|
final SpanExporterFactory spanExporterFactory =
|
||||||
getExporterFactory(SpanExporterFactory.class, exporterLoader);
|
getExporterFactory(SpanExporterFactory.class, exporterLoader);
|
||||||
|
|
|
@ -32,7 +32,8 @@ class ClassLoaderMatcherTest extends AgentSpecification {
|
||||||
|
|
||||||
def "skips exporter classloader"() {
|
def "skips exporter classloader"() {
|
||||||
setup:
|
setup:
|
||||||
final URLClassLoader exporterLoader = new ExporterClassLoader(new URL[0], null)
|
URL url = new URL("file://")
|
||||||
|
final URLClassLoader exporterLoader = new ExporterClassLoader(url, null)
|
||||||
expect:
|
expect:
|
||||||
ClassLoaderMatcher.skipClassLoader().matches(exporterLoader)
|
ClassLoaderMatcher.skipClassLoader().matches(exporterLoader)
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,7 @@ class ExporterAdaptersTest extends Specification {
|
||||||
def file = new File(exporter)
|
def file = new File(exporter)
|
||||||
println "Attempting to load ${file.toString()} for ${classname}"
|
println "Attempting to load ${file.toString()} for ${classname}"
|
||||||
assert file.exists(): "${file.toString()} does not exist"
|
assert file.exists(): "${file.toString()} does not exist"
|
||||||
URL[] urls = [file.toURI().toURL()]
|
def classLoader = new ExporterClassLoader(file.toURI().toURL(), this.getClass().getClassLoader())
|
||||||
def classLoader = new ExporterClassLoader(urls, this.getClass().getClassLoader())
|
|
||||||
def serviceLoader = ServiceLoader.load(SpanExporterFactory, classLoader)
|
def serviceLoader = ServiceLoader.load(SpanExporterFactory, classLoader)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
|
|
Loading…
Reference in New Issue