Add support for direct binding
and refactor channel proxy behaviour - Remove DirectChannelProxyFactory; - Proxies create channels as necessary, including support for PollableChannels; - Channel beans are using the bean factory methods for the proxies; - Coordinate bind/unbind across a context through the internal Bindable interface; - Add support for embedding modules as independent child contexts of the main module, wrapped in a Bindable interface; - Add support for channel name namespacing; - Use a shared channel registry (consulted by the proxies) to create direct bindings; Restrict the use of SharedChannelRegistry to aggregation Fixed copyrights Polishing based on comments from the previous branch Tweaks - namespacing takes into account module index, so multiple modules with the same class can partiticipate - fix how properties are passes to parent/child - Split ChannelBindingLifecycle from ChannelBindingAdapter and register it with each child; Javadoc updates Pass only prefixed properties Actually use the filtering method Lifecycle simplifications - Removing BindableChannelWrapper and UnbindOnCloseApplicationListener and relying on ChannelBindingListener solely; - Some code cleanup Further simplifications More cleanup Remove BindableUtils, rename ChannelBindingAdapter Minor improvements Addressing some comments, renaming, etc More polishing
This commit is contained in:
committed by
Mark Fisher
parent
80391fa876
commit
bd47560bd5
@@ -1 +0,0 @@
|
||||
artifacts
|
||||
@@ -84,6 +84,7 @@ public class ModuleJarLauncher extends ExecutableArchiveLauncher {
|
||||
if (systemClassLoader instanceof URLClassLoader) {
|
||||
// add the URLs of the application classloader to the created classloader
|
||||
// to compensate for LaunchedURLClassLoader not delegating to parent to retrieve resources
|
||||
@SuppressWarnings("resource")
|
||||
URLClassLoader systemUrlClassLoader = (URLClassLoader) systemClassLoader;
|
||||
URL[] mergedUrls = new URL[urls.length + systemUrlClassLoader.getURLs().length];
|
||||
System.arraycopy(urls, 0, mergedUrls, 0, urls.length);
|
||||
@@ -91,9 +92,8 @@ public class ModuleJarLauncher extends ExecutableArchiveLauncher {
|
||||
systemUrlClassLoader.getURLs().length);
|
||||
// add the extension classloader as parent to the created context, if accessible
|
||||
return new LaunchedURLClassLoader(mergedUrls, systemUrlClassLoader.getParent());
|
||||
} else {
|
||||
return new LaunchedURLClassLoader(urls, systemClassLoader);
|
||||
}
|
||||
return new LaunchedURLClassLoader(urls, systemClassLoader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2015 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.stream.module.launcher;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.util.AsciiBytes;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
class ArchiveMatchingEntryFilter implements Archive.EntryFilter {
|
||||
|
||||
public static final ArchiveMatchingEntryFilter FILTER = new ArchiveMatchingEntryFilter();
|
||||
|
||||
private static final AsciiBytes LIB = new AsciiBytes("lib/");
|
||||
|
||||
@Override
|
||||
public boolean matches(Archive.Entry entry) {
|
||||
return isNestedArchive(entry);
|
||||
}
|
||||
|
||||
protected boolean isNestedArchive(Archive.Entry entry) {
|
||||
return !entry.isDirectory() && entry.getName().startsWith(LIB);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.cloud.stream.module.launcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -24,11 +26,19 @@ import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.loader.LaunchedURLClassLoader;
|
||||
import org.springframework.boot.loader.ModuleJarLauncher;
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.archive.JarFileArchive;
|
||||
import org.springframework.cloud.stream.module.resolver.ModuleResolver;
|
||||
import org.springframework.cloud.stream.module.utils.ClassloaderUtils;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -42,6 +52,14 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class ModuleLauncher {
|
||||
|
||||
public static final String AGGREGATE_APPLICATION_CLASS = "org.springframework.cloud.stream.aggregate.AggregateApplication";
|
||||
|
||||
public static final String AGGREGATE_APPLICATION_RUN_METHOD = "run";
|
||||
|
||||
public static final String MODULE_AGGREGATOR_RUNNER_THREAD_NAME = "module-aggregator-runner";
|
||||
|
||||
private Log log = LogFactory.getLog(ModuleLauncher.class);
|
||||
|
||||
private static final String DEFAULT_EXTENSION = "jar";
|
||||
|
||||
private static final String DEFAULT_CLASSIFIER = "exec";
|
||||
@@ -68,15 +86,26 @@ public class ModuleLauncher {
|
||||
* <code><groupId>:<artifactId>[:<extension>[:<classifier>]]:<version></code>
|
||||
*
|
||||
* @param moduleLaunchRequests a list of modules with their arguments
|
||||
* @param aggregate whether the modules should be aggregated at launch
|
||||
* @param parentArgs a list of arguments for the whole aggregate
|
||||
*/
|
||||
public void launch(List<ModuleLaunchRequest> moduleLaunchRequests) {
|
||||
public void launch(List<ModuleLaunchRequest> moduleLaunchRequests, boolean aggregate, String parentArgs[]) {
|
||||
List<ModuleLaunchRequest> reversed = new ArrayList<>(moduleLaunchRequests);
|
||||
Collections.reverse(reversed);
|
||||
for (ModuleLaunchRequest moduleLaunchRequest : reversed) {
|
||||
String module = moduleLaunchRequest.getModule();
|
||||
moduleLaunchRequest.addArgument("spring.jmx.default-domain", module.replace("/", ".").replace(":", "."));
|
||||
launchModule(module, toArgArray(moduleLaunchRequest.getArguments()));
|
||||
if (moduleLaunchRequests.size() == 1 || !aggregate) {
|
||||
launchIndividualModules(moduleLaunchRequests);
|
||||
}
|
||||
else {
|
||||
launchAggregatedModules(moduleLaunchRequests, parentArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public void launch(List<ModuleLaunchRequest> moduleLaunchRequests, boolean aggregate) {
|
||||
this.launch(moduleLaunchRequests, aggregate, new String[0]);
|
||||
}
|
||||
|
||||
public void launch(List<ModuleLaunchRequest> moduleLaunchRequests) {
|
||||
this.launch(moduleLaunchRequests, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,6 +121,56 @@ public class ModuleLauncher {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void launchAggregatedModules(List<ModuleLaunchRequest> moduleLaunchRequests, final String[] parentArgs) {
|
||||
try {
|
||||
List<String> mainClassNames = new ArrayList<>();
|
||||
List<URL> jarURLs = new ArrayList<>();
|
||||
List<String> seenArchives = new ArrayList<>();
|
||||
final List<String[]> arguments = new ArrayList<>();
|
||||
// aggregate jars from all modules and extract their main Classes
|
||||
for (ModuleLaunchRequest moduleLaunchRequest : moduleLaunchRequests) {
|
||||
Resource resource = resolveModule(moduleLaunchRequest.getModule());
|
||||
JarFileArchive jarFileArchive = new JarFileArchive(resource.getFile());
|
||||
jarURLs.add(jarFileArchive.getUrl());
|
||||
for (Archive archive : jarFileArchive.getNestedArchives(ArchiveMatchingEntryFilter.FILTER)) {
|
||||
// avoid duplication based on unique JAR names
|
||||
// TODO - read the metadata from the JARs, do proper version resolution on merge
|
||||
String urlAsString = archive.getUrl().toString();
|
||||
String jarNameWithExtension = urlAsString.substring(0, urlAsString.lastIndexOf("!/"));
|
||||
String jarNameWithoutExtension = jarNameWithExtension.substring(jarNameWithExtension.lastIndexOf("/") + 1);
|
||||
if (!seenArchives.contains(jarNameWithoutExtension)) {
|
||||
seenArchives.add(jarNameWithoutExtension);
|
||||
jarURLs.add(archive.getUrl());
|
||||
}
|
||||
}
|
||||
mainClassNames.add(jarFileArchive.getMainClass());
|
||||
arguments.add(toArgArray(moduleLaunchRequest.getArguments()));
|
||||
}
|
||||
final ClassLoader classLoader = new LaunchedURLClassLoader(jarURLs.toArray(new URL[jarURLs.size()]),
|
||||
ClassloaderUtils.getExtensionClassloader());
|
||||
final List<Class<?>> mainClasses = new ArrayList<>();
|
||||
for (String mainClass : mainClassNames) {
|
||||
mainClasses.add(ClassUtils.forName(mainClass, classLoader));
|
||||
}
|
||||
Runnable moduleAggregatorRunner = new ModuleAggregatorRunner(classLoader, mainClasses, parentArgs, arguments);
|
||||
|
||||
Thread moduleAggregatorRunnerThread = new Thread(moduleAggregatorRunner);
|
||||
moduleAggregatorRunnerThread.setContextClassLoader(classLoader);
|
||||
moduleAggregatorRunnerThread.setName(MODULE_AGGREGATOR_RUNNER_THREAD_NAME);
|
||||
moduleAggregatorRunnerThread.start();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("failed to start aggregated modules: " + StringUtils.collectionToCommaDelimitedString(moduleLaunchRequests), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void launchIndividualModules(List<ModuleLaunchRequest> reversed) {
|
||||
for (ModuleLaunchRequest moduleLaunchRequest : reversed) {
|
||||
String module = moduleLaunchRequest.getModule();
|
||||
moduleLaunchRequest.addArgument("spring.jmx.default-domain", module.replace("/", ".").replace(":", "."));
|
||||
launchModule(module, toArgArray(moduleLaunchRequest.getArguments()));
|
||||
}
|
||||
}
|
||||
|
||||
private void launchModule(String module, String[] args) {
|
||||
try {
|
||||
Resource resource = resolveModule(module);
|
||||
@@ -116,4 +195,37 @@ public class ModuleLauncher {
|
||||
return this.moduleResolver.resolve(groupId, artifactId, extension, classifier, version);
|
||||
}
|
||||
|
||||
private class ModuleAggregatorRunner implements Runnable {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private final String[] parentArgs;
|
||||
|
||||
private final List<Class<?>> mainClasses;
|
||||
|
||||
private final List<String[]> arguments;
|
||||
|
||||
public ModuleAggregatorRunner(ClassLoader classLoader, List<Class<?>> mainClasses, String[] parentArgs, List<String[]> moduleArguments) {
|
||||
this.classLoader = classLoader;
|
||||
this.parentArgs = parentArgs;
|
||||
this.mainClasses = mainClasses;
|
||||
this.arguments = moduleArguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// we expect the class and method to be found on the module classpath
|
||||
Class<?> moduleAggregatorClass = ClassUtils.forName(AGGREGATE_APPLICATION_CLASS, classLoader);
|
||||
Method aggregateMethod = ReflectionUtils.findMethod(moduleAggregatorClass,
|
||||
AGGREGATE_APPLICATION_RUN_METHOD, Class[].class, String[].class, String[][].class);
|
||||
aggregateMethod.invoke(null,
|
||||
mainClasses.toArray(new Class<?>[mainClasses.size()]),
|
||||
parentArgs, arguments.toArray(new String[][] {}));
|
||||
} catch (Exception e) {
|
||||
log.error("failed to launch aggregated modules :" + StringUtils.collectionToCommaDelimitedString(mainClasses), e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,12 +49,17 @@ import org.springframework.core.env.Environment;
|
||||
public class ModuleLauncherProperties {
|
||||
|
||||
/**
|
||||
* Array of coordinates for modules that need to be launched.
|
||||
* True if aggregating multiple modules when launched together
|
||||
*/
|
||||
private boolean aggregate;
|
||||
|
||||
/**
|
||||
* File path to a locally available maven repository, where modules will be downloaded.
|
||||
*/
|
||||
private String[] modules;
|
||||
|
||||
/**
|
||||
* Map of arguments, keyed by the 0-based index in the {@kink #modules array}.
|
||||
* Map of arguments, keyed by the 0-based index in the {@link #modules array}.
|
||||
*/
|
||||
private Map<Integer, Map<String, String>> args = new HashMap<>();
|
||||
|
||||
@@ -62,6 +67,14 @@ public class ModuleLauncherProperties {
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
public boolean isAggregate() {
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
public void setAggregate(boolean aggregate) {
|
||||
this.aggregate = aggregate;
|
||||
}
|
||||
|
||||
@NotEmpty(message = "A list of modules must be specified.")
|
||||
public String[] getModules() {
|
||||
return modules;
|
||||
|
||||
@@ -58,7 +58,9 @@ public class ModuleLauncherRunner implements CommandLineRunner {
|
||||
}
|
||||
log.info(sb.toString());
|
||||
}
|
||||
this.moduleLauncher.launch(launchRequests);
|
||||
this.moduleLauncher.launch(launchRequests,
|
||||
moduleLauncherProperties.isAggregate(),
|
||||
moduleLauncherProperties.isAggregate() ? extractAggregateProperties(args) : new String[0]);
|
||||
}
|
||||
|
||||
private List<ModuleLaunchRequest> generateModuleLaunchRequests() {
|
||||
@@ -71,4 +73,15 @@ public class ModuleLauncherRunner implements CommandLineRunner {
|
||||
}
|
||||
return requests;
|
||||
}
|
||||
|
||||
private String[] extractAggregateProperties(String[] args) {
|
||||
List<String> filteredProperties = new ArrayList<>();
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith("--spring.")) {
|
||||
filteredProperties.add(arg);
|
||||
}
|
||||
}
|
||||
return filteredProperties.toArray(new String[filteredProperties.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,6 @@ public interface ModuleResolver {
|
||||
* @param version the version
|
||||
* @return the resource
|
||||
*/
|
||||
public Resource resolve(String groupId, String artifactId, String extension, String classifier, String version);
|
||||
Resource resolve(String groupId, String artifactId, String extension, String classifier, String version);
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2015 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.stream.module.utils;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class ClassloaderUtils {
|
||||
|
||||
/**
|
||||
* Retrieves the extension classloader of the current JVM, if accessible. In general the extension classloader is
|
||||
* found in a hierarchy as the parent of the application classloader. If such a hierarchy does not exist, it will
|
||||
* return the application classloader itself.
|
||||
*
|
||||
* @return the classloader
|
||||
*/
|
||||
public static ClassLoader getExtensionClassloader() {
|
||||
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
|
||||
// try to retrieve the extension classloader
|
||||
ClassLoader extensionClassLoader = systemClassLoader != null ? systemClassLoader.getParent() : null;
|
||||
// set the classloader for the module as the extension classloader if available
|
||||
// fall back to the system classloader (which can also be null) if not available
|
||||
return extensionClassLoader != null ? extensionClassLoader : systemClassLoader;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user