Sự kiện mùa xuân

1. Khái quát chung

Trong bài viết này, chúng ta sẽ thảo luận về cách sử dụng sự kiện trong Spring .

Sự kiện là một trong những chức năng bị bỏ qua nhiều hơn trong khuôn khổ nhưng cũng là một trong những chức năng hữu ích hơn. Và - giống như nhiều thứ khác trong Spring - xuất bản sự kiện là một trong những khả năng được cung cấp bởi ApplicationContext.

Có một số hướng dẫn đơn giản để làm theo:

  • sự kiện sẽ mở rộng ApplicationEvent
  • nhà xuất bản nên đưa một đối tượng ApplicationEventPublisher vào
  • người nghe nên triển khai giao diện ApplicationListener

2. Sự kiện tùy chỉnh

Spring cho phép chúng tôi tạo và xuất bản các sự kiện tùy chỉnh - theo mặc định - là đồng bộ . Điều này có một số lợi thế - chẳng hạn như người nghe có thể tham gia vào bối cảnh giao dịch của nhà xuất bản.

2.1. Một sự kiện ứng dụng đơn giản

Hãy tạo một lớp sự kiện đơn giản - chỉ là một trình giữ chỗ để lưu trữ dữ liệu sự kiện. Trong trường hợp này, lớp sự kiện giữ một thông báo Chuỗi:

public class CustomSpringEvent extends ApplicationEvent { private String message; public CustomSpringEvent(Object source, String message) { super(source); this.message = message; } public String getMessage() { return message; } }

2.2. Một nhà xuất bản

Bây giờ hãy tạo một nhà xuất bản của sự kiện đó . Nhà xuất bản xây dựng đối tượng sự kiện và xuất bản nó cho bất kỳ ai đang lắng nghe.

Để xuất bản sự kiện, nhà xuất bản chỉ cần đưa ApplicationEventPublisher vào và sử dụng API PublishingEvent () :

@Component public class CustomSpringEventPublisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; public void publishCustomEvent(final String message) { System.out.println("Publishing custom event. "); CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message); applicationEventPublisher.publishEvent(customSpringEvent); } }

Ngoài ra, lớp nhà xuất bản có thể triển khai giao diện ApplicationEventPublisherAware - giao diện này cũng sẽ đưa nhà xuất bản sự kiện vào phần khởi động ứng dụng. Thông thường, sẽ đơn giản hơn nếu bạn chỉ cần đưa @Autowire vào nhà xuất bản .

2.3. Một người nghe

Cuối cùng, hãy tạo trình nghe.

Yêu cầu duy nhất đối với trình lắng nghe là phải là một bean và triển khai giao diện ApplicationListener :

@Component public class CustomSpringEventListener implements ApplicationListener { @Override public void onApplicationEvent(CustomSpringEvent event) { System.out.println("Received spring custom event - " + event.getMessage()); } }

Lưu ý cách trình xử lý tùy chỉnh của chúng tôi được tham số với loại sự kiện tùy chỉnh chung - điều này làm cho loại phương thức onApplicationEvent () trở nên an toàn. Điều này cũng tránh phải kiểm tra xem đối tượng có phải là một thể hiện của một lớp sự kiện cụ thể hay không và truyền nó.

Và, như đã thảo luận - theo mặc định các sự kiện mùa xuân là đồng bộ - phương thức doStuffAndPublishAnEvent () chặn cho đến khi tất cả người nghe xử lý xong sự kiện.

3. Tạo sự kiện không đồng bộ

Trong một số trường hợp, việc xuất bản các sự kiện một cách đồng bộ không thực sự là điều chúng tôi đang tìm kiếm - chúng tôi có thể cần xử lý không đồng bộ các sự kiện của mình .

Bạn có thể bật điều đó trong cấu hình bằng cách tạo một bean ApplicationEventMulticaster với một trình thực thi; cho các mục đích của chúng tôi ở đây SimpleAsyncTaskExecutor hoạt động tốt:

@Configuration public class AsynchronousSpringEventsConfig { @Bean(name = "applicationEventMulticaster") public ApplicationEventMulticaster simpleApplicationEventMulticaster() { SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); return eventMulticaster; } }

Sự kiện, nhà xuất bản và triển khai trình lắng nghe vẫn giống như trước - nhưng bây giờ, trình xử lý sẽ xử lý không đồng bộ với sự kiện trong một chuỗi riêng biệt .

4. Sự kiện khung hiện có

Spring tự công bố nhiều sự kiện khác nhau. Ví dụ: ApplicationContext sẽ kích hoạt các sự kiện khung khác nhau. Ví dụ: ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent, v.v.

Những sự kiện này cung cấp cho các nhà phát triển ứng dụng một tùy chọn để kết nối vào vòng đời của ứng dụng và ngữ cảnh và thêm vào logic tùy chỉnh của riêng họ nếu cần.

Dưới đây là một ví dụ nhanh về một người nghe đang lắng nghe để làm mới ngữ cảnh:

public class ContextRefreshedListener implements ApplicationListener { @Override public void onApplicationEvent(ContextRefreshedEvent cse) { System.out.println("Handling context re-freshed event. "); } }

Để tìm hiểu thêm về các sự kiện khuôn khổ hiện có, hãy xem hướng dẫn tiếp theo của chúng tôi tại đây.

5. Trình xử lý sự kiện theo hướng chú thích

Bắt đầu với Spring 4.2, trình xử lý sự kiện không bắt buộc phải là bean triển khai giao diện ApplicationListener - nó có thể được đăng ký trên bất kỳ phương thức công khai nào của bean được quản lý thông qua chú thích @EventListener :

@Component public class AnnotationDrivenEventListener { @EventListener public void handleContextStart(ContextStartedEvent cse) { System.out.println("Handling context started event."); } }

Như trước đây, chữ ký phương thức khai báo kiểu sự kiện mà nó sử dụng.

Theo mặc định, trình nghe được gọi đồng bộ. Tuy nhiên, chúng tôi có thể dễ dàng làm cho nó không đồng bộ bằng cách thêm chú thích @Async . Tuy nhiên, chúng ta phải nhớ bật hỗ trợ Async trong ứng dụng.

6. Hỗ trợ Generics

Cũng có thể gửi các sự kiện với thông tin chung trong loại sự kiện.

6.1. Sự kiện ứng dụng chung

Hãy tạo một loại sự kiện chung . Trong ví dụ của chúng tôi, lớp sự kiện chứa bất kỳ nội dung nào và chỉ báo trạng thái thành công :

public class GenericSpringEvent { private T what; protected boolean success; public GenericSpringEvent(T what, boolean success) { this.what = what; this.success = success; } // ... standard getters }

Lưu ý sự khác biệt giữa GenericSpringEventCustomSpringEvent . Giờ đây, chúng tôi có thể linh hoạt xuất bản bất kỳ sự kiện tùy ý nào và không bắt buộc phải mở rộng từ ApplicationEvent nữa.

6.2. Một người nghe

Bây giờ hãy tạo một trình lắng nghe của sự kiện đó . Chúng tôi có thể xác định trình lắng nghe bằng cách triển khai giao diện ApplicationListener như trước đây:

@Component public class GenericSpringEventListener implements ApplicationListener
    
      { @Override public void onApplicationEvent(@NonNull GenericSpringEvent event) { System.out.println("Received spring generic event - " + event.getWhat()); } }
    

But unfortunately, this definition requires us to inherit GenericSpringEvent from the ApplicationEvent class. So for this tutorial, let's make use of an annotation-driven event listener discussed previously.

It is also possible to make the event listener conditional by defining a boolean SpEL expression on the @EventListener annotation. In this case, the event handler will only be invoked for a successful GenericSpringEvent of String:

@Component public class AnnotationDrivenEventListener { @EventListener(condition = "#event.success") public void handleSuccessful(GenericSpringEvent event) { System.out.println("Handling generic event (conditional)."); } }

The Spring Expression Language (SpEL) is a powerful expression language that's covered in detail in another tutorial.

6.3. A Publisher

The event publisher is similar to the one described above. But due to type erasure, we need to publish an event that resolves the generics parameter we would filter on. For example, class GenericStringSpringEvent extends GenericSpringEvent.

And there's an alternative way of publishing events. If we return a non-null value from a method annotated with @EventListener as the result, Spring Framework will send that result as a new event for us. Moreover, we can publish multiple new events by returning them in a collection as the result of event processing.

7. Transaction Bound Events

This paragraph is about using the @TransactionalEventListener annotation. To learn more about transaction management check out the Transactions with Spring and JPA tutorial.

Since Spring 4.2, the framework provides a new @TransactionalEventListener annotation, which is an extension of @EventListener, that allows binding the listener of an event to a phase of the transaction. Binding is possible to the following transaction phases:

  • AFTER_COMMIT (default) is used to fire the event if the transaction has completed successfully
  • AFTER_ROLLBACK – if the transaction has rolled back
  • AFTER_COMPLETION – if the transaction has completed (an alias for AFTER_COMMIT and AFTER_ROLLBACK)
  • BEFORE_COMMIT is used to fire the event right before transaction commit

Here's a quick example of transactional event listener:

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) public void handleCustom(CustomSpringEvent event) { System.out.println("Handling event inside a transaction BEFORE COMMIT."); }

This listener will be invoked only if there's a transaction in which the event producer is running and it's about to be committed.

And, if no transaction is running the event isn’t sent at all unless we override this by setting fallbackExecution attribute to true.

8. Conclusion

Trong hướng dẫn nhanh này, chúng ta đã đi qua các kiến ​​thức cơ bản về xử lý các sự kiện trong Spring - tạo một sự kiện tùy chỉnh đơn giản, xuất bản và sau đó xử lý nó trong trình lắng nghe.

Chúng tôi cũng đã có một cái nhìn ngắn gọn về cách kích hoạt xử lý không đồng bộ các sự kiện trong cấu hình.

Sau đó, chúng tôi đã tìm hiểu về các cải tiến được giới thiệu trong Spring 4.2, chẳng hạn như trình nghe theo hướng chú thích, hỗ trợ generics tốt hơn và các sự kiện liên kết với các giai đoạn giao dịch.

Như mọi khi, mã được trình bày trong bài viết này có sẵn trên Github. Đây là một dự án dựa trên Maven, vì vậy nó sẽ dễ dàng nhập và chạy như nó vốn có.