From 2c1f389dedbaf7f97177befc0e201e3d3542d3d9 Mon Sep 17 00:00:00 2001 From: damon Date: Sat, 16 May 2026 00:09:58 +0800 Subject: [PATCH 1/5] Refactor exception handling strategies and improve Validator interface generics --- src/main/java/com/neroyun/mediator/Validator.java | 2 +- .../strategy/HandlerExceptionStrategy.java | 4 ++-- .../mediator/strategy/HandlerParallelStrategy.java | 14 +++++++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/neroyun/mediator/Validator.java b/src/main/java/com/neroyun/mediator/Validator.java index 7d5ac9c..12dc0d1 100644 --- a/src/main/java/com/neroyun/mediator/Validator.java +++ b/src/main/java/com/neroyun/mediator/Validator.java @@ -9,7 +9,7 @@ * Implementations of this interface can be used to ensure that messages meet certain criteria or constraints before they are handled by the appropriate handlers in the mediator pattern. * @param the type of message to be validated. Only messages that extend the Validatable class can be validated using this interface, ensuring that the validation logic is specific to the types of messages being processed in the mediator pattern. */ -public interface Validator { +public interface Validator> { /** * Validates the given message and returns a ValidationResult indicating whether the validation was successful or if there were any errors. diff --git a/src/main/java/com/neroyun/mediator/strategy/HandlerExceptionStrategy.java b/src/main/java/com/neroyun/mediator/strategy/HandlerExceptionStrategy.java index 652225e..741c7d1 100644 --- a/src/main/java/com/neroyun/mediator/strategy/HandlerExceptionStrategy.java +++ b/src/main/java/com/neroyun/mediator/strategy/HandlerExceptionStrategy.java @@ -18,13 +18,13 @@ /** * Stops the processing of the message if an exception occurs in any handler. */ - public final String STOP = "STOP"; + String STOP = "STOP"; /** * Continues to the next handler if an exception occurs in the current handler. * This allows other handlers to attempt to process the message, even if one handler fails. */ - public final String CONTINUE = "CONTINUE"; + String CONTINUE = "CONTINUE"; String value() default CONTINUE; } diff --git a/src/main/java/com/neroyun/mediator/strategy/HandlerParallelStrategy.java b/src/main/java/com/neroyun/mediator/strategy/HandlerParallelStrategy.java index 11d7dbf..1d62255 100644 --- a/src/main/java/com/neroyun/mediator/strategy/HandlerParallelStrategy.java +++ b/src/main/java/com/neroyun/mediator/strategy/HandlerParallelStrategy.java @@ -5,6 +5,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Defines the strategy for executing handlers in parallel within the mediator pattern. + * This annotation can be applied to handler classes to specify how they should be executed in parallel when processing messages. + * The available strategies include: + * 1. NO_WAIT: Runs handlers in parallel without waiting for any of them to complete. This is useful for fire-and-forget scenarios where handlers can execute independently without blocking the main thread or waiting for their results. + * 2. WHEN_ALL: Waits for all handlers to complete before proceeding. This is useful when you need to ensure that all handlers have finished processing before moving on to the next step in the workflow. The main thread will block until all handlers have completed their execution. + * 3. WHEN_ANY: Waits for any handler to complete before proceeding. This is useful when you need to continue processing as soon as the first handler finishes, without waiting for all handlers to complete. The main thread will block until at least one handler has completed its execution. + */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface HandlerParallelStrategy { @@ -14,21 +22,21 @@ * allowing them to execute independently without blocking the main thread or waiting for their results. * Handlers will be executed concurrently, and the main thread will continue immediately after dispatching the handlers. */ - public final String No_WAIT = "NO_WAIT"; + String No_WAIT = "NO_WAIT"; /** * Waits for all handlers to complete before proceeding. * This strategy is useful when you need to ensure that all handlers have finished processing before moving on to the next step in the workflow. * The main thread will block until all handlers have completed their execution. */ - public final String WHEN_ALL = "WHEN_ALL"; + String WHEN_ALL = "WHEN_ALL"; /** * Waits for any handler to complete before proceeding. * This strategy is useful when you need to continue processing as soon as the first handler finishes, without waiting for all handlers to complete. * The main thread will block until at least one handler has completed its execution. */ - public final String WHEN_ANY = "WHEN_ANY"; + String WHEN_ANY = "WHEN_ANY"; String value() default No_WAIT; } From d52d190e13fc349e561b1a2a222e254462f07d38 Mon Sep 17 00:00:00 2001 From: damon Date: Sat, 16 May 2026 00:23:55 +0800 Subject: [PATCH 2/5] Enhance Executor class with detailed method documentation for concurrency strategies --- .../java/com/neroyun/mediator/Executor.java | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/neroyun/mediator/Executor.java b/src/main/java/com/neroyun/mediator/Executor.java index c02eee8..c1c0a87 100644 --- a/src/main/java/com/neroyun/mediator/Executor.java +++ b/src/main/java/com/neroyun/mediator/Executor.java @@ -8,7 +8,20 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; +/** + * Executor class for running tasks with different concurrency strategies. + * This class provides methods to run tasks either immediately, wait for all tasks to complete, or wait for any task to complete. + * It also handles exceptions using the provided ExceptionHandle. + * + */ class Executor { + + /** + * Runs a list of tasks concurrently using the provided ExecutorService. If any task throws an exception, it will be handled by the onException handler. + * @param tasks The list of tasks to be executed. + * @param concurrentPolicy The ExecutorService to use for running the tasks. + * @param onException The handler to use for any exceptions thrown by the tasks. + */ static void run(List tasks, ExecutorService concurrentPolicy, ExceptionHandle onException) { try { tasks.forEach(task -> runAsync(task, concurrentPolicy)); @@ -17,19 +30,29 @@ static void run(List tasks, ExecutorService concurrentPolicy, Exceptio } } + /** + * Runs a list of tasks concurrently and waits for all of them to complete. If any task throws an exception, it will be handled by the onException handler. + * @param tasks The list of tasks to be executed. + * @param concurrentPolicy The ExecutorService to use for running the tasks. + * @param onException The handler to use for any exceptions thrown by the tasks. + */ static void whenAll(List tasks, ExecutorService concurrentPolicy, ExceptionHandle onException) { CompletableFuture.allOf(tasks.stream() - .map(task -> { - return CompletableFuture.runAsync(task, concurrentPolicy) - .exceptionally(ex -> { - onException.handleException(ex); - return null; - }); - }) + .map(task -> CompletableFuture.runAsync(task, concurrentPolicy) + .exceptionally(ex -> { + onException.handleException(ex); + return null; + })) .toArray(CompletableFuture[]::new)) .join(); } + /** + * Runs a list of tasks concurrently and waits for any one of them to complete. If any task throws an exception, it will be handled by the onException handler. + * @param tasks The list of tasks to be executed. + * @param concurrentPolicy The ExecutorService to use for running the tasks. + * @param onException The handler to use for any exceptions thrown by the tasks. + */ static void whenAny(List tasks, ExecutorService concurrentPolicy, ExceptionHandle onException) { List> futures = tasks.stream() .map(task -> CompletableFuture.runAsync(task, concurrentPolicy) From 3ddbaf285faefe6cd63d1831dbdb7c8a6dbb653c Mon Sep 17 00:00:00 2001 From: damon Date: Sat, 16 May 2026 00:40:19 +0800 Subject: [PATCH 3/5] Add nullability annotations and refactor ValidationResult class for improved error handling --- pom.xml | 8 +++++++- .../neroyun/mediator/PipelinedMediator.java | 20 +++++++++---------- .../validation/ValidationException.java | 6 +++++- .../mediator/validation/ValidationResult.java | 16 ++++----------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index 91af388..e091605 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.neroyun mediator - 1.0.0 + 1.0.1 Mediator A simple mediator pattern implementation in Java https://github.com/nerosoftdev/mediator @@ -44,6 +44,12 @@ + + org.jetbrains + annotations + 24.1.0 + provided + org.junit.jupiter junit-jupiter diff --git a/src/main/java/com/neroyun/mediator/PipelinedMediator.java b/src/main/java/com/neroyun/mediator/PipelinedMediator.java index cc6fab8..ad5aeab 100644 --- a/src/main/java/com/neroyun/mediator/PipelinedMediator.java +++ b/src/main/java/com/neroyun/mediator/PipelinedMediator.java @@ -1,11 +1,11 @@ package com.neroyun.mediator; -import com.neroyun.mediator.internal.*; import com.neroyun.mediator.internal.*; import com.neroyun.mediator.strategy.HandlerExceptionStrategy; import com.neroyun.mediator.strategy.HandlerParallelStrategy; import com.neroyun.mediator.validation.ValidationException; import com.neroyun.mediator.validation.ValidationResult; +import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Objects; @@ -34,28 +34,28 @@ public class PipelinedMediator implements Mediator { * @param handlers the stream of handlers to be used by the mediator * @return the current instance of PipelinedMediator for method chaining */ - public PipelinedMediator use(HandlerStream handlers) { + public PipelinedMediator use(@NotNull HandlerStream handlers) { this.handlers = handlers::supply; return this; } - public PipelinedMediator use(MiddlewareStream middlewares) { + public PipelinedMediator use(@NotNull MiddlewareStream middlewares) { this.middlewares = middlewares::supply; return this; } - public PipelinedMediator use(ValidatorStream validators) { + public PipelinedMediator use(@NotNull ValidatorStream validators) { this.validators = validators::supply; return this; } - public PipelinedMediator use(Supplier concurrentPolicy) { + public PipelinedMediator use(@NotNull Supplier concurrentPolicy) { this.concurrentPolicy = concurrentPolicy; return this; } @Override - public void send(T command) { + public void send(@NotNull T command) { checkArguments(command, "Command can not be null."); validate(command); var handler = resolveHandler(command); @@ -64,7 +64,7 @@ public void send(T command) { } @Override - public , R> R execute(T query) { + public , R> R execute(@NotNull T query) { checkArguments(query, "Query can not be null."); validate(query); var handler = resolveHandler(query); @@ -73,12 +73,12 @@ public , R> R execute(T query) { } @Override - public , R> void execute(T query, R response) { + public , R> void execute(@NotNull T query, R response) { checkArguments(query, "Query can not be null."); } @Override - public void publish(T event) { + public void publish(@NotNull T event) { checkArguments(event, "Event can not be null."); List tasks = handlers.supply() @@ -153,7 +153,7 @@ private , R> void validate(T message) { var errors = validators.supply() .map(validator -> validator.validate(message)) .filter(ValidationResult::isFailure) - .flatMap(result -> result.getErrors().stream()) + .flatMap(result -> result.errors().stream()) .toList(); if (!errors.isEmpty()) { throw new ValidationException(errors); diff --git a/src/main/java/com/neroyun/mediator/validation/ValidationException.java b/src/main/java/com/neroyun/mediator/validation/ValidationException.java index 8a788a8..8b88b50 100644 --- a/src/main/java/com/neroyun/mediator/validation/ValidationException.java +++ b/src/main/java/com/neroyun/mediator/validation/ValidationException.java @@ -1,6 +1,7 @@ package com.neroyun.mediator.validation; import java.util.List; +import org.jetbrains.annotations.NotNull; /** * Represents an exception that is thrown when validation fails in the mediator pattern. @@ -40,6 +41,7 @@ public ValidationException(String message) { * Gets the ValidationResult associated with this exception, which contains the details of the validation failure, including any error messages. * @return the ValidationResult associated with this exception */ + @NotNull public ValidationResult getResult() { return result; } @@ -48,7 +50,9 @@ public ValidationResult getResult() { * Gets the list of error messages describing the validation failure. * @return the list of error messages describing the validation failure */ + @SuppressWarnings("unused") + @NotNull public List getErrors() { - return result.getErrors(); + return result.errors(); } } diff --git a/src/main/java/com/neroyun/mediator/validation/ValidationResult.java b/src/main/java/com/neroyun/mediator/validation/ValidationResult.java index 19faf53..eac8332 100644 --- a/src/main/java/com/neroyun/mediator/validation/ValidationResult.java +++ b/src/main/java/com/neroyun/mediator/validation/ValidationResult.java @@ -1,6 +1,7 @@ package com.neroyun.mediator.validation; import java.util.List; +import org.jetbrains.annotations.NotNull; /** * Defines the result of a validation operation, which can be either successful or failed with a list of error messages. @@ -8,15 +9,9 @@ * The ValidationResult class is designed to be immutable and thread-safe, making it suitable for use in concurrent environments where multiple threads may be performing validation operations simultaneously. * By encapsulating the validation result in a dedicated class, it promotes a clear and consistent way to handle validation outcomes throughout the application, allowing for better error handling and improved code readability when dealing with validation logic in the mediator pattern. */ -public final class ValidationResult { +public record ValidationResult(@NotNull List errors) { private static final ValidationResult SUCCESS = new ValidationResult(List.of()); - private final List errors; - - public ValidationResult(List errors) { - this.errors = errors; - } - public static ValidationResult success() { return SUCCESS; } @@ -29,10 +24,6 @@ public static ValidationResult failure(String message) { return new ValidationResult(List.of(message)); } - public List getErrors() { - return errors; - } - public boolean isSuccess() { return errors.isEmpty(); } @@ -41,8 +32,9 @@ public boolean isFailure() { return !isSuccess(); } + @NotNull @Override public String toString() { - return isSuccess()? "ValidationResult{success}" : "ValidationResult{failure, errors=" + errors + "}"; + return isSuccess() ? "ValidationResult{success}" : "ValidationResult{failure, errors=" + errors + "}"; } } From 53469c5567fab6c86d7e79312d731f8375ea8098 Mon Sep 17 00:00:00 2001 From: damon Date: Sat, 16 May 2026 00:45:00 +0800 Subject: [PATCH 4/5] Update version placeholders in README for consistency --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 613fc83..f9d9b91 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ - `groupId`: `com.neroyun` - `artifactId`: `mediator` -- `version`: `1.0.0` +- `version`: ${VERSION} - 测试依赖:`org.junit.jupiter:junit-jupiter:6.0.3` - 编译版本:`maven.compiler.source/target = 25` @@ -100,7 +100,7 @@ mediator.send(new UserCreateCommand("Alice", "alice@example.com")); com.neroyun mediator - 1.0.0 + ${VERSION} ``` From ba2802e7e6e3077ccd0fdf4bc55e0d3467946e9b Mon Sep 17 00:00:00 2001 From: damon Date: Sat, 16 May 2026 00:59:00 +0800 Subject: [PATCH 5/5] Add English README for Mediator library with detailed usage instructions and examples --- README.en.md | 306 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 8 ++ 2 files changed, 314 insertions(+) create mode 100644 README.en.md diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..217ec48 --- /dev/null +++ b/README.en.md @@ -0,0 +1,306 @@ +# Mediator + +A lightweight Java Mediator library for CQRS scenarios, supporting unified handling of `Command`, `Query`, and `Event` with middleware pipelines, message validation, and event parallel dispatch strategies. + +[![Maven Central](https://img.shields.io/maven-central/v/com.neroyun/mediator)](https://central.sonatype.com/artifact/com.neroyun/mediator) +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/NerosoftDev/Mediator/blob/master/LICENSE) + +## Overview + +`Mediator` decouples message senders from their handlers using a message-driven approach: + +- **`Command`** — Triggers an action (typically no return value) +- **`Query`** — Requests data and returns a result of type `R` +- **`Event`** — Publishes a notification that can be handled by multiple subscribers + +The default implementation is `PipelinedMediator`, which provides: + +- Automatic handler resolution by message type (`Handler`) +- Middleware pipeline support (`Middleware`) +- Message validation (`Validator`), throwing `ValidationException` on failure +- Event parallel dispatch strategies (`HandlerParallelStrategy`) +- Event exception handling strategies (`HandlerExceptionStrategy`) + +## Requirements + +- Java 17+ +- Maven + +## Installation + +Add the following dependency to your `pom.xml`: + +```xml + + com.neroyun + mediator + ${VERSION} + +``` + +## Quick Start + +### 1. Define a Command and its Handler + +```java +public record UserCreateCommand(String name, String email) implements Command {} + +public class UserCreateCommandHandler implements Handler { + @Override + public Void handle(UserCreateCommand message) { + System.out.println("Creating user: " + message.email()); + return null; + } +} +``` + +### 2. (Optional) Define a Validator + +```java +public class UserCreateCommandValidator implements Validator { + @Override + public ValidationResult validate(UserCreateCommand message) { + if (message.name() == null || message.name().isBlank()) { + return ValidationResult.failure("Name is required"); + } + if (message.email() == null || !message.email().contains("@")) { + return ValidationResult.failure("Email is invalid"); + } + return ValidationResult.success(); + } +} +``` + +### 3. Assemble the Mediator + +```java +Mediator mediator = new PipelinedMediator() + .use(() -> Stream.of(new UserCreateCommandHandler())) + .use(() -> Stream.of(new UserCreateCommandValidator())) + .use(() -> Stream.of( + (message, next) -> { + long start = System.nanoTime(); + try { + return next.invoke(); + } finally { + long cost = System.nanoTime() - start; + System.out.println("Handled " + message.getClass().getSimpleName() + " in " + cost + " ns"); + } + } + )); +``` + +### 4. Send a Message + +```java +mediator.send(new UserCreateCommand("Alice", "alice@example.com")); +``` + +If validation fails, a `ValidationException` is thrown. Use `getErrors()` to retrieve the list of error messages. + +--- + +## Middleware + +`Middleware` intercepts messages before or after they reach a `Handler`. Typical use cases include logging, performance monitoring, authentication, auditing, and distributed tracing. + +### Middleware Interface + +`Middleware` is a `@FunctionalInterface`: + +```java +@FunctionalInterface +public interface Middleware { + Object handle(Message message, MiddlewareDelegate next); +} +``` + +- `message` — The message currently being processed +- `next` — Invokes the next middleware or the final handler in the chain + +### Registering Middleware + +Pass middleware via `.use(() -> Stream.of(...))` when building `PipelinedMediator`: + +```java +Mediator mediator = new PipelinedMediator() + .use(() -> Stream.of(new UserCreateCommandHandler())) + .use(() -> Stream.of(new UserCreateCommandValidator())) + .use(() -> Stream.of( + (message, next) -> { + System.out.println("Before: " + message.getClass().getSimpleName()); + try { + return next.invoke(); + } finally { + System.out.println("After: " + message.getClass().getSimpleName()); + } + } + )); +``` + +### Execution Order + +Middleware forms a chain in registration order: + +1. The first registered middleware executes first +2. Calling `next.invoke()` passes control to the next middleware +3. Finally, the matching `Handler` is invoked +4. After the handler returns, each middleware continues its post-processing in reverse order + +### Common Patterns + +#### Logging and Timing + +```java +(message, next) -> { + long start = System.nanoTime(); + try { + return next.invoke(); + } finally { + System.out.println("Elapsed (ns): " + (System.nanoTime() - start)); + } +} +``` + +#### Pre-condition / Authorization Check + +```java +(message, next) -> { + if (message == null) { + throw new IllegalArgumentException("Message must not be null"); + } + return next.invoke(); +} +``` + +--- + +## Event Parallel Dispatch Strategies + +Annotate your event class to control how its handlers are dispatched: + +```java +@HandlerParallelStrategy(HandlerParallelStrategy.WHEN_ALL) +@HandlerExceptionStrategy(HandlerExceptionStrategy.CONTINUE) +public class UserCreatedEvent implements Event {} +``` + +### `@HandlerParallelStrategy` + +| Value | Description | +|-------|-------------| +| `NO_WAIT` *(default)* | Dispatches handlers asynchronously without waiting for completion (fire-and-forget) | +| `WHEN_ALL` | Waits for all handlers to complete before returning | +| `WHEN_ANY` | Waits until any one handler completes, then continues | + +### `@HandlerExceptionStrategy` + +| Value | Description | +|-------|-------------| +| `CONTINUE` *(default)* | Collects exceptions from all handlers and throws an `AggregateException` at the end | +| `STOP` | Stops processing immediately when any handler throws an exception | + +--- + +## Spring Boot Integration + +This library has no dependency on Spring. To integrate it into a Spring Boot application, wire a `PipelinedMediator` bean in a `@Configuration` class. + +### Register Handlers, Validators, and Middlewares as Spring Beans + +```java +@Component +public class UserCreateCommandHandler implements Handler { + @Override + public Void handle(UserCreateCommand message) { + // business logic + return null; + } +} + +@Component +public class UserCreateCommandValidator implements Validator { + @Override + public ValidationResult validate(UserCreateCommand message) { + if (message.name() == null || message.name().isBlank()) { + return ValidationResult.failure("Name is required"); + } + return ValidationResult.success(); + } +} +``` + +### Assemble the Mediator Bean + +```java +import com.neroyun.mediator.*; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import java.util.concurrent.Executors; + +@Configuration +public class MediatorConfiguration { + + @Bean + public Mediator mediator(ApplicationContext applicationContext) { + return new PipelinedMediator() + .use(() -> applicationContext.getBeansOfType(Handler.class).values().stream()) + .use(() -> applicationContext.getBeansOfType(Validator.class).values().stream()) + .use(() -> applicationContext.getBeansOfType(Middleware.class).values().stream()) + .use(() -> Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())); + } +} +``` + +### Inject into a Service + +```java +import com.neroyun.mediator.Mediator; +import org.springframework.stereotype.Service; + +@Service +public class UserApplicationService { + private final Mediator mediator; + + public UserApplicationService(Mediator mediator) { + this.mediator = mediator; + } + + public void createUser(String name, String email) { + mediator.send(new UserCreateCommand(name, email)); + } +} +``` + +> **Notes:** +> - Handlers are matched automatically by their generic message type +> - Multiple middlewares form a chain in stream order +> - When a `Validator` returns a failure, a `ValidationException` is thrown — catch it in a global exception handler (e.g., `@ControllerAdvice`) to return a proper HTTP error response + +--- + +## Package Structure + +| Package | Contents | +|---------|----------| +| `com.neroyun.mediator` | Core abstractions: `Mediator`, `Command`, `Query`, `Event`; extension points: `Handler`, `Middleware`, `Validator`; default implementation: `PipelinedMediator` | +| `com.neroyun.mediator.strategy` | Event parallel and exception strategy annotations | +| `com.neroyun.mediator.validation` | `ValidationResult`, `ValidationException` | +| `com.neroyun.mediator.internal` | Internal support types (message base, stream suppliers, exception aggregation, etc.) | + +--- + +## Building + +```bash +mvn clean test +``` + +Ensure your local JDK version matches the `maven.compiler.release` setting in `pom.xml` (currently Java 17). + +--- + +## License + +This project is licensed under the [GNU General Public License v3.0](https://github.com/NerosoftDev/Mediator/blob/master/LICENSE). diff --git a/README.md b/README.md index f9d9b91..81025fb 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ 一个轻量级的 Java Mediator 组件,用于在 CQRS 场景下统一处理 `Command`、`Query`、`Event`,并支持中间件管道、消息验证和事件并行策略。 +[![Maven Central](https://img.shields.io/maven-central/v/com.neroyun/mediator)](https://central.sonatype.com/artifact/com.neroyun/mediator) +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/NerosoftDev/Mediator/blob/master/LICENSE) + ## 项目简介 `Mediator` 通过“消息 + 处理器”的模式解耦业务调用方与实现方: @@ -18,6 +21,11 @@ - 支持事件并行分发策略(`HandlerParallelStrategy`) - 支持事件异常处理策略(`HandlerExceptionStrategy`) +## 环境要求 + +- Java 17+ +- Maven + ## 依赖与环境 项目为 Maven 工程(见 `pom.xml`):