1. 概要

そのため、他の多くのチュートリアルでは、BeanPostProcessorについて説明しました。 このチュートリアルでは、GuavaのEventBusを使用した実際の例で使用するためにそれらを配置します。

SpringのBeanPostProcessor は、Springbeanライフサイクルへのフックを提供して構成を変更します。

BeanPostProcessor を使用すると、Bean自体を直接変更できます。

このチュートリアルでは、GuavaのEventBusを統合するこれらのクラスの具体例を見ていきます。

2. 設定

まず、環境を整える必要があります。 Spring Context 、Spring Expression 、およびGuavaの依存関係をpom.xmlに追加しましょう。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

次に、私たちの目標について話し合いましょう。

3. 目標と実装

最初の目標として、 GuavaのEventBusを利用して、システムのさまざまな側面に非同期でメッセージを渡します

次に、 EventBus が提供する手動の方法を使用する代わりに、Beanの作成/破棄時にイベントのオブジェクトを自動的に登録および登録解除します。

これで、コーディングを開始する準備が整いました。

私たちの実装は、Guavaの EventBus のラッパークラス、カスタムマーカーアノテーション、 Bean PostProcessor 、モデルオブジェクト、およびbeanで構成されます。 EventBus。  さらに、必要な機能を検証するためのテストケースを作成します。

3.1. EventBusラッパー

そのために、 EventBus ラッパーを定義して、BeanPostProcessorによって使用されるイベントのBeanを簡単に登録および登録解除するための静的メソッドを提供します。

public final class GlobalEventBus {

    public static final String GLOBAL_EVENT_BUS_EXPRESSION
      = "T(com.baeldung.postprocessor.GlobalEventBus).getEventBus()";

    private static final String IDENTIFIER = "global-event-bus";
    private static final GlobalEventBus GLOBAL_EVENT_BUS = new GlobalEventBus();
    private final EventBus eventBus = new AsyncEventBus(IDENTIFIER, Executors.newCachedThreadPool());

    private GlobalEventBus() {}

    public static GlobalEventBus getInstance() {
        return GlobalEventBus.GLOBAL_EVENT_BUS;
    }

    public static EventBus getEventBus() {
        return GlobalEventBus.GLOBAL_EVENT_BUS.eventBus;
    }

    public static void subscribe(Object obj) {
        getEventBus().register(obj);
    }
    public static void unsubscribe(Object obj) {
        getEventBus().unregister(obj);
    }
    public static void post(Object event) {
        getEventBus().post(event);
    }
}

このコードは、GlobalEventBusおよび基盤となるEventBusにアクセスし、イベントの登録と登録解除およびイベントの投稿を行うための静的メソッドを提供します。 また、カスタムアノテーションのデフォルト式として使用されるSpEL式があり、使用するEventBusを定義します。

3.2. カスタムマーカー注釈

次に、 BeanPostProcessor がBeanを識別してイベントを自動的に登録/登録解除するために使用する、カスタムマーカーアノテーションを定義しましょう。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Subscriber {
    String value() default GlobalEventBus.GLOBAL_EVENT_BUS_EXPRESSION;
}

3.3. BeanPostProcessor

次に、 Bean PostProcessor を定義します。これにより、各beanでSubscriberアノテーションがチェックされます。 このクラスはDestructionAwareBeanPostProcessor、でもあり、BeanPostProcessorに破壊前のコールバックを追加するSpringインターフェイスです。 注釈が存在する場合は、beanの初期化時に注釈のSpEL式で識別される EventBus に登録し、beanの破棄時に登録を解除します。

public class GuavaEventBusBeanPostProcessor
  implements DestructionAwareBeanPostProcessor {

    Logger logger = LoggerFactory.getLogger(this.getClass());
    SpelExpressionParser expressionParser = new SpelExpressionParser();

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName)
      throws BeansException {
        this.process(bean, EventBus::unregister, "destruction");
    }

    @Override
    public boolean requiresDestruction(Object bean) {
        return true;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
      throws BeansException {
        this.process(bean, EventBus::register, "initialization");
        return bean;
    }

    private void process(Object bean, BiConsumer<EventBus, Object> consumer, String action) {
       // See implementation below
    }
}

上記のコードは、すべてのBeanを取得し、以下に定義されているprocessメソッドを介して実行します。 Beanが初期化された後、破棄される前に処理します。 requireDestruction メソッドはデフォルトでtrueを返し、postProcessBeforeDestructionコールバックに@Subscriberアノテーションが存在するかどうかを確認するときに、この動作を維持します。

次に、processメソッドを見てみましょう。

private void process(Object bean, BiConsumer<EventBus, Object> consumer, String action) {
    Object proxy = this.getTargetObject(bean);
    Subscriber annotation = AnnotationUtils.getAnnotation(proxy.getClass(), Subscriber.class);
    if (annotation == null)
        return;
    this.logger.info("{}: processing bean of type {} during {}",
      this.getClass().getSimpleName(), proxy.getClass().getName(), action);
    String annotationValue = annotation.value();
    try {
        Expression expression = this.expressionParser.parseExpression(annotationValue);
        Object value = expression.getValue();
        if (!(value instanceof EventBus)) {
            this.logger.error(
              "{}: expression {} did not evaluate to an instance of EventBus for bean of type {}",
              this.getClass().getSimpleName(), annotationValue, proxy.getClass().getSimpleName());
            return;
        }
        EventBus eventBus = (EventBus)value;
        consumer.accept(eventBus, proxy);
    } catch (ExpressionException ex) {
        this.logger.error("{}: unable to parse/evaluate expression {} for bean of type {}",
          this.getClass().getSimpleName(), annotationValue, proxy.getClass().getName());
    }
}

このコードは、 Subscriber という名前のカスタムマーカーアノテーションの存在を確認し、存在する場合は、そのvalueプロパティからSpEL式を読み取ります。 次に、式がオブジェクトに評価されます。 EventBus、のインスタンスの場合、BiConsumer関数パラメーターをbeanに適用します。 BiConsumer は、EventBusからのBeanの登録と登録解除に使用されます。

メソッドgetTargetObjectの実装は次のとおりです。

private Object getTargetObject(Object proxy) throws BeansException {
    if (AopUtils.isJdkDynamicProxy(proxy)) {
        try {
            return ((Advised)proxy).getTargetSource().getTarget();
        } catch (Exception e) {
            throw new FatalBeanException("Error getting target of JDK proxy", e);
        }
    }
    return proxy;
}

3.4. StockTradeモデルオブジェクト

次に、StockTradeモデルオブジェクトを定義しましょう。

public class StockTrade {

    private String symbol;
    private int quantity;
    private double price;
    private Date tradeDate;
    
    // constructor
}

3.5. StockTradePublisherイベントレシーバー

次に、テストを作成できるように、取引が受信されたことを通知するリスナークラスを定義しましょう。

@FunctionalInterface
public interface StockTradeListener {
    void stockTradePublished(StockTrade trade);
}

最後に、新しいStockTradeイベントのレシーバーを定義します。

@Subscriber
public class StockTradePublisher {

    Set<StockTradeListener> stockTradeListeners = new HashSet<>();

    public void addStockTradeListener(StockTradeListener listener) {
        synchronized (this.stockTradeListeners) {
            this.stockTradeListeners.add(listener);
        }
    }

    public void removeStockTradeListener(StockTradeListener listener) {
        synchronized (this.stockTradeListeners) {
            this.stockTradeListeners.remove(listener);
        }
    }

    @Subscribe
    @AllowConcurrentEvents
    void handleNewStockTradeEvent(StockTrade trade) {
        // publish to DB, send to PubNub, ...
        Set<StockTradeListener> listeners;
        synchronized (this.stockTradeListeners) {
            listeners = new HashSet<>(this.stockTradeListeners);
        }
        listeners.forEach(li -> li.stockTradePublished(trade));
    }
}

上記のコードは、このクラスをGuava EventBusイベントのSubscriberとしてマークし、Guavaの@SubscribeアノテーションはメソッドhandleNewStockTradeEventをのレシーバーとしてマークします。イベント。 受け取るイベントのタイプは、メソッドの単一パラメーターのクラスに基づいています。 この場合、タイプStockTradeのイベントを受け取ります。

@AllowConcurrentEvents アノテーションにより、このメソッドの同時呼び出しが可能になります。 取引を受け取ったら、希望する処理を行い、リスナーに通知します。

3.6. テスト

次に、コーディングを統合テストで締めくくり、BeanPostProcessorが正しく機能することを確認します。 まず、Springコンテキストが必要です。

@Configuration
public class PostProcessorConfiguration {

    @Bean
    public GlobalEventBus eventBus() {
        return GlobalEventBus.getInstance();
    }

    @Bean
    public GuavaEventBusBeanPostProcessor eventBusBeanPostProcessor() {
        return new GuavaEventBusBeanPostProcessor();
    }

    @Bean
    public StockTradePublisher stockTradePublisher() {
        return new StockTradePublisher();
    }
}

これで、テストを実装できます。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = PostProcessorConfiguration.class)
public class StockTradeIntegrationTest {

    @Autowired
    StockTradePublisher stockTradePublisher;

    @Test
    public void givenValidConfig_whenTradePublished_thenTradeReceived() {
        Date tradeDate = new Date();
        StockTrade stockTrade = new StockTrade("AMZN", 100, 2483.52d, tradeDate);
        AtomicBoolean assertionsPassed = new AtomicBoolean(false);
        StockTradeListener listener = trade -> assertionsPassed
          .set(this.verifyExact(stockTrade, trade));
        this.stockTradePublisher.addStockTradeListener(listener);
        try {
            GlobalEventBus.post(stockTrade);
            await().atMost(Duration.ofSeconds(2L))
              .untilAsserted(() -> assertThat(assertionsPassed.get()).isTrue());
        } finally {
            this.stockTradePublisher.removeStockTradeListener(listener);
        }
    }

    boolean verifyExact(StockTrade stockTrade, StockTrade trade) {
        return Objects.equals(stockTrade.getSymbol(), trade.getSymbol())
          && Objects.equals(stockTrade.getTradeDate(), trade.getTradeDate())
          && stockTrade.getQuantity() == trade.getQuantity()
          && stockTrade.getPrice() == trade.getPrice();
    }
}

上記のテストコードは株取引を生成し、それをGlobalEventBusに投稿します。 アクションが完了し、stockTradePublisherによって取引が受信されたことが通知されるまで最大2秒待ちます。 さらに、受け取った取引が輸送中に変更されていないことを検証します。

4. 結論

結論として、Springの Bean PostProcessor を使用すると、自体をカスタマイズして、beanアクションを自動化する手段を提供できます。手動で行う。

いつものように、ソースコードはGitHubから入手できます。