From fe203fb7127a1fa141077ab967873cfb8066d8db Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Thu, 25 Jun 2015 15:28:36 -0400 Subject: [PATCH] INT-3747: Zookeeper Documentation JIRA: https://jira.spring.io/browse/INT-3747 INT-3747: Polishing; PR Comments; Factory Beans Fix JavaDocs and simple polishing --- .../config/CuratorFrameworkFactoryBean.java | 152 ++++++++++++++++++ .../config/LeaderInitiatorFactoryBean.java | 147 +++++++++++++++++ .../CuratorFrameworkFactoryBeanTests.java | 45 ++++++ .../LeaderInitiatorFactoryBeanTests.java | 112 +++++++++++++ src/reference/asciidoc/aggregator.adoc | 12 +- src/reference/asciidoc/endpoint.adoc | 59 +++++++ src/reference/asciidoc/index.adoc | 2 + src/reference/asciidoc/meta-data-store.adoc | 21 +++ src/reference/asciidoc/whats-new.adoc | 11 ++ src/reference/asciidoc/zookeeper.adoc | 90 +++++++++++ 10 files changed, 649 insertions(+), 2 deletions(-) create mode 100644 spring-integration-zookeeper/src/main/java/org/springframework/integration/zookeeper/config/CuratorFrameworkFactoryBean.java create mode 100644 spring-integration-zookeeper/src/main/java/org/springframework/integration/zookeeper/config/LeaderInitiatorFactoryBean.java create mode 100644 spring-integration-zookeeper/src/test/java/org/springframework/integration/zookeeper/config/CuratorFrameworkFactoryBeanTests.java create mode 100644 spring-integration-zookeeper/src/test/java/org/springframework/integration/zookeeper/config/LeaderInitiatorFactoryBeanTests.java create mode 100644 src/reference/asciidoc/zookeeper.adoc diff --git a/spring-integration-zookeeper/src/main/java/org/springframework/integration/zookeeper/config/CuratorFrameworkFactoryBean.java b/spring-integration-zookeeper/src/main/java/org/springframework/integration/zookeeper/config/CuratorFrameworkFactoryBean.java new file mode 100644 index 0000000000..9d270bb9b1 --- /dev/null +++ b/spring-integration-zookeeper/src/main/java/org/springframework/integration/zookeeper/config/CuratorFrameworkFactoryBean.java @@ -0,0 +1,152 @@ +/* + * 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.integration.zookeeper.config; + +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.imps.CuratorFrameworkState; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.curator.utils.CloseableUtils; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.context.SmartLifecycle; +import org.springframework.util.Assert; + +/** + * A spring-friendly way to build a {@link CuratorFramework} and implementing {@link SmartLifecycle}. + * + * @author Gary Russell + * + */ +public class CuratorFrameworkFactoryBean implements FactoryBean, SmartLifecycle { + + private final Object lifecycleLock = new Object(); + + private final CuratorFramework client; + + /** + * @see SmartLifecycle + */ + private volatile boolean autoStartup = true; + + /** + * @see SmartLifecycle + */ + private volatile boolean running; + + /** + * @see SmartLifecycle + */ + private volatile int phase; + + + /** + * Construct an instance using the supplied connection string and using a default + * retry policy {@code new ExponentialBackoffRetry(1000, 3)}. + * @param connectionString list of servers to connect to + */ + public CuratorFrameworkFactoryBean(String connectionString) { + this(connectionString, new ExponentialBackoffRetry(1000, 3)); + } + + /** + * Construct an instance using the supplied connection string and retry policy. + * @param connectionString list of servers to connect to + * @param retryPolicy the retry policy + */ + public CuratorFrameworkFactoryBean(String connectionString, RetryPolicy retryPolicy) { + Assert.notNull(connectionString, "'connectionString' cannot be null"); + Assert.notNull(retryPolicy, "'retryPolicy' cannot be null"); + this.client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy); + } + + @Override + public int getPhase() { + return this.phase; + } + + /** + * @param phase the phase + * @see SmartLifecycle + */ + public void setPhase(int phase) { + this.phase = phase; + } + + @Override + public boolean isRunning() { + return this.running; + } + + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * @param autoStartup true to automatically start + * @see SmartLifecycle + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + @Override + public void start() { + synchronized (this.lifecycleLock) { + if (!this.running) { + if (this.client != null) { + this.client.start(); + } + this.running = true; + } + } + } + + @Override + public void stop() { + synchronized (this.lifecycleLock) { + if (this.running) { + if (this.client.getState().equals(CuratorFrameworkState.STARTED)) { + CloseableUtils.closeQuietly(this.client); + } + } + } + } + + @Override + public void stop(Runnable runnable) { + stop(); + runnable.run(); + } + + @Override + public CuratorFramework getObject() throws Exception { + return this.client; + } + + @Override + public Class getObjectType() { + return CuratorFramework.class; + } + + @Override + public boolean isSingleton() { + return true; + } + +} diff --git a/spring-integration-zookeeper/src/main/java/org/springframework/integration/zookeeper/config/LeaderInitiatorFactoryBean.java b/spring-integration-zookeeper/src/main/java/org/springframework/integration/zookeeper/config/LeaderInitiatorFactoryBean.java new file mode 100644 index 0000000000..2d50721f1e --- /dev/null +++ b/spring-integration-zookeeper/src/main/java/org/springframework/integration/zookeeper/config/LeaderInitiatorFactoryBean.java @@ -0,0 +1,147 @@ +/* + * 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.integration.zookeeper.config; + +import java.util.UUID; + +import org.apache.curator.framework.CuratorFramework; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.SmartLifecycle; +import org.springframework.integration.leader.Candidate; +import org.springframework.integration.leader.DefaultCandidate; +import org.springframework.integration.leader.event.DefaultLeaderEventPublisher; +import org.springframework.integration.zookeeper.leader.LeaderInitiator; + +/** + * Creates a {@link LeaderInitiator}. + * + * @author Gary Russell + * @since 4.2 + * + */ +public class LeaderInitiatorFactoryBean + implements FactoryBean, SmartLifecycle, InitializingBean, ApplicationEventPublisherAware { + + private final CuratorFramework client; + + private final Candidate candidate; + + private final String path; + + private LeaderInitiator leaderInitiator; + + private boolean autoStartup = true; + + private int phase; + + private ApplicationEventPublisher applicationEventPublisher; + + /** + * Construct the instance. + * @param client the {@link CuratorFramework}. + * @param path the path in zookeeper. + * @param role the role of the leader. + */ + public LeaderInitiatorFactoryBean(CuratorFramework client, String path, String role) { + this.client = client; + this.candidate = new DefaultCandidate(UUID.randomUUID().toString(), role); + this.path = path; + } + + @Override + public void start() { + if (this.leaderInitiator != null) { + this.leaderInitiator.start(); + } + } + + @Override + public void stop() { + if (this.leaderInitiator != null) { + this.leaderInitiator.stop(); + } + } + + @Override + public boolean isRunning() { + return this.leaderInitiator != null && this.leaderInitiator.isRunning(); + } + + @Override + public int getPhase() { + if (this.leaderInitiator != null) { + return this.leaderInitiator.getPhase(); + } + return 0; + } + + public void setPhase(int phase) { + this.phase = phase; + } + + @Override + public boolean isAutoStartup() { + return this.leaderInitiator != null && this.leaderInitiator.isAutoStartup(); + } + + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + @Override + public void stop(Runnable callback) { + stop(); + callback.run(); + } + + @Override + public void afterPropertiesSet() throws Exception { + if (this.leaderInitiator == null) { + this.leaderInitiator = new LeaderInitiator(this.client, this.candidate, this.path); + this.leaderInitiator.setPhase(this.phase); + this.leaderInitiator.setAutoStartup(this.autoStartup); + if (this.applicationEventPublisher != null) { + this.leaderInitiator.setLeaderEventPublisher( + new DefaultLeaderEventPublisher(this.applicationEventPublisher)); + } + } + } + + @Override + public synchronized LeaderInitiator getObject() throws Exception { + return this.leaderInitiator; + } + + @Override + public Class getObjectType() { + return LeaderInitiator.class; + } + + @Override + public boolean isSingleton() { + return true; + } + +} diff --git a/spring-integration-zookeeper/src/test/java/org/springframework/integration/zookeeper/config/CuratorFrameworkFactoryBeanTests.java b/spring-integration-zookeeper/src/test/java/org/springframework/integration/zookeeper/config/CuratorFrameworkFactoryBeanTests.java new file mode 100644 index 0000000000..1eb028e68f --- /dev/null +++ b/spring-integration-zookeeper/src/test/java/org/springframework/integration/zookeeper/config/CuratorFrameworkFactoryBeanTests.java @@ -0,0 +1,45 @@ +/* + * 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.integration.zookeeper.config; + +import static org.junit.Assert.assertTrue; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.imps.CuratorFrameworkState; +import org.apache.curator.test.TestingServer; +import org.junit.Test; + +import org.springframework.integration.zookeeper.config.CuratorFrameworkFactoryBean; + +/** + * @author Gary Russell + * + */ +public class CuratorFrameworkFactoryBeanTests { + + @Test + public void test() throws Exception { + TestingServer testingServer = new TestingServer(); + CuratorFrameworkFactoryBean fb = new CuratorFrameworkFactoryBean(testingServer.getConnectString()); + CuratorFramework client = fb.getObject(); + fb.start(); + assertTrue(client.getState().equals(CuratorFrameworkState.STARTED)); + fb.stop(); + assertTrue(client.getState().equals(CuratorFrameworkState.STOPPED)); + testingServer.close(); + } + +} diff --git a/spring-integration-zookeeper/src/test/java/org/springframework/integration/zookeeper/config/LeaderInitiatorFactoryBeanTests.java b/spring-integration-zookeeper/src/test/java/org/springframework/integration/zookeeper/config/LeaderInitiatorFactoryBeanTests.java new file mode 100644 index 0000000000..a58a11e35a --- /dev/null +++ b/spring-integration-zookeeper/src/test/java/org/springframework/integration/zookeeper/config/LeaderInitiatorFactoryBeanTests.java @@ -0,0 +1,112 @@ +/* + * 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.integration.zookeeper.config; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.curator.framework.CuratorFramework; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.leader.event.AbstractLeaderEvent; +import org.springframework.integration.leader.event.OnGrantedEvent; +import org.springframework.integration.leader.event.OnRevokedEvent; +import org.springframework.integration.zookeeper.ZookeeperTestSupport; +import org.springframework.integration.zookeeper.leader.LeaderInitiator; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Gary Russell + * @since 4.2 + * + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext +public class LeaderInitiatorFactoryBeanTests extends ZookeeperTestSupport { + + private static CuratorFramework client; + + @Autowired + private LeaderInitiator leaderInitiator; + + @Autowired + private Config config; + + @BeforeClass + public static void getClient() throws Exception { + client = createNewClient(); + } + + @Test + public void test() throws Exception { + assertTrue(this.config.latch1.await(10, TimeUnit.SECONDS)); + assertThat(this.config.events.get(0), instanceOf(OnGrantedEvent.class)); + this.leaderInitiator.stop(); + assertTrue(this.config.latch2.await(10, TimeUnit.SECONDS)); + assertThat(this.config.events.get(1), instanceOf(OnRevokedEvent.class)); + } + + @Configuration + public static class Config { + + private final List events = new ArrayList(); + + private final CountDownLatch latch1 = new CountDownLatch(1); + + private final CountDownLatch latch2 = new CountDownLatch(2); + + @Bean + public LeaderInitiatorFactoryBean leaderInitiator(CuratorFramework client) { + return new LeaderInitiatorFactoryBean(client, "/siTest/", "foo"); + } + + @Bean + public CuratorFramework client() { + return LeaderInitiatorFactoryBeanTests.client; + } + + @Bean + public ApplicationListener listener() { + return new ApplicationListener() { + + @Override + public void onApplicationEvent(AbstractLeaderEvent event) { + events.add(event); + latch1.countDown(); + latch2.countDown(); + } + + }; + } + + } + +} diff --git a/src/reference/asciidoc/aggregator.adoc b/src/reference/asciidoc/aggregator.adoc index a1155733eb..d57f6df7af 100644 --- a/src/reference/asciidoc/aggregator.adoc +++ b/src/reference/asciidoc/aggregator.adoc @@ -235,6 +235,14 @@ This implementation returns the value of one of the message headers (whose name By default, the correlation strategy is a `HeaderAttributeCorrelationStrategy` returning the value of the CORRELATION_ID header attribute. If you have a custom header name you would like to use for correlation, then simply configure that on an instance of `HeaderAttributeCorrelationStrategy` and provide that as a reference for the Aggregator's correlation-strategy. +===== LockRegistry + +Changes to groups are thread safe; a `LockRegistry` is used to obtain a lock for the resolved correlation id. +A `DefaultLockRegistry` is used by default (in-memory). +For synchronizing updates across servers, where a shared `MessageGroupStore` is being used, a shared lock registry +must be configured. +See <> below for more information. + [[aggregator-config]] ==== Configuring an Aggregator @@ -426,8 +434,8 @@ Note that the actual time to expire an empty group will also be affected by the <20> A reference to a `org.springframework.integration.util.LockRegistry` bean; used to obtain a `Lock` based on the `groupId` for concurrent operations on the `MessageGroup`. By default, an internal `DefaultLockRegistry` is used. -Use of a distributed `LockRegistry`, such as the `RedisLockRegistry`, ensures only one instance of the aggregator will operate on a group concurrently. -See <> for more information. +Use of a distributed `LockRegistry`, such as the `ZookeeperLockRegistry`, ensures only one instance of the aggregator will operate on a group concurrently. +See <>, <>, <> for more information. diff --git a/src/reference/asciidoc/endpoint.adoc b/src/reference/asciidoc/endpoint.adoc index b9db4729fc..b5351bfd6e 100644 --- a/src/reference/asciidoc/endpoint.adoc +++ b/src/reference/asciidoc/endpoint.adoc @@ -564,3 +564,62 @@ If you use an inner bean definition such as this: the bean is treated like any inner bean declared that way and is not registered with the application context. If you wish to access this bean in some other manner, declare it at the top level with an `id` and use the `ref` attribute instead. See the http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/beans.html#beans-inner-beans[Spring Documentation] for more information. + +[[endpoint-roles]] +=== Endpoint Roles + +Starting with _version 4.2_, endpoints can be assigned to roles. +Roles allow endpoints to be started and stopped as a group; this is particularly useful when using leadership election +where a set of endpoints can be started or stopped when leadership is granted or revoked respectively. + +You can assign endpoints to roles using XML, Java configuration, or programmatically: + +[source, xml] +---- + + + +---- + +[source, java] +---- +@Bean +@ServiceActivator(inputChannel = "sendAsyncChannel") +@Role("cluster") +public MessageHandler sendAsyncHandler() { + return // some MessageHandler +} +---- + +[source, java] +---- +@Payload("#args[0].toLowerCase()") +@Role("cluster") +public String handle(String payload) { + return payload.toUpperCase(); +} +---- + +[source, java] +---- +@Autowired +private SmartLifecycleRoleController roleController; + +... + + this.roleController.addSmartLifeCycleToRole("cluster", someEndpoint); +... +---- + +Each of these adds the endpoint to the role `cluster`. + +Invoking `roleController.startLifecyclesInRole("cluster")` (and the corresponding `stop...` method) will start/stop +the endpoints. + +NOTE: Any object implementing `SmartLifecycle` can be programmatically added, not just endpoints. + +The `SmartLifecycleRoleController` implements `ApplicationListener` and it will automatically +start/stop its configured `SmartLifecycle` objects when leadership is granted/revoked (when some bean publishes +`OnGrantedEvent` or `OnRevokedEvent` respectively). + +See <> for more information about leadership election and events. diff --git a/src/reference/asciidoc/index.adoc b/src/reference/asciidoc/index.adoc index b65ce4cf21..dead119ea2 100644 --- a/src/reference/asciidoc/index.adoc +++ b/src/reference/asciidoc/index.adoc @@ -103,6 +103,8 @@ include::./ws.adoc[] include::./xml.adoc[] include::./xmpp.adoc[] + +include::./zookeeper.adoc[] // BE SURE TO PRECEDE ALL include:: with a blank line - see https://github.com/asciidoctor/asciidoctor/issues/1297 [[spring-integration-appendices]] diff --git a/src/reference/asciidoc/meta-data-store.adoc b/src/reference/asciidoc/meta-data-store.adoc index cfbfa22ae1..22f623a51a 100644 --- a/src/reference/asciidoc/meta-data-store.adoc +++ b/src/reference/asciidoc/meta-data-store.adoc @@ -17,6 +17,7 @@ the framework: * <> * <> * <> +* <> @@ -61,3 +62,23 @@ The following configuration is an example of how to do this: The `value` of the idempotent entry may be some expiration date, after which that entry should be removed from _Metadata Store_ by some scheduled reaper. Also see <>. + +[[metadatastore-listener]] +==== MetadataStoreListener + +Some metadata stores (currently only zookeeper) support registering a listener to receive events when items change. + +[source, java] +---- +public interface MetadataStoreListener { + + void onAdd(String key, String value); + + void onRemove(String key, String oldValue); + + void onUpdate(String key, String newValue); +} +---- + +See the javadocs for more information. +The `MetadataStoreListenerAdapter` can be subclassed if you are only interested in a subset of events. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index e21a82edc3..f47e12c2bc 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -35,6 +35,17 @@ For more information, see <>. The `FileSplitter`, which splits text files into lines, was added in 4.1.2. It now has full support in the `int-file:` namespace; see <> for more information. +[[x4.2-zk]] +==== Zookeeper Support + +Zookeeper support has been added to the framework to assist when running on a clustered/multi-host environment. + +* ZookeeperMetadataStore +* ZookeeperLockRegistry +* Zookeeper Leadership + +See <> for more information. + [[x4.2-general]] === General Changes diff --git a/src/reference/asciidoc/zookeeper.adoc b/src/reference/asciidoc/zookeeper.adoc new file mode 100644 index 0000000000..44ad1fe714 --- /dev/null +++ b/src/reference/asciidoc/zookeeper.adoc @@ -0,0 +1,90 @@ +[[zookeeper]] +== Zookeeper Support + +=== Introduction + +https://zookeeper.apache.org/[Zookeeper] support was added to the framework in _version 4.2_, comprised of: + +* MetadataStore +* LockRegistry +* Leadership Event Handling + +[[zk-metadata-store]] +=== Zookeeper Metadata Store + +The `ZookeeperMetadataStore` can be used where any `MetadataStore` is needed, such as peristent file list filters, +etc. +See <> for more information. + +[source, xml] +---- + + + + + + + +---- + +[source, java] +---- +@Bean +public MetadataStore zkStore(CuratorFramework client) { + return new ZookeeperMetadataStore(client); +} +---- + +[[zk-lock-registry]] +=== Zookeeper Lock Registry + +The `ZookeeperLockRegistry` can be used where any `LockRegistry` is needed, such as when using an `Aggregator` in a +clustered environment, with a shared `MessageStore`. + +A `LocRegistry` is used to "look up" a lock based on a key (the aggregator uses the `correlationId`). +By default, locks in the `ZookeeperLockRegistry` are maintained in zookeeper under the path +`/SpringIntegration-LockRegistry/`. +You can customize the path by providing an implementation of `ZookeeperLockRegistry.KeyToPathStrategy`. + +[source, java] +---- +public interface KeyToPathStrategy { + + String pathFor(String key); + + boolean bounded(); + +} +---- + +If the strategy returns `true` from `isBounded`, unused locks do not need to be harvested. +For unbounded strategies (such as the default) you will need to invoke `expireUnusedOlderThan(long age)` from time +to time, to remove old unused locks from memory. + +[[zk-leadership]] +=== Zookeeper Leadership Event Handling + +Groups of endpoints can be started/stopped based on leadership being granted or revoked respectively. +This is useful in clustered scenarios where shared resources must only be consumed by a single instance. +An example of this is a file inbound channel adapter that is polling a shared directory. +(See <>). + +[source, xml] +---- + +---- + +`client` is a reference to a `CuratorFramework` bean; a `CuratorFrameworkFactoryBean` is available. +When a leader is elected, an `OnGrantedEvent` will be published for the role `cluster`; any endpoints in that role +will be started. +When leadership is revoked, an `OnRevokedEvent` will be published for the role `cluster`; any endpoints in that role +will be stopped. +See <> for more information. + +[source, java] +---- +@Bean +public LeaderInitiatorFactoryBean leaderInitiator(CuratorFramework client) { + return new LeaderInitiatorFactoryBean(client, "/siTest/", "cluster"); +} +----