From 9f9c8c3e8bd0a5d2c79840f8f74c7d3abc282281 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 11:35:28 +0800 Subject: [PATCH 01/58] Add test: testExecute3OnHandleException Adds a new unit test in ThrowableConsumerTest that calls execute(String, ThrowableConsumer, BiConsumer) with a no-op exception handler to verify the exception-handling path does not propagate an error. Improves coverage for the execute-with-handler scenario. --- .../microsphere/lang/function/ThrowableConsumerTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableConsumerTest.java b/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableConsumerTest.java index ed91c7f90..dcae2fee8 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableConsumerTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableConsumerTest.java @@ -61,6 +61,12 @@ void testExecute3() { }); } + @Test + void testExecute3OnHandleException() { + execute("For testing", throwableConsumer, (t, e) -> { + }); + } + @Test void testExecute3OnException() { assertThrows(RuntimeException.class, () -> execute("For testing", m -> { @@ -69,4 +75,4 @@ void testExecute3OnException() { throw new RuntimeException(t, e); })); } -} +} \ No newline at end of file From 1547458f46b347248886bf994cedc58552448206 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 11:54:02 +0800 Subject: [PATCH 02/58] Add abstract parameterized LoggingTest Introduce an abstract test utility LoggingTest that manages Logback logger levels for tests. The class (microsphere-java-core/src/test/java/io/microsphere/LoggingTest.java) uses JUnit 5 annotations to run parameterized (INFO, TRACE) scenarios, with @BeforeEach/@AfterEach methods to set and restore the logger level via a shared LoggerContext. The test is currently @Disabled and provides getTargetLogger() to target the package logger. --- .../test/java/io/microsphere/LoggingTest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 microsphere-java-core/src/test/java/io/microsphere/LoggingTest.java diff --git a/microsphere-java-core/src/test/java/io/microsphere/LoggingTest.java b/microsphere-java-core/src/test/java/io/microsphere/LoggingTest.java new file mode 100644 index 000000000..578b52537 --- /dev/null +++ b/microsphere-java-core/src/test/java/io/microsphere/LoggingTest.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.microsphere; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.ValueSource; + +import static ch.qos.logback.classic.Level.valueOf; +import static org.slf4j.LoggerFactory.getILoggerFactory; + +/** + * The abstract class for loggging with repeated levels + * + * @author Mercy + * @see LoggerContext + * @since 1.0.0 + */ +@ParameterizedClass +@ValueSource(strings = {"INFO", "TRACE"}) +@Disabled +public abstract class LoggingTest { + + private static final LoggerContext loggerContext = (LoggerContext) getILoggerFactory(); + + @Parameter + private String logLevel; + + private Level orginalLevel; + + @BeforeEach + final void setLoggingLevel() { + Logger logger = getTargetLogger(); + this.orginalLevel = logger.getLevel(); + Level level = valueOf(logLevel); + logger.setLevel(level); + } + + @AfterEach + final void resetLoggingLevel() { + Logger logger = getTargetLogger(); + logger.setLevel(this.orginalLevel); + } + + protected Logger getTargetLogger() { + return loggerContext.getLogger(getClass().getPackage().getName()); + } +} From c00043c3df0e0d90e10c02353d36b25a078323ac Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 11:54:15 +0800 Subject: [PATCH 03/58] Make ShutdownHookCallbacksThreadTest extend LoggingTest Import LoggingTest and have ShutdownHookCallbacksThreadTest extend it so the test can leverage shared logging utilities/capture. Also remove an extra trailing blank line. --- .../io/microsphere/util/ShutdownHookCallbacksThreadTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookCallbacksThreadTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookCallbacksThreadTest.java index 26dfe3b1e..36a25dd7b 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookCallbacksThreadTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookCallbacksThreadTest.java @@ -1,5 +1,6 @@ package io.microsphere.util; +import io.microsphere.LoggingTest; import org.junit.jupiter.api.Test; import static io.microsphere.util.ShutdownHookCallbacksThread.INSTANCE; @@ -12,7 +13,7 @@ * @see ShutdownHookCallbacksThread * @since 1.0.0 */ -class ShutdownHookCallbacksThreadTest { +class ShutdownHookCallbacksThreadTest extends LoggingTest { @Test void testRun() { @@ -25,6 +26,5 @@ void testRun() { } thread.run(); - } } \ No newline at end of file From 6614752c9d6791663eded09dd7d6abf507cf299f Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 11:56:55 +0800 Subject: [PATCH 04/58] Have test class extend LoggingTest Update ClassPathResourceConfigurationPropertyLoaderTest to extend LoggingTest and add the corresponding import. This enables logging support/capture for the test class (facilitating inspection of logs during test execution). --- .../ClassPathResourceConfigurationPropertyLoaderTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/metadata/ClassPathResourceConfigurationPropertyLoaderTest.java b/microsphere-java-core/src/test/java/io/microsphere/metadata/ClassPathResourceConfigurationPropertyLoaderTest.java index 2850b6724..a1e54172a 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/metadata/ClassPathResourceConfigurationPropertyLoaderTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/metadata/ClassPathResourceConfigurationPropertyLoaderTest.java @@ -18,6 +18,7 @@ package io.microsphere.metadata; +import io.microsphere.LoggingTest; import io.microsphere.beans.ConfigurationProperty; import org.junit.jupiter.api.Test; @@ -35,7 +36,7 @@ * @see ClassPathResourceConfigurationPropertyLoader * @since 1.0.0 */ -class ClassPathResourceConfigurationPropertyLoaderTest { +class ClassPathResourceConfigurationPropertyLoaderTest extends LoggingTest { @Test void testLoadWithSingleResource() throws Throwable { From 22f05dcc1649e13a62429d167c15a44244a9b57a Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 11:58:07 +0800 Subject: [PATCH 05/58] Extend ArtifactDetectorTest with LoggingTest Make ArtifactDetectorTest extend LoggingTest to enable logging support in the test. Added import for io.microsphere.LoggingTest and updated the class declaration so tests can leverage the shared logging test utilities. --- .../java/io/microsphere/classloading/ArtifactDetectorTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/classloading/ArtifactDetectorTest.java b/microsphere-java-core/src/test/java/io/microsphere/classloading/ArtifactDetectorTest.java index 47161aa8c..efb90f837 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/classloading/ArtifactDetectorTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/classloading/ArtifactDetectorTest.java @@ -17,6 +17,7 @@ package io.microsphere.classloading; +import io.microsphere.LoggingTest; import org.junit.jupiter.api.Test; import java.util.List; @@ -33,7 +34,7 @@ * @see ArtifactDetector * @since 1.0.0 */ -class ArtifactDetectorTest { +class ArtifactDetectorTest extends LoggingTest { @Test void testDetect() { From 226fd8bd5046af26eb698cce47cae83dd74fb498 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:08:08 +0800 Subject: [PATCH 06/58] Guard trace logging with isTraceEnabled check Wrap the logger.trace call in JarUtils with a logger.isTraceEnabled() check to avoid unnecessary message formatting and exception handling overhead when trace logging is disabled. This change improves performance on the error path when opening a JarFile fails. --- .../src/main/java/io/microsphere/util/jar/JarUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/util/jar/JarUtils.java b/microsphere-java-core/src/main/java/io/microsphere/util/jar/JarUtils.java index 0fd1a9d73..de940e334 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/util/jar/JarUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/util/jar/JarUtils.java @@ -89,7 +89,9 @@ public static JarFile toJarFile(URL jarURL) throws IllegalArgumentException { try { jarFile = new JarFile(jarAbsolutePath); } catch (IOException e) { - logger.trace("The JarFile can't be open from the url : {}", jarURL, e); + if (logger.isTraceEnabled()) { + logger.trace("The JarFile can't be open from the url : {}", jarURL, e); + } } return jarFile; } From f7d89a66179c4f85c3874a6056089c51556e544c Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:08:14 +0800 Subject: [PATCH 07/58] Make JarUtilsTest extend LoggingTest Import LoggingTest and have JarUtilsTest extend it so test logging utilities are available. This enables shared logging setup/behavior for the JarUtils tests to improve diagnostics during test runs. --- .../src/test/java/io/microsphere/util/jar/JarUtilsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/jar/JarUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/jar/JarUtilsTest.java index 4964e755a..11c03dea8 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/jar/JarUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/jar/JarUtilsTest.java @@ -3,6 +3,7 @@ */ package io.microsphere.util.jar; +import io.microsphere.LoggingTest; import io.microsphere.filter.JarEntryFilter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -50,7 +51,7 @@ * @see JarUtilsTest * @since 1.0.0 */ -class JarUtilsTest { +class JarUtilsTest extends LoggingTest { private File targetDirectory; From 7ced9ea56e286b1b4c85fde2cd39defba1e772fd Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:09:01 +0800 Subject: [PATCH 08/58] Guard trace logs with isTraceEnabled checks Wrap two logger.trace calls in ClassUtils.java with logger.isTraceEnabled() checks (in class path scanning and in the SimpleJarEntryScanner exception handler). This avoids unnecessary logging overhead/formatting when TRACE is disabled and preserves existing behavior when enabled. --- .../src/main/java/io/microsphere/util/ClassUtils.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/util/ClassUtils.java b/microsphere-java-core/src/main/java/io/microsphere/util/ClassUtils.java index 53a1430bf..c9166b8fa 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/util/ClassUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/util/ClassUtils.java @@ -1119,7 +1119,9 @@ public static Set findClassNamesInClassPath(@Nullable File classPath, bo classNames = findClassNamesInJarFile(classPath, recursive); } - logger.trace("To find the class names in the class path['{}' , recursive : {}] : {}", classPath, recursive, classNames); + if (logger.isTraceEnabled()) { + logger.trace("To find the class names in the class path['{}' , recursive : {}] : {}", classPath, recursive, classNames); + } return classNames; } @@ -1218,8 +1220,10 @@ public static Set findClassNamesInJarFile(@Nullable File jarFile, boolea } } catch (Exception e) { classNames = emptySet(); - logger.trace("The class names can't be resolved by SimpleJarEntryScanner#scan(jarFile = {} ," + - " recursive = {} , jarEntryFilter = ClassFileJarEntryFilter)", jarFile, recursive, e); + if (logger.isTraceEnabled()) { + logger.trace("The class names can't be resolved by SimpleJarEntryScanner#scan(jarFile = {} ," + + " recursive = {} , jarEntryFilter = ClassFileJarEntryFilter)", jarFile, recursive, e); + } } return classNames; } From ed84b2688492a7d2018f4a4d864fa7406756b1d8 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:09:06 +0800 Subject: [PATCH 09/58] Make ClassUtilsTest extend LoggingTest Add import for io.microsphere.LoggingTest and change ClassUtilsTest to extend LoggingTest so the test can leverage shared logging/test utilities. No other test logic was modified. --- .../src/test/java/io/microsphere/util/ClassUtilsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/ClassUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/ClassUtilsTest.java index 185715140..0c7623b60 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/ClassUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/ClassUtilsTest.java @@ -3,6 +3,7 @@ */ package io.microsphere.util; +import io.microsphere.LoggingTest; import io.microsphere.lang.ClassDataRepository; import io.microsphere.test.A; import org.junit.jupiter.api.Test; @@ -110,7 +111,7 @@ * @see ClassUtilsTest * @since 1.0.0 */ -class ClassUtilsTest { +class ClassUtilsTest extends LoggingTest { @Test void testConstants() { From 78dac16430af72cf46cd10c67e835d26745e446d Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:09:50 +0800 Subject: [PATCH 10/58] Guard logger.trace with isTraceEnabled() Wrap trace-level logging calls in FieldUtils with logger.isTraceEnabled() checks to avoid unnecessary string construction when trace logging is disabled. Changes apply to findField and the exception handlers handleIllegalAccessException and handleIllegalArgumentException. No functional change to exception behavior; this is an optimization to reduce logging overhead. --- .../main/java/io/microsphere/reflect/FieldUtils.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/reflect/FieldUtils.java b/microsphere-java-core/src/main/java/io/microsphere/reflect/FieldUtils.java index fe139f1d3..3e5bcafce 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/reflect/FieldUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/reflect/FieldUtils.java @@ -115,7 +115,9 @@ public static Field findField(Class klass, String fieldName) { // ignore, try the super class field = findField(klass.getSuperclass(), fieldName); } - logger.trace("To find the field[name :'{}'] from the class : '{}'", fieldName, klass); + if (logger.isTraceEnabled()) { + logger.trace("To find the field[name :'{}'] from the class : '{}'", fieldName, klass); + } return field; } @@ -682,14 +684,18 @@ public static void assertFieldMatchType(Object instance, String fieldName, Class static void handleIllegalAccessException(IllegalAccessException e, Object instance, Field field, boolean accessible) { String errorMessage = format("The instance [object : {} , class : {} ] can't access the field[name : '{}' , type : {} , accessible : {}]", instance, getTypeName(instance.getClass()), field.getName(), getTypeName(field.getType()), accessible); - logger.trace(errorMessage); + if (logger.isTraceEnabled()) { + logger.trace(errorMessage); + } throw new IllegalStateException(errorMessage, e); } static void handleIllegalArgumentException(IllegalArgumentException e, Object instance, Field field) { String errorMessage = format("The instance[object : {} , class : {}] can't match the field[name : '{}' , type : {}]", instance, getTypeName(instance.getClass()), field.getName(), getTypeName(field.getType())); - logger.trace(errorMessage); + if (logger.isTraceEnabled()) { + logger.trace(errorMessage); + } throw new IllegalArgumentException(errorMessage, e); } From 1049f2e50c7947f7af57712472a0106d01c3799a Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:09:58 +0800 Subject: [PATCH 11/58] Make FieldUtilsTest extend LoggingTest Add io.microsphere.LoggingTest import and update FieldUtilsTest to extend LoggingTest so the test class inherits logging/diagnostic behavior. No other logic changes were made. --- .../src/test/java/io/microsphere/reflect/FieldUtilsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/reflect/FieldUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/reflect/FieldUtilsTest.java index 27be787b9..3b507864a 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/reflect/FieldUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/reflect/FieldUtilsTest.java @@ -16,6 +16,7 @@ */ package io.microsphere.reflect; +import io.microsphere.LoggingTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -48,7 +49,7 @@ * @author Mercy * @since 1.0.0 */ -class FieldUtilsTest { +class FieldUtilsTest extends LoggingTest { private static String value = "1"; From 635ed52db3ea5f3937ffd7d7e01cf0bae5fe758d Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:11:09 +0800 Subject: [PATCH 12/58] Guard trace logging with isTraceEnabled Add a logger.isTraceEnabled() check before invoking logger.trace in ManifestArtifactResourceResolver. This avoids unnecessary string formatting and potential expensive artifact representation creation when trace logging is disabled, reducing runtime overhead without changing behavior. --- .../classloading/ManifestArtifactResourceResolver.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/classloading/ManifestArtifactResourceResolver.java b/microsphere-java-core/src/main/java/io/microsphere/classloading/ManifestArtifactResourceResolver.java index 97f79ff08..3521245dc 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/classloading/ManifestArtifactResourceResolver.java +++ b/microsphere-java-core/src/main/java/io/microsphere/classloading/ManifestArtifactResourceResolver.java @@ -207,7 +207,9 @@ Artifact resolveArtifactMetaInfoInManifest(Manifest manifest, URL resourceURL) { } String version = resolveVersion(mainAttributes); Artifact artifact = create(artifactId, version, resourceURL); - logger.trace("The artifactId was resolved from the resource URL['{}']: {}", artifactId, artifact); + if (logger.isTraceEnabled()) { + logger.trace("The artifactId was resolved from the resource URL['{}']: {}", artifactId, artifact); + } return artifact; } From 420dc40e152e1ffcd0341eb2d6a0892d894c2782 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:11:16 +0800 Subject: [PATCH 13/58] Guard trace logs with isTraceEnabled() Wrap existing logger.trace(...) calls with logger.isTraceEnabled() checks to avoid unnecessary string formatting when trace logging is disabled. Applied to three locations in StreamArtifactResourceResolver where missing archive, missing JarEntry, or missing artifact metadata file are logged. No behavioral changes beyond logging performance improvement. --- .../classloading/StreamArtifactResourceResolver.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/classloading/StreamArtifactResourceResolver.java b/microsphere-java-core/src/main/java/io/microsphere/classloading/StreamArtifactResourceResolver.java index 58ed3de1a..0b4312624 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/classloading/StreamArtifactResourceResolver.java +++ b/microsphere-java-core/src/main/java/io/microsphere/classloading/StreamArtifactResourceResolver.java @@ -113,7 +113,9 @@ public final Artifact resolve(URL resourceURL) { Artifact artifact = null; try { if (archiveFile == null) { - logger.trace("The resourceURL['{}'] can't be resolved to be an archive file", resourceURL); + if (logger.isTraceEnabled()) { + logger.trace("The resourceURL['{}'] can't be resolved to be an archive file", resourceURL); + } artifactMetadataData = readArtifactMetadataDataFromResource(resourceURL, classLoader); } else { artifactMetadataData = readArtifactMetadataDataFromArchiveFile(archiveFile); @@ -149,7 +151,9 @@ protected InputStream readArtifactMetadataDataFromFile(File archiveFile) throws JarFile jarFile = new JarFile(archiveFile); JarEntry jarEntry = findArtifactMetadataEntry(jarFile); if (jarEntry == null) { - logger.trace("The artifact metadata entry can't be resolved from the JarFile[path: '{}']", archiveFile); + if (logger.isTraceEnabled()) { + logger.trace("The artifact metadata entry can't be resolved from the JarFile[path: '{}']", archiveFile); + } return null; } return jarFile.getInputStream(jarEntry); @@ -159,7 +163,9 @@ protected InputStream readArtifactMetadataDataFromFile(File archiveFile) throws protected InputStream readArtifactMetadataDataFromDirectory(File directory) throws IOException { File artifactMetadataFile = findArtifactMetadata(directory); if (artifactMetadataFile == null) { - logger.trace("The artifact metadata file can't be found in the directory[path: '{}']", directory); + if (logger.isTraceEnabled()) { + logger.trace("The artifact metadata file can't be found in the directory[path: '{}']", directory); + } return null; } return new FileInputStream(artifactMetadataFile); From ac64d1d75e3e6a501c8fd7b7d841560276fd5dd1 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:11:35 +0800 Subject: [PATCH 14/58] Extend resolver test with LoggingTest Import LoggingTest and make AbstractArtifactResourceResolverTest extend it so the artifact resolver tests can use shared logging utilities and capture test logs consistently. --- .../classloading/AbstractArtifactResourceResolverTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/classloading/AbstractArtifactResourceResolverTest.java b/microsphere-java-core/src/test/java/io/microsphere/classloading/AbstractArtifactResourceResolverTest.java index 581f5b1ec..319600b0b 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/classloading/AbstractArtifactResourceResolverTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/classloading/AbstractArtifactResourceResolverTest.java @@ -16,6 +16,7 @@ */ package io.microsphere.classloading; +import io.microsphere.LoggingTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,7 +36,7 @@ * @see AbstractArtifactResourceResolver * @since 1.0.0 */ -abstract class AbstractArtifactResourceResolverTest { +abstract class AbstractArtifactResourceResolverTest extends LoggingTest { static final Class TEST_ANNOTATION_CLASS = Nonnull.class; From 8482399ed7b4a9fb12727d9d9c90867403066564 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:25:04 +0800 Subject: [PATCH 15/58] Convert Loggable to interface; update tests Replace Loggable abstract class with a Loggable interface that provides default logging methods (logger is now obtained per-call). Update test classes to implement Loggable instead of extending it, adjust class/interface declaration orders where necessary, and add a LoggingTest base class to ExecutorUtilsTest (with import). Also update ModifierTest to avoid referencing the removed protected logger field by checking Proxy.h instead. --- .../test/java/io/microsphere/AbstractTestCase.java | 2 +- .../src/test/java/io/microsphere/Loggable.java | 13 +++++++------ .../microsphere/collection/CollectionUtilsTest.java | 2 +- .../io/microsphere/collection/ListUtilsTest.java | 2 +- .../collection/ReadOnlyIteratorTest.java | 2 +- .../DelegatingScheduledExecutorServiceTest.java | 2 +- .../microsphere/concurrent/ExecutorUtilsTest.java | 3 ++- .../io/microsphere/convert/BaseConverterTest.java | 2 +- .../io/microsphere/event/AbstractEventListener.java | 2 +- .../test/java/io/microsphere/io/FileUtilsTest.java | 2 +- .../io/microsphere/io/FileWatchServiceTest.java | 2 +- .../io/StandardFileWatchServiceTest.java | 2 +- .../io/event/FileChangedListenerTest.java | 2 +- .../lang/function/ThrowableActionTest.java | 2 +- .../lang/function/ThrowableBiConsumerTest.java | 2 +- .../performance/AbstractPerformanceTest.java | 2 +- .../io/microsphere/process/ProcessExecutorTest.java | 2 +- .../io/microsphere/process/ProcessManagerTest.java | 2 +- .../reflect/AbstractReflectiveDefinitionTest.java | 2 +- .../java/io/microsphere/reflect/ModifierTest.java | 4 ++-- .../java/io/microsphere/util/ArrayUtilsTest.java | 2 +- .../io/microsphere/util/ClassPathUtilsTest.java | 2 +- .../io/microsphere/util/ShutdownHookCallback.java | 2 +- .../io/microsphere/util/ShutdownHookUtilsTest.java | 2 +- .../java/io/microsphere/util/StopWatchTest.java | 2 +- 25 files changed, 33 insertions(+), 31 deletions(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/AbstractTestCase.java b/microsphere-java-core/src/test/java/io/microsphere/AbstractTestCase.java index a4ab1c011..f8ea785fa 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/AbstractTestCase.java +++ b/microsphere-java-core/src/test/java/io/microsphere/AbstractTestCase.java @@ -50,7 +50,7 @@ * @since 1.0.0 */ @Disabled -public abstract class AbstractTestCase extends Loggable { +public abstract class AbstractTestCase implements Loggable { public static final String TEST_ELEMENT = "test"; diff --git a/microsphere-java-core/src/test/java/io/microsphere/Loggable.java b/microsphere-java-core/src/test/java/io/microsphere/Loggable.java index 13aa8c44b..03d5641ac 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/Loggable.java +++ b/microsphere-java-core/src/test/java/io/microsphere/Loggable.java @@ -29,23 +29,24 @@ * @see Logger * @since 1.0.0 */ -public abstract class Loggable { +public interface Loggable { - protected final Logger logger = getLogger(getClass()); - - protected void log(Object object) { + default void log(Object object) { + Logger logger = getLogger(getClass()); if (logger.isTraceEnabled()) { logger.trace(valueOf(object)); } } - protected void log(String object, Object... args) { + default void log(String object, Object... args) { + Logger logger = getLogger(getClass()); if (logger.isTraceEnabled()) { logger.trace(object, args); } } - protected void log(String message, Throwable t) { + default void log(String message, Throwable t) { + Logger logger = getLogger(getClass()); if (logger.isTraceEnabled()) { logger.trace(message, t); } diff --git a/microsphere-java-core/src/test/java/io/microsphere/collection/CollectionUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/collection/CollectionUtilsTest.java index 7a2aef4f5..cfe3a7a38 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/collection/CollectionUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/collection/CollectionUtilsTest.java @@ -74,7 +74,7 @@ * @author Mercy * @since 1.0.0 */ -class CollectionUtilsTest extends Loggable { +class CollectionUtilsTest implements Loggable { @Test void testIsEmpty() { diff --git a/microsphere-java-core/src/test/java/io/microsphere/collection/ListUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/collection/ListUtilsTest.java index 2efe188d9..adb75ead1 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/collection/ListUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/collection/ListUtilsTest.java @@ -61,7 +61,7 @@ * @see ListUtils * @since 1.0.0 */ -class ListUtilsTest extends Loggable { +class ListUtilsTest implements Loggable { private static final List TEST_LIST = asList("A", "B", "C"); diff --git a/microsphere-java-core/src/test/java/io/microsphere/collection/ReadOnlyIteratorTest.java b/microsphere-java-core/src/test/java/io/microsphere/collection/ReadOnlyIteratorTest.java index 260a1c33a..0867b97a8 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/collection/ReadOnlyIteratorTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/collection/ReadOnlyIteratorTest.java @@ -34,7 +34,7 @@ * @see ReadOnlyIterator * @since 1.0.0 */ -public abstract class ReadOnlyIteratorTest extends Loggable { +public abstract class ReadOnlyIteratorTest implements Loggable { Iterator instance = createIterator(); diff --git a/microsphere-java-core/src/test/java/io/microsphere/concurrent/DelegatingScheduledExecutorServiceTest.java b/microsphere-java-core/src/test/java/io/microsphere/concurrent/DelegatingScheduledExecutorServiceTest.java index 87d369b78..212252e6b 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/concurrent/DelegatingScheduledExecutorServiceTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/concurrent/DelegatingScheduledExecutorServiceTest.java @@ -23,7 +23,7 @@ * @see DelegatingScheduledExecutorService * @since 1.0.0 */ -class DelegatingScheduledExecutorServiceTest extends Loggable { +class DelegatingScheduledExecutorServiceTest implements Loggable { private ScheduledExecutorService delegate = newSingleThreadScheduledExecutor(); diff --git a/microsphere-java-core/src/test/java/io/microsphere/concurrent/ExecutorUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/concurrent/ExecutorUtilsTest.java index 4babd9e25..106cbeee7 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/concurrent/ExecutorUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/concurrent/ExecutorUtilsTest.java @@ -1,6 +1,7 @@ package io.microsphere.concurrent; import io.microsphere.Loggable; +import io.microsphere.LoggingTest; import io.microsphere.util.ShutdownHookUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -28,7 +29,7 @@ * @see ExecutorUtils * @since 1.0.0 */ -class ExecutorUtilsTest extends Loggable { +class ExecutorUtilsTest extends LoggingTest implements Loggable { private ExecutorService executorService; diff --git a/microsphere-java-core/src/test/java/io/microsphere/convert/BaseConverterTest.java b/microsphere-java-core/src/test/java/io/microsphere/convert/BaseConverterTest.java index 08b5e08d9..481858868 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/convert/BaseConverterTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/convert/BaseConverterTest.java @@ -36,7 +36,7 @@ * @see AbstractConverter * @since 1.0.0 */ -abstract class BaseConverterTest extends Loggable { +abstract class BaseConverterTest implements Loggable { protected AbstractConverter converter; diff --git a/microsphere-java-core/src/test/java/io/microsphere/event/AbstractEventListener.java b/microsphere-java-core/src/test/java/io/microsphere/event/AbstractEventListener.java index 68de276a3..36e9c4074 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/event/AbstractEventListener.java +++ b/microsphere-java-core/src/test/java/io/microsphere/event/AbstractEventListener.java @@ -20,7 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger; -public abstract class AbstractEventListener extends Loggable implements EventListener { +public abstract class AbstractEventListener implements Loggable, EventListener { private final AtomicInteger eventOccurs = new AtomicInteger(0); diff --git a/microsphere-java-core/src/test/java/io/microsphere/io/FileUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/io/FileUtilsTest.java index 0649d43bb..39c774ee4 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/io/FileUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/io/FileUtilsTest.java @@ -60,7 +60,7 @@ * @see FileUtils * @since 1.0.0 */ -class FileUtilsTest extends Loggable { +class FileUtilsTest implements Loggable { private final URL classFileResource = getClassResource(TEST_CLASS_LOADER, FileUtilsTest.class); diff --git a/microsphere-java-core/src/test/java/io/microsphere/io/FileWatchServiceTest.java b/microsphere-java-core/src/test/java/io/microsphere/io/FileWatchServiceTest.java index ca8ece861..6d392e557 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/io/FileWatchServiceTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/io/FileWatchServiceTest.java @@ -17,7 +17,7 @@ * @see FileWatchService * @since 1.0.0 */ -class FileWatchServiceTest extends Loggable { +class FileWatchServiceTest implements Loggable { private FileWatchService service = (file, listener, kinds) -> { log("Watching : {} , listener : {} , kinds : {}", file, listener, kinds); diff --git a/microsphere-java-core/src/test/java/io/microsphere/io/StandardFileWatchServiceTest.java b/microsphere-java-core/src/test/java/io/microsphere/io/StandardFileWatchServiceTest.java index 020c92afa..5a732f1a5 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/io/StandardFileWatchServiceTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/io/StandardFileWatchServiceTest.java @@ -74,7 +74,7 @@ * @author Mercy * @since 1.0.0 */ -class StandardFileWatchServiceTest extends Loggable { +class StandardFileWatchServiceTest implements Loggable { private File testDir; diff --git a/microsphere-java-core/src/test/java/io/microsphere/io/event/FileChangedListenerTest.java b/microsphere-java-core/src/test/java/io/microsphere/io/event/FileChangedListenerTest.java index 7bfbfd32a..c65fa3add 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/io/event/FileChangedListenerTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/io/event/FileChangedListenerTest.java @@ -28,7 +28,7 @@ * @see FileChangedListener * @since */ -class FileChangedListenerTest extends Loggable { +class FileChangedListenerTest implements Loggable { private FileChangedListener listener; diff --git a/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableActionTest.java b/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableActionTest.java index 297b6ee34..5dbe3452f 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableActionTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableActionTest.java @@ -27,7 +27,7 @@ * * @since 1.0.0 */ -class ThrowableActionTest extends Loggable { +class ThrowableActionTest implements Loggable { private final ThrowableAction action = () -> { log("ThrowableAction#execute()"); diff --git a/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableBiConsumerTest.java b/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableBiConsumerTest.java index 58ab7a4cf..13463dca8 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableBiConsumerTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/lang/function/ThrowableBiConsumerTest.java @@ -27,7 +27,7 @@ * @see ThrowableBiConsumer * @since 1.0.0 */ -class ThrowableBiConsumerTest extends Loggable { +class ThrowableBiConsumerTest implements Loggable { @Test void testAccept() throws Throwable { diff --git a/microsphere-java-core/src/test/java/io/microsphere/performance/AbstractPerformanceTest.java b/microsphere-java-core/src/test/java/io/microsphere/performance/AbstractPerformanceTest.java index 8a6dfdb49..0ff95947b 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/performance/AbstractPerformanceTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/performance/AbstractPerformanceTest.java @@ -11,7 +11,7 @@ * @since 1.0.0 */ @Disabled -public abstract class AbstractPerformanceTest extends Loggable { +public abstract class AbstractPerformanceTest implements Loggable { protected void execute(PerformanceAction action) { long startTime = System.nanoTime(); diff --git a/microsphere-java-core/src/test/java/io/microsphere/process/ProcessExecutorTest.java b/microsphere-java-core/src/test/java/io/microsphere/process/ProcessExecutorTest.java index e296b1025..0c81927b7 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/process/ProcessExecutorTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/process/ProcessExecutorTest.java @@ -20,7 +20,7 @@ * @see ProcessExecutorTest * @since 1.0.0 */ -class ProcessExecutorTest extends Loggable { +class ProcessExecutorTest implements Loggable { private FastByteArrayOutputStream outputStream; diff --git a/microsphere-java-core/src/test/java/io/microsphere/process/ProcessManagerTest.java b/microsphere-java-core/src/test/java/io/microsphere/process/ProcessManagerTest.java index 47e666867..7ad4cfbb1 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/process/ProcessManagerTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/process/ProcessManagerTest.java @@ -23,7 +23,7 @@ * @see ProcessManager * @since 1.0.0 */ -class ProcessManagerTest extends Loggable { +class ProcessManagerTest implements Loggable { @BeforeEach void setUp() { diff --git a/microsphere-java-core/src/test/java/io/microsphere/reflect/AbstractReflectiveDefinitionTest.java b/microsphere-java-core/src/test/java/io/microsphere/reflect/AbstractReflectiveDefinitionTest.java index 56c1d0f64..f1c3724fc 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/reflect/AbstractReflectiveDefinitionTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/reflect/AbstractReflectiveDefinitionTest.java @@ -30,7 +30,7 @@ * @see ReflectiveDefinition * @since 1.0.0 */ -public abstract class AbstractReflectiveDefinitionTest extends Loggable { +public abstract class AbstractReflectiveDefinitionTest implements Loggable { private final List[] headConstructorArgumentsArray = ofArray( ofList(SINCE, getClassName()), diff --git a/microsphere-java-core/src/test/java/io/microsphere/reflect/ModifierTest.java b/microsphere-java-core/src/test/java/io/microsphere/reflect/ModifierTest.java index ecaa38576..c7bee110d 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/reflect/ModifierTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/reflect/ModifierTest.java @@ -16,9 +16,9 @@ */ package io.microsphere.reflect; -import io.microsphere.Loggable; import org.junit.jupiter.api.Test; +import java.lang.reflect.Proxy; import java.util.AbstractList; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.AbstractQueuedSynchronizer; @@ -99,7 +99,7 @@ void testIsPrivate() throws NoSuchFieldException { @Test void testIsProtected() throws NoSuchFieldException { - assertTrue(isProtected(Loggable.class.getDeclaredField("logger").getModifiers())); + assertTrue(isProtected(Proxy.class.getDeclaredField("h").getModifiers())); assertFalse(isProtected(String.class.getModifiers())); } diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/ArrayUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/ArrayUtilsTest.java index 580aca9ed..202159e11 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/ArrayUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/ArrayUtilsTest.java @@ -89,7 +89,7 @@ * @author Mercy * @since 1.0.0 */ -class ArrayUtilsTest extends Loggable { +class ArrayUtilsTest implements Loggable { @Test void testConstants() { diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/ClassPathUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/ClassPathUtilsTest.java index 66e449a72..bcbc1b748 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/ClassPathUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/ClassPathUtilsTest.java @@ -30,7 +30,7 @@ * @see ClassPathUtilsTest * @since 1.0.0 */ -class ClassPathUtilsTest extends Loggable { +class ClassPathUtilsTest implements Loggable { @Test void testGetBootstrapClassPaths() { diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookCallback.java b/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookCallback.java index f8305e561..9087a3cab 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookCallback.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookCallback.java @@ -27,7 +27,7 @@ * @see ShutdownHookUtils * @since 1.0.0 */ -class ShutdownHookCallback extends Loggable implements Runnable, Prioritized { +class ShutdownHookCallback implements Loggable, Runnable, Prioritized { private final int priority; diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookUtilsTest.java index 89799e3cb..0009475d6 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/ShutdownHookUtilsTest.java @@ -42,7 +42,7 @@ * @author Mercy * @since 1.0.0 */ -class ShutdownHookUtilsTest extends Loggable { +class ShutdownHookUtilsTest implements Loggable { @BeforeEach void setUp() { diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/StopWatchTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/StopWatchTest.java index e811476f0..55bdb6782 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/StopWatchTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/StopWatchTest.java @@ -40,7 +40,7 @@ * @author Mercy * @since 1.0.0 */ -class StopWatchTest extends Loggable { +class StopWatchTest implements Loggable { private static final String testName = "test"; From 26b214ca00d042a28b22ce1f40f59248bfa49f90 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:25:18 +0800 Subject: [PATCH 16/58] Guard trace logging with isTraceEnabled Wrap the trace log call in ExecutorUtils.shutdown(...) with a logger.isTraceEnabled() check to avoid unnecessary logging overhead when trace level is disabled. This prevents potential cost from message formatting or parameter evaluation in high-frequency shutdown paths. --- .../main/java/io/microsphere/concurrent/ExecutorUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/concurrent/ExecutorUtils.java b/microsphere-java-core/src/main/java/io/microsphere/concurrent/ExecutorUtils.java index 930ce67d8..b49c49e93 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/concurrent/ExecutorUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/concurrent/ExecutorUtils.java @@ -135,7 +135,9 @@ public static boolean shutdown(ExecutorService executorService) { if (!executorService.isShutdown()) { executorService.shutdown(); } - logger.trace("The ExecutorService({}) has been shutdown", executorService); + if (logger.isTraceEnabled()) { + logger.trace("The ExecutorService({}) has been shutdown", executorService); + } return true; } From 61cfb742434d1c10e789ab4f0aa2b84ec457d357 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:26:02 +0800 Subject: [PATCH 17/58] Guard trace logging with isTraceEnabled Wrap the trace log call in IOUtils with logger.isTraceEnabled() to avoid unnecessary message formatting and argument evaluation when trace level is disabled. This reduces overhead in the stream copy path while preserving the original logging behavior. --- .../src/main/java/io/microsphere/io/IOUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/io/IOUtils.java b/microsphere-java-core/src/main/java/io/microsphere/io/IOUtils.java index c5bd0b47e..424454bff 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/io/IOUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/io/IOUtils.java @@ -367,7 +367,9 @@ public static int copy(InputStream in, OutputStream out) throws IOException { byteCount += bytesRead; } out.flush(); - logger.trace("Copied {} bytes[buffer size : {}] from InputStream[{}] to OutputStream[{}]", byteCount, BUFFER_SIZE, in, out); + if (logger.isTraceEnabled()) { + logger.trace("Copied {} bytes[buffer size : {}] from InputStream[{}] to OutputStream[{}]", byteCount, BUFFER_SIZE, in, out); + } return byteCount; } From 26aa1dace5fd6180373f38991e3e77de1cd1c253 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:26:10 +0800 Subject: [PATCH 18/58] Make IOUtilsTest extend LoggingTest Import io.microsphere.LoggingTest and update IOUtilsTest to extend LoggingTest so the test class can reuse shared logging/test utilities (captures logging or common setup/teardown). --- .../src/test/java/io/microsphere/io/IOUtilsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/io/IOUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/io/IOUtilsTest.java index 0f5994c10..63ade98f1 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/io/IOUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/io/IOUtilsTest.java @@ -1,5 +1,6 @@ package io.microsphere.io; +import io.microsphere.LoggingTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,7 +32,7 @@ * @see IOUtils * @since 1.0.0 */ -class IOUtilsTest { +class IOUtilsTest extends LoggingTest { private static final String TEST_VALUE = "Hello"; From e2bd8ae58e6809b20b4e0bcdd1d35525f9d881d9 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:27:15 +0800 Subject: [PATCH 19/58] Guard trace logs with isTraceEnabled Wrap trace logging calls with logger.isTraceEnabled() checks to avoid unnecessary message formatting and potential expensive operations when trace level is disabled. Applied to the file-dispatch log in the event dispatch path and the directory registration log in registerDirectoriesToWatchService. --- .../io/microsphere/io/StandardFileWatchService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/io/StandardFileWatchService.java b/microsphere-java-core/src/main/java/io/microsphere/io/StandardFileWatchService.java index ba892d397..80b334813 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/io/StandardFileWatchService.java +++ b/microsphere-java-core/src/main/java/io/microsphere/io/StandardFileWatchService.java @@ -245,7 +245,9 @@ private void dispatchFileChangedEvent(Path filePath, WatchEvent.Kind watchEventK Kind kind = toKind(watchEventKind); FileChangedEvent fileChangedEvent = new FileChangedEvent(file, kind); eventDispatcher.dispatch(fileChangedEvent); - logger.trace("The {} was dispatched", fileChangedEvent); + if (logger.isTraceEnabled()) { + logger.trace("The {} was dispatched", fileChangedEvent); + } } private void registerDirectoriesToWatchService(WatchService watchService) throws Exception { @@ -254,8 +256,10 @@ private void registerDirectoriesToWatchService(WatchService watchService) throws FileChangedMetadata metadata = entry.getValue(); WatchEvent.Kind[] kinds = metadata.watchEventKinds; directoryPath.register(watchService, kinds); - logger.trace("The directory[path : '{}' , event kinds : {}] registers the WatchService : {}", - directoryPath, arrayToString(kinds), watchService); + if (logger.isTraceEnabled()) { + logger.trace("The directory[path : '{}' , event kinds : {}] registers the WatchService : {}", + directoryPath, arrayToString(kinds), watchService); + } } } From c7d216397ee774d1fac7ac5cc8da08d689b1fe74 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:27:29 +0800 Subject: [PATCH 20/58] Make StandardFileWatchServiceTest extend LoggingTest Import io.microsphere.LoggingTest and update StandardFileWatchServiceTest to extend LoggingTest (while retaining Loggable). This enables reuse of logging test utilities in the test class. --- .../java/io/microsphere/io/StandardFileWatchServiceTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/io/StandardFileWatchServiceTest.java b/microsphere-java-core/src/test/java/io/microsphere/io/StandardFileWatchServiceTest.java index 5a732f1a5..721d0c541 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/io/StandardFileWatchServiceTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/io/StandardFileWatchServiceTest.java @@ -17,6 +17,7 @@ package io.microsphere.io; import io.microsphere.Loggable; +import io.microsphere.LoggingTest; import io.microsphere.io.event.DefaultFileChangedListener; import io.microsphere.io.event.FileChangedEvent; import io.microsphere.io.event.FileChangedEvent.Kind; @@ -74,7 +75,7 @@ * @author Mercy * @since 1.0.0 */ -class StandardFileWatchServiceTest implements Loggable { +class StandardFileWatchServiceTest extends LoggingTest implements Loggable { private File testDir; From 1f2f4be372adbbdd8c7b1fd3866f70c8b60cd3a9 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:27:56 +0800 Subject: [PATCH 21/58] Guard trace logging with isTraceEnabled Wrap the logger.trace call in StandardURLStreamHandlerFactory#createURLStreamHandlerFromDefaultFactory with a logger.isTraceEnabled() check to avoid unnecessary logging overhead and follow conditional logging best practices. --- .../io/microsphere/net/StandardURLStreamHandlerFactory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/net/StandardURLStreamHandlerFactory.java b/microsphere-java-core/src/main/java/io/microsphere/net/StandardURLStreamHandlerFactory.java index 9a23d0c79..71c6161b7 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/net/StandardURLStreamHandlerFactory.java +++ b/microsphere-java-core/src/main/java/io/microsphere/net/StandardURLStreamHandlerFactory.java @@ -86,7 +86,9 @@ URLStreamHandler createURLStreamHandler(Field defaultFactoryField, String protoc URLStreamHandler createURLStreamHandlerFromDefaultFactory(Field defaultFactoryField, String protocol) { if (defaultFactoryField == null) { - logger.trace("The 'defaultFactory' field can't be found in the class URL."); + if (logger.isTraceEnabled()) { + logger.trace("The 'defaultFactory' field can't be found in the class URL."); + } return null; } URLStreamHandlerFactory factory = getStaticFieldValue(defaultFactoryField); From aa2e719fe4c403a863571dc50118ba8c6d6185d3 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:28:13 +0800 Subject: [PATCH 22/58] Make test extend LoggingTest Import LoggingTest and have StandardURLStreamHandlerFactoryTest extend it so the test inherits shared logging setup/fixtures. This enables consistent logging behavior for the test class. --- .../microsphere/net/StandardURLStreamHandlerFactoryTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/net/StandardURLStreamHandlerFactoryTest.java b/microsphere-java-core/src/test/java/io/microsphere/net/StandardURLStreamHandlerFactoryTest.java index 527850016..a75c25ff3 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/net/StandardURLStreamHandlerFactoryTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/net/StandardURLStreamHandlerFactoryTest.java @@ -18,6 +18,7 @@ package io.microsphere.net; +import io.microsphere.LoggingTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,7 +31,7 @@ * @see StandardURLStreamHandlerFactory * @since 1.0.0 */ -class StandardURLStreamHandlerFactoryTest { +class StandardURLStreamHandlerFactoryTest extends LoggingTest { private StandardURLStreamHandlerFactory factory; From 5f51bf85ccd5e1f9452edc9f3edc6f4256fb716d Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:28:48 +0800 Subject: [PATCH 23/58] Guard trace log with isTraceEnabled Wrap the logger.trace call in URLUtils.java with an if (logger.isTraceEnabled()) check to avoid unnecessary logging message formatting when trace is disabled. Preserves existing behavior (returns null for whitespace in protocol) while reducing runtime overhead during protocol validation. --- .../src/main/java/io/microsphere/net/URLUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/net/URLUtils.java b/microsphere-java-core/src/main/java/io/microsphere/net/URLUtils.java index 8f23b03f0..e8387f49d 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/net/URLUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/net/URLUtils.java @@ -1184,7 +1184,9 @@ public static String resolveProtocol(String url) { } for (int i = 0; i <= indexOfColon; i++) { if (isWhitespace(url.charAt(i))) { - logger.trace("The protocol content should not contain the whitespace[url : '{}' , index : {}]", url, i); + if (logger.isTraceEnabled()) { + logger.trace("The protocol content should not contain the whitespace[url : '{}' , index : {}]", url, i); + } return null; } } From 5b8a0d6e01b5bc94eaba4b34b9e34cee6b0c4dbd Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:29:03 +0800 Subject: [PATCH 24/58] Make URLUtilsTest extend LoggingTest Add io.microsphere.LoggingTest import and have URLUtilsTest extend LoggingTest so the test can use shared logging/test utilities (enabling log capture/configuration during test execution). --- .../src/test/java/io/microsphere/net/URLUtilsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/net/URLUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/net/URLUtilsTest.java index 923ddd112..7b5738ac4 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/net/URLUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/net/URLUtilsTest.java @@ -3,6 +3,7 @@ */ package io.microsphere.net; +import io.microsphere.LoggingTest; import io.microsphere.net.console.Handler; import io.microsphere.util.StringUtils; import org.junit.jupiter.api.AfterEach; @@ -101,7 +102,7 @@ * @see URLUtilsTest * @since 1.0.0 */ -class URLUtilsTest { +class URLUtilsTest extends LoggingTest { private static final String TEST_PATH = "/abc/def"; From 1089014a55c7c01157cdaa09884b034edb12238d Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:29:33 +0800 Subject: [PATCH 25/58] Guard trace logging with isTraceEnabled Wrap the trace log call in ClassicProcessIdResolver#current() with logger.isTraceEnabled() to avoid unnecessary message construction when trace logging is disabled. Preserves existing behavior when trace is enabled while reducing potential performance overhead. --- .../java/io/microsphere/process/ClassicProcessIdResolver.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/process/ClassicProcessIdResolver.java b/microsphere-java-core/src/main/java/io/microsphere/process/ClassicProcessIdResolver.java index 10ff9f6ac..496914eab 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/process/ClassicProcessIdResolver.java +++ b/microsphere-java-core/src/main/java/io/microsphere/process/ClassicProcessIdResolver.java @@ -82,7 +82,9 @@ public boolean supports() { @Override public Long current() { Long processId = valueOf(processIdValue); - logger.trace("The PID was resolved from the method 'java.lang.management.RuntimeMXBean#getName()' = {} : {}", runtimeName, processId); + if (logger.isTraceEnabled()) { + logger.trace("The PID was resolved from the method 'java.lang.management.RuntimeMXBean#getName()' = {} : {}", runtimeName, processId); + } return processId; } From 96e03aa918a7589a371b881db37ce355bb99a73a Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:29:45 +0800 Subject: [PATCH 26/58] Make ClassicProcessIdResolverTest extend LoggingTest Update the test class to extend io.microsphere.LoggingTest and add the required import so the test can use shared logging utilities/capture. No behavior changes to test logic. --- .../io/microsphere/process/ClassicProcessIdResolverTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/process/ClassicProcessIdResolverTest.java b/microsphere-java-core/src/test/java/io/microsphere/process/ClassicProcessIdResolverTest.java index 30cb457e4..4d4acb7a2 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/process/ClassicProcessIdResolverTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/process/ClassicProcessIdResolverTest.java @@ -1,5 +1,6 @@ package io.microsphere.process; +import io.microsphere.LoggingTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,7 +15,7 @@ * @see ClassicProcessIdResolver * @since 1.0.0 */ -class ClassicProcessIdResolverTest { +class ClassicProcessIdResolverTest extends LoggingTest { private ClassicProcessIdResolver resolver; From 28509827533ed0404bc8cf8484eb20538b6842e2 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:30:13 +0800 Subject: [PATCH 27/58] Guard trace logging with isTraceEnabled Wrap the logger.trace call in ModernProcessIdResolver.current() with logger.isTraceEnabled() to avoid unnecessary logging overhead (message formatting) when TRACE is disabled. This reduces runtime cost for the PID resolution path. --- .../java/io/microsphere/process/ModernProcessIdResolver.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/process/ModernProcessIdResolver.java b/microsphere-java-core/src/main/java/io/microsphere/process/ModernProcessIdResolver.java index c1c606418..65dd726d3 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/process/ModernProcessIdResolver.java +++ b/microsphere-java-core/src/main/java/io/microsphere/process/ModernProcessIdResolver.java @@ -64,7 +64,9 @@ public boolean supports() { public Long current() { Object processHandle = invokeStaticMethod(PROCESS_HANDLE_CLASS, "current"); Long pid = invokeMethod(processHandle, PROCESS_HANDLE_CLASS, "pid"); - logger.trace("The PID was resolved from the method 'java.lang.ProcessHandle#pid()' : {}", pid); + if (logger.isTraceEnabled()) { + logger.trace("The PID was resolved from the method 'java.lang.ProcessHandle#pid()' : {}", pid); + } return pid; } From ef201f98635c3b097da8e1c69d18dc656fce1c8b Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:30:23 +0800 Subject: [PATCH 28/58] Extend ModernProcessIdResolverTest with LoggingTest Import io.microsphere.LoggingTest and make ModernProcessIdResolverTest extend LoggingTest. This enables shared logging setup/utility for the test class so test logging can be captured or configured consistently. --- .../io/microsphere/process/ModernProcessIdResolverTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/process/ModernProcessIdResolverTest.java b/microsphere-java-core/src/test/java/io/microsphere/process/ModernProcessIdResolverTest.java index f98cb7a7b..8a82333e0 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/process/ModernProcessIdResolverTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/process/ModernProcessIdResolverTest.java @@ -1,5 +1,6 @@ package io.microsphere.process; +import io.microsphere.LoggingTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,7 +18,7 @@ * @see ModernProcessIdResolver * @since 1.0.0 */ -class ModernProcessIdResolverTest { +class ModernProcessIdResolverTest extends LoggingTest { private ModernProcessIdResolver resolver; From 62ee6a3197eabec6916943c4bb10a6b68827acf5 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:30:53 +0800 Subject: [PATCH 29/58] Guard trace logging with isTraceEnabled() Wrap the logger.trace(...) call in ProcessExecutor with a logger.isTraceEnabled() check to avoid unnecessary message formatting when trace logging is disabled. The change sits in the finally block after removing unfinished processes and is a small performance optimization. --- .../src/main/java/io/microsphere/process/ProcessExecutor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/process/ProcessExecutor.java b/microsphere-java-core/src/main/java/io/microsphere/process/ProcessExecutor.java index 043b5d155..a21a695d2 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/process/ProcessExecutor.java +++ b/microsphere-java-core/src/main/java/io/microsphere/process/ProcessExecutor.java @@ -175,7 +175,9 @@ public void execute(OutputStream outputStream, long timeout, TimeUnit timeUnit) } } finally { processManager.removeUnfinishedProcess(process, options); - logger.trace("The command['{}'] is executed with exit value : {}", commandLine, exitValue); + if (logger.isTraceEnabled()) { + logger.trace("The command['{}'] is executed with exit value : {}", commandLine, exitValue); + } } return targetOutputStream.toByteArray(); }); From d2bba53243451c3359b98b84d2108398b50ac908 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:31:06 +0800 Subject: [PATCH 30/58] Make ProcessExecutorTest extend LoggingTest Add import for LoggingTest and change ProcessExecutorTest from implementing Loggable to extending LoggingTest (while still implementing Loggable). This lets the test reuse shared logging/test utilities (captures/initialization) when running process-related tests. --- .../test/java/io/microsphere/process/ProcessExecutorTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/process/ProcessExecutorTest.java b/microsphere-java-core/src/test/java/io/microsphere/process/ProcessExecutorTest.java index 0c81927b7..1c9f0ecbb 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/process/ProcessExecutorTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/process/ProcessExecutorTest.java @@ -1,6 +1,7 @@ package io.microsphere.process; import io.microsphere.Loggable; +import io.microsphere.LoggingTest; import io.microsphere.io.FastByteArrayOutputStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -20,7 +21,7 @@ * @see ProcessExecutorTest * @since 1.0.0 */ -class ProcessExecutorTest implements Loggable { +class ProcessExecutorTest extends LoggingTest implements Loggable { private FastByteArrayOutputStream outputStream; From f7d5611ee8eadec9d06e34b106ba50f002c19077 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:31:53 +0800 Subject: [PATCH 31/58] Guard trace logging with isTraceEnabled Wrap the trace logging call in a logger.isTraceEnabled() check to avoid unnecessary logging overhead when trace level is disabled. This change limits work done for the PID resolution log message in VirtualMachineProcessIdResolver. --- .../microsphere/process/VirtualMachineProcessIdResolver.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/process/VirtualMachineProcessIdResolver.java b/microsphere-java-core/src/main/java/io/microsphere/process/VirtualMachineProcessIdResolver.java index a2e48bbba..de4c5bead 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/process/VirtualMachineProcessIdResolver.java +++ b/microsphere-java-core/src/main/java/io/microsphere/process/VirtualMachineProcessIdResolver.java @@ -90,7 +90,9 @@ public Long current() { RuntimeMXBean runtimeMXBean = getRuntimeMXBean(); Object jvm = getFieldValue(runtimeMXBean, JVM_FIELD); Integer processId = invokeMethod(jvm, GET_PROCESS_ID_METHOD_NAME); - logger.trace("The PID was resolved from the native method 'sun.management.VMManagementImpl#getProcessId()' : {}", processId); + if (logger.isTraceEnabled()) { + logger.trace("The PID was resolved from the native method 'sun.management.VMManagementImpl#getProcessId()' : {}", processId); + } return valueOf(processId.longValue()); } From 2c6e3e188a152e18911ba2dd4397ec9fa2a71097 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 13:32:07 +0800 Subject: [PATCH 32/58] Make VM process test extend LoggingTest Add LoggingTest support to VirtualMachineProcessIdResolverTest by importing io.microsphere.LoggingTest and changing the test class to extend LoggingTest. This enables logging capture/behavior provided by the base test class during the resolver tests. --- .../process/VirtualMachineProcessIdResolverTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/process/VirtualMachineProcessIdResolverTest.java b/microsphere-java-core/src/test/java/io/microsphere/process/VirtualMachineProcessIdResolverTest.java index f727f8a1c..c80f8828e 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/process/VirtualMachineProcessIdResolverTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/process/VirtualMachineProcessIdResolverTest.java @@ -1,5 +1,6 @@ package io.microsphere.process; +import io.microsphere.LoggingTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,7 +15,7 @@ * @see VirtualMachineProcessIdResolver * @since 1.0.0 */ -class VirtualMachineProcessIdResolverTest { +class VirtualMachineProcessIdResolverTest extends LoggingTest { private VirtualMachineProcessIdResolver resolver; From 4cd9b50a829696238d778772d2c01fe915de8655 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 21:16:40 +0800 Subject: [PATCH 33/58] Add addIfAbsent to ListUtils Introduce addIfAbsent(List, T) utility method that adds an element only if it's not already present in the list. The method returns true when the element was added and false if the list already contained the value (uses contains() then add()). Provides a convenient way to avoid duplicates for list-based collections. --- .../src/main/java/io/microsphere/collection/ListUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/microsphere-java-core/src/main/java/io/microsphere/collection/ListUtils.java b/microsphere-java-core/src/main/java/io/microsphere/collection/ListUtils.java index 88a357691..5bc2a60fc 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/collection/ListUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/collection/ListUtils.java @@ -668,6 +668,10 @@ public static void forEach(List values, Consumer consumer) { forEach(values, (i, e) -> consumer.accept(e)); } + public static boolean addIfAbsent(List values, T newValue) { + return values.contains(newValue) ? false : values.add(newValue); + } + private ListUtils() { } } From 72b50285a1ece4da8634ec94100391d8d559a621 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 21:18:02 +0800 Subject: [PATCH 34/58] Add test for ListUtils.addIfAbsent Add a static import and a new unit test (testAddIfAbsent) in ListUtilsTest to verify ListUtils.addIfAbsent behavior: it returns true when adding a new element, false when adding a duplicate, and true when adding another distinct element. --- .../java/io/microsphere/collection/ListUtilsTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/microsphere-java-core/src/test/java/io/microsphere/collection/ListUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/collection/ListUtilsTest.java index adb75ead1..a51411614 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/collection/ListUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/collection/ListUtilsTest.java @@ -30,6 +30,7 @@ import static io.microsphere.AbstractTestCase.TEST_NULL_ITERABLE; import static io.microsphere.AbstractTestCase.TEST_NULL_ITERATOR; import static io.microsphere.AbstractTestCase.TEST_NULL_LIST; +import static io.microsphere.collection.ListUtils.addIfAbsent; import static io.microsphere.collection.ListUtils.first; import static io.microsphere.collection.ListUtils.forEach; import static io.microsphere.collection.ListUtils.isList; @@ -204,4 +205,12 @@ void testForEach() { assertEquals(iterator2.next(), value); }); } + + @Test + void testAddIfAbsent() { + List values = newArrayList(); + assertTrue(addIfAbsent(values, "A")); + assertFalse(addIfAbsent(values, "A")); + assertTrue(addIfAbsent(values, "B")); + } } \ No newline at end of file From 28c3398d0e94f3026366e697caa670680a9cd30d Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 21:18:20 +0800 Subject: [PATCH 35/58] Use addIfAbsent and length util in TypeFinder Import ListUtils.addIfAbsent and ArrayUtils.length, replace manual array length checks and explicit contains()-then-add patterns with addIfAbsent for interface and supertype insertion, and use length(...) for array sizing. Also simplify superclass addition and perform minor cleanup (removed trailing newline). Improves readability and centralizes duplicate-check logic. --- .../java/io/microsphere/util/TypeFinder.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/util/TypeFinder.java b/microsphere-java-core/src/main/java/io/microsphere/util/TypeFinder.java index 66c21dcf2..438f8ae20 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/util/TypeFinder.java +++ b/microsphere-java-core/src/main/java/io/microsphere/util/TypeFinder.java @@ -24,6 +24,7 @@ import java.util.function.Function; import java.util.function.Predicate; +import static io.microsphere.collection.ListUtils.addIfAbsent; import static io.microsphere.collection.ListUtils.newArrayList; import static io.microsphere.collection.ListUtils.newLinkedList; import static io.microsphere.lang.function.Predicates.EMPTY_PREDICATE_ARRAY; @@ -34,6 +35,7 @@ import static io.microsphere.util.ArrayUtils.EMPTY_TYPE_ARRAY; import static io.microsphere.util.ArrayUtils.contains; import static io.microsphere.util.ArrayUtils.isNotEmpty; +import static io.microsphere.util.ArrayUtils.length; import static io.microsphere.util.Assert.assertNoNullElements; import static io.microsphere.util.Assert.assertNotEmpty; import static io.microsphere.util.Assert.assertNotNull; @@ -169,7 +171,7 @@ protected List getSuperTypes(T type, boolean includeSuperclass, boolean inclu } T[] interfaceTypes = includedGenericInterfaces ? getInterfaces(type) : (T[]) EMPTY_TYPE_ARRAY; - int interfaceTypesLength = interfaceTypes.length; + int interfaceTypesLength = length(interfaceTypes); int size = interfaceTypesLength + (hasSuperclass ? 1 : 0); @@ -180,16 +182,12 @@ protected List getSuperTypes(T type, boolean includeSuperclass, boolean inclu List types = newArrayList(size); if (hasSuperclass) { - if (!types.contains(superclass)) { - types.add(superclass); - } + types.add(superclass); } for (int i = 0; i < interfaceTypesLength; i++) { T interfaceType = interfaceTypes[i]; - if (!types.contains(interfaceType)) { - types.add(interfaceType); - } + addIfAbsent(types, interfaceType); } return types; } @@ -225,9 +223,8 @@ protected void addSuperTypes(List allTypes, T type, boolean includeHierarchic for (int i = 0; i < superTypesSize; i++) { T superType = superTypes.get(i); - if (!allTypes.contains(superType)) { - allTypes.add(superType); - } + addIfAbsent(allTypes, superType); + if (includeHierarchicalTypes) { addSuperTypes(allTypes, superType, true, includeSuperclass, includeInterfaces); } @@ -273,5 +270,4 @@ public static TypeFinder genericTypeFinder(Type type, boolean includeSelf, return new TypeFinder(type, genericTypeGetSuperClassFunction, genericTypeGetInterfacesFunction, includeSelf, includeHierarchicalTypes, includeSuperclass, includeInterfaces); } - -} +} \ No newline at end of file From cf531eb60163bf8e32e7f5c6f70b5beb6454761a Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 21:18:34 +0800 Subject: [PATCH 36/58] Add tests for TypeFinder.getSuperTypes Add unit tests verifying TypeFinder.getSuperTypes behavior: testGetSuperTypes checks resolution of String's supertypes (Object, Serializable, Comparable) using classGetSuperClassFunction; testGetSuperTypesWithNullType verifies that passing null returns an emptyList for various include flags. Also add required imports and static imports (Serializable, ofList, ofArray, classGetSuperClassFunction) to support the new tests. --- .../io/microsphere/util/TypeFinderTest.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/TypeFinderTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/TypeFinderTest.java index fa0ed880d..84390936f 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/TypeFinderTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/TypeFinderTest.java @@ -8,6 +8,7 @@ import io.microsphere.util.TypeFinder.Include; import org.junit.jupiter.api.Test; +import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -15,12 +16,15 @@ import java.util.function.BiFunction; import static io.microsphere.AbstractTestCase.assertValues; +import static io.microsphere.collection.Lists.ofList; import static io.microsphere.reflect.TypeUtils.NON_OBJECT_TYPE_FILTER; +import static io.microsphere.util.ArrayUtils.ofArray; import static io.microsphere.util.TypeFinder.Include.HIERARCHICAL; import static io.microsphere.util.TypeFinder.Include.INTERFACES; import static io.microsphere.util.TypeFinder.Include.SUPER_CLASS; import static io.microsphere.util.TypeFinder.Include.values; import static io.microsphere.util.TypeFinder.classFinder; +import static io.microsphere.util.TypeFinder.classGetSuperClassFunction; import static io.microsphere.util.TypeFinder.genericTypeFinder; import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -33,7 +37,6 @@ * {@link TypeFinder} Test * * @author Mercy - * @see io.microsphere.util.ClassFinder * @since 1.0.0 */ class TypeFinderTest { @@ -187,6 +190,30 @@ void testFindAllGenericClasses() { assertGenericInterfaces(types); } + @Test + void testGetSuperTypes() { + TypeFinder> typeFinder = new TypeFinder(String.class, classGetSuperClassFunction, + t -> ofArray(Serializable.class, Serializable.class, Comparable.class), true, true, true, true); + List> superTypes = typeFinder.getSuperTypes(String.class, true, true); + assertEquals(ofList(Object.class, Serializable.class, Comparable.class), superTypes); + } + + @Test + void testGetSuperTypesWithNullType() { + TypeFinder typeFinder = genericTypeFinder(StringIntegerToBooleanClass.class, values()); + List superTypes = typeFinder.getSuperTypes(null, true, true); + assertSame(emptyList(), superTypes); + + superTypes = typeFinder.getSuperTypes(null, false, true); + assertSame(emptyList(), superTypes); + + superTypes = typeFinder.getSuperTypes(null, false, false); + assertSame(emptyList(), superTypes); + + superTypes = typeFinder.getSuperTypes(null, true, false); + assertSame(emptyList(), superTypes); + } + private void assertGenericInterfaces(List types) { assertEquals(StringIntegerToBoolean.class, types.get(types.size() - 5)); assertStringIntegerF1(types.get(types.size() - 4)); From db49d6b4968e2f07ff1d8db2e999e346d1a3d439 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Thu, 26 Feb 2026 21:39:08 +0800 Subject: [PATCH 37/58] Add tests for generic type helpers Add unit tests for TypeFinder utilities: verify genericTypeGetSuperClassFunction and genericTypeGetInterfacesFunction return null for null input, and test addSuperTypes behavior (no additions when flags are false, and additions when interfaces/superclasses are enabled). Also add necessary static imports and assertions used by the new tests. --- .../io/microsphere/util/TypeFinderTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/TypeFinderTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/TypeFinderTest.java index 84390936f..5f461ce59 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/TypeFinderTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/TypeFinderTest.java @@ -16,6 +16,7 @@ import java.util.function.BiFunction; import static io.microsphere.AbstractTestCase.assertValues; +import static io.microsphere.collection.ListUtils.newArrayList; import static io.microsphere.collection.Lists.ofList; import static io.microsphere.reflect.TypeUtils.NON_OBJECT_TYPE_FILTER; import static io.microsphere.util.ArrayUtils.ofArray; @@ -26,9 +27,13 @@ import static io.microsphere.util.TypeFinder.classFinder; import static io.microsphere.util.TypeFinder.classGetSuperClassFunction; import static io.microsphere.util.TypeFinder.genericTypeFinder; +import static io.microsphere.util.TypeFinder.genericTypeGetInterfacesFunction; +import static io.microsphere.util.TypeFinder.genericTypeGetSuperClassFunction; import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -214,6 +219,27 @@ void testGetSuperTypesWithNullType() { assertSame(emptyList(), superTypes); } + @Test + void testGenericTypeGetSuperClassFunctionWithNull() { + assertNull(genericTypeGetSuperClassFunction.apply(null)); + } + + @Test + void testGenericTypeGetInterfacesFunction() { + assertNull(genericTypeGetInterfacesFunction.apply(null)); + } + + @Test + void testAddSuperTypes() { + TypeFinder typeFinder = genericTypeFinder(String.class, values()); + List types = newArrayList(); + typeFinder.addSuperTypes(types, String.class, false, false, false); + assertTrue(types.isEmpty()); + + typeFinder.addSuperTypes(types, String.class, false, true, true); + assertFalse(types.isEmpty()); + } + private void assertGenericInterfaces(List types) { assertEquals(StringIntegerToBoolean.class, types.get(types.size() - 5)); assertStringIntegerF1(types.get(types.size() - 4)); From 6d572b247a38ba4bd02242c4b8dc28fad710096f Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Fri, 27 Feb 2026 11:06:42 +0800 Subject: [PATCH 38/58] Update FieldUtils.java --- .../io/microsphere/reflect/FieldUtils.java | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/reflect/FieldUtils.java b/microsphere-java-core/src/main/java/io/microsphere/reflect/FieldUtils.java index 3e5bcafce..da327d5b2 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/reflect/FieldUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/reflect/FieldUtils.java @@ -509,8 +509,6 @@ public static V getFieldValue(Object instance, String fieldName, Class fi * @param instance The object instance from which to retrieve the field value * @param field The {@link Field} object representing the field to retrieve * @return The value of the field if found and accessible; otherwise, {@code null} - * @throws IllegalStateException if this Field object is enforcing Java language access control and the underlying - * field is inaccessible * @throws IllegalArgumentException if the specified object is not an instance of the class or interface declaring * the underlying field (or a subclass or implementor thereof) */ @@ -525,10 +523,9 @@ public static V getFieldValue(Object instance, Field field) throws IllegalSt try { accessible = trySetAccessible(field); fieldValue = (V) field.get(instance); - } catch (IllegalAccessException e) { - handleIllegalAccessException(e, instance, field, accessible); - } catch (IllegalArgumentException e) { - handleIllegalArgumentException(e, instance, field); + } catch (IllegalAccessException | IllegalArgumentException e) { + handleFieldException(e, instance, field); + throw new IllegalArgumentException(e); } return fieldValue; @@ -612,7 +609,6 @@ public static V setFieldValue(Object instance, String fieldName, V value) th * @param field The {@link Field} object representing the field to modify * @param value The new value to assign to the field * @return The previous value of the field before modification, or {@code null} if the field was not found or inaccessible - * @throws IllegalStateException If this Field object is enforcing Java language access control and the underlying field is inaccessible * @throws IllegalArgumentException If the specified object is not an instance of the class or interface declaring the underlying field */ @Nullable @@ -622,17 +618,15 @@ public static V setFieldValue(Object instance, Field field, V value) throws } V previousValue = null; - boolean accessible = false; try { - accessible = trySetAccessible(field); + trySetAccessible(field); previousValue = (V) field.get(instance); if (!Objects.equals(previousValue, value)) { field.set(instance, value); } - } catch (IllegalAccessException e) { - handleIllegalAccessException(e, instance, field, accessible); - } catch (IllegalArgumentException e) { - handleIllegalArgumentException(e, instance, field); + } catch (IllegalAccessException | IllegalArgumentException e) { + handleFieldException(e, instance, field); + throw new IllegalArgumentException(e); } return previousValue; @@ -681,22 +675,11 @@ public static void assertFieldMatchType(Object instance, String fieldName, Class } } - static void handleIllegalAccessException(IllegalAccessException e, Object instance, Field field, boolean accessible) { - String errorMessage = format("The instance [object : {} , class : {} ] can't access the field[name : '{}' , type : {} , accessible : {}]", - instance, getTypeName(instance.getClass()), field.getName(), getTypeName(field.getType()), accessible); - if (logger.isTraceEnabled()) { - logger.trace(errorMessage); - } - throw new IllegalStateException(errorMessage, e); - } - - static void handleIllegalArgumentException(IllegalArgumentException e, Object instance, Field field) { - String errorMessage = format("The instance[object : {} , class : {}] can't match the field[name : '{}' , type : {}]", - instance, getTypeName(instance.getClass()), field.getName(), getTypeName(field.getType())); + static void handleFieldException(Exception e, Object instance, Field field) { if (logger.isTraceEnabled()) { - logger.trace(errorMessage); + logger.trace("The instance[object : {} , class : {}] can't match the field[name : '{}' , type : {}]", + instance, getTypeName(instance.getClass()), field.getName(), getTypeName(field.getType()), e); } - throw new IllegalArgumentException(errorMessage, e); } private FieldUtils() { From 42f38c37a4c26a1a4655ad3fbd670ce6e8bda8f4 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Fri, 27 Feb 2026 11:06:57 +0800 Subject: [PATCH 39/58] Use handleFieldException in FieldUtilsTest Update FieldUtilsTest to use the new FieldUtils.handleFieldException API. Replaced the static import of handleIllegalAccessException with handleFieldException, added assertDoesNotThrow import, renamed the test to testHandleFieldException, and adjusted the invocation to match the new method signature (removed the accessibility argument). This reflects an API change in FieldUtils. --- .../test/java/io/microsphere/reflect/FieldUtilsTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/reflect/FieldUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/reflect/FieldUtilsTest.java index 3b507864a..4dbc8c77a 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/reflect/FieldUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/reflect/FieldUtilsTest.java @@ -30,12 +30,13 @@ import static io.microsphere.reflect.FieldUtils.getDeclaredField; import static io.microsphere.reflect.FieldUtils.getFieldValue; import static io.microsphere.reflect.FieldUtils.getStaticFieldValue; -import static io.microsphere.reflect.FieldUtils.handleIllegalAccessException; +import static io.microsphere.reflect.FieldUtils.handleFieldException; import static io.microsphere.reflect.FieldUtils.setFieldValue; import static io.microsphere.reflect.FieldUtils.setStaticFieldValue; import static io.microsphere.util.VersionUtils.CURRENT_JAVA_VERSION; import static io.microsphere.util.VersionUtils.JAVA_VERSION_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -263,9 +264,9 @@ void testAssertFieldMatchTypeOnIllegalArgumentException() { } @Test - void testHandleIllegalAccessException() { + void testHandleFieldException() { Field field = findField(ReflectionTest.class, "staticField"); - assertThrows(IllegalStateException.class, () -> handleIllegalAccessException(new IllegalAccessException(), test, field, field.isAccessible())); + assertDoesNotThrow(() -> handleFieldException(new IllegalAccessException(), test, field)); } private void assertFindField(Object object, String fieldName) { From 9507cf4fe5ee20c1f1e239aa0501d94738ca933f Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Fri, 27 Feb 2026 11:19:04 +0800 Subject: [PATCH 40/58] Test JSONArray(JSONTokener) constructor Add a unit test for the JSONArray(JSONTokener) constructor in JSONArrayTest. Adds a static import for assertDoesNotThrow and a test method that asserts a JSONTokener with '[1,2,3]' does not throw, while a JSONTokener with '{}' results in a JSONException. --- .../src/test/java/io/microsphere/json/JSONArrayTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/microsphere-java-core/src/test/java/io/microsphere/json/JSONArrayTest.java b/microsphere-java-core/src/test/java/io/microsphere/json/JSONArrayTest.java index 8b5a6ab8f..4947aa4b4 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/json/JSONArrayTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/json/JSONArrayTest.java @@ -30,6 +30,7 @@ import static io.microsphere.util.ArrayUtils.ofArray; import static java.lang.Boolean.TRUE; import static java.lang.Double.NaN; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; @@ -117,6 +118,12 @@ void testConstructorWithArrayOnNotArray() { assertThrows(JSONException.class, () -> new JSONArray(new Object())); } + @Test + void testConstructorWithJSONTokener() { + assertDoesNotThrow(() -> new JSONArray(new JSONTokener("[1,2,3]"))); + assertThrows(JSONException.class, () -> new JSONArray(new JSONTokener("{}"))); + } + @Test void testLength() { assertEquals(0, jsonArray.length()); From 1b42d0049c4d5f53ac4b63cb958bf6945364aa87 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Fri, 27 Feb 2026 11:38:27 +0800 Subject: [PATCH 41/58] Remove redundant 'this.' in recursive scan call In SimpleFileScanner, replace `this.scan(subFile, recursive, ioFileFilter)` with `scan(subFile, recursive, ioFileFilter)` to remove an unnecessary qualifier. This is a minor stylistic cleanup with no functional change. --- .../main/java/io/microsphere/io/scanner/SimpleFileScanner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/io/scanner/SimpleFileScanner.java b/microsphere-java-core/src/main/java/io/microsphere/io/scanner/SimpleFileScanner.java index 6356cc99a..48adfa663 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/io/scanner/SimpleFileScanner.java +++ b/microsphere-java-core/src/main/java/io/microsphere/io/scanner/SimpleFileScanner.java @@ -100,7 +100,7 @@ public Set scan(File rootDirectory, boolean recursive, IOFileFilter ioFile filesSet.add(subFile); } if (recursive && subFile.isDirectory()) { - filesSet.addAll(this.scan(subFile, recursive, ioFileFilter)); + filesSet.addAll(scan(subFile, recursive, ioFileFilter)); } } } From d6ee1e6236c555f4692f51f611c48282462fd4d3 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Fri, 27 Feb 2026 11:38:33 +0800 Subject: [PATCH 42/58] Add tests for scanning empty directory and file Add two unit tests to SimpleFileScannerTest: one verifies scanning an empty temporary directory returns that directory, and the other verifies scanning a temporary file returns that file. Also add imports for helper methods (createRandomTempDirectory/createRandomTempFile), IOException, and assertTrue. --- .../io/scanner/SimpleFileScannerTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/microsphere-java-core/src/test/java/io/microsphere/io/scanner/SimpleFileScannerTest.java b/microsphere-java-core/src/test/java/io/microsphere/io/scanner/SimpleFileScannerTest.java index e8209df7e..9c84641a2 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/io/scanner/SimpleFileScannerTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/io/scanner/SimpleFileScannerTest.java @@ -8,12 +8,16 @@ import org.junit.jupiter.api.Test; import java.io.File; +import java.io.IOException; import java.util.Set; +import static io.microsphere.AbstractTestCase.createRandomTempDirectory; +import static io.microsphere.AbstractTestCase.createRandomTempFile; import static io.microsphere.io.scanner.SimpleFileScanner.INSTANCE; import static io.microsphere.util.SystemUtils.JAVA_HOME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * {@link SimpleFileScanner} {@link Test} @@ -48,4 +52,20 @@ void testScanOnBinDirectory() { Set directories = simpleFileScanner.scan(JAVA_HOME_DIR, true, new NameFileFilter("bin")); assertEquals(1, directories.size()); } + + @Test + void testScanOnEmptyDirectory() { + File tempDir = createRandomTempDirectory(); + Set directories = simpleFileScanner.scan(tempDir, true); + assertEquals(1, directories.size()); + assertTrue(directories.contains(tempDir)); + } + + @Test + void testScanOnFile() throws IOException { + File file = createRandomTempFile(); + Set files = simpleFileScanner.scan(file, true); + assertEquals(1, files.size()); + assertTrue(files.contains(file)); + } } \ No newline at end of file From 55f94b1ab779e0334edab7e279013d034bc1ccf5 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 11:58:29 +0800 Subject: [PATCH 43/58] Add StackWalker & MethodHandle caller lookup Introduce JDK9+ StackWalker support and MethodHandle usage for sun.reflect.Reflection#getCallerClass. This change: - Adds optional STACK_WALKER and STACK_WALKER_STACK_FRAME class resolution and initializes a StackWalker instance for efficient frame inspection. - Replaces direct reflective invocation of sun.reflect.Reflection#getCallerClass with a MethodHandle (findPublicStatic) and prefers it when available. - Adds new helper paths to obtain caller class names: prefer sun.reflect Reflection, then StackWalker, then fall back to stack trace. - Refactors and consolidates multiple getCallerClass/getCallerClassName implementations and related invocation-frame calculations. - Updates imports to use MethodHandle, Streams, and MethodUtils helpers and removes some older reflection usages. These changes improve compatibility with newer JDKs (noisy reflective access / InaccessibleObjectException) and provide a more robust, layered strategy for determining caller classes. --- .../microsphere/reflect/ReflectionUtils.java | 270 ++++++++++++------ 1 file changed, 183 insertions(+), 87 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java b/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java index f5bf4c3ca..19b124b20 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java @@ -7,6 +7,7 @@ import io.microsphere.logging.Logger; import io.microsphere.util.Utils; +import java.lang.invoke.MethodHandle; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -14,17 +15,24 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static io.microsphere.collection.MapUtils.newLinkedHashMap; +import static io.microsphere.invoke.MethodHandlesLookupUtils.findPublicStatic; import static io.microsphere.logging.LoggerFactory.getLogger; import static io.microsphere.reflect.FieldUtils.getFieldValue; import static io.microsphere.reflect.MemberUtils.isStatic; +import static io.microsphere.reflect.MethodUtils.findMethod; +import static io.microsphere.reflect.MethodUtils.invokeMethod; +import static io.microsphere.reflect.MethodUtils.invokeStaticMethod; import static io.microsphere.reflect.TypeUtils.getTypeName; import static io.microsphere.util.ClassLoaderUtils.resolveClass; import static io.microsphere.util.ClassUtils.getType; import static io.microsphere.util.ClassUtils.isPrimitive; import static io.microsphere.util.ClassUtils.isSimpleType; -import static java.lang.Class.forName; +import static io.microsphere.util.StackTraceUtils.getStackTrace; import static java.lang.Thread.currentThread; import static java.lang.reflect.Array.get; import static java.lang.reflect.Array.getLength; @@ -59,11 +67,23 @@ public abstract class ReflectionUtils implements Utils { */ public static final String SUN_REFLECT_REFLECTION_CLASS_NAME = "sun.reflect.Reflection"; + /** + * The {@link Class} of sun.reflect.Reflection + */ + @Nullable + public static final Class SUN_REFLECT_REFLECTION_CLASS = resolveClass(SUN_REFLECT_REFLECTION_CLASS_NAME); + /** * sun.reflect.Reflection method name */ private static final String getCallerClassMethodName = "getCallerClass"; + /** + * The {@link MethodHandle} of Reflection#getCallerClass(int) + */ + @Nullable + private static final MethodHandle getCallerClassMethodHandle = findPublicStatic(SUN_REFLECT_REFLECTION_CLASS, getCallerClassMethodName, int.class); + /** * sun.reflect.Reflection invocation frame */ @@ -77,12 +97,60 @@ public abstract class ReflectionUtils implements Utils { /** * Is Supported sun.reflect.Reflection ? */ - private static final boolean supportedSunReflectReflection; + private static final boolean supportedSunReflectReflection = getCallerClassMethodHandle != null; + + /** + * The class name of {@linkplain java.lang.StackWalker} that was introduced in JDK 9. + */ + public static final String STACK_WALKER_CLASS_NAME = "java.lang.StackWalker"; + + /** + * The class name of {@linkplain java.lang.StackWalker.StackFrame} that was introduced in JDK 9. + */ + public static final String STACK_WALKER_STACK_FRAME_CLASS_NAME = "java.lang.StackWalker$StackFrame"; + + /** + * The {@link Class} of {@linkplain java.lang.StackWalker} that was introduced in JDK 9. + * (optional) + */ + @Nullable + public static final Class STACK_WALKER_CLASS = resolveClass(STACK_WALKER_CLASS_NAME); + + /** + * The {@link Class} of {@linkplain java.lang.StackWalker.StackFrame} that was introduced in JDK 9. + * (optional) + */ + @Nullable + public static final Class STACK_WALKER_STACK_FRAME_CLASS = resolveClass(STACK_WALKER_STACK_FRAME_CLASS_NAME); + + /** + * The {@link Method method} name of {@linkplain java.lang.StackWalker#getInstance()} + */ + static final String GET_INSTANCE_METHOD_NAME = "getInstance"; /** - * sun.reflect.Reflection#getCallerClass(int) method + * The {@link Method method} name of {{@linkplain java.lang.StackWalker#walk(java.util.function.Function)} */ - private static final Method getCallerClassMethod; + static final String WALK_METHOD_NAME = "walk"; + + /** + * The {@link Method method} name of {@linkplain java.lang.StackWalker.StackFrame#getClassName()} + */ + static final String GET_CLASS_NAME_METHOD_NAME = "getClassName"; + + static final Method WALK_METHOD = findMethod(STACK_WALKER_CLASS, WALK_METHOD_NAME, Function.class); + + static final Method GET_CLASS_NAME_METHOD = findMethod(STACK_WALKER_STACK_FRAME_CLASS, GET_CLASS_NAME_METHOD_NAME); + + @Nullable + private static Object stackWalkerInstance; + + /** + * {@linkplain java.lang.StackWalker} invocation frame. + */ + private static final int stackWalkerInvocationFrame; + + private static final Function, Object> getClassNamesFunction = ReflectionUtils::getCallerClassNamesInStackWalker; /** * The class name of {@linkplain java.lang.reflect.InaccessibleObjectException} since JDK 9 @@ -96,40 +164,44 @@ public abstract class ReflectionUtils implements Utils { @Nullable public static final Class INACCESSIBLE_OBJECT_EXCEPTION_CLASS = (Class) resolveClass(INACCESSIBLE_OBJECT_EXCEPTION_CLASS_NAME); + // Initialize java.lang.StackWalker + static { + int invocationFrame = 0; + if (STACK_WALKER_CLASS != null) { + stackWalkerInstance = invokeStaticMethod(STACK_WALKER_CLASS, GET_INSTANCE_METHOD_NAME); + List stackFrameClassNames = getCallerClassNamesInStackWalker(); + for (String stackFrameClassName : stackFrameClassNames) { + if (TYPE.getName().equals(stackFrameClassName)) { + break; + } + invocationFrame++; + } + } + stackWalkerInvocationFrame = invocationFrame + 2; + } + // Initialize sun.reflect.Reflection static { - Method method = null; - boolean supported = false; int invocationFrame = 0; - try { - // Use sun.reflect.Reflection to calculate frame - Class type = forName(SUN_REFLECT_REFLECTION_CLASS_NAME); - method = type.getMethod(getCallerClassMethodName, int.class); - method.setAccessible(true); - // Adapt SUN JDK ,The value of invocation frame in JDK 6/7/8 may be different + if (supportedSunReflectReflection) { + // Adapt SUN JDK ,The value of invocation frame in JDK 7/8 may be different for (int i = 0; i < 9; i++) { - Class callerClass = (Class) method.invoke(null, i); + Class callerClass = getCallerClassInSunReflectReflection(i); if (TYPE.equals(callerClass)) { invocationFrame = i; break; } } - supported = true; - } catch (Throwable e) { - if (logger.isTraceEnabled()) { - logger.trace("The class '{}' or its' method '{}({})' can't be initialized.", SUN_REFLECT_REFLECTION_CLASS_NAME, getCallerClassMethodName, int.class, e); - } } + // set method info - getCallerClassMethod = method; - supportedSunReflectReflection = supported; // getCallerClass() -> getCallerClass(int) // Plugs 1 , because Invocation getCallerClass() method was considered as increment invocation frame // Plugs 1 , because Invocation getCallerClass(int) method was considered as increment invocation frame sunReflectReflectionInvocationFrame = invocationFrame + 2; } - // Initialize StackTraceElement + // Initialize java.lang.StackTraceElement static { int invocationFrame = 0; // Use java.lang.StackTraceElement to calculate frame @@ -141,10 +213,7 @@ public abstract class ReflectionUtils implements Utils { } invocationFrame++; } - // getCallerClass() -> getCallerClass(int) - // Plugs 1 , because Invocation getCallerClass() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClass(int) method was considered as increment invocation frame - stackTraceElementInvocationFrame = invocationFrame + 2; + stackTraceElementInvocationFrame = invocationFrame; } /** @@ -198,21 +267,56 @@ public static boolean isSupportedSunReflectReflection() { */ @Nonnull public static String getCallerClassName() { - if (supportedSunReflectReflection) { - Class callerClass = getCallerClassInSunJVM(sunReflectReflectionInvocationFrame); - if (callerClass != null) return callerClass.getName(); + Class callerClass = getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrame); + return callerClass == null ? getCallerClassName(stackWalkerInstance, 1) : callerClass.getName(); + } + + @Nullable + static String getCallerClassName(Object stackWalkerInstance, int frameOffSet) { + if (stackWalkerInstance == null) { + // Plugs 1 , because Invocation getStackTrace() method was considered as increment invocation frame + // Plugs 1 , because Invocation getCallerClassName() method was considered as increment invocation frame + // Plugs 1 , because Invocation getCallerClassNameInStackTrace(int) method was considered as increment invocation frame + return getCallerClassNameInStackTrace(stackTraceElementInvocationFrame + 3 + frameOffSet); } - return getCallerClassNameInGeneralJVM(stackTraceElementInvocationFrame); + List callerClassNames = getCallerClassNamesInStackWalker(stackWalkerInstance); + int frame = stackWalkerInvocationFrame + frameOffSet; + if (frame < callerClassNames.size()) { + return callerClassNames.get(frame); + } + return null; + } + + @Nonnull + static List getCallerClassNamesInStackWalker(@Nonnull Object stackWalkerInstance) { + return invokeMethod(stackWalkerInstance, WALK_METHOD, getClassNamesFunction); + } + + static List getCallerClassNamesInStackWalker() { + return invokeMethod(stackWalkerInstance, WALK_METHOD, getClassNamesFunction); + } + + private static List getCallerClassNamesInStackWalker(Stream stackFrames) { + return stackFrames.limit(5) + .map(ReflectionUtils::getClassName) + .collect(Collectors.toList()); + } + + private static String getClassName(Object stackFrame) { + return invokeMethod(stackFrame, GET_CLASS_NAME_METHOD); } /** * General implementation, get the calling class name * * @return call class name - * @see #getCallerClassNameInGeneralJVM(int) + * @see #getCallerClassNameInStackTrace(int) */ - static String getCallerClassNameInGeneralJVM() { - return getCallerClassNameInGeneralJVM(stackTraceElementInvocationFrame); + static String getCallerClassNameInStackTrace() { + // Plugs 1 , because Invocation getStackTrace() method was considered as increment invocation frame + // Plugs 1 , because Invocation getCallerClassNameInStackTrace() method was considered as increment invocation frame + // Plugs 1 , because Invocation getCallerClassNameInStackTrace(int) method was considered as increment invocation frame + return getCallerClassNameInStackTrace(stackTraceElementInvocationFrame + 3); } /** @@ -221,8 +325,8 @@ static String getCallerClassNameInGeneralJVM() { * @param invocationFrame invocation frame * @return specified invocation frame class */ - static String getCallerClassNameInGeneralJVM(int invocationFrame) throws IndexOutOfBoundsException { - StackTraceElement[] elements = currentThread().getStackTrace(); + static String getCallerClassNameInStackTrace(int invocationFrame) throws IndexOutOfBoundsException { + StackTraceElement[] elements = getStackTrace(); if (invocationFrame < elements.length) { StackTraceElement targetStackTraceElement = elements[invocationFrame]; return targetStackTraceElement.getClassName(); @@ -230,37 +334,6 @@ static String getCallerClassNameInGeneralJVM(int invocationFrame) throws IndexOu return null; } - static Class getCallerClassInSunJVM(int realFramesToSkip) throws UnsupportedOperationException { - if (!supportedSunReflectReflection) { - throw new UnsupportedOperationException("Requires SUN's JVM!"); - } - Class callerClass = null; - if (getCallerClassMethod != null) { - try { - callerClass = (Class) getCallerClassMethod.invoke(null, realFramesToSkip); - } catch (Exception ignored) { - } - } - return callerClass; - } - - /** - * Get caller class in General JVM - * - * @param invocationFrame invocation frame - * @return caller class - * @see #getCallerClassNameInGeneralJVM(int) - */ - static Class getCallerClassInGeneralJVM(int invocationFrame) { - String className = getCallerClassNameInGeneralJVM(invocationFrame + 1); - Class targetClass = null; - try { - targetClass = className == null ? null : forName(className); - } catch (Throwable ignored) { - } - return targetClass; - } - /** * Gets the {@link Class} of the method caller. * @@ -284,24 +357,46 @@ static Class getCallerClassInGeneralJVM(int invocationFrame) { */ @Nonnull public static Class getCallerClass() throws IllegalStateException { + Class callerClass = getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrame); + if (callerClass != null) { + return callerClass; + } + String className = getCallerClassName(stackWalkerInstance, 1); + return resolveClass(className); + } + + @Nullable + static Class getCallerClassInSunReflectReflection(int realFramesToSkip) { if (supportedSunReflectReflection) { - Class callerClass = getCallerClassInSunJVM(sunReflectReflectionInvocationFrame); - if (callerClass != null) { - return callerClass; + try { + return (Class) getCallerClassMethodHandle.invokeExact(realFramesToSkip); + } catch (Throwable ignored) { } } - return getCallerClassInGeneralJVM(stackTraceElementInvocationFrame); + return null; + } + + /** + * Get caller class in General JVM + * + * @param invocationFrame invocation frame + * @return caller class + * @see #getCallerClassNameInStackTrace(int) + */ + static Class getCallerClassInStatckTrace(int invocationFrame) { + String className = getCallerClassNameInStackTrace(invocationFrame + 1); + return className == null ? null : resolveClass(className); } /** * Get caller class In SUN HotSpot JVM * * @return Caller Class - * @throws UnsupportedOperationException If JRE is not a SUN HotSpot JVM - * @see #getCallerClassInSunJVM(int) + * @see #getCallerClassInSunReflectReflection(int) */ - static Class getCallerClassInSunJVM() throws UnsupportedOperationException { - return getCallerClassInSunJVM(sunReflectReflectionInvocationFrame); + @Nullable + static Class getCallerClassInSunReflectReflection() throws UnsupportedOperationException { + return getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrame); } /** @@ -309,11 +404,12 @@ static Class getCallerClassInSunJVM() throws UnsupportedOperationException { * * @return Caller Class * @throws UnsupportedOperationException If JRE is not a SUN HotSpot JVM - * @see #getCallerClassInSunJVM(int) + * @see #getCallerClassInSunReflectReflection(int) */ - static String getCallerClassNameInSunJVM() throws UnsupportedOperationException { - Class callerClass = getCallerClassInSunJVM(sunReflectReflectionInvocationFrame); - return callerClass.getName(); + @Nullable + static String getCallerClassNameInSunReflectReflection() throws UnsupportedOperationException { + Class callerClass = getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrame); + return callerClass == null ? null : callerClass.getName(); } /** @@ -340,24 +436,24 @@ static String getCallerClassNameInSunJVM() throws UnsupportedOperationException * @return The class of the caller at the specified invocation frame. * @throws IllegalStateException if an error occurs while determining the caller class. */ + @Nullable public static Class getCallerClass(int invocationFrame) { - if (supportedSunReflectReflection) { - Class callerClass = getCallerClassInSunJVM(invocationFrame + 1); - if (callerClass != null) { - return callerClass; - } + Class callerClass = getCallerClassInSunReflectReflection(invocationFrame + 1); + if (callerClass != null) { + return callerClass; } - return getCallerClassInGeneralJVM(invocationFrame + 1); + String className = getCallerClassName(stackWalkerInstance, invocationFrame + 1); + return resolveClass(className); } /** - * Get caller class in General JVM + * Get caller class from {@link Thread#getStackTrace() stack traces} * * @return Caller Class - * @see #getCallerClassInGeneralJVM(int) + * @see #getCallerClassInStatckTrace(int) */ - static Class getCallerClassInGeneralJVM() { - return getCallerClassInGeneralJVM(stackTraceElementInvocationFrame); + static Class getCallerClassInStatckTrace() { + return getCallerClassInStatckTrace(stackTraceElementInvocationFrame + 3); } /** From f4b3788ef956b297ad0fc647db6244b58950a197 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 11:58:38 +0800 Subject: [PATCH 44/58] Enhance ReflectionUtilsTest for StackWalker Refactor and expand ReflectionUtilsTest to exercise new caller-resolution strategies (StackTrace/StackWalker and sun.reflect reflection). Introduces CALLER_CLASS/CALLER_CLASS_NAME constants, replaces older GeneralJVM/SunJVM assertions with StackTrace/StackWalker variants, and adds tests for frame bounds, StackWalker behavior across Java versions (using VersionUtils), and expected exceptions on unsupported JVMs. Also adjusts imports and uses assertThrows where appropriate. --- .../reflect/ReflectionUtilsTest.java | 68 ++++++++++++++----- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java index 3b3d1f8dd..a82e07686 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java @@ -11,21 +11,25 @@ import static io.microsphere.reflect.ReflectionUtils.INACCESSIBLE_OBJECT_EXCEPTION_CLASS; import static io.microsphere.reflect.ReflectionUtils.INACCESSIBLE_OBJECT_EXCEPTION_CLASS_NAME; import static io.microsphere.reflect.ReflectionUtils.getCallerClass; -import static io.microsphere.reflect.ReflectionUtils.getCallerClassInGeneralJVM; -import static io.microsphere.reflect.ReflectionUtils.getCallerClassInSunJVM; +import static io.microsphere.reflect.ReflectionUtils.getCallerClassInStatckTrace; +import static io.microsphere.reflect.ReflectionUtils.getCallerClassInSunReflectReflection; import static io.microsphere.reflect.ReflectionUtils.getCallerClassName; -import static io.microsphere.reflect.ReflectionUtils.getCallerClassNameInGeneralJVM; -import static io.microsphere.reflect.ReflectionUtils.getCallerClassNameInSunJVM; +import static io.microsphere.reflect.ReflectionUtils.getCallerClassNameInStackTrace; +import static io.microsphere.reflect.ReflectionUtils.getCallerClassNameInSunReflectReflection; +import static io.microsphere.reflect.ReflectionUtils.getCallerClassNamesInStackWalker; import static io.microsphere.reflect.ReflectionUtils.isInaccessibleObjectException; import static io.microsphere.reflect.ReflectionUtils.isSupportedSunReflectReflection; import static io.microsphere.reflect.ReflectionUtils.readFieldsAsMap; import static io.microsphere.reflect.ReflectionUtils.toList; import static io.microsphere.reflect.ReflectionUtils.toObject; import static io.microsphere.util.ArrayUtils.ofArray; +import static io.microsphere.util.VersionUtils.JAVA_VERSION_9; +import static io.microsphere.util.VersionUtils.testCurrentJavaVersion; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -37,39 +41,71 @@ */ class ReflectionUtilsTest { + private static final Class CALLER_CLASS = ReflectionUtilsTest.class; + + private static final String CALLER_CLASS_NAME = CALLER_CLASS.getName(); + @Test void testGetCallerClassX() { - Class expectedClass = ReflectionUtilsTest.class; - Class callerClass = getCallerClass(); - assertEquals(expectedClass, callerClass); + assertEquals(CALLER_CLASS, callerClass); + Class callerClassInSunReflectReflection = getCallerClassInSunReflectReflection(); if (isSupportedSunReflectReflection()) { - Class callerClassInSunJVM = getCallerClassInSunJVM(); - assertEquals(callerClassInSunJVM, callerClass); + assertEquals(callerClassInSunReflectReflection, callerClass); + } else { + assertNull(callerClassInSunReflectReflection); } - Class callerClassInGeneralJVM = getCallerClassInGeneralJVM(); - assertEquals(callerClassInGeneralJVM, callerClass); + Class callerClassInStatckTrace = getCallerClassInStatckTrace(); + assertEquals(callerClassInStatckTrace, callerClass); + } + @Test + void testGetCallerClassWithFrame() { + assertNull(getCallerClass(99999)); } @Test void testGetCallerClassName() { - String expectedClassName = ReflectionUtilsTest.class.getName(); - String callerClassName = getCallerClassName(); - assertEquals(expectedClassName, callerClassName); + assertEquals(CALLER_CLASS_NAME, callerClassName); if (isSupportedSunReflectReflection()) { - String callerClassNameInSunJVM = getCallerClassNameInSunJVM(); + String callerClassNameInSunJVM = getCallerClassNameInSunReflectReflection(); assertEquals(callerClassNameInSunJVM, callerClassName); } - String callerClassNameInGeneralJVM = getCallerClassNameInGeneralJVM(); + String callerClassNameInGeneralJVM = getCallerClassNameInStackTrace(); assertEquals(callerClassNameInGeneralJVM, callerClassName); } + @Test + void testGetCallerClassNameOnStackWalkerSupportedForTesting() { + assertEquals(getCallerClassNameInStackTrace(), getCallerClassName(null, 0)); + assertEquals(CALLER_CLASS_NAME, getCallerClassName()); + } + + @Test + void testGetCallerClassNamesInStackWalker() { + if (testCurrentJavaVersion("<", JAVA_VERSION_9)) { + assertThrows(NullPointerException.class, () -> getCallerClassNamesInStackWalker()); + } else { + assertTrue(getCallerClassNamesInStackWalker().contains(CALLER_CLASS_NAME)); + } + } + + @Test + void testGetCallerClassNameInStackTrace() { + String callerClassName = getCallerClassNameInStackTrace(); + assertEquals(CALLER_CLASS_NAME, callerClassName); + } + + @Test + void testGetCallerClassInStatckTraceWithFrame() { + assertNull(getCallerClassInStatckTrace(99999)); + } + @Test void testToList() { int[] intArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}; From b93bf241335faaf79c1959b3c8b49e9b08a60cd3 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 11:58:51 +0800 Subject: [PATCH 45/58] Refactor ReflectionUtils benchmark methods Update ReflectionUtilsBenchmark: add static imports for new helpers, rename benchmark methods to reflect StackWalker/StackTrace strategies, and add a benchmark that invokes the sun.reflect.Reflection-based lookup (commented out). These changes clarify which caller lookup approach is being measured and prepare the test for additional comparisons. --- .../reflect/ReflectionUtilsBenchmark.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsBenchmark.java b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsBenchmark.java index fc2ec7771..a6d12aa7b 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsBenchmark.java +++ b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsBenchmark.java @@ -25,6 +25,8 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; +import static io.microsphere.reflect.ReflectionUtils.getCallerClassInSunReflectReflection; +import static io.microsphere.reflect.ReflectionUtils.getCallerClassNamesInStackWalker; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.openjdk.jmh.annotations.Mode.AverageTime; @@ -44,13 +46,23 @@ @State(Scope.Thread) public class ReflectionUtilsBenchmark { +// @Benchmark +// public void getCallerClassDirectly() { +// sun.reflect.Reflection.getCallerClass(1); +// } + + @Benchmark + public void getCallerClassOnMethodHanle() { + getCallerClassInSunReflectReflection(); + } + @Benchmark - public void getCallerClassNameInSunJVM() { - ReflectionUtils.getCallerClassNameInSunJVM(); + public void getCallerClassNameInStackWalker() { + getCallerClassNamesInStackWalker(); } @Benchmark - public void getCallerClassNameInGeneralJVM() { - ReflectionUtils.getCallerClassNameInGeneralJVM(); + public void getCallerClassNameOnStackTrace() { + ReflectionUtils.getCallerClassNameInStackTrace(); } } From 550f41cb1ea4c417dafb7262d1142cdc8e932b7a Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 12:06:26 +0800 Subject: [PATCH 46/58] Make getCallerClass robust to large frames Call getCallerClassName with a fixed index (1) instead of using invocationFrame + 1 to avoid out-of-range/throwing behavior for large frame values. Update the test to expect no exception when passing an excessively large frame (use assertDoesNotThrow) and add the necessary import. --- .../src/main/java/io/microsphere/reflect/ReflectionUtils.java | 2 +- .../test/java/io/microsphere/reflect/ReflectionUtilsTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java b/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java index 19b124b20..62f70c4ec 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java @@ -442,7 +442,7 @@ public static Class getCallerClass(int invocationFrame) { if (callerClass != null) { return callerClass; } - String className = getCallerClassName(stackWalkerInstance, invocationFrame + 1); + String className = getCallerClassName(stackWalkerInstance, 1); return resolveClass(className); } diff --git a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java index a82e07686..a5285203a 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java @@ -26,6 +26,7 @@ import static io.microsphere.util.VersionUtils.JAVA_VERSION_9; import static io.microsphere.util.VersionUtils.testCurrentJavaVersion; import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; @@ -63,7 +64,7 @@ void testGetCallerClassX() { @Test void testGetCallerClassWithFrame() { - assertNull(getCallerClass(99999)); + assertDoesNotThrow(() -> getCallerClass(99999)); } @Test From fd6b0003f7f197c172f1b56ea9988d68d3053dcc Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 19:58:16 +0800 Subject: [PATCH 47/58] Increase StackWalker depth and adjust caller offset Raise the StackWalker limit from 5 to 9 in getCallerClassNamesInStackWalker to collect more frames. Replace the previous hardcoded caller offset (1) with a computed offset (invocationFrame + 2) when resolving the caller class, ensuring correct caller resolution for deeper or nested invocation stacks. --- .../src/main/java/io/microsphere/reflect/ReflectionUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java b/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java index 62f70c4ec..bdf2c5158 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java @@ -297,7 +297,7 @@ static List getCallerClassNamesInStackWalker() { } private static List getCallerClassNamesInStackWalker(Stream stackFrames) { - return stackFrames.limit(5) + return stackFrames.limit(9) .map(ReflectionUtils::getClassName) .collect(Collectors.toList()); } @@ -442,7 +442,7 @@ public static Class getCallerClass(int invocationFrame) { if (callerClass != null) { return callerClass; } - String className = getCallerClassName(stackWalkerInstance, 1); + String className = getCallerClassName(stackWalkerInstance, invocationFrame + 2); return resolveClass(className); } From 2f7daf2b013f1707dd0b93f34dcc98b21fab5ac8 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 20:00:59 +0800 Subject: [PATCH 48/58] Remove StackWalker logic from StackTraceUtils Remove StackWalker-based caller lookup and related reflection/version utilities from StackTraceUtils. This change deletes imports, constants, static initializers, logger, and methods such as getCallerClassName, getCallerClassNames, getCallerClassNameInGeneralJVM and other StackWalker/StackFrame helpers, leaving only getStackTrace() and the private constructor. The class is simplified to no longer perform stack-walking or reflectively invoke StackWalker APIs. --- .../io/microsphere/util/StackTraceUtils.java | 212 +----------------- 1 file changed, 1 insertion(+), 211 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/util/StackTraceUtils.java b/microsphere-java-core/src/main/java/io/microsphere/util/StackTraceUtils.java index f120b9d96..1f710a65c 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/util/StackTraceUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/util/StackTraceUtils.java @@ -17,22 +17,7 @@ package io.microsphere.util; import io.microsphere.annotation.Nonnull; -import io.microsphere.annotation.Nullable; -import io.microsphere.logging.Logger; -import java.lang.reflect.Method; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static io.microsphere.logging.LoggerFactory.getLogger; -import static io.microsphere.reflect.MethodUtils.findMethod; -import static io.microsphere.reflect.MethodUtils.invokeMethod; -import static io.microsphere.reflect.MethodUtils.invokeStaticMethod; -import static io.microsphere.util.ClassLoaderUtils.resolveClass; -import static io.microsphere.util.VersionUtils.CURRENT_JAVA_VERSION; -import static io.microsphere.util.VersionUtils.JAVA_VERSION_9; import static java.lang.Thread.currentThread; /** @@ -44,124 +29,6 @@ */ public abstract class StackTraceUtils implements Utils { - private static final Class TYPE = StackTraceUtils.class; - - private static final Logger logger = getLogger(TYPE); - - private static final boolean IS_JDK_9_OR_LATER = CURRENT_JAVA_VERSION.ge(JAVA_VERSION_9); - - /** - * The class name of {@linkplain java.lang.StackWalker} that was introduced in JDK 9. - */ - public static final String STACK_WALKER_CLASS_NAME = "java.lang.StackWalker"; - - /** - * The class name of {@linkplain java.lang.StackWalker.Option} that was introduced in JDK 9. - */ - public static final String STACK_WALKER_OPTION_CLASS_NAME = "java.lang.StackWalker$Option"; - - /** - * The class name of {@linkplain java.lang.StackWalker.StackFrame} that was introduced in JDK 9. - */ - public static final String STACK_WALKER_STACK_FRAME_CLASS_NAME = "java.lang.StackWalker$StackFrame"; - - /** - * The {@link Class} of {@linkplain java.lang.StackWalker} that was introduced in JDK 9. - * (optional) - */ - public static final @Nullable Class STACK_WALKER_CLASS = resolveClass(STACK_WALKER_CLASS_NAME); - - /** - * The {@link Class} of {@linkplain java.lang.StackWalker.Option} that was introduced in JDK 9. - * (optional) - */ - public static final @Nullable Class STACK_WALKER_OPTION_CLASS = resolveClass(STACK_WALKER_OPTION_CLASS_NAME); - - /** - * The {@link Class} of {@linkplain java.lang.StackWalker.StackFrame} that was introduced in JDK 9. - * (optional) - */ - public static final @Nullable Class STACK_WALKER_STACK_FRAME_CLASS = resolveClass(STACK_WALKER_STACK_FRAME_CLASS_NAME); - - /** - * The name of {@linkplain java.lang.StackWalker.Option#RETAIN_CLASS_REFERENCE} - */ - static final String RETAIN_CLASS_REFERENCE_OPTION_NAME = "RETAIN_CLASS_REFERENCE"; - - /** - * The name of {@linkplain java.lang.StackWalker.Option#SHOW_REFLECT_FRAMES} - */ - static final String SHOW_REFLECT_FRAMES_OPTION_NAME = "SHOW_REFLECT_FRAMES"; - - /** - * The name of {@linkplain java.lang.StackWalker.Option#SHOW_HIDDEN_FRAMES} - */ - static final String SHOW_HIDDEN_FRAMES_OPTION_NAME = "SHOW_HIDDEN_FRAMES"; - - /** - * The {@link Method method} name of {@linkplain java.lang.StackWalker#getInstance()} - */ - static final String GET_INSTANCE_METHOD_NAME = "getInstance"; - - /** - * The {@link Method method} name of {{@linkplain java.lang.StackWalker#walk(java.util.function.Function)} - */ - static final String WALK_METHOD_NAME = "walk"; - - /** - * The {@link Method method} name of {@linkplain java.lang.StackWalker.StackFrame#getClassName()} - */ - static final String GET_CLASS_NAME_METHOD_NAME = "getClassName"; - - static final Method WALK_METHOD = findMethod(STACK_WALKER_CLASS, WALK_METHOD_NAME, Function.class); - - static final Method GET_CLASS_NAME_METHOD = findMethod(STACK_WALKER_STACK_FRAME_CLASS, GET_CLASS_NAME_METHOD_NAME); - - private static @Nullable Object stackWalkerInstance; - - private static final Function, Object> getClassNamesFunction = StackTraceUtils::getCallerClassNames; - - /** - * {@link StackTraceElement} invocation frame. - */ - private static final int stackTraceElementInvocationFrame; - - /** - * {@linkplain java.lang.StackWalker} invocation frame. - */ - private static final int stackWalkerInvocationFrame; - - // Initialize java.lang.StackWalker - static { - int invocationFrame = 0; - if (IS_JDK_9_OR_LATER) { - stackWalkerInstance = invokeStaticMethod(STACK_WALKER_CLASS, GET_INSTANCE_METHOD_NAME); - List stackFrameClassNames = getCallerClassNames(); - for (String stackFrameClassName : stackFrameClassNames) { - if (TYPE.getName().equals(stackFrameClassName)) { - break; - } - invocationFrame++; - } - } - stackWalkerInvocationFrame = invocationFrame + 2; - } - - // Initialize java.lang.StackTraceElement - static { - int invocationFrame = 0; - // Use java.lang.StackTraceElement to calculate frame - StackTraceElement[] stackTraceElements = getStackTrace(); - for (StackTraceElement stackTraceElement : stackTraceElements) { - String className = stackTraceElement.getClassName(); - if (TYPE.getName().equals(className)) { - break; - } - invocationFrame++; - } - stackTraceElementInvocationFrame = invocationFrame; - } - /** * Get the {@link StackTraceElement} array on the current thread * @@ -188,83 +55,6 @@ public static StackTraceElement[] getStackTrace() { return currentThread().getStackTrace(); } - /** - * Retrieves the fully qualified name of the class that called this method. - * - *

This method utilizes either {@link java.lang.StackWalker} (available in JDK 9+) or falls back to - * using {@link StackTraceElement} to determine the caller's class name. It ensures compatibility across different JVM versions.

- * - *

Example Usage

- *
{@code
-     * String callerClassName = StackTraceUtils.getCallerClassName();
-     * System.out.println("Caller class: " + callerClassName);
-     * }
- * - * @return the fully qualified name of the calling class - * @throws IndexOutOfBoundsException if the stack trace does not have enough frames to determine the caller - */ - public static String getCallerClassName() { - return getCallerClassName(stackWalkerInstance, 1); - } - - static String getCallerClassName(Object stackWalkerInstance, int frameOffSet) { - if (stackWalkerInstance == null) { - // Plugs 1 , because Invocation getStackTrace() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClassName() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClassNameInGeneralJVM(int) method was considered as increment invocation frame - return getCallerClassNameInGeneralJVM(stackTraceElementInvocationFrame + 3 + frameOffSet); - } - List callerClassNames = getCallerClassNames(stackWalkerInstance); - String className = callerClassNames.get(stackWalkerInvocationFrame + frameOffSet); - return className; - } - - static List getCallerClassNames(Object stackWalkerInstance) { - return invokeMethod(stackWalkerInstance, WALK_METHOD, getClassNamesFunction); - } - - static List getCallerClassNames() { - return invokeMethod(stackWalkerInstance, WALK_METHOD, getClassNamesFunction); - } - - private static List getCallerClassNames(Stream stackFrames) { - return stackFrames.limit(5) - .map(StackTraceUtils::getClassName) - .collect(Collectors.toList()); - } - - private static String getClassName(Object stackFrame) { - return invokeMethod(stackFrame, GET_CLASS_NAME_METHOD); - } - - /** - * General implementation, get the calling class name - * - * @return call class name - * @see #getCallerClassNameInGeneralJVM(int) - */ - static String getCallerClassNameInGeneralJVM() { - // Plugs 1 , because Invocation getStackTrace() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClassNameInGeneralJVM() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClassNameInGeneralJVM(int) method was considered as increment invocation frame - return getCallerClassNameInGeneralJVM(stackTraceElementInvocationFrame + 3); - } - - /** - * General implementation, get the calling class name by specifying the calling level value - * - * @param invocationFrame invocation frame - * @return specified invocation frame class - */ - static String getCallerClassNameInGeneralJVM(int invocationFrame) throws IndexOutOfBoundsException { - StackTraceElement[] elements = getStackTrace(); - if (invocationFrame < elements.length) { - StackTraceElement targetStackTraceElement = elements[invocationFrame]; - return targetStackTraceElement.getClassName(); - } - return null; - } - private StackTraceUtils() { } -} +} \ No newline at end of file From afceab24fce68432bf3ddcd435b369354ca67bb4 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 20:01:13 +0800 Subject: [PATCH 49/58] Reduce StackTraceUtilsTest to test getStackTrace Replace multiple caller-name and Java-version-dependent tests with a single simple test that asserts StackTraceUtils.getStackTrace() is not null. Clean up imports accordingly and remove tests for getCallerClassName, getCallerClassNames and VersionUtils checks. --- .../microsphere/util/StackTraceUtilsTest.java | 45 ++----------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceUtilsTest.java index d774da899..ac1844f59 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceUtilsTest.java @@ -2,16 +2,8 @@ import org.junit.jupiter.api.Test; -import static io.microsphere.util.StackTraceUtils.getCallerClassName; -import static io.microsphere.util.StackTraceUtils.getCallerClassNameInGeneralJVM; -import static io.microsphere.util.StackTraceUtils.getCallerClassNames; -import static io.microsphere.util.VersionUtils.JAVA_VERSION_9; -import static io.microsphere.util.VersionUtils.testCurrentJavaVersion; -import static java.lang.Integer.MAX_VALUE; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static io.microsphere.util.StackTraceUtils.getStackTrace; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * {@link StackTraceUtils} Test @@ -22,37 +14,8 @@ */ class StackTraceUtilsTest { - private static final String CALLER_CLASS_NAME = StackTraceUtilsTest.class.getName(); - - @Test - void testGetCallerClassName() { - assertEquals(CALLER_CLASS_NAME, getCallerClassName()); - } - - @Test - void testGetCallerClassNameOnStackWalkerSupportedForTesting() { - assertEquals(getCallerClassNameInGeneralJVM(), getCallerClassName(null, 0)); - assertEquals(CALLER_CLASS_NAME, getCallerClassName()); - } - @Test - void testGetCallerClassNames() { - if (testCurrentJavaVersion("<", JAVA_VERSION_9)) { - assertThrows(NullPointerException.class, () -> getCallerClassNames()); - } else { - assertTrue(getCallerClassNames().contains(CALLER_CLASS_NAME)); - } + void testGetStackTrace() { + assertNotNull(getStackTrace()); } - - @Test - void testGetCallerClassNameInGeneralJVM() { - String callerClassName = getCallerClassNameInGeneralJVM(); - assertEquals(CALLER_CLASS_NAME, callerClassName); - } - - @Test - void testGetCallerClassNameInGeneralJVMOnOverStack() { - assertNull(getCallerClassNameInGeneralJVM(MAX_VALUE)); - } - } \ No newline at end of file From 4cc004ab0aab54a26dd6a1cdefc5839d7ec1c382 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 20:08:24 +0800 Subject: [PATCH 50/58] Remove StackTraceElementUtilsBenchmark test Delete the JMH benchmark class StackTraceElementUtilsBenchmark from microsphere-java-core tests. Removes io/microsphere/util/StackTraceElementUtilsBenchmark.java which contained benchmarks for StackTraceUtils (getCallerClassName and getCallerClassNameInGeneralJVM). --- .../util/StackTraceElementUtilsBenchmark.java | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 microsphere-java-core/src/test/java/io/microsphere/util/StackTraceElementUtilsBenchmark.java diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceElementUtilsBenchmark.java b/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceElementUtilsBenchmark.java deleted file mode 100644 index 306ec684c..000000000 --- a/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceElementUtilsBenchmark.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.microsphere.util; - -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Warmup; - -import static io.microsphere.util.StackTraceUtils.getCallerClassName; -import static io.microsphere.util.StackTraceUtils.getCallerClassNameInGeneralJVM; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.openjdk.jmh.annotations.Mode.AverageTime; - -/** - * {@link StackTraceUtils} Benchmark - * - * @author
Mercy - * @see StackTraceUtils - * @since 1.0.0 - */ -@Warmup(iterations = 5, time = 1, timeUnit = SECONDS) -@Measurement(iterations = 20, time = 1, timeUnit = SECONDS) -@Fork(1) -@BenchmarkMode(AverageTime) -@OutputTimeUnit(NANOSECONDS) -@State(Scope.Thread) -public class StackTraceElementUtilsBenchmark { - - @Benchmark - public void testGetCallerClassNameInGeneralJVM() { - getCallerClassNameInGeneralJVM(); - } - - @Benchmark - public void testGetCallerClassName() { - getCallerClassName(); - } -} From 54bb2ae8072f4d34160dc5510ef7f7f4ecc3527a Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 21:28:00 +0800 Subject: [PATCH 51/58] Add stack-trace based caller lookup utilities Introduce utilities to determine caller classes/names using Thread#getStackTrace. A static invocationFrameOffset is computed at class init by scanning StackTraceElement frames for this utility class, and helper methods were added: getCallerClassInStatckTrace(), getCallerClassNameInStackTrace(), getCallerClassInStatckTrace(int) and getCallerClassNameInStackTrace(int). The implementation resolves class names to Class via resolveClass and returns null when a target frame is unavailable. --- .../io/microsphere/util/StackTraceUtils.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/microsphere-java-core/src/main/java/io/microsphere/util/StackTraceUtils.java b/microsphere-java-core/src/main/java/io/microsphere/util/StackTraceUtils.java index 1f710a65c..4ad4b6f97 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/util/StackTraceUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/util/StackTraceUtils.java @@ -18,6 +18,7 @@ import io.microsphere.annotation.Nonnull; +import static io.microsphere.util.ClassLoaderUtils.resolveClass; import static java.lang.Thread.currentThread; /** @@ -29,6 +30,82 @@ */ public abstract class StackTraceUtils implements Utils { + private static final Class TYPE = StackTraceUtils.class; + + /** + * {@link StackTraceElement} invocation frame offset + */ + private static final int invocationFrameOffset; + + // Initialize java.lang.StackTraceElement + static { + int offset = 0; + // Use java.lang.StackTraceElement to calculate frame + StackTraceElement[] stackTraceElements = getStackTrace(); + for (; ; offset++) { + StackTraceElement stackTraceElement = stackTraceElements[offset]; + String className = stackTraceElement.getClassName(); + if (TYPE.getName().equals(className)) { + break; + } + } + invocationFrameOffset = offset; + } + + /** + * Get caller class from {@link Thread#getStackTrace() stack traces} + * + * @return Caller Class + * @see #getCallerClassInStatckTrace(int) + */ + public static Class getCallerClassInStatckTrace() { + // Plus 1 , because Invocation getStackTrace() method was considered as increment invocation frame + // Plus 1 , because Invocation getCallerClassNameInStackTrace(int) method was considered as increment invocation frame + // Plus 1 , because Invocation getCallerClassInStatckTrace(int) method was considered as increment invocation frame + return getCallerClassInStatckTrace(invocationFrameOffset + 3); + } + + /** + * General implementation, get the calling class name + * + * @return call class name + * @see #getCallerClassNameInStackTrace(int) + */ + public static String getCallerClassNameInStackTrace() { + // Plus 1 , because Invocation getStackTrace() method was considered as increment invocation frame + // Plus 1 , because Invocation getCallerClassNameInStackTrace() method was considered as increment invocation frame + // Plus 1 , because Invocation getCallerClassNameInStackTrace(int) method was considered as increment invocation frame + return getCallerClassNameInStackTrace(invocationFrameOffset + 3); + } + + /** + * Get caller class in General JVM + * + * @param invocationFrame invocation frame + * @return caller class + * @see #getCallerClassNameInStackTrace(int) + */ + public static Class getCallerClassInStatckTrace(int invocationFrame) { + // Plus 1 , because Invocation getCallerClassNameInStackTrace(int) method was considered as increment invocation frame + String className = getCallerClassNameInStackTrace(invocationFrame + 1); + return className == null ? null : resolveClass(className); + } + + /** + * General implementation, get the calling class name by specifying the calling level value + * + * @param invocationFrame invocation frame + * @return specified invocation frame class + */ + public static String getCallerClassNameInStackTrace(int invocationFrame) throws IndexOutOfBoundsException { + StackTraceElement[] elements = getStackTrace(); + if (invocationFrame < elements.length) { + StackTraceElement targetStackTraceElement = elements[invocationFrame]; + return targetStackTraceElement.getClassName(); + } + return null; + } + /** * Get the {@link StackTraceElement} array on the current thread * From 46fcf6ac28adbd4559d67a7d5c17b70698d02b42 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sun, 1 Mar 2026 21:28:13 +0800 Subject: [PATCH 52/58] Add tests for StackTraceUtils caller methods Add unit tests to StackTraceUtilsTest covering caller-related utilities. Introduces CALLER_CLASS and CALLER_CLASS_NAME constants and tests for getCallerClassInStatckTrace(), getCallerClassNameInStackTrace(), and an out-of-range frame case (assertNull). Also adds necessary static imports for the new assertions and methods. --- .../microsphere/util/StackTraceUtilsTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceUtilsTest.java index ac1844f59..12fb90663 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/util/StackTraceUtilsTest.java @@ -2,8 +2,12 @@ import org.junit.jupiter.api.Test; +import static io.microsphere.util.StackTraceUtils.getCallerClassInStatckTrace; +import static io.microsphere.util.StackTraceUtils.getCallerClassNameInStackTrace; import static io.microsphere.util.StackTraceUtils.getStackTrace; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; /** * {@link StackTraceUtils} Test @@ -14,6 +18,27 @@ */ class StackTraceUtilsTest { + private static final Class CALLER_CLASS = StackTraceUtilsTest.class; + + private static final String CALLER_CLASS_NAME = CALLER_CLASS.getName(); + + @Test + void testGetCallerClassInStatckTrace() { + Class callerClassInStatckTrace = getCallerClassInStatckTrace(); + assertEquals(CALLER_CLASS, callerClassInStatckTrace); + } + + @Test + void testGetCallerClassNameInStackTrace() { + String callerClassName = getCallerClassNameInStackTrace(); + assertEquals(CALLER_CLASS_NAME, callerClassName); + } + + @Test + void testGetCallerClassInStatckTraceWithFrame() { + assertNull(getCallerClassInStatckTrace(99999)); + } + @Test void testGetStackTrace() { assertNotNull(getStackTrace()); From 55b88a7af5dbf686585b0853a764c486f5651be1 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Mon, 2 Mar 2026 10:11:44 +0800 Subject: [PATCH 53/58] Refactor caller-class stack frame offsets Rename and consolidate stack-frame offset fields and simplify caller-class detection logic. Fields like sunReflectReflectionInvocationFrame and stackWalkerInvocationFrame were renamed to sunReflectReflectionInvocationFrameOffset and stackWalkerInvocationFrameOffset, and offset calculations were adjusted to account for invocation-level increments. Removed several stack-trace-based helper methods and fallbacks, simplified getCallerClassInSunReflectReflection to always try the MethodHandle call, and updated StackWalker-related logic to use the new offsets. Tests were updated to import StackTraceUtils helpers and to reflect the new offset behavior and assertions. This change reduces reliance on fragile stack-trace indexing and unifies offset handling across JVM detection paths. --- .../microsphere/reflect/ReflectionUtils.java | 163 ++++-------------- .../reflect/ReflectionUtilsTest.java | 25 +-- 2 files changed, 33 insertions(+), 155 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java b/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java index bdf2c5158..2f5e5aaf9 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/reflect/ReflectionUtils.java @@ -32,8 +32,7 @@ import static io.microsphere.util.ClassUtils.getType; import static io.microsphere.util.ClassUtils.isPrimitive; import static io.microsphere.util.ClassUtils.isSimpleType; -import static io.microsphere.util.StackTraceUtils.getStackTrace; -import static java.lang.Thread.currentThread; +import static io.microsphere.util.StackTraceUtils.getCallerClassNameInStackTrace; import static java.lang.reflect.Array.get; import static java.lang.reflect.Array.getLength; import static java.util.Collections.emptyMap; @@ -85,14 +84,9 @@ public abstract class ReflectionUtils implements Utils { private static final MethodHandle getCallerClassMethodHandle = findPublicStatic(SUN_REFLECT_REFLECTION_CLASS, getCallerClassMethodName, int.class); /** - * sun.reflect.Reflection invocation frame + * sun.reflect.Reflection invocation frame offset */ - private static final int sunReflectReflectionInvocationFrame; - - /** - * {@link StackTraceElement} invocation frame - */ - private static final int stackTraceElementInvocationFrame; + private static final int sunReflectReflectionInvocationFrameOffset; /** * Is Supported sun.reflect.Reflection ? @@ -146,9 +140,9 @@ public abstract class ReflectionUtils implements Utils { private static Object stackWalkerInstance; /** - * {@linkplain java.lang.StackWalker} invocation frame. + * {@linkplain java.lang.StackWalker} invocation frame offset. */ - private static final int stackWalkerInvocationFrame; + private static final int stackWalkerInvocationFrameOffset; private static final Function, Object> getClassNamesFunction = ReflectionUtils::getCallerClassNamesInStackWalker; @@ -177,7 +171,7 @@ public abstract class ReflectionUtils implements Utils { invocationFrame++; } } - stackWalkerInvocationFrame = invocationFrame + 2; + stackWalkerInvocationFrameOffset = invocationFrame; } // Initialize sun.reflect.Reflection @@ -194,26 +188,9 @@ public abstract class ReflectionUtils implements Utils { } } - // set method info - // getCallerClass() -> getCallerClass(int) - // Plugs 1 , because Invocation getCallerClass() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClass(int) method was considered as increment invocation frame - sunReflectReflectionInvocationFrame = invocationFrame + 2; - } - - // Initialize java.lang.StackTraceElement - static { - int invocationFrame = 0; - // Use java.lang.StackTraceElement to calculate frame - StackTraceElement[] stackTraceElements = currentThread().getStackTrace(); - for (StackTraceElement stackTraceElement : stackTraceElements) { - String className = stackTraceElement.getClassName(); - if (TYPE.getName().equals(className)) { - break; - } - invocationFrame++; - } - stackTraceElementInvocationFrame = invocationFrame; + // Plus 1 , because Invocation getCallerClass()/getCallerClassName() method was considered as increment invocation frame + // Plus 1 , because Invocation getCallerClassInSunReflectReflection(int) method was considered as increment invocation frame + sunReflectReflectionInvocationFrameOffset = invocationFrame + 2; } /** @@ -267,20 +244,25 @@ public static boolean isSupportedSunReflectReflection() { */ @Nonnull public static String getCallerClassName() { - Class callerClass = getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrame); - return callerClass == null ? getCallerClassName(stackWalkerInstance, 1) : callerClass.getName(); + if (supportedSunReflectReflection) { + return getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrameOffset).getName(); + } + return getCallerClassName(stackWalkerInstance, 1); } @Nullable static String getCallerClassName(Object stackWalkerInstance, int frameOffSet) { if (stackWalkerInstance == null) { - // Plugs 1 , because Invocation getStackTrace() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClassName() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClassNameInStackTrace(int) method was considered as increment invocation frame - return getCallerClassNameInStackTrace(stackTraceElementInvocationFrame + 3 + frameOffSet); + // Plus 1 , because Invocation getCallerClassName() method was considered as increment invocation frame + // Plus 1 , because Invocation getCallerClassName(Object stackWalkerInstance, int frameOffSet) method was considered as increment invocation frame + // Plus 1 , because Invocation getCallerClassNameInStackTrace(int) method was considered as increment invocation frame + return getCallerClassNameInStackTrace(3 + frameOffSet); } + + // Plus 1 , because Invocation getCallerClassName() method was considered as increment invocation frame + // Plus 1, because Invocation getCallerClassName(Object,int) method was considered as increment invocation frame List callerClassNames = getCallerClassNamesInStackWalker(stackWalkerInstance); - int frame = stackWalkerInvocationFrame + frameOffSet; + int frame = stackWalkerInvocationFrameOffset + 2 + frameOffSet; if (frame < callerClassNames.size()) { return callerClassNames.get(frame); } @@ -306,34 +288,6 @@ private static String getClassName(Object stackFrame) { return invokeMethod(stackFrame, GET_CLASS_NAME_METHOD); } - /** - * General implementation, get the calling class name - * - * @return call class name - * @see #getCallerClassNameInStackTrace(int) - */ - static String getCallerClassNameInStackTrace() { - // Plugs 1 , because Invocation getStackTrace() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClassNameInStackTrace() method was considered as increment invocation frame - // Plugs 1 , because Invocation getCallerClassNameInStackTrace(int) method was considered as increment invocation frame - return getCallerClassNameInStackTrace(stackTraceElementInvocationFrame + 3); - } - - /** - * General implementation, get the calling class name by specifying the calling level value - * - * @param invocationFrame invocation frame - * @return specified invocation frame class - */ - static String getCallerClassNameInStackTrace(int invocationFrame) throws IndexOutOfBoundsException { - StackTraceElement[] elements = getStackTrace(); - if (invocationFrame < elements.length) { - StackTraceElement targetStackTraceElement = elements[invocationFrame]; - return targetStackTraceElement.getClassName(); - } - return null; - } - /** * Gets the {@link Class} of the method caller. * @@ -357,7 +311,7 @@ static String getCallerClassNameInStackTrace(int invocationFrame) throws IndexOu */ @Nonnull public static Class getCallerClass() throws IllegalStateException { - Class callerClass = getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrame); + Class callerClass = getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrameOffset); if (callerClass != null) { return callerClass; } @@ -367,27 +321,13 @@ public static Class getCallerClass() throws IllegalStateException { @Nullable static Class getCallerClassInSunReflectReflection(int realFramesToSkip) { - if (supportedSunReflectReflection) { - try { - return (Class) getCallerClassMethodHandle.invokeExact(realFramesToSkip); - } catch (Throwable ignored) { - } + try { + return (Class) getCallerClassMethodHandle.invokeExact(realFramesToSkip); + } catch (Throwable ignored) { } return null; } - /** - * Get caller class in General JVM - * - * @param invocationFrame invocation frame - * @return caller class - * @see #getCallerClassNameInStackTrace(int) - */ - static Class getCallerClassInStatckTrace(int invocationFrame) { - String className = getCallerClassNameInStackTrace(invocationFrame + 1); - return className == null ? null : resolveClass(className); - } - /** * Get caller class In SUN HotSpot JVM * @@ -395,67 +335,22 @@ static Class getCallerClassInStatckTrace(int invocationFrame) { * @see #getCallerClassInSunReflectReflection(int) */ @Nullable - static Class getCallerClassInSunReflectReflection() throws UnsupportedOperationException { - return getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrame); + static Class getCallerClassInSunReflectReflection() { + return getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrameOffset); } /** * Get caller class name In SUN HotSpot JVM * * @return Caller Class - * @throws UnsupportedOperationException If JRE is not a SUN HotSpot JVM * @see #getCallerClassInSunReflectReflection(int) */ @Nullable - static String getCallerClassNameInSunReflectReflection() throws UnsupportedOperationException { - Class callerClass = getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrame); + static String getCallerClassNameInSunReflectReflection() { + Class callerClass = getCallerClassInSunReflectReflection(sunReflectReflectionInvocationFrameOffset); return callerClass == null ? null : callerClass.getName(); } - /** - * Retrieves the class of the caller at the specified invocation frame. - * - *

This method attempts to use the internal Sun JDK class - * {@code sun.reflect.Reflection} for high-performance caller class detection if - * available and supported. If not supported (e.g., non-Sun/HotSpot JVM), it falls back to using - * the {@link StackTraceElement} approach.

- * - *

Example Usage

- *
{@code
-     * public class Example {
-     *     public void exampleMethod() {
-     *         Class callerClass = ReflectionUtils.getCallerClass(2);
-     *         System.out.println("Caller class: " + callerClass.getName());
-     *     }
-     * }
-     * }
- * - * @param invocationFrame The depth in the call stack to retrieve the caller class from. - * A value of 0 typically represents the immediate caller, but this may vary - * depending on the JVM implementation and call context. - * @return The class of the caller at the specified invocation frame. - * @throws IllegalStateException if an error occurs while determining the caller class. - */ - @Nullable - public static Class getCallerClass(int invocationFrame) { - Class callerClass = getCallerClassInSunReflectReflection(invocationFrame + 1); - if (callerClass != null) { - return callerClass; - } - String className = getCallerClassName(stackWalkerInstance, invocationFrame + 2); - return resolveClass(className); - } - - /** - * Get caller class from {@link Thread#getStackTrace() stack traces} - * - * @return Caller Class - * @see #getCallerClassInStatckTrace(int) - */ - static Class getCallerClassInStatckTrace() { - return getCallerClassInStatckTrace(stackTraceElementInvocationFrame + 3); - } - /** * Converts an array object into a {@link List}. * diff --git a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java index a5285203a..8060c6a43 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java +++ b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsTest.java @@ -11,10 +11,8 @@ import static io.microsphere.reflect.ReflectionUtils.INACCESSIBLE_OBJECT_EXCEPTION_CLASS; import static io.microsphere.reflect.ReflectionUtils.INACCESSIBLE_OBJECT_EXCEPTION_CLASS_NAME; import static io.microsphere.reflect.ReflectionUtils.getCallerClass; -import static io.microsphere.reflect.ReflectionUtils.getCallerClassInStatckTrace; import static io.microsphere.reflect.ReflectionUtils.getCallerClassInSunReflectReflection; import static io.microsphere.reflect.ReflectionUtils.getCallerClassName; -import static io.microsphere.reflect.ReflectionUtils.getCallerClassNameInStackTrace; import static io.microsphere.reflect.ReflectionUtils.getCallerClassNameInSunReflectReflection; import static io.microsphere.reflect.ReflectionUtils.getCallerClassNamesInStackWalker; import static io.microsphere.reflect.ReflectionUtils.isInaccessibleObjectException; @@ -23,10 +21,11 @@ import static io.microsphere.reflect.ReflectionUtils.toList; import static io.microsphere.reflect.ReflectionUtils.toObject; import static io.microsphere.util.ArrayUtils.ofArray; +import static io.microsphere.util.StackTraceUtils.getCallerClassInStatckTrace; +import static io.microsphere.util.StackTraceUtils.getCallerClassNameInStackTrace; import static io.microsphere.util.VersionUtils.JAVA_VERSION_9; import static io.microsphere.util.VersionUtils.testCurrentJavaVersion; import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; @@ -53,7 +52,7 @@ void testGetCallerClassX() { Class callerClassInSunReflectReflection = getCallerClassInSunReflectReflection(); if (isSupportedSunReflectReflection()) { - assertEquals(callerClassInSunReflectReflection, callerClass); + assertEquals(CALLER_CLASS, callerClassInSunReflectReflection); } else { assertNull(callerClassInSunReflectReflection); } @@ -62,11 +61,6 @@ void testGetCallerClassX() { assertEquals(callerClassInStatckTrace, callerClass); } - @Test - void testGetCallerClassWithFrame() { - assertDoesNotThrow(() -> getCallerClass(99999)); - } - @Test void testGetCallerClassName() { String callerClassName = getCallerClassName(); @@ -83,7 +77,7 @@ void testGetCallerClassName() { @Test void testGetCallerClassNameOnStackWalkerSupportedForTesting() { - assertEquals(getCallerClassNameInStackTrace(), getCallerClassName(null, 0)); + assertEquals(getCallerClassNameInStackTrace(), getCallerClassName(null, 1)); assertEquals(CALLER_CLASS_NAME, getCallerClassName()); } @@ -96,17 +90,6 @@ void testGetCallerClassNamesInStackWalker() { } } - @Test - void testGetCallerClassNameInStackTrace() { - String callerClassName = getCallerClassNameInStackTrace(); - assertEquals(CALLER_CLASS_NAME, callerClassName); - } - - @Test - void testGetCallerClassInStatckTraceWithFrame() { - assertNull(getCallerClassInStatckTrace(99999)); - } - @Test void testToList() { int[] intArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}; From 912807a7d4067b0d3f528c4e450f32ac86041b31 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Mon, 2 Mar 2026 10:11:57 +0800 Subject: [PATCH 54/58] Use StackTraceUtils static import in benchmark Replace the fully-qualified ReflectionUtils.getCallerClassNameInStackTrace() call with a static import of getCallerClassNameInStackTrace from StackTraceUtils in ReflectionUtilsBenchmark. This clarifies the method origin and keeps the test import style consistent with other helper methods. --- .../java/io/microsphere/reflect/ReflectionUtilsBenchmark.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsBenchmark.java b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsBenchmark.java index a6d12aa7b..bc861f313 100644 --- a/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsBenchmark.java +++ b/microsphere-java-core/src/test/java/io/microsphere/reflect/ReflectionUtilsBenchmark.java @@ -27,6 +27,7 @@ import static io.microsphere.reflect.ReflectionUtils.getCallerClassInSunReflectReflection; import static io.microsphere.reflect.ReflectionUtils.getCallerClassNamesInStackWalker; +import static io.microsphere.util.StackTraceUtils.getCallerClassNameInStackTrace; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.openjdk.jmh.annotations.Mode.AverageTime; @@ -63,6 +64,6 @@ public void getCallerClassNameInStackWalker() { @Benchmark public void getCallerClassNameOnStackTrace() { - ReflectionUtils.getCallerClassNameInStackTrace(); + getCallerClassNameInStackTrace(); } } From 8a285d07592e69da4da3986d8a5e47449444fb25 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Mon, 2 Mar 2026 10:12:16 +0800 Subject: [PATCH 55/58] Use stack-trace caller lookup and increment frame Replace reflection-based getCallerClass(...) with StackTraceUtils.getCallerClassInStatckTrace(...) and update import accordingly. Adjust the default caller frame in getCallerClassLoader() from 4 to 5 to account for the different stack depth used by the new stack-trace-based lookup. --- .../src/main/java/io/microsphere/util/ClassLoaderUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/util/ClassLoaderUtils.java b/microsphere-java-core/src/main/java/io/microsphere/util/ClassLoaderUtils.java index 4bb15f331..bcae9cacc 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/util/ClassLoaderUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/util/ClassLoaderUtils.java @@ -47,12 +47,12 @@ 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.reflect.ReflectionUtils.getCallerClass; import static io.microsphere.util.ArrayUtils.asArray; import static io.microsphere.util.Assert.assertNoNullElements; import static io.microsphere.util.Assert.assertNotNull; import static io.microsphere.util.ClassLoaderUtils.ResourceType.values; import static io.microsphere.util.ClassUtils.resolvePrimitiveClassForName; +import static io.microsphere.util.StackTraceUtils.getCallerClassInStatckTrace; import static io.microsphere.util.StringUtils.contains; import static io.microsphere.util.StringUtils.endsWith; import static io.microsphere.util.StringUtils.isBlank; @@ -288,7 +288,7 @@ public static ClassLoader getClassLoader(@Nullable Class loadedClass) { */ @Nullable public static ClassLoader getCallerClassLoader() { - return getCallerClassLoader(4); + return getCallerClassLoader(5); } /** @@ -1757,7 +1757,7 @@ public static URLClassLoader resolveURLClassLoader(@Nullable ClassLoader classLo */ static ClassLoader getCallerClassLoader(int invocationFrame) { ClassLoader classLoader = null; - Class callerClass = getCallerClass(invocationFrame); + Class callerClass = getCallerClassInStatckTrace(invocationFrame); if (callerClass != null) { classLoader = callerClass.getClassLoader(); } From 570c446ebbe37ba701f500cdbb44d361249e0313 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Mon, 2 Mar 2026 10:43:42 +0800 Subject: [PATCH 56/58] Increase caller depth in ClassLoaderUtils Change the stack depth used to resolve the caller ClassLoader from 4 to 5 when loadedClass is null. This adjusts the caller lookup to account for an additional stack frame in the call chain so the correct ClassLoader is returned, preventing incorrect loader resolution in certain call contexts. --- .../src/main/java/io/microsphere/util/ClassLoaderUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsphere-java-core/src/main/java/io/microsphere/util/ClassLoaderUtils.java b/microsphere-java-core/src/main/java/io/microsphere/util/ClassLoaderUtils.java index bcae9cacc..a1293c6f4 100644 --- a/microsphere-java-core/src/main/java/io/microsphere/util/ClassLoaderUtils.java +++ b/microsphere-java-core/src/main/java/io/microsphere/util/ClassLoaderUtils.java @@ -257,7 +257,7 @@ public static ClassLoader getClassLoader(@Nullable Class loadedClass) { ClassLoader classLoader = null; try { if (loadedClass == null) { - classLoader = getCallerClassLoader(4); + classLoader = getCallerClassLoader(5); } else { classLoader = loadedClass.getClassLoader(); } From da25f26709514a9ecb5e61b915a4ae5032695889 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Mon, 2 Mar 2026 14:06:36 +0800 Subject: [PATCH 57/58] Add SecurityUtils and tests Introduce SecurityUtils utility to manage the java.security.policy system property (constant, getter, and setters accepting String and File). Adds annotations and a private constructor. Include JUnit 5 tests (SecurityUtilsTest) that verify the constant and set/get behavior, prepare a module base path for resources, and clear system properties after each test. --- .../microsphere/security/SecurityUtils.java | 77 +++++++++++++++++++ .../security/SecurityUtilsTest.java | 73 ++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 microsphere-java-core/src/main/java/io/microsphere/security/SecurityUtils.java create mode 100644 microsphere-java-core/src/test/java/io/microsphere/security/SecurityUtilsTest.java diff --git a/microsphere-java-core/src/main/java/io/microsphere/security/SecurityUtils.java b/microsphere-java-core/src/main/java/io/microsphere/security/SecurityUtils.java new file mode 100644 index 000000000..ff80ca478 --- /dev/null +++ b/microsphere-java-core/src/main/java/io/microsphere/security/SecurityUtils.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.microsphere.security; + +import io.microsphere.annotation.ConfigurationProperty; +import io.microsphere.annotation.Nonnull; +import io.microsphere.annotation.Nullable; +import io.microsphere.util.Utils; + +import java.io.File; + +import static io.microsphere.annotation.ConfigurationProperty.SYSTEM_PROPERTIES_SOURCE; +import static java.lang.System.getProperty; +import static java.lang.System.setProperty; + +/** + * The utilities class for Java Security + * + * @author
Mercy + * @see SecurityManager + * @see SecurityException + * @since 1.0.0 + */ +public abstract class SecurityUtils implements Utils { + + /** + * The System Property name of Java Security Policy File. + */ + @ConfigurationProperty(source = SYSTEM_PROPERTIES_SOURCE) + public static final String JAVA_SECURITY_POLICY_FILE_PROPERTY_NAME = "java.security.policy"; + + /** + * Set the Java Security Policy File + * + * @param javaSecurityPolicyFilePath the absolute path of Java Security Policy File + */ + public static void setJavaSecurityPolicyFile(@Nonnull String javaSecurityPolicyFilePath) { + setProperty(JAVA_SECURITY_POLICY_FILE_PROPERTY_NAME, javaSecurityPolicyFilePath); + } + + /** + * Set the Java Security Policy File + * + * @param javaSecurityPolicyFile the Java Security Policy File + */ + public static void setJavaSecurityPolicyFile(@Nonnull File javaSecurityPolicyFile) { + setJavaSecurityPolicyFile(javaSecurityPolicyFile.getAbsolutePath()); + } + + /** + * Get the Java Security Policy File + * + * @return the Java Security Policy File + */ + @Nullable + public static String getJavaSecurityPolicyFile() { + return getProperty(JAVA_SECURITY_POLICY_FILE_PROPERTY_NAME); + } + + private SecurityUtils() { + } +} \ No newline at end of file diff --git a/microsphere-java-core/src/test/java/io/microsphere/security/SecurityUtilsTest.java b/microsphere-java-core/src/test/java/io/microsphere/security/SecurityUtilsTest.java new file mode 100644 index 000000000..3ac8ddb6e --- /dev/null +++ b/microsphere-java-core/src/test/java/io/microsphere/security/SecurityUtilsTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.microsphere.security; + + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.net.URL; + +import static io.microsphere.security.SecurityUtils.JAVA_SECURITY_POLICY_FILE_PROPERTY_NAME; +import static io.microsphere.security.SecurityUtils.getJavaSecurityPolicyFile; +import static io.microsphere.security.SecurityUtils.setJavaSecurityPolicyFile; +import static io.microsphere.util.ClassLoaderUtils.ResourceType.CLASS; +import static io.microsphere.util.ClassLoaderUtils.getResource; +import static io.microsphere.util.StringUtils.substringBefore; +import static java.lang.System.clearProperty; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link SecurityUtils} + * + * @author Mercy + * @see SecurityUtils + * @since 1.0.0 + */ +class SecurityUtilsTest { + + private static final String MODULE_BASE_PROPERTY_NAME = "module.base"; + + private static final URL policyResource = getResource("META-INF/class-load-test.policy"); + + private static final File policyFile = new File(policyResource.getFile()); + + static { + URL classResource = getResource(SecurityUtilsTest.class.getClassLoader(), CLASS, SecurityUtilsTest.class.getName() + ".class"); + String moduleBasePath = substringBefore(classResource.getFile(), "target/"); + System.setProperty(MODULE_BASE_PROPERTY_NAME, moduleBasePath); + } + + @AfterEach + void tearDown() { + clearProperty(JAVA_SECURITY_POLICY_FILE_PROPERTY_NAME); + clearProperty(MODULE_BASE_PROPERTY_NAME); + } + + @Test + void testtConstants() { + assertEquals("java.security.policy", JAVA_SECURITY_POLICY_FILE_PROPERTY_NAME); + } + + @Test + void testJavaSecurityPolicyFile() { + setJavaSecurityPolicyFile(policyFile); + assertEquals(policyFile.getAbsolutePath(), getJavaSecurityPolicyFile()); + } +} \ No newline at end of file From 3d2a2fa82bfba03974d9d8fcba9d42d0c2c7d083 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Mon, 2 Mar 2026 14:06:51 +0800 Subject: [PATCH 58/58] Add class-load test policy granting AllPermission Add class-load-test.policy under microsphere-java-core/src/test/resources/META-INF that grants java.security.AllPermission for test runs. This enables unrestricted class loading and security permissions during class-load related tests. --- .../src/test/resources/META-INF/class-load-test.policy | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 microsphere-java-core/src/test/resources/META-INF/class-load-test.policy diff --git a/microsphere-java-core/src/test/resources/META-INF/class-load-test.policy b/microsphere-java-core/src/test/resources/META-INF/class-load-test.policy new file mode 100644 index 000000000..4511f0aa5 --- /dev/null +++ b/microsphere-java-core/src/test/resources/META-INF/class-load-test.policy @@ -0,0 +1,4 @@ + +grant { + permission java.security.AllPermission; +}; \ No newline at end of file