diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FunctionRegistrySupport.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/AbstractFunctionRegistry.java similarity index 67% rename from spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FunctionRegistrySupport.java rename to spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/AbstractFunctionRegistry.java index 3d693a32a..43d4e674f 100644 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FunctionRegistrySupport.java +++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/AbstractFunctionRegistry.java @@ -16,6 +16,8 @@ package org.springframework.cloud.function.registry; +import java.util.HashMap; +import java.util.Map; import java.util.function.Function; import org.springframework.cloud.function.compiler.CompiledFunctionFactory; @@ -27,11 +29,13 @@ import org.springframework.util.Assert; /** * @author Mark Fisher */ -public abstract class FunctionRegistrySupport implements FunctionRegistry { +public abstract class AbstractFunctionRegistry implements FunctionRegistry { + + private static final Map> FACTORY_CACHE = new HashMap<>(); private final FunctionCompiler compiler = new FunctionCompiler(); - private final SimpleClassLoader classLoader = new SimpleClassLoader(FunctionRegistrySupport.class.getClassLoader()); + private final SimpleClassLoader classLoader = new SimpleClassLoader(AbstractFunctionRegistry.class.getClassLoader()); @Override @SuppressWarnings("unchecked") @@ -44,19 +48,33 @@ public abstract class FunctionRegistrySupport implements FunctionRegistry { return function; } + @Override + public final Function lookup(String name) { + @SuppressWarnings("unchecked") + FunctionFactory factory = (FunctionFactory) FACTORY_CACHE.get(name); + if (factory != null) { + return factory.getFunction(); + } + return this.doLookup(name); + } + + protected abstract Function doLookup(String name); + protected CompiledFunctionFactory compile(String name, String code) { return this.compiler.compile(name, code); } @SuppressWarnings("unchecked") - protected Function deserialize(String name, byte[] bytes) { + protected Function deserialize(final String name, byte[] bytes) { Assert.hasLength(name, "name must not be empty"); String firstLetter = name.substring(0, 1).toUpperCase(); - name = (name.length() > 1) ? firstLetter + name.substring(1) : firstLetter; - String className = String.format("%s.%sFunctionFactory", FunctionCompiler.class.getPackage().getName(), name); + String upperCasedName = (name.length() > 1) ? firstLetter + name.substring(1) : firstLetter; + String className = String.format("%s.%sFunctionFactory", FunctionCompiler.class.getPackage().getName(), upperCasedName); Class factoryClass = this.classLoader.defineClass(className, bytes); try { - return ((FunctionFactory) factoryClass.newInstance()).getFunction(); + FunctionFactory factory = ((FunctionFactory) factoryClass.newInstance()); + FACTORY_CACHE.put(name, factory); + return factory.getFunction(); } catch (InstantiationException | IllegalAccessException e) { throw new IllegalArgumentException("failed to deserialize function", e); diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistry.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistry.java index 95dd7c3c2..5fbb3bd0f 100644 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistry.java +++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/FileSystemFunctionRegistry.java @@ -27,7 +27,7 @@ import org.springframework.util.FileCopyUtils; /** * @author Mark Fisher */ -public class FileSystemFunctionRegistry extends FunctionRegistrySupport { +public class FileSystemFunctionRegistry extends AbstractFunctionRegistry { private final File directory; @@ -48,7 +48,7 @@ public class FileSystemFunctionRegistry extends FunctionRegistrySupport { } @Override - public Function lookup(String name) { + public Function doLookup(String name) { try { byte[] bytes = FileCopyUtils.copyToByteArray(new File(this.directory, fileName(name))); return this.deserialize(name, bytes); diff --git a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/InMemoryFunctionRegistry.java b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/InMemoryFunctionRegistry.java index 346725ae4..0105d0c1a 100644 --- a/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/InMemoryFunctionRegistry.java +++ b/spring-cloud-function-core/src/main/java/org/springframework/cloud/function/registry/InMemoryFunctionRegistry.java @@ -22,13 +22,13 @@ import java.util.function.Function; /** * @author Mark Fisher */ -public class InMemoryFunctionRegistry extends FunctionRegistrySupport { +public class InMemoryFunctionRegistry extends AbstractFunctionRegistry { private final ConcurrentHashMap> map = new ConcurrentHashMap<>(); @Override @SuppressWarnings("unchecked") - public Function lookup(String name) { + public Function doLookup(String name) { return this.map.get(name); } diff --git a/spring-cloud-function-core/src/test/java/org/springframework/cloud/function/gateway/LocalFunctionGatewayTests.java b/spring-cloud-function-core/src/test/java/org/springframework/cloud/function/gateway/LocalFunctionGatewayTests.java new file mode 100644 index 000000000..a67d6615c --- /dev/null +++ b/spring-cloud-function-core/src/test/java/org/springframework/cloud/function/gateway/LocalFunctionGatewayTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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 org.springframework.cloud.function.gateway; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.cloud.function.registry.FileSystemFunctionRegistry; +import org.springframework.cloud.function.registry.FunctionRegistry; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import reactor.core.publisher.Flux; + +/** + * @author Mark Fisher + */ +public class LocalFunctionGatewayTests { + + private final FunctionRegistry registry = new FileSystemFunctionRegistry(); + + private final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + + @Before + public void init() { + this.scheduler.initialize(); + } + + @Test + public void test() { + LocalFunctionGateway gateway = new LocalFunctionGateway(registry, scheduler); + Flux output = gateway.invoke("uppercase", Flux.just("foo", "bar")); + List results = output.collectList().block(); + assertEquals("FOO", results.get(0)); + assertEquals("BAR", results.get(1)); + } + + @Test + public void testMultiple() { + for (int i = 0; i < 100; i++) { + test(); + } + } +}