diff --git a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/ApplicationBootstrap.java b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/ApplicationBootstrap.java index f9222aabd..260238192 100644 --- a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/ApplicationBootstrap.java +++ b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/ApplicationBootstrap.java @@ -51,6 +51,9 @@ public class ApplicationBootstrap { /** * Run the provided main class as a Spring Boot application with the provided command * line arguments. + * + * @param mainClass the main class + * @param args the command line arguments */ public void run(Class mainClass, String... args) { if (ApplicationBootstrap.isolated(args)) { @@ -91,7 +94,7 @@ public class ApplicationBootstrap { if (this.runner == null) { synchronized (this) { if (this.runner == null) { - this.classLoader = createClassLoader(); + this.classLoader = createClassLoader(mainClass); this.runner = new ApplicationRunner(this.classLoader, mainClass.getName()); Runtime.getRuntime().addShutdownHook(new Thread(this::close)); @@ -110,8 +113,8 @@ public class ApplicationBootstrap { return true; } - private URLClassLoader createClassLoader() { - URL[] urls = findClassPath(); + private URLClassLoader createClassLoader(Class mainClass) { + URL[] urls = findClassPath(mainClass); if (urls.length == 1) { URL[] classpath = extractClasspath(urls[0]); if (classpath != null) { @@ -138,16 +141,15 @@ public class ApplicationBootstrap { return new URLClassLoader(child.toArray(new URL[0]), base); } - private URL[] findClassPath() { - ClassLoader base = getClass().getClassLoader(); + private URL[] findClassPath(Class mainClass) { + ClassLoader base = mainClass.getClassLoader(); if (!(base instanceof URLClassLoader)) { try { // Guess the classpath, based on where we can resolve existing resources List list = Collections - .list(getClass().getClassLoader().getResources("META-INF")); + .list(mainClass.getClassLoader().getResources("META-INF")); List result = new ArrayList<>(); - result.add( - getClass().getProtectionDomain().getCodeSource().getLocation()); + result.add(mainClass.getProtectionDomain().getCodeSource().getLocation()); for (URL url : list) { String path = url.toString(); path = path.substring(0, path.length() - "/META-INF".length()); diff --git a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/ApplicationRunner.java b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/ApplicationRunner.java index b3e1acd47..02a10c2fa 100644 --- a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/ApplicationRunner.java +++ b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/ApplicationRunner.java @@ -27,6 +27,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.support.LiveBeansView; import org.springframework.expression.Expression; +import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeLocator; @@ -50,9 +51,15 @@ public class ApplicationRunner { private StandardEvaluationContext app; + private final SpelParserConfiguration config; + + private final StandardTypeLocator typeLocator; + public ApplicationRunner(ClassLoader classLoader, String source) { this.classLoader = classLoader; this.source = source; + this.config = new SpelParserConfiguration(null, this.classLoader); + this.typeLocator = new StandardTypeLocator(this.classLoader); } public void run(String... args) { @@ -138,8 +145,10 @@ public class ApplicationRunner { } public Object evaluate(String expression, Object root, Object... attrs) { - Expression parsed = new SpelExpressionParser().parseExpression(expression); + Expression parsed = new SpelExpressionParser(this.config) + .parseExpression(expression); StandardEvaluationContext context = new StandardEvaluationContext(root); + context.setTypeLocator(this.typeLocator); if (attrs.length % 2 != 0) { throw new IllegalArgumentException( "Context attributes must be name, value pairs"); diff --git a/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/SpringFunctionFluxConfigurationTests.java b/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/SpringFunctionFluxConfigurationTests.java index d2603e6a9..730fe05da 100644 --- a/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/SpringFunctionFluxConfigurationTests.java +++ b/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/SpringFunctionFluxConfigurationTests.java @@ -16,6 +16,8 @@ package org.springframework.cloud.function.deployer; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.function.Function; import org.junit.After; @@ -27,11 +29,10 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.system.JavaVersion; import org.springframework.cloud.function.context.FunctionCatalog; +import org.springframework.cloud.function.context.catalog.FunctionInspector; import static org.assertj.core.api.Assertions.assertThat; -import reactor.core.publisher.Flux; - @SpringBootConfiguration @EnableAutoConfiguration @EnableFunctionDeployer @@ -39,12 +40,14 @@ public class SpringFunctionFluxConfigurationTests { private Object catalog; + private Object inspector; + private ApplicationBootstrap bootstrap; @Before public void run() { Assume.assumeTrue("Java > 8", - JavaVersion.getJavaVersion().isOlderThan(JavaVersion.EIGHT)); + JavaVersion.getJavaVersion().isOlderThan(JavaVersion.NINE)); if (bootstrap == null) { bootstrap = new ApplicationBootstrap(); bootstrap.run(SpringFunctionFluxConfigurationTests.class, @@ -52,6 +55,7 @@ public class SpringFunctionFluxConfigurationTests { "--function.bean=foos", "--function.main=com.example.functions.FunctionApp"); catalog = bootstrap.getRunner().getBean(FunctionCatalog.class.getName()); + inspector = bootstrap.getRunner().getBean(FunctionInspector.class.getName()); } } @@ -62,18 +66,31 @@ public class SpringFunctionFluxConfigurationTests { } } - // @TestPropertySource(properties = { "", - // "function.main=com.example.functions.FunctionApp" }) - // public static class SourceTests extends SpringFunctionFluxConfigurationTests { - @Test public void test() throws Exception { @SuppressWarnings("unchecked") - Function, Flux> function = (Function, Flux>) bootstrap + Function function = (Function) bootstrap .getRunner() .evaluate("lookup(T(java.util.function.Function), 'function0')", catalog); - assertThat(function.apply(Flux.just(new Foo("foo"))).blockFirst().getValue()) - .isEqualTo("FOO"); + assertThat(function).isNotNull(); + Class inputType = (Class) bootstrap.getRunner() + .evaluate("getInputType(#function)", inspector, "function", function); + assertThat(inputType.getName()).isEqualTo("com.example.functions.Foo"); + Object foo = create(inputType); + Class outputType = (Class) bootstrap.getRunner() + .evaluate("getOutputType(#function)", inspector, "function", function); + assertThat(outputType.getName()).isEqualTo("com.example.functions.Foo"); + String value = (String) bootstrap.getRunner().evaluate( + "apply(T(reactor.core.publisher.Flux).just(#foo)).blockFirst().getValue()", + function, "foo", foo); + assertThat(value).isEqualTo("FOO"); + } + + private Object create(Class inputType) throws InstantiationException, + IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Constructor constructor = inputType.getConstructor(String.class); + constructor.setAccessible(true); + return constructor.newInstance("foo"); } }