イベント機構はSpringの機能ですが、現在はSpringBootフレームワークを使用しているため、SpringBootフレームワーク上でイベント機構の利用を記録し、同時に非同期処理を実現します。イベント メカニズムは実際にはオブザーバー モード (パブリッシュ/サブスクライブ モード) を使用します。
Spring のイベント メカニズムは次のプロセスを経ます。
- 1. カスタム イベント。 org.springframework.context.ApplicationEvent 抽象クラスを継承します。
- 2. イベントリスナーを定義し、 org.springframework.context.ApplicationListener インターフェースを実装する
- 3. Spring コンテナーでイベントを公開する
SpringBootのプログラム例
ユーザー保存時にユーザーが用意したメールボックスにメールを送信する機能を実現し、同時に非同期処理を採用します。
カスタムイベント
import org.springframework.context.ApplicationEvent;
public class EmailEvent extends ApplicationEvent {
private static final long serialVersionUID = 3733891603598996786L;
private String emailAddress;
public EmailEvent(String emailAddress) {
super(emailAddress);
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
}
イベントリスナーを定義する
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class EmailEventListener implements ApplicationListener<EmailEvent> {
private static Logger log = LoggerFactory.getLogger(EmailEventListener.class);
// 异步处理
@Async
@Override
public void onApplicationEvent(EmailEvent event) {
log.info("监听到事件--邮箱地址:" + event.getEmailAddress());
//模拟处理的耗时3s
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("事件处理完成");
}
}
イベント後
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class EmailEventPublish {
@Autowired
private ApplicationContext applicationContext;
public void publishEvent(String emailAddress) {
EmailEvent event = new EmailEvent(emailAddress);
applicationContext.publishEvent(event);
}
}
通話イベント
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.event.EmailEventPublish;
@RestController
public class EventController {
private static Logger log = LoggerFactory.getLogger(EventController.class);
@Autowired
private EmailEventPublish emailEventPublish;
@RequestMapping("/event")
public void publishEvent(@RequestParam String emailAddress) {
// 发布事件 -- 采用异步处理
emailEventPublish.publishEvent(emailAddress);
// 正常该语句先执行
log.info("Controller业务处理");
}
}
結果
次のアドレスにアクセスしてください
http://localhost:8080/[email protected]
結果は
2023-08-04 21:21:14.338 INFO 6400 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2023-08-04 21:21:14.338 INFO 6400 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2023-08-04 21:21:14.370 INFO 6400 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 32 ms
2023-08-04 21:21:14.429 INFO 6400 --- [nio-8080-exec-1] .s.a.AnnotationAsyncExecutionInterceptor : No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either
2023-08-04 21:21:14.534 INFO 6400 --- [nio-8080-exec-1] c.e.demo.controller.EventController : Controller业务处理
2023-08-04 21:21:14.535 INFO 6400 --- [cTaskExecutor-1] c.example.demo.event.EmailEventListener : 监听到事件--邮箱地址:[email protected]
2023-08-04 21:21:17.536 INFO 6400 --- [cTaskExecutor-1] c.example.demo.event.EmailEventListener : 事件处理完成
上記の結果より、イベント後のプログラムが先に出力され、タイムアップ後にリスナープログラムのコードが実行されるという非同期処理が実現されていることがわかります。
非同期処理を実現するには、イベントをリッスンして業務コードを実行するメソッドにアノテーションを追加し@Async
、同時にスタートアップクラスに追加します@EnableAsync
。
上記のログには、TaskExecutor
カスタム スレッド プールがある場合はそれが呼び出され、ない場合はデフォルトのプールが使用されることも記載されています。自分で定義することもできますTaskExecutor
。
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@EnableAsync
@Configuration
public class ThreadPool implements AsyncConfigurer {
@Nullable
@Override
@Bean("taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
// 线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(20);
// 用来缓冲执行任务的队列
executor.setQueueCapacity(200);
// 允许线程的空闲时间60秒
executor.setKeepAliveSeconds(60);
// 线程池名的前缀
executor.setThreadNamePrefix("taskExecutor-");
// 线程池对拒绝任务的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Nullable
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
結果
2023-08-04 21:27:36.507 INFO 7848 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2023-08-04 21:27:36.507 INFO 7848 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2023-08-04 21:27:36.537 INFO 7848 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 30 ms
2023-08-04 21:27:36.757 INFO 7848 --- [nio-8080-exec-2] c.e.demo.controller.EventController : Controller业务处理
2023-08-04 21:27:36.757 INFO 7848 --- [ taskExecutor-1] c.example.demo.event.EmailEventListener : 监听到事件--邮箱地址:[email protected]
2023-08-04 21:27:39.757 INFO 7848 --- [ taskExecutor-1] c.example.demo.event.EmailEventListener : 事件处理完成
定義したスレッド プールが使用されていることがわかります[ taskExecutor-1]
。
要約する
Springのイベント機構は非常に実用的な機能であり、監視や非同期処理に関する機能に適しています。