diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a1115f8ae..ca6a5b91ec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Performance + +- Probe class availability without initializing the class during SDK init ([#5635](https://github.com/getsentry/sentry-java/pull/5635)) + ## 8.45.0 ### Features diff --git a/sentry/src/main/java/io/sentry/util/LoadClass.java b/sentry/src/main/java/io/sentry/util/LoadClass.java index 1946ce8381f..c639f62b9c4 100644 --- a/sentry/src/main/java/io/sentry/util/LoadClass.java +++ b/sentry/src/main/java/io/sentry/util/LoadClass.java @@ -20,7 +20,9 @@ public class LoadClass { */ public @Nullable Class loadClass(final @NotNull String clazz, final @Nullable ILogger logger) { try { - return Class.forName(clazz); + // Don't initialize the class just to probe for availability; it gets initialized lazily on + // first use. This avoids running unrelated static initializers during SDK init. + return Class.forName(clazz, false, LoadClass.class.getClassLoader()); } catch (ClassNotFoundException e) { if (logger != null) { logger.log(SentryLevel.INFO, "Class not available: " + clazz); diff --git a/sentry/src/test/java/io/sentry/util/LoadClassTest.kt b/sentry/src/test/java/io/sentry/util/LoadClassTest.kt new file mode 100644 index 00000000000..0c8dacd1c54 --- /dev/null +++ b/sentry/src/test/java/io/sentry/util/LoadClassTest.kt @@ -0,0 +1,49 @@ +package io.sentry.util + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class LoadClassTest { + @Test + fun `loadClass returns the class when it is available`() { + assertNotNull(LoadClass().loadClass("io.sentry.SentryEvent", null)) + } + + @Test + fun `loadClass returns null when the class is not available`() { + assertNull(LoadClass().loadClass("io.sentry.ThisClassDoesNotExist", null)) + } + + @Test + fun `isClassAvailable reflects whether the class is on the classpath`() { + val loadClass = LoadClass() + assertNotNull(loadClass.loadClass("io.sentry.SentryEvent", null)) + assertFalse( + loadClass.isClassAvailable("io.sentry.ThisClassDoesNotExist", null as io.sentry.ILogger?) + ) + } + + @Test + fun `loadClass does not run the static initializer of the probed class`() { + // Reading the flag initializes the flag holder, not the probe. + assertFalse(LoadClassNoInitFlag.initialized) + + // Obtaining the name via ::class.java does not initialize the probe either. + LoadClass().loadClass(LoadClassNoInitProbe::class.java.name, null) + + // Availability probing must not trigger the probe's static initializer. + assertFalse(LoadClassNoInitFlag.initialized) + } +} + +private object LoadClassNoInitFlag { + @JvmField var initialized = false +} + +private object LoadClassNoInitProbe { + init { + LoadClassNoInitFlag.initialized = true + } +}