GH-9683: Bring back controlBus() API

Fixes: https://github.com/spring-projects/spring-integration/issues/9683

Since we don't support SpEL-based Control Bus functionality anymore,
there is no need to keep separate `controlBusOnRegistry()`

* In all DSLs Deprecate `controlBusOnRegistry()` and restore `controlBus()` which is now fully based
on the `ControlBusCommandRegistry`
* Deprecate now out of use `<control-bus use-registry="">` attribute
* Fix `ControlBusParser` to not deal with `use-registry` attribute anymore
* Remove deprecated before `ExpressionControlBusFactoryBean` and `ExpressionCommandMessageProcessor`
* Remove `use-registry` from test configs
* Fix `ControlBusChainTests` to rely on a new Control Bus functionality
This commit is contained in:
Artem Bilan
2025-01-02 14:06:31 -05:00
parent 37664fb2aa
commit d9f21fef42
24 changed files with 54 additions and 284 deletions

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2002-2024 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
*
* https://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.integration.config;
import org.springframework.expression.MethodFilter;
import org.springframework.integration.handler.ServiceActivatingHandler;
import org.springframework.messaging.MessageHandler;
/**
* FactoryBean for creating {@link MessageHandler} instances to handle a message as a SpEL expression.
*
* @author Dave Syer
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
*
* @since 2.0
*
* @deprecated in favor of {@link ControlBusFactoryBean}
*/
@Deprecated(since = "6.4", forRemoval = true)
public class ExpressionControlBusFactoryBean extends AbstractSimpleMessageHandlerFactoryBean<MessageHandler> {
@SuppressWarnings("removal")
private static final MethodFilter METHOD_FILTER = new org.springframework.integration.expression.ControlBusMethodFilter();
private Long sendTimeout;
public void setSendTimeout(Long sendTimeout) {
this.sendTimeout = sendTimeout;
}
@SuppressWarnings("removal")
@Override
protected MessageHandler createHandler() {
org.springframework.integration.handler.ExpressionCommandMessageProcessor processor =
new org.springframework.integration.handler.ExpressionCommandMessageProcessor(METHOD_FILTER, getBeanFactory());
ServiceActivatingHandler handler = new ServiceActivatingHandler(processor);
if (this.sendTimeout != null) {
handler.setSendTimeout(this.sendTimeout);
}
return handler;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@@ -32,18 +32,8 @@ import org.springframework.integration.config.ControlBusFactoryBean;
public class ControlBusParser extends AbstractConsumerEndpointParser {
@Override
@SuppressWarnings("removal")
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ControlBusFactoryBean.class);
if (Boolean.FALSE.equals(Boolean.parseBoolean(element.getAttribute("use-registry")))) {
builder = BeanDefinitionBuilder.genericBeanDefinition(
org.springframework.integration.config.ExpressionControlBusFactoryBean.class);
parserContext.getReaderContext()
.warning("The 'ExpressionControlBusFactoryBean' for '<control-bus>' is deprecated (for removal) " +
"in favor of 'ControlBusFactoryBean'. " +
"Set 'use-registry' attribute to 'true' to switch to a new functionality.",
element);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "order");
return builder;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2024 the original author or authors.
* Copyright 2019-2025 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.
@@ -518,10 +518,12 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
* at the current {@link IntegrationFlow} chain position.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @since 6.4
* @deprecated since 6.5 in favor of {@link #controlBus()}
* @see ControlBusMessageProcessor
*/
@Deprecated(since = "6.5", forRemoval = true)
public B controlBusOnRegistry() {
return controlBusOnRegistry(null);
return controlBus();
}
/**
@@ -530,20 +532,20 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
* @param endpointConfigurer the {@link Consumer} to accept integration endpoint options.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @since 6.4
* @deprecated since 6.5 in favor of {@link #controlBus(Consumer)}
* @see GenericEndpointSpec
* @see ControlBusMessageProcessor
*/
@Deprecated(since = "6.5", forRemoval = true)
public B controlBusOnRegistry(@Nullable Consumer<GenericEndpointSpec<ServiceActivatingHandler>> endpointConfigurer) {
return handle(new ServiceActivatingHandler(new ControlBusMessageProcessor()), endpointConfigurer);
return controlBus(endpointConfigurer);
}
/**
* Populate the {@code Control Bus} EI Pattern specific {@link MessageHandler} implementation
* at the current {@link IntegrationFlow} chain position.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @deprecated in favor of {@link #controlBusOnRegistry()} - will be restored in next version.
*/
@Deprecated(since = "6.4")
public B controlBus() {
return controlBus(null);
}
@@ -553,15 +555,10 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
* at the current {@link IntegrationFlow} chain position.
* @param endpointConfigurer the {@link Consumer} to accept integration endpoint options.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @deprecated in favor of {@link #controlBusOnRegistry(Consumer)} - will be restored in next version.
* @see GenericEndpointSpec
*/
@Deprecated(since = "6.4")
@SuppressWarnings("removal")
public B controlBus(@Nullable Consumer<GenericEndpointSpec<ServiceActivatingHandler>> endpointConfigurer) {
return handle(new ServiceActivatingHandler(
new org.springframework.integration.handler.ExpressionCommandMessageProcessor(
new org.springframework.integration.expression.ControlBusMethodFilter())), endpointConfigurer);
return handle(new ServiceActivatingHandler(new ControlBusMessageProcessor()), endpointConfigurer);
}
/**

View File

@@ -1,150 +0,0 @@
/*
* Copyright 2002-2024 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
*
* https://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.integration.handler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.MethodFilter;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.IntegrationPattern;
import org.springframework.integration.IntegrationPatternType;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.util.CollectionUtils;
/**
* A MessageProcessor implementation that expects an Expression or expressionString
* as the Message payload. When processing, it simply evaluates that expression.
*
* @author Dave Syer
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @author Ngoc Nhan
*
* @since 2.0
*
* @deprecated in favor of {@link ControlBusMessageProcessor}
*/
@Deprecated(since = "6.4", forRemoval = true)
public class ExpressionCommandMessageProcessor extends AbstractMessageProcessor<Object>
implements IntegrationPattern {
@Nullable
private final MethodFilter methodFilter;
public ExpressionCommandMessageProcessor() {
this.methodFilter = null;
}
public ExpressionCommandMessageProcessor(@Nullable MethodFilter methodFilter) {
this(methodFilter, null);
}
public ExpressionCommandMessageProcessor(@Nullable MethodFilter methodFilter, @Nullable BeanFactory beanFactory) {
this.methodFilter = methodFilter;
if (beanFactory != null) {
setBeanFactory(beanFactory);
}
}
@Override
public final void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
if (this.methodFilter != null) {
MethodResolver methodResolver = new ExpressionCommandMethodResolver(this.methodFilter);
if (getEvaluationContext() instanceof StandardEvaluationContext standardEvaluationContext) {
standardEvaluationContext.setMethodResolvers(Collections.singletonList(methodResolver));
}
else {
logger.warn("Cannot customize the 'SimpleEvaluationContext'");
}
}
}
@Override
public IntegrationPatternType getIntegrationPatternType() {
return IntegrationPatternType.control_bus;
}
/**
* Evaluates the Message payload expression as a command.
* @throws IllegalArgumentException if the payload is not an Exception or String
*/
@Override
@Nullable
public Object processMessage(Message<?> message) {
Object expression = message.getPayload();
if (expression instanceof Expression) {
return evaluateExpression((Expression) expression, message);
}
if (expression instanceof String) {
return evaluateExpression((String) expression, message);
}
throw new IllegalArgumentException("Message payload must be an Expression instance or an expression String.");
}
private static final class ExpressionCommandMethodResolver extends ReflectiveMethodResolver {
private final MethodFilter methodFilter;
ExpressionCommandMethodResolver(MethodFilter methodFilter) {
this.methodFilter = methodFilter;
}
@Override
public MethodExecutor resolve(EvaluationContext context,
Object targetObject, String name, List<TypeDescriptor> argumentTypes)
throws AccessException {
validateMethod(targetObject, name, !CollectionUtils.isEmpty(argumentTypes) ? argumentTypes.size() : 0);
return super.resolve(context, targetObject, name, argumentTypes);
}
private void validateMethod(Object targetObject, String name, int argumentCount) {
Class<?> type = (targetObject instanceof Class ? (Class<?>) targetObject : targetObject.getClass());
Method[] methods = type.getMethods();
List<Method> candidates = new ArrayList<>();
for (Method method : methods) {
if (method.getName().equals(name) && method.getParameterTypes().length == argumentCount) {
candidates.add(method);
}
}
List<Method> supportedMethods = this.methodFilter.filter(candidates);
if (supportedMethods.isEmpty()) {
String methodDescription = (!candidates.isEmpty()) ? candidates.get(0).toString() : name;
throw new EvaluationException("The method '" + methodDescription +
"' is not supported by this command processor. " +
"If using the Control Bus, consider adding @ManagedOperation or @ManagedAttribute.");
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2024 the original author or authors.
* Copyright 2020-2025 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.
@@ -285,17 +285,15 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
* at the current [IntegrationFlow] chain position.
* @since 6.4
*/
@Deprecated("Use 'controlBus()' instead.", replaceWith = ReplaceWith("controlBus()"))
fun controlBusOnRegistry(endpointConfigurer: GenericEndpointSpec<ServiceActivatingHandler>.() -> Unit = {}) {
this.delegate.controlBusOnRegistry(endpointConfigurer)
controlBus(endpointConfigurer)
}
/**
* Populate the `Control Bus` EI Pattern specific [MessageHandler] implementation
* at the current [IntegrationFlow] chain position.
*/
@Deprecated("Use 'controlBusOnRegistry()' instead.",
replaceWith = ReplaceWith("controlBusOnRegistry()"))
@Suppress("DEPRECATION", "REMOVAL")
fun controlBus(endpointConfigurer: GenericEndpointSpec<ServiceActivatingHandler>.() -> Unit = {}) {
this.delegate.controlBus(endpointConfigurer)
}

View File

@@ -4978,13 +4978,12 @@ The list of component name patterns you want to track (e.g., tracked-components
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="use-registry" type="xsd:boolean" default="false">
<xsd:attribute name="use-registry" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation>
Set true to make Control Bus based on the global 'ControlBusCommandRegistry'
which is a recommended way to configure Control Bus functionality.
The false is by default for backward compatibility and is deprecated.
This attribute will be true by default in the next major version and removed altogether eventually.
[DEPRECATED]
The attribute is out of use since 6.5 and 'ControlBusCommandRegistry' is always configured.
Will be removed in the next version.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -20,6 +20,6 @@
<beans:bean id="aggregatorBean"
class="org.springframework.integration.config.TestAggregatorBean" />
<control-bus input-channel="controlBusChannel" output-channel="nullChannel" use-registry="true"/>
<control-bus input-channel="controlBusChannel" output-channel="nullChannel"/>
</beans:beans>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:groovy="http://www.springframework.org/schema/integration/groovy"
xsi:schemaLocation="http://www.springframework.org/schema/integration
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
@@ -14,6 +15,6 @@
<control-bus/>
</chain>
<beans:bean id="service" class="org.springframework.integration.config.xml.ControlBusChainTests$Service" />
<beans:bean id="service" class="org.springframework.integration.config.xml.ControlBusChainTests$Service"/>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@@ -16,17 +16,17 @@
package org.springframework.integration.config.xml;
import java.util.Date;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@@ -47,13 +47,13 @@ public class ControlBusChainTests {
private PollableChannel output;
@Test
public void testDefaultEvaluationContext() {
public void controlBusInChain() {
Message<?> message =
MessageBuilder.withPayload("@service.convert('aardvark')+headers.foo")
.setHeader("foo", "bar")
MessageBuilder.withPayload("service.convert")
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("data"))
.build();
this.input.send(message);
assertThat(output.receive(0).getPayload()).isEqualTo("catbar");
assertThat(output.receive(0)).extracting(Message::getPayload).isEqualTo("some data");
assertThat(output.receive(0)).isNull();
}
@@ -61,15 +61,7 @@ public class ControlBusChainTests {
@ManagedOperation
public String convert(String input) {
return "cat";
}
}
public static class AdapterService {
public Message<String> receive() {
return new GenericMessage<>(new Date().toString());
return "some " + input;
}
}

View File

@@ -14,7 +14,7 @@
<queue />
</channel>
<control-bus input-channel="input" output-channel="output" use-registry="true">
<control-bus input-channel="input" output-channel="output">
<poller fixed-rate="100" />
</control-bus>

View File

@@ -7,7 +7,7 @@
https://www.springframework.org/schema/beans/spring-beans.xsd">
<control-bus input-channel="inputChannel" auto-startup="true" use-registry="true"/>
<control-bus input-channel="inputChannel" auto-startup="true"/>
<inbound-channel-adapter id="adapter" channel="outputChannel" auto-startup="false" method="receive">
<poller fixed-rate="1000"/>

View File

@@ -14,7 +14,7 @@
<queue />
</channel>
<control-bus input-channel="input" output-channel="output" use-registry="true"/>
<control-bus input-channel="input" output-channel="output"/>
<poller default="true" fixed-rate="100"/>

View File

@@ -10,7 +10,7 @@
<queue/>
</channel>
<control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="false" use-registry="true"/>
<control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="false"/>
<recipient-list-router id="simpleRouter" input-channel="routingChannelA"/>

View File

@@ -15,7 +15,7 @@
<queue/>
</channel>
<control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="true" use-registry="true"/>
<control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="true"/>
<beans:bean id="service" class="org.springframework.integration.config.xml.ControlBusTests$Service" />

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2024 the original author or authors.
* Copyright 2016-2025 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.
@@ -636,7 +636,7 @@ public class IntegrationFlowTests {
@Bean
public IntegrationFlow controlBusFlow() {
return IntegrationFlow.from(ControlBusGateway.class, (gateway) -> gateway.beanName("controlBusGateway"))
.controlBusOnRegistry((endpoint) -> endpoint.id("controlBus"))
.controlBus((endpoint) -> endpoint.id("controlBus"))
.get();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2024 the original author or authors.
* Copyright 2016-2025 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.
@@ -322,7 +322,7 @@ public class FileTests {
@Bean
public IntegrationFlow controlBus() {
return IntegrationFlowDefinition::controlBusOnRegistry;
return IntegrationFlowDefinition::controlBus;
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022-2024 the original author or authors.
* Copyright 2022-2025 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.
@@ -269,14 +269,16 @@ class GroovyIntegrationFlowDefinition {
* at the current {@link IntegrationFlow} chain position.
* @param endpointConfigurer the {@link Consumer} to accept integration endpoint options.
* @since 6.4
* @deprecated in favor of {@link #controlBus}
*/
@Deprecated(since = '6.5', forRemoval = true)
@SuppressWarnings('removal')
GroovyIntegrationFlowDefinition controlBusOnRegistry(
@DelegatesTo(value = GenericEndpointSpec<ServiceActivatingHandler>, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType.class, options = 'org.springframework.integration.dsl.GenericEndpointSpec')
Closure<?> endpointConfigurer = null) {
this.delegate.controlBusOnRegistry createConfigurerIfAny(endpointConfigurer)
this
controlBus endpointConfigurer
}
/**
@@ -284,10 +286,7 @@ class GroovyIntegrationFlowDefinition {
* at the current {@link IntegrationFlow} chain position.
* @param endpointConfigurer the {@link Consumer} to accept integration endpoint options.
* @see GenericEndpointSpec
* @deprecated in favor of {@link #controlBusOnRegistry}
*/
@Deprecated(since = '6.4', forRemoval = true)
@SuppressWarnings('removal')
GroovyIntegrationFlowDefinition controlBus(
@DelegatesTo(value = GenericEndpointSpec<ServiceActivatingHandler>, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType.class, options = 'org.springframework.integration.dsl.GenericEndpointSpec')

View File

@@ -45,7 +45,7 @@
<int:channel id="cbChannel" />
<int:control-bus input-channel="cbChannel" use-registry="true"/>
<int:control-bus input-channel="cbChannel"/>
<int:gateway default-request-channel="cbChannel"
service-interface="org.springframework.integration.ip.tcp.ClientModeControlBusTests$ControlBus"/>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2024 the original author or authors.
* Copyright 2016-2025 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.
@@ -358,7 +358,7 @@ public class JmsTests extends ActiveMQMultiContextTests {
@Bean
public IntegrationFlow controlBus() {
return IntegrationFlowDefinition::controlBusOnRegistry;
return IntegrationFlowDefinition::controlBus;
}
@Bean

View File

@@ -21,7 +21,7 @@
<int:channel id="control" />
<int:control-bus input-channel="control" use-registry="true"/>
<int:control-bus input-channel="control"/>
<int:channel id="qux">
<int:queue />

View File

@@ -16,7 +16,7 @@
<si:channel id="controlChannel"/>
<si:control-bus id = "cb" input-channel="controlChannel" use-registry="true"/>
<si:control-bus id = "cb" input-channel="controlChannel"/>
<jmx:mbean-export id="integrationMbeanExporter" server="mbs" default-domain="tests.ControlBusParser"/>

View File

@@ -14,7 +14,7 @@
<int:channel id="toControlBus" />
<int:control-bus input-channel="toControlBus" use-registry="true"/>
<int:control-bus input-channel="toControlBus"/>
<int-jmx:mbean-export id="integrationMbeanExporter"
default-domain="self-destruct" />

View File

@@ -17,6 +17,6 @@
selector-expression="payload.contains('stop')" />
</int:recipient-list-router>
<int:control-bus input-channel="controlBusChannel" use-registry="true"/>
<int:control-bus input-channel="controlBusChannel"/>
</beans>

View File

@@ -18,3 +18,6 @@ In general the project has been moved to the latest dependency versions.
The deprecated previously usage of `org.springframework.util.concurrent.ListenableFuture` has been removed in favor of `CompletableFuture`.
The previously deprecated SpEL-based Control Bus components have been removed in favor of functionality around `ControlBusCommandRegistry`.
The `<control-bus use-registry="">` attribute is deprecated now without replacement since only `ControlBusCommandRegistry` functionality is available.
The Java DSL `controlBusOnRegistry()` operator is deprecated in favor of restored `controlBus()` which is fully based now on the `ControlBusCommandRegistry`.