Support for isolated class loaders extended to cover more functions

Functions with Flux and Message (as well as POJOs and Flux of POJO
which were already supported) should now work if they are created in
an isolated class loader. Preconditions:

* The class loaders must have the reactor-core (and reactive-streams)
shared between the app and the function. Practically speaking this means
there has to be a parent class loader with just reactive types, and
sibling children for the app and the function. This is not a new
requirement (it was needed for Flux of POJO anyway).

* Message types are handled reflectively, so they don't have to be in a
shared class loader. But they do have to be  on the class path on
both sides (obviously).
This commit is contained in:
Dave Syer
2018-02-16 08:16:55 +00:00
parent ccd3953163
commit 1b624c3531
17 changed files with 615 additions and 33 deletions

View File

@@ -457,6 +457,8 @@ public class ContextFunctionCatalogAutoConfiguration {
if (target instanceof Supplier) {
type = Supplier.class;
findType(target, ParamType.OUTPUT);
findType(target, ParamType.OUTPUT_WRAPPER);
isMessage(target);
registration.target(target((Supplier<?>) target, key));
for (String name : registration.getNames()) {
this.suppliers.put(name, (Supplier<?>) registration.getTarget());
@@ -465,6 +467,8 @@ public class ContextFunctionCatalogAutoConfiguration {
else if (target instanceof Consumer) {
type = Consumer.class;
findType(target, ParamType.INPUT);
findType(target, ParamType.INPUT_WRAPPER);
isMessage(target); // cache wrapper types
registration.target(target((Consumer<?>) target, key));
for (String name : registration.getNames()) {
this.consumers.put(name, (Consumer<?>) registration.getTarget());
@@ -474,6 +478,8 @@ public class ContextFunctionCatalogAutoConfiguration {
type = Function.class;
findType(target, ParamType.INPUT);
findType(target, ParamType.OUTPUT);
findType(target, ParamType.INPUT_WRAPPER);
findType(target, ParamType.OUTPUT_WRAPPER);
isMessage(target); // cache wrapper types
registration.target(target((Function<?, ?>) target, key));
for (String name : registration.getNames()) {

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2016-2017 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.context.message;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import org.springframework.cloud.function.core.FluxWrapper;
import org.springframework.cloud.function.core.Isolated;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author Dave Syer
*
*/
public abstract class MessageUtils {
/**
* Create a message for the handler. If the handler is a wrapper for a function in an
* isolated class loader, then the message will be created with the target class
* loader (therefore the {@link Message} class must be on the classpath of the target
* class loader).
*
* @param handler the function that will be applied to the message
* @param payload the payload of the message
* @param headers the headers for the message
* @return a message with the correct class loader
*/
public static Object create(Object handler, Object payload,
Map<String, Object> headers) {
if (handler instanceof FluxWrapper) {
handler = ((FluxWrapper<?>) handler).getTarget();
}
if (!(handler instanceof Isolated)) {
return MessageBuilder.withPayload(payload).copyHeaders(headers).build();
}
ClassLoader classLoader = ((Isolated) handler).getClassLoader();
Class<?> builder = ClassUtils.resolveClassName(MessageBuilder.class.getName(),
classLoader);
Method withPayload = ClassUtils.getMethod(builder, "withPayload", Object.class);
Method copyHeaders = ClassUtils.getMethod(builder, "copyHeaders", Map.class);
Method build = ClassUtils.getMethod(builder, "build");
Object instance = ReflectionUtils.invokeMethod(withPayload, null, payload);
ReflectionUtils.invokeMethod(copyHeaders, instance, headers);
return ReflectionUtils.invokeMethod(build, instance);
}
/**
* Convert a message from the handler into one that is safe to consume in the caller's
* class laoder. If the handler is a wrapper for a function in an isolated class
* loader, then the message will be created with the target class loader (therefore
* the {@link Message} class must be on the classpath of the target class loader).
*
* @param handler the function that generated the message
* @param message the message to convert
* @return a message with the correct class loader
*/
public static Message<?> unpack(Object handler, Object message) {
if (handler instanceof FluxWrapper) {
handler = ((FluxWrapper<?>) handler).getTarget();
}
if (!(handler instanceof Isolated)) {
if (message instanceof Message) {
return (Message<?>) message;
}
return MessageBuilder.withPayload(message).build();
}
ClassLoader classLoader = ((Isolated) handler).getClassLoader();
Class<?> type = ClassUtils.resolveClassName(Message.class.getName(), classLoader);
Object payload;
Map<String, Object> headers;
if (type.isAssignableFrom(message.getClass())) {
Method getPayload = ClassUtils.getMethod(type, "getPayload");
Method getHeaders = ClassUtils.getMethod(type, "getHeaders");
payload = ReflectionUtils.invokeMethod(getPayload, message);
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) ReflectionUtils
.invokeMethod(getHeaders, message);
headers = map;
} else {
payload = message;
headers = Collections.emptyMap();
}
return MessageBuilder.withPayload(payload).copyHeaders(headers).build();
}
}