diff --git a/powerjob-common/src/main/java/tech/powerjob/common/enums/DispatchStrategy.java b/powerjob-common/src/main/java/tech/powerjob/common/enums/DispatchStrategy.java index 23157a00..deebc350 100644 --- a/powerjob-common/src/main/java/tech/powerjob/common/enums/DispatchStrategy.java +++ b/powerjob-common/src/main/java/tech/powerjob/common/enums/DispatchStrategy.java @@ -13,8 +13,19 @@ import lombok.Getter; @AllArgsConstructor public enum DispatchStrategy { + /** + * 健康度优先 + */ HEALTH_FIRST(1), - RANDOM(2); + /** + * 随机 + */ + RANDOM(2), + /** + * 指定执行 + */ + SPECIFY(11) + ; private final int v; diff --git a/powerjob-common/src/main/java/tech/powerjob/common/request/http/SaveJobInfoRequest.java b/powerjob-common/src/main/java/tech/powerjob/common/request/http/SaveJobInfoRequest.java index 65fd892b..d3006ec3 100644 --- a/powerjob-common/src/main/java/tech/powerjob/common/request/http/SaveJobInfoRequest.java +++ b/powerjob-common/src/main/java/tech/powerjob/common/request/http/SaveJobInfoRequest.java @@ -134,6 +134,11 @@ public class SaveJobInfoRequest { private DispatchStrategy dispatchStrategy; + /** + * 某种派发策略背后的具体配置,值取决于 dispatchStrategy + */ + private String dispatchStrategyConfig; + private LifeCycle lifeCycle; /** * alarm config diff --git a/powerjob-common/src/main/java/tech/powerjob/common/response/JobInfoDTO.java b/powerjob-common/src/main/java/tech/powerjob/common/response/JobInfoDTO.java index 34b63613..2f616c20 100644 --- a/powerjob-common/src/main/java/tech/powerjob/common/response/JobInfoDTO.java +++ b/powerjob-common/src/main/java/tech/powerjob/common/response/JobInfoDTO.java @@ -121,7 +121,14 @@ public class JobInfoDTO { private String extra; + /** + * 派发策略 + */ private Integer dispatchStrategy; + /** + * 某种派发策略背后的具体配置,值取决于 dispatchStrategy + */ + private String dispatchStrategyConfig; private String lifecycle; diff --git a/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/DispatchService.java b/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/DispatchService.java index b0821b62..be565503 100644 --- a/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/DispatchService.java +++ b/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/DispatchService.java @@ -26,6 +26,7 @@ import tech.powerjob.server.persistence.remote.repository.InstanceInfoRepository import tech.powerjob.server.remote.transporter.TransportService; import tech.powerjob.server.remote.transporter.impl.ServerURLFactory; import tech.powerjob.server.remote.worker.WorkerClusterQueryService; +import tech.powerjob.server.remote.worker.selector.TaskTrackerSelectorService; import java.util.ArrayList; import java.util.Date; @@ -58,6 +59,8 @@ public class DispatchService { private final InstanceInfoRepository instanceInfoRepository; + private final TaskTrackerSelectorService taskTrackerSelectorService; + /** * 异步重新派发 * @@ -145,7 +148,7 @@ public class DispatchService { } // 获取当前最合适的 worker 列表 - List suitableWorkers = workerClusterQueryService.getSuitableWorkers(jobInfo); + List suitableWorkers = workerClusterQueryService.geAvailableWorkers(jobInfo); if (CollectionUtils.isEmpty(suitableWorkers)) { log.warn("[Dispatcher-{}|{}] cancel dispatch job due to no worker available", jobId, instanceId); @@ -167,7 +170,7 @@ public class DispatchService { ServerScheduleJobReq req = constructServerScheduleJobReq(jobInfo, instanceInfo, workerIpList); // 发送请求(不可靠,需要一个后台线程定期轮询状态) - WorkerInfo taskTracker = suitableWorkers.get(0); + WorkerInfo taskTracker = taskTrackerSelectorService.select(jobInfo, instanceInfo, suitableWorkers); String taskTrackerAddress = taskTracker.getAddress(); URL workerUrl = ServerURLFactory.dispatchJob2Worker(taskTrackerAddress); diff --git a/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/handler/AbWorkerRequestHandler.java b/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/handler/AbWorkerRequestHandler.java index 90c0e59e..13065d1e 100644 --- a/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/handler/AbWorkerRequestHandler.java +++ b/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/handler/AbWorkerRequestHandler.java @@ -134,7 +134,7 @@ public abstract class AbWorkerRequestHandler implements IWorkerRequestHandler { if (!jobInfo.getAppId().equals(appId)) { askResponse = AskResponse.failed("Permission Denied!"); }else { - List sortedAvailableWorker = workerClusterQueryService.getSuitableWorkers(jobInfo) + List sortedAvailableWorker = workerClusterQueryService.geAvailableWorkers(jobInfo) .stream().map(WorkerInfo::getAddress).collect(Collectors.toList()); askResponse = AskResponse.succeed(sortedAvailableWorker); } diff --git a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/JobInfoDO.java b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/JobInfoDO.java index a3b4bfbf..212f10ce 100644 --- a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/JobInfoDO.java +++ b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/JobInfoDO.java @@ -139,7 +139,14 @@ public class JobInfoDO { */ private String extra; + /** + * 派发策略 + */ private Integer dispatchStrategy; + /** + * 某种派发策略背后的具体配置,值取决于 dispatchStrategy + */ + private String dispatchStrategyConfig; private String lifecycle; /** diff --git a/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/WorkerClusterQueryService.java b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/WorkerClusterQueryService.java index 3b44c4cf..9364a5a1 100644 --- a/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/WorkerClusterQueryService.java +++ b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/WorkerClusterQueryService.java @@ -3,12 +3,11 @@ package tech.powerjob.server.remote.worker; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import tech.powerjob.common.enums.DispatchStrategy; import tech.powerjob.common.model.DeployedContainerInfo; import tech.powerjob.server.common.module.WorkerInfo; -import tech.powerjob.server.remote.worker.filter.WorkerFilter; import tech.powerjob.server.persistence.remote.model.JobInfoDO; import tech.powerjob.server.remote.server.redirector.DesignateServer; +import tech.powerjob.server.remote.worker.filter.WorkerFilter; import java.util.Collections; import java.util.List; @@ -37,24 +36,13 @@ public class WorkerClusterQueryService { * @param jobInfo job * @return worker cluster info, sorted by metrics desc */ - public List getSuitableWorkers(JobInfoDO jobInfo) { + public List geAvailableWorkers(JobInfoDO jobInfo) { List workers = Lists.newLinkedList(getWorkerInfosByAppId(jobInfo.getAppId()).values()); + // 过滤不符合要求的机器 workers.removeIf(workerInfo -> filterWorker(workerInfo, jobInfo)); - DispatchStrategy dispatchStrategy = DispatchStrategy.of(jobInfo.getDispatchStrategy()); - switch (dispatchStrategy) { - case RANDOM: - Collections.shuffle(workers); - break; - case HEALTH_FIRST: - workers.sort((o1, o2) -> o2.getSystemMetrics().calculateScore() - o1.getSystemMetrics().calculateScore()); - break; - default: - // do nothing - } - // 限定集群大小(0代表不限制) if (!workers.isEmpty() && jobInfo.getMaxWorkerCount() > 0 && workers.size() > jobInfo.getMaxWorkerCount()) { workers = workers.subList(0, jobInfo.getMaxWorkerCount()); diff --git a/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/TaskTrackerSelector.java b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/TaskTrackerSelector.java new file mode 100644 index 00000000..b3261234 --- /dev/null +++ b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/TaskTrackerSelector.java @@ -0,0 +1,32 @@ +package tech.powerjob.server.remote.worker.selector; + +import tech.powerjob.common.enums.DispatchStrategy; +import tech.powerjob.server.common.module.WorkerInfo; +import tech.powerjob.server.persistence.remote.model.InstanceInfoDO; +import tech.powerjob.server.persistence.remote.model.JobInfoDO; + +import java.util.List; + +/** + * 主节点选择方式 + * + * @author tjq + * @since 2024/2/24 + */ +public interface TaskTrackerSelector { + + /** + * 支持的策略 + * @return 派发策略 + */ + DispatchStrategy strategy(); + + /** + * 选择主节点 + * @param jobInfoDO 任务信息 + * @param instanceInfoDO 任务实例 + * @param availableWorkers 可用 workers + * @return 主节点 worker + */ + WorkerInfo select(JobInfoDO jobInfoDO, InstanceInfoDO instanceInfoDO, List availableWorkers); +} diff --git a/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/TaskTrackerSelectorService.java b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/TaskTrackerSelectorService.java new file mode 100644 index 00000000..85951a27 --- /dev/null +++ b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/TaskTrackerSelectorService.java @@ -0,0 +1,33 @@ +package tech.powerjob.server.remote.worker.selector; + +import com.google.common.collect.Maps; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import tech.powerjob.server.common.module.WorkerInfo; +import tech.powerjob.server.persistence.remote.model.InstanceInfoDO; +import tech.powerjob.server.persistence.remote.model.JobInfoDO; + +import java.util.List; +import java.util.Map; + +/** + * TaskTrackerSelectorService + * + * @author tjq + * @since 2024/2/24 + */ +@Service +public class TaskTrackerSelectorService { + + private final Map taskTrackerSelectorMap = Maps.newHashMap(); + + @Autowired + public TaskTrackerSelectorService(List taskTrackerSelectors) { + taskTrackerSelectors.forEach(ts -> taskTrackerSelectorMap.put(ts.strategy().getV(), ts)); + } + + public WorkerInfo select(JobInfoDO jobInfoDO, InstanceInfoDO instanceInfoDO, List availableWorkers) { + TaskTrackerSelector taskTrackerSelector = taskTrackerSelectorMap.get(jobInfoDO.getDispatchStrategy()); + return taskTrackerSelector.select(jobInfoDO, instanceInfoDO, availableWorkers); + } +} diff --git a/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/impl/HealthFirstTaskTrackerSelector.java b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/impl/HealthFirstTaskTrackerSelector.java new file mode 100644 index 00000000..b562e40a --- /dev/null +++ b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/impl/HealthFirstTaskTrackerSelector.java @@ -0,0 +1,33 @@ +package tech.powerjob.server.remote.worker.selector.impl; + +import com.google.common.collect.Lists; +import org.springframework.stereotype.Component; +import tech.powerjob.common.enums.DispatchStrategy; +import tech.powerjob.server.common.module.WorkerInfo; +import tech.powerjob.server.persistence.remote.model.InstanceInfoDO; +import tech.powerjob.server.persistence.remote.model.JobInfoDO; +import tech.powerjob.server.remote.worker.selector.TaskTrackerSelector; + +import java.util.List; + +/** + * HealthFirst + * + * @author (疑似)新冠帕鲁 + * @since 2024/2/24 + */ +@Component +public class HealthFirstTaskTrackerSelector implements TaskTrackerSelector { + + @Override + public DispatchStrategy strategy() { + return DispatchStrategy.HEALTH_FIRST; + } + + @Override + public WorkerInfo select(JobInfoDO jobInfoDO, InstanceInfoDO instanceInfoDO, List availableWorkers) { + List workers = Lists.newArrayList(availableWorkers); + workers.sort((o1, o2) -> o2.getSystemMetrics().calculateScore() - o1.getSystemMetrics().calculateScore()); + return workers.get(0); + } +} diff --git a/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/impl/RandomTaskTrackerSelector.java b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/impl/RandomTaskTrackerSelector.java new file mode 100644 index 00000000..97de378d --- /dev/null +++ b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/impl/RandomTaskTrackerSelector.java @@ -0,0 +1,32 @@ +package tech.powerjob.server.remote.worker.selector.impl; + +import org.springframework.stereotype.Component; +import tech.powerjob.common.enums.DispatchStrategy; +import tech.powerjob.server.common.module.WorkerInfo; +import tech.powerjob.server.persistence.remote.model.InstanceInfoDO; +import tech.powerjob.server.persistence.remote.model.JobInfoDO; +import tech.powerjob.server.remote.worker.selector.TaskTrackerSelector; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * RANDOM + * + * @author (疑似)新冠帕鲁 + * @since 2024/2/24 + */ +@Component +public class RandomTaskTrackerSelector implements TaskTrackerSelector { + + @Override + public DispatchStrategy strategy() { + return DispatchStrategy.RANDOM; + } + + @Override + public WorkerInfo select(JobInfoDO jobInfoDO, InstanceInfoDO instanceInfoDO, List availableWorkers) { + int randomIdx = ThreadLocalRandom.current().nextInt(availableWorkers.size()); + return availableWorkers.get(randomIdx); + } +} diff --git a/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/impl/SpecifyTaskTrackerSelector.java b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/impl/SpecifyTaskTrackerSelector.java new file mode 100644 index 00000000..de2b738b --- /dev/null +++ b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/selector/impl/SpecifyTaskTrackerSelector.java @@ -0,0 +1,59 @@ +package tech.powerjob.server.remote.worker.selector.impl; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import tech.powerjob.common.enums.DispatchStrategy; +import tech.powerjob.common.utils.CollectionUtils; +import tech.powerjob.server.common.module.WorkerInfo; +import tech.powerjob.server.persistence.remote.model.InstanceInfoDO; +import tech.powerjob.server.persistence.remote.model.JobInfoDO; +import tech.powerjob.server.remote.worker.selector.TaskTrackerSelector; +import tech.powerjob.server.remote.worker.utils.SpecifyUtils; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 指定工作的主节点,大规模运算需要隔离主节点,以防止 worker 部署打断整体的任务执行 + * + * @author tjq + * @since 2024/2/24 + */ +@Slf4j +@Component +public class SpecifyTaskTrackerSelector implements TaskTrackerSelector { + @Override + public DispatchStrategy strategy() { + return DispatchStrategy.SPECIFY; + } + + @Override + public WorkerInfo select(JobInfoDO jobInfoDO, InstanceInfoDO instanceInfoDO, List availableWorkers) { + + String dispatchStrategyConfig = jobInfoDO.getDispatchStrategyConfig(); + + // 降级到随机 + if (StringUtils.isEmpty(dispatchStrategyConfig)) { + log.warn("[SpecifyTaskTrackerSelector] job[id={}]'s dispatchStrategyConfig is empty, use random as bottom DispatchStrategy!", jobInfoDO.getId()); + return availableWorkers.get(ThreadLocalRandom.current().nextInt(availableWorkers.size())); + } + + List targetWorkers = Lists.newArrayList(); + availableWorkers.forEach(aw -> { + boolean match = SpecifyUtils.match(aw, dispatchStrategyConfig); + if (match) { + targetWorkers.add(aw); + } + }); + + if (CollectionUtils.isEmpty(targetWorkers)) { + log.warn("[SpecifyTaskTrackerSelector] Unable to find available nodes based on conditions for job(id={},dispatchStrategyConfig={}), use random as bottom DispatchStrategy!", jobInfoDO.getId(), dispatchStrategyConfig); + return availableWorkers.get(ThreadLocalRandom.current().nextInt(availableWorkers.size())); + } + + // 如果有多个 worker 符合条件,最终还是随机选择出一个 + return targetWorkers.get(ThreadLocalRandom.current().nextInt(targetWorkers.size())); + } +} diff --git a/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/utils/SpecifyUtils.java b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/utils/SpecifyUtils.java new file mode 100644 index 00000000..ac85b903 --- /dev/null +++ b/powerjob-server/powerjob-server-remote/src/main/java/tech/powerjob/server/remote/worker/utils/SpecifyUtils.java @@ -0,0 +1,51 @@ +package tech.powerjob.server.remote.worker.utils; + +import com.google.common.collect.Sets; +import org.apache.commons.lang3.StringUtils; +import tech.powerjob.server.common.SJ; +import tech.powerjob.server.common.module.WorkerInfo; + +import java.util.Optional; +import java.util.Set; + +/** + * 指定工具 + * + * @author tjq + * @since 2024/2/24 + */ +public class SpecifyUtils { + + private static final String TAG_EQUALS = "tagEquals:"; + + private static final String TAG_IN = "tagIn:"; + + public static boolean match(WorkerInfo workerInfo, String specifyInfo) { + + String workerTag = workerInfo.getTag(); + + // tagIn 语法,worker 可上报多个tag,如 WorkerInfo#tag=tag1,tag2,tag3,配置中指定 tagIn=tag1 即可命中 + if (specifyInfo.startsWith(TAG_IN)) { + String targetTag = specifyInfo.replace(TAG_IN, StringUtils.EMPTY); + return Optional.ofNullable(workerTag).orElse(StringUtils.EMPTY).contains(targetTag); + } + + // tagEquals 语法,字符串完全匹配,worker 只可上报一个 tag,如 WorkerInfo#tag=tag1,配置中指定 tagEquals=tag1 即可命中 + if (specifyInfo.startsWith(TAG_EQUALS)) { + String targetTag = specifyInfo.replace(TAG_EQUALS, StringUtils.EMPTY); + return Optional.ofNullable(workerTag).orElse(StringUtils.EMPTY).equals(targetTag); + } + + // 默认情况,IP 和 tag 逗号分割后任意完全匹配即视为命中(兼容 4.3.8 版本前序逻辑) + Set designatedWorkersSet = Sets.newHashSet(SJ.COMMA_SPLITTER.splitToList(specifyInfo)); + + for (String tagOrAddress : designatedWorkersSet) { + if (tagOrAddress.equals(workerInfo.getTag()) || tagOrAddress.equals(workerInfo.getAddress())) { + return true; + } + } + + return false; + } + +} diff --git a/powerjob-server/powerjob-server-remote/src/test/java/tech/powerjob/server/remote/worker/utils/SpecifyUtilsTest.java b/powerjob-server/powerjob-server-remote/src/test/java/tech/powerjob/server/remote/worker/utils/SpecifyUtilsTest.java new file mode 100644 index 00000000..740ad6b5 --- /dev/null +++ b/powerjob-server/powerjob-server-remote/src/test/java/tech/powerjob/server/remote/worker/utils/SpecifyUtilsTest.java @@ -0,0 +1,45 @@ +package tech.powerjob.server.remote.worker.utils; + +import org.junit.jupiter.api.Test; +import tech.powerjob.server.common.module.WorkerInfo; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * SpecifyUtilsTest + * + * @author tjq + * @since 2024/2/24 + */ +class SpecifyUtilsTest { + + @Test + void match() { + + WorkerInfo workerInfo = new WorkerInfo(); + workerInfo.setAddress("192.168.1.1"); + workerInfo.setTag("tag1"); + + assert SpecifyUtils.match(workerInfo, "192.168.1.1"); + assert SpecifyUtils.match(workerInfo, "192.168.1.1,192.168.1.2,192.168.1.3,192.168.1.4"); + + assert !SpecifyUtils.match(workerInfo, "172.168.1.1"); + assert !SpecifyUtils.match(workerInfo, "172.168.1.1,172.168.1.2,172.168.1.3"); + + assert SpecifyUtils.match(workerInfo, "tag1"); + assert SpecifyUtils.match(workerInfo, "tag1,tag2"); + assert !SpecifyUtils.match(workerInfo, "t1"); + assert !SpecifyUtils.match(workerInfo, "t1,t2"); + + assert SpecifyUtils.match(workerInfo, "tagIn:tag1"); + assert !SpecifyUtils.match(workerInfo, "tagIn:tag2"); + + assert SpecifyUtils.match(workerInfo, "tagEquals:tag1"); + assert !SpecifyUtils.match(workerInfo, "tagEquals:tag2"); + + workerInfo.setTag("tag1,tag2,tag3"); + assert SpecifyUtils.match(workerInfo, "tagIn:tag1"); + assert SpecifyUtils.match(workerInfo, "tagIn:tag3"); + assert !SpecifyUtils.match(workerInfo, "tagIn:tag99"); + } +} \ No newline at end of file diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/JobInfoVO.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/JobInfoVO.java index f100474c..98bb9f0d 100644 --- a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/JobInfoVO.java +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/JobInfoVO.java @@ -142,6 +142,11 @@ public class JobInfoVO { private String dispatchStrategy; + /** + * 某种派发策略背后的具体配置,值取决于 dispatchStrategy + */ + private String dispatchStrategyConfig; + private LifeCycle lifeCycle; private AlarmConfig alarmConfig; diff --git a/powerjob-worker-samples/src/main/java/tech/powerjob/samples/processors/StandaloneProcessorDemo.java b/powerjob-worker-samples/src/main/java/tech/powerjob/samples/processors/StandaloneProcessorDemo.java index d039c73d..81fee86c 100644 --- a/powerjob-worker-samples/src/main/java/tech/powerjob/samples/processors/StandaloneProcessorDemo.java +++ b/powerjob-worker-samples/src/main/java/tech/powerjob/samples/processors/StandaloneProcessorDemo.java @@ -17,7 +17,7 @@ import java.util.Collections; * @since 2020/4/17 */ @Slf4j -@Component +@Component("testBaseProcessor") public class StandaloneProcessorDemo implements BasicProcessor { @Override