This is an automated email from the ASF dual-hosted git repository. wujimin pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git
The following commit(s) were added to refs/heads/master by this push: new b110d7e [SCB-1179]Optimize the mainclass auto-discovery logic to cover more scenes. b110d7e is described below commit b110d7efbb38cbc958bc8b6a43a72ca90f8d6487 Author: yangshaoqin <yangshaoq...@huawei.com> AuthorDate: Fri Mar 1 18:46:28 2019 +0800 [SCB-1179]Optimize the mainclass auto-discovery logic to cover more scenes. --- .../foundation/common/utils/BeanUtils.java | 32 ++++++++--- .../foundation/common/utils/JvmUtils.java | 52 ++++++++++++++--- .../foundation/common/utils/TestBeanUtils.java | 14 ++++- .../foundation/common/utils/TestJvmUtils.java | 66 ++++++++++++++++++++-- 4 files changed, 143 insertions(+), 21 deletions(-) diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/BeanUtils.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/BeanUtils.java index 103ac79..f7f4647 100644 --- a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/BeanUtils.java +++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/BeanUtils.java @@ -21,12 +21,17 @@ import java.util.LinkedHashSet; import java.util.Set; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.aop.TargetClassAware; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class BeanUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(BeanUtils.class); + public static final String DEFAULT_BEAN_RESOURCE = "classpath*:META-INF/spring/*.bean.xml"; public static final String SCB_SCAN_PACKAGE = "scb-scan-package"; @@ -49,6 +54,16 @@ public final class BeanUtils { context = new ClassPathXmlApplicationContext(configLocations); } + private static void addItem(Set<String> set, String item){ + + for(String it: set){ + if(item.startsWith(it)){ + return; + } + } + set.add(item); + } + public static void prepareServiceCombScanPackage() { Set<String> scanPackags = new LinkedHashSet<>(); // add exists settings @@ -56,25 +71,26 @@ public final class BeanUtils { if (exists != null) { for (String exist : exists.trim().split(",")) { if (!exist.isEmpty()) { - scanPackags.add(exist.trim()); + addItem(scanPackags, exist.trim()); } } } // ensure servicecomb package exist - scanPackags.add(SCB_PACKAGE); + addItem(scanPackags, SCB_PACKAGE); // add main class package - Class<?> mainClass = JvmUtils.findMainClass(); - if (mainClass != null) { - String pkg = mainClass.getPackage().getName(); - if (!pkg.startsWith(SCB_PACKAGE)) { - scanPackags.add(pkg); + for (Class<?> mainClass : new Class<?>[] {JvmUtils.findMainClass(), JvmUtils.findMainClassByStackTrace()}) { + if (mainClass != null) { + String pkg = mainClass.getPackage().getName(); + addItem(scanPackags, pkg); } } // finish - System.setProperty(SCB_SCAN_PACKAGE, StringUtils.join(scanPackags, ",")); + String scbScanPackages = StringUtils.join(scanPackags, ","); + System.setProperty(SCB_SCAN_PACKAGE, scbScanPackages); + LOGGER.info("Scb scan package list: " + scbScanPackages); } public static ApplicationContext getContext() { diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/JvmUtils.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/JvmUtils.java index e6315e9..04196d3 100644 --- a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/JvmUtils.java +++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/utils/JvmUtils.java @@ -22,6 +22,7 @@ import java.net.URL; import java.util.jar.JarFile; import java.util.jar.Manifest; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,24 +40,55 @@ public final class JvmUtils { /** * - * @return main class or null, never throw exception + * @return main class or null, never throw exception. + * Note that this method does not ensure that the scbMainClass can be returned correctly in the some scene. + */ + public static Class<?> findMainClassByStackTrace() { + String mainClass = null; + StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); + if (stackTrace != null && stackTrace.length > 0) { + for (StackTraceElement stackTraceElement : stackTrace) { + if ("main".equals(stackTraceElement.getMethodName())) { + mainClass = stackTraceElement.getClassName(); + break; + } + } + } + if(StringUtils.isEmpty(mainClass)){ + LOGGER.info("Can't found main class by stackTrace."); + return null; + } + try { + Class<?> cls = Class.forName(mainClass); + LOGGER.info("Found main class \"{}\" by stackTrace.", mainClass); + return cls; + } catch (Throwable e) { + LOGGER.warn("\"{}\" is not a valid class.", mainClass, e); + return null; + } + } + + /** + * + * @return main class or null, never throw exception. + * Note that this method does not ensure that the scbMainClass can be returned correctly in the some scene,like "mvn spring-boot:run". */ public static Class<?> findMainClass() { + //Get the mainClass from the call stack + String mainClass = null; // 1.run with java -cp ...... // command is main class and args // 2.run with java -jar ...... // command is jar file name and args String command = System.getProperty(SUN_JAVA_COMMAND); - if (command == null || command.isEmpty()) { - return null; + if (StringUtils.isNotEmpty(command)) { + String mainClassOrJar = command.trim().split(" ")[0]; + mainClass = readFromJar(mainClassOrJar); } - - String mainClassOrJar = command.trim().split(" ")[0]; - String mainClass = readFromJar(mainClassOrJar); - if (mainClass == null || mainClass.isEmpty()) { + if(StringUtils.isEmpty(mainClass)){ + LOGGER.info("Can't found main class by manifest."); return null; } - try { Class<?> cls = Class.forName(mainClass); LOGGER.info("Found main class \"{}\".", mainClass); @@ -78,6 +110,10 @@ public final class JvmUtils { URL url = new URL(manifestUri); try (InputStream inputStream = url.openStream()) { Manifest manifest = new Manifest(inputStream); + String startClass = manifest.getMainAttributes().getValue("Start-Class"); + if (StringUtils.isNotEmpty(startClass)) { + return startClass; + } return manifest.getMainAttributes().getValue("Main-Class"); } } catch (Throwable e) { diff --git a/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestBeanUtils.java b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestBeanUtils.java index c9a28c1..70c888e 100644 --- a/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestBeanUtils.java +++ b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestBeanUtils.java @@ -16,6 +16,8 @@ */ package org.apache.servicecomb.foundation.common.utils; +import java.math.BigDecimal; + import org.aspectj.lang.annotation.Aspect; import org.junit.AfterClass; import org.junit.Assert; @@ -69,6 +71,8 @@ public class TestBeanUtils { { JvmUtils.findMainClass(); result = null; + JvmUtils.findMainClassByStackTrace(); + result = null; } }; @@ -84,6 +88,8 @@ public class TestBeanUtils { { JvmUtils.findMainClass(); result = TestBeanUtils.class; + JvmUtils.findMainClassByStackTrace(); + result = TestBeanUtils.class; } }; @@ -99,12 +105,14 @@ public class TestBeanUtils { { JvmUtils.findMainClass(); result = String.class; + JvmUtils.findMainClassByStackTrace(); + result = BigDecimal.class; } }; BeanUtils.prepareServiceCombScanPackage(); - Assert.assertEquals("org.apache.servicecomb,java.lang", System.getProperty(BeanUtils.SCB_SCAN_PACKAGE)); + Assert.assertEquals("org.apache.servicecomb,java.lang,java.math", System.getProperty(BeanUtils.SCB_SCAN_PACKAGE)); } @Test @@ -114,6 +122,8 @@ public class TestBeanUtils { { JvmUtils.findMainClass(); result = null; + JvmUtils.findMainClassByStackTrace(); + result = null; } }; @@ -129,6 +139,8 @@ public class TestBeanUtils { { JvmUtils.findMainClass(); result = TestBeanUtils.class; + JvmUtils.findMainClassByStackTrace(); + result = TestBeanUtils.class; } }; BeanUtils.init(); diff --git a/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestJvmUtils.java b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestJvmUtils.java index 1d6630f..890ea21 100644 --- a/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestJvmUtils.java +++ b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/utils/TestJvmUtils.java @@ -33,6 +33,7 @@ import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; + @RunWith(PowerMockRunner.class) @PrepareForTest({JvmUtils.class}) public class TestJvmUtils { @@ -84,16 +85,16 @@ public class TestJvmUtils { @Test public void findMainClass_jar_normal() throws Exception { - String content = String.format("Manifest-Version: 1.0\nMain-Class: %s\n", TestJvmUtils.class.getName()); - InputStream inputStream = new ByteArrayInputStream(content.getBytes()); URL url = PowerMockito.mock(URL.class); String command = "a.jar"; String manifestUri = "jar:file:" + new File(command).getAbsolutePath() + "!/" + JarFile.MANIFEST_NAME; - PowerMockito.whenNew(URL.class).withParameterTypes(String.class) .withArguments(manifestUri).thenReturn(url); + + String content = String.format("Manifest-Version: 1.0\nMain-Class: %s\n", TestJvmUtils.class.getName()); + InputStream inputStream = new ByteArrayInputStream(content.getBytes()); PowerMockito.when(url.openStream()).thenReturn(inputStream); System.setProperty(JvmUtils.SUN_JAVA_COMMAND, command + " arg"); @@ -123,8 +124,8 @@ public class TestJvmUtils { @Test public void findMainClass_jar_readFailed() throws Exception { URL url = PowerMockito.mock(URL.class); - String command = "a.jar"; + String manifestUri = "jar:file:" + new File(command).getAbsolutePath() + "!/" + JarFile.MANIFEST_NAME; PowerMockito.whenNew(URL.class).withParameterTypes(String.class) @@ -135,4 +136,61 @@ public class TestJvmUtils { Assert.assertNull(JvmUtils.findMainClass()); } + + + @Test + public void findMainClassByStackTrace_normal() throws Exception{ + RuntimeException re = PowerMockito.mock(RuntimeException.class); + PowerMockito.when(re.getStackTrace()).thenReturn(new StackTraceElement[]{ + new StackTraceElement("declaring.class.fileName", "methodName", "fileName", 100), + new StackTraceElement("java.lang.String", "main", "fileName", 120) + }); + PowerMockito.whenNew(RuntimeException.class).withNoArguments().thenReturn(re); + + Assert.assertEquals(String.class, JvmUtils.findMainClassByStackTrace()); + } + + @Test + public void findMainClassByStackTrace_invalidClass() throws Exception{ + RuntimeException re = PowerMockito.mock(RuntimeException.class); + PowerMockito.when(re.getStackTrace()).thenReturn(new StackTraceElement[]{ + new StackTraceElement("declaring.class.fileName", "methodName", "fileName", 100), + new StackTraceElement("InvalidClass", "main", "fileName", 120) + }); + PowerMockito.whenNew(RuntimeException.class).withNoArguments().thenReturn(re); + + Assert.assertNull(JvmUtils.findMainClassByStackTrace()); + } + + + @Test + public void findMainClassByStackTrace_withoutMainMethod() throws Exception{ + RuntimeException re = PowerMockito.mock(RuntimeException.class); + PowerMockito.when(re.getStackTrace()).thenReturn(new StackTraceElement[]{ + new StackTraceElement("declaring.class.fileName", "methodName", "fileName", 100), + new StackTraceElement("InvalidClass", "methodName", "fileName", 120) + }); + PowerMockito.whenNew(RuntimeException.class).withNoArguments().thenReturn(re); + + Assert.assertNull(JvmUtils.findMainClassByStackTrace()); + } + + @Test + public void findMainClassByStackTrace_emptyStackTrace() throws Exception{ + RuntimeException re = PowerMockito.mock(RuntimeException.class); + PowerMockito.when(re.getStackTrace()).thenReturn(new StackTraceElement[]{}); + PowerMockito.whenNew(RuntimeException.class).withNoArguments().thenReturn(re); + + Assert.assertNull(JvmUtils.findMainClassByStackTrace()); + } + + @Test + public void findMainClassByStackTrace_nullStackTrace() throws Exception{ + RuntimeException re = PowerMockito.mock(RuntimeException.class); + PowerMockito.when(re.getStackTrace()).thenReturn(null); + PowerMockito.whenNew(RuntimeException.class).withNoArguments().thenReturn(re); + + Assert.assertNull(JvmUtils.findMainClassByStackTrace()); + } + }