From 57ae22adb93eeb11d804ba7d7fd17b48ec1e5064 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 2 Apr 2019 09:02:08 +0200 Subject: [PATCH] GH-350 Fixed NPE due to missing main class Fixed NPE due to the missing main class - a condition that could be cause my missconfiguration Added additional assertions Added more descriptive error message Added MAIN_CLASS system property to the search path Added tests Resolves #350 --- .../src/main/asciidoc/adapters/aws-intro.adoc | 10 ++++- ...tractSpringFunctionAdapterInitializer.java | 37 ++++++++++++------- ...SpringFunctionAdapterInitializerTests.java | 20 ++++++++++ 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/docs/src/main/asciidoc/adapters/aws-intro.adoc b/docs/src/main/asciidoc/adapters/aws-intro.adoc index 515519b7f..8d60da627 100644 --- a/docs/src/main/asciidoc/adapters/aws-intro.adoc +++ b/docs/src/main/asciidoc/adapters/aws-intro.adoc @@ -4,7 +4,15 @@ If your app has more than one `@Bean` of type `Function` etc. then you can choos == Notes on JAR Layout -You don't need the Spring Cloud Function Web or Stream adapter at runtime in Lambda, so you might need to exclude those before you create the JAR you send to AWS. A Lambda application has to be shaded, but a Spring Boot standalone application does not, so you can run the same app using 2 separate jars (as per the sample). The sample app creates 2 jar files, one with an `aws` classifier for deploying in Lambda, and one executable (thin) jar that includes `spring-cloud-function-web` at runtime. Spring Cloud Function will try and locate a "main class" for you from the JAR file manifest, using the `Start-Class` attribute (which will be added for you by the Spring Boot tooling if you use the starter parent). If there is no `Start-Class` in your manifest you can use an environment variable `MAIN_CLASS` when you deploy the function to AWS. +You don't need the Spring Cloud Function Web or Stream adapter at runtime in Lambda, so you might +need to exclude those before you create the JAR you send to AWS. A Lambda application has to be +shaded, but a Spring Boot standalone application does not, so you can run the same app using 2 +separate jars (as per the sample). The sample app creates 2 jar files, one with an `aws` +classifier for deploying in Lambda, and one executable (thin) jar that includes `spring-cloud-function-web` +at runtime. Spring Cloud Function will try and locate a "main class" for you from the JAR file +manifest, using the `Start-Class` attribute (which will be added for you by the Spring Boot +tooling if you use the starter parent). If there is no `Start-Class` in your manifest you can +use an environment variable or system property `MAIN_CLASS` when you deploy the function to AWS. == Upload diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java index bc7df7c9c..1067569df 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java @@ -45,6 +45,7 @@ import org.springframework.cloud.function.context.config.FunctionContextUtils; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -89,6 +90,7 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo } public AbstractSpringFunctionAdapterInitializer(Class configurationClass) { + Assert.notNull(configurationClass, "'configurationClass' must not be null"); this.configurationClass = configurationClass; } @@ -254,23 +256,32 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo private static Class getStartClass() { ClassLoader classLoader = AbstractSpringFunctionAdapterInitializer.class.getClassLoader(); + Class mainClass = null; if (System.getenv("MAIN_CLASS") != null) { - return ClassUtils.resolveClassName(System.getenv("MAIN_CLASS"), classLoader); + mainClass = ClassUtils.resolveClassName(System.getenv("MAIN_CLASS"), classLoader); } - try { - Class result = getStartClass( - Collections.list(classLoader.getResources("META-INF/MANIFEST.MF"))); - if (result == null) { - result = getStartClass(Collections - .list(classLoader.getResources("meta-inf/manifest.mf"))); + if (System.getProperty("MAIN_CLASS") != null) { + mainClass = ClassUtils.resolveClassName(System.getProperty("MAIN_CLASS"), classLoader); + } + else { + try { + Class result = getStartClass( + Collections.list(classLoader.getResources("META-INF/MANIFEST.MF"))); + if (result == null) { + result = getStartClass(Collections + .list(classLoader.getResources("meta-inf/manifest.mf"))); + } + Assert.notNull(result, "Failed to locate main class"); + mainClass = result; + } + catch (Exception ex) { + throw new IllegalStateException("Failed to discover main class. An attempt was made to discover " + + "main class as 'MAIN_CLASS' environment variable, system property as well as " + + "entry in META-INF/MANIFEST.MF (in that order).", ex); } - logger.info("Main class: " + result); - return result; - } - catch (Exception ex) { - logger.error("Failed to find main class", ex); - return null; } + logger.info("Main class: " + mainClass); + return mainClass; } private static Class getStartClass(List list) { diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/SpringFunctionAdapterInitializerTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/SpringFunctionAdapterInitializerTests.java index f6dc36217..125d2d86f 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/SpringFunctionAdapterInitializerTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/SpringFunctionAdapterInitializerTests.java @@ -54,6 +54,26 @@ public class SpringFunctionAdapterInitializerTests { } } + @Test(expected = IllegalArgumentException.class) + public void nullSource() { + this.initializer = new AbstractSpringFunctionAdapterInitializer(null) { + + }; + } + + @Test + public void sourceAsMainClassProperty() { + try { + System.setProperty("MAIN_CLASS", FluxFunctionConfig.class.getName()); + this.initializer = new AbstractSpringFunctionAdapterInitializer() { + + }; + } + finally { + System.clearProperty("MAIN_CLASS"); + } + } + @Test public void functionBean() { this.initializer = new AbstractSpringFunctionAdapterInitializer(FluxFunctionConfig.class) {