From c6f9159f75cb5a120e5dc5f284f1efa23b0374da Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Tue, 28 Apr 2026 16:02:38 +0200 Subject: [PATCH] perf: Replace Reflections with ClassGraph and share scan across factories (TEDEFO-5033) --- pom.xml | 12 +- .../sdk/component/SdkComponentFactory.java | 116 +++++++++--------- 2 files changed, 63 insertions(+), 65 deletions(-) diff --git a/pom.xml b/pom.xml index 551f822..873fc67 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ 3.8.6 11.1.3 7.1.1 - 0.10.2 + 4.8.179 1.8.2 3.1.0 2.0.3 @@ -76,9 +76,9 @@ - org.reflections - reflections - ${version.reflections} + io.github.classgraph + classgraph + ${version.classgraph} @@ -228,8 +228,8 @@ - org.reflections - reflections + io.github.classgraph + classgraph diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactory.java b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactory.java index 80a3482..2cc9f5e 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactory.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactory.java @@ -9,11 +9,9 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; -import java.util.stream.Stream; import eu.europa.ted.eforms.sdk.SdkVersion; -import org.reflections.Reflections; -import org.reflections.util.ClasspathHelper; -import org.reflections.util.ConfigurationBuilder; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ScanResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +21,26 @@ public abstract class SdkComponentFactory { private static final Logger logger = LoggerFactory.getLogger(SdkComponentFactory.class); + /** + * Lazy-init holder for the JVM-wide classpath scan. Triggered the first time any + * {@link SdkComponentFactory} subclass is constructed; the resulting list is shared + * by all subclasses so that the (potentially expensive) classpath walk runs only once + * per JVM rather than once per factory subclass. + */ + private static final class AnnotatedClassesHolder { + static final List> CLASSES = scan(); + + private static List> scan() { + logger.debug("Scanning the classpath for types annotated with {}", SdkComponent.class); + try (ScanResult result = new ClassGraph() + .enableAnnotationInfo() + .ignoreClassVisibility() + .scan()) { + return result.getClassesWithAnnotation(SdkComponent.class).loadClasses(); + } + } + } + private Map>> componentsMap; class ComponentSelector { @@ -65,67 +83,47 @@ protected SdkComponentFactory() { private void populateComponents() { Class annotationType = SdkComponent.class; - logger.debug("Looking in the classpath for types annotated with {}", annotationType); - if (componentsMap == null) { componentsMap = new HashMap<>(); } - // Get a list of all the packages loaded by the available classloaders. - // This can be a bit expensive in some situations, so this method factory should - // be ensured to run as less as possible (ideally only once). - String[] availablePackages = Arrays - .stream(ClasspathHelper.classLoaders()) - .map(ClassLoader::getDefinedPackages) - .flatMap(Stream::of) - .map(Package::getName) - .toArray(String[]::new); + AnnotatedClassesHolder.CLASSES.forEach((Class clazz) -> { + logger.trace("Processing type [{}]", clazz); - if (logger.isTraceEnabled()) { - final List packages = Arrays.asList(availablePackages); - packages.stream().sorted() - .forEach(p -> logger.trace(p)); - } + SdkComponent annotation = clazz.getAnnotation(annotationType); + + String[] supportedSdkVersions = annotation.versions(); + SdkComponentType componentType = annotation.componentType(); + String qualifier = annotation.qualifier(); + ComponentSelector selector = new ComponentSelector(componentType, qualifier); + + logger.trace("Class [{}] has a component type of [{}] and supports SDK versions [{}]", + clazz, componentType, supportedSdkVersions); + + Arrays.asList(supportedSdkVersions).forEach((String sdkVersion) -> { + SdkComponentDescriptor component = + new SdkComponentDescriptor<>(sdkVersion, componentType, clazz); + + Map> components = + componentsMap.get(sdkVersion); + + if (components != null) { + SdkComponentDescriptor existingComponent = components.get(selector); + + if (existingComponent != null && !existingComponent.equals(component)) { + throw new IllegalArgumentException(MessageFormat.format( + "More than one components of type [{0}] have been found for SDK version [{1}]:\n\t- {2}\n\t- {3}", + componentType, sdkVersion, existingComponent.getImplType().getName(), + clazz.getName())); + } + } else { + components = new HashMap<>(); + componentsMap.put(sdkVersion, components); + } - new Reflections(ConfigurationBuilder.build().forPackages(availablePackages)) - .getTypesAnnotatedWith(annotationType).stream() - .forEach((Class clazz) -> { - logger.trace("Processing type [{}]", clazz); - - SdkComponent annotation = clazz.getAnnotation(annotationType); - - String[] supportedSdkVersions = annotation.versions(); - SdkComponentType componentType = annotation.componentType(); - String qualifier = annotation.qualifier(); - ComponentSelector selector = new ComponentSelector(componentType, qualifier); - - logger.trace("Class [{}] has a component type of [{}] and supports SDK versions [{}]", - clazz, componentType, supportedSdkVersions); - - Arrays.asList(supportedSdkVersions).forEach((String sdkVersion) -> { - SdkComponentDescriptor component = - new SdkComponentDescriptor<>(sdkVersion, componentType, clazz); - - Map> components = - componentsMap.get(sdkVersion); - - if (components != null) { - SdkComponentDescriptor existingComponent = components.get(selector); - - if (existingComponent != null && !existingComponent.equals(component)) { - throw new IllegalArgumentException(MessageFormat.format( - "More than one components of type [{0}] have been found for SDK version [{1}]:\n\t- {2}\n\t- {3}", - componentType, sdkVersion, existingComponent.getImplType().getName(), - clazz.getName())); - } - } else { - components = new HashMap<>(); - componentsMap.put(sdkVersion, components); - } - - components.put(selector, component); - }); - }); + components.put(selector, component); + }); + }); } protected T getComponentImpl(String sdkVersion, final SdkComponentType componentType,