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:
Marius Bogoevici
2015-08-06 09:04:57 -04:00
committed by Mark Fisher
parent 80391fa876
commit bd47560bd5
55 changed files with 1368 additions and 1451 deletions

View File

@@ -1 +0,0 @@
artifacts

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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>&lt;groupId&gt;:&lt;artifactId&gt;[:&lt;extension&gt;[:&lt;classifier&gt;]]:&lt;version&gt;</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);
}
}
}
}

View File

@@ -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;

View File

@@ -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()]);
}
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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;
}
}