I've answered half of my own question (re: the ExecutorChannel) and wanted to document it here, although I'm still interested in thoughts and confirmation from the group at large.
I just read through the source for ExecutorChannel. Here's a synopsis.
ExecutorChannel is a very thin wrapper around a UnicastingDispatcher and provides no real concurrency itself. The magic is actually in the latter.
UnicastingDispatcher (an implementation of MessageDispatcher) takes an optional org.springframework.core.task.TaskExecutor as a constructor arg. During invocation of doDispatch() (called when actually attempting to deliver a Message), if there is a supplied TaskExecutor, a one line anonymous Runnable wraps the call and passes it to the executor. If there is no executor supplied, it invokes doDispatch() directly. Here's the relevant code.
Code:
// From UnicastingDispatcher#dispatch() SI 1.0.3.RELEASE
public final boolean dispatch(final Message<?> message) {
if (this.taskExecutor != null) {
this.taskExecutor.execute(new Runnable() {
public void run() {
doDispatch(message);
}
});
return true;
}
return this.doDispatch(message);
}
The only thing special about ExecutorChannel is that it accepts a TaskExecutor and passes it to the instance of UnicastingDispatcher when it's instantiated. This answers my question about whether or not custom code was required when using an ExecutorChannel. The answer is no; it will do The Right Thing(tm) when given a configured executor.
SS-SI folk, please feel free to correct this if I missed / incorrectly represented anything.