Jarný BeanPostProcesor

1. Prehľad

Takže v rade ďalších návodov sme hovorili BeanPostProcessor. V tomto tutoriáli ich uvedieme do použitia v príklade z reálneho sveta s použitím Guava EventBus.

Jarné BeanPostProcessor dáva nám háčiky do životného cyklu Spring Bean, aby sme mohli upraviť jeho konfiguráciu.

BeanPostProcessor umožňuje priamu úpravu samotných bôbov.

V tomto výučbe sa pozrieme na konkrétny príklad týchto tried integrujúcich Guava EventBus.

2. Inštalácia

Najskôr musíme nastaviť naše prostredie. Pridajme do našich závislostí Jarný kontext, Jarný výraz a Guava pom.xml:

 org.springframework jarný kontext 5.2.6.RELEASE org.springframework jarný výraz 5.2.6.RELEASE com.google.guava guava 29.0-jre 

Ďalej poďme diskutovať o našich cieľoch.

3. Ciele a implementácia

Pre náš prvý cieľ chceme využiť Guava EventBus asynchrónne prenášať správy v rôznych aspektoch systému.

Ďalej chceme registrovať a zrušiť registráciu objektov pre udalosti automaticky pri vytváraní / zničení fazule namiesto použitia manuálnej metódy poskytnutej používateľom EventBus.

Teraz sme teda pripravení začať programovať!

Naša implementácia bude pozostávať z triedy wrapper pre Guava EventBus, anotácia vlastnej značky, a BeanPostProcessor, modelový objekt a fazuľa na prijímanie udalostí obchodovania s akciami od spoločnosti EventBus. Okrem toho vytvoríme testovací prípad na overenie požadovanej funkčnosti.

3.1. EventBus Zavinovačka

Aby sme boli s, definujeme EventBus wrapper poskytuje niektoré statické metódy na ľahkú registráciu a zrušenie registrácie fazule pre udalosti, ktoré použije BeanPostProcessor:

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"; súkromný statický konečný GlobalEventBus GLOBAL_EVENT_BUS = nový GlobalEventBus (); súkromný konečný EventBus eventBus = nový 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); }}

Tento kód poskytuje statické metódy prístupu k súboru GlobalEventBus a podkladové EventBus ako aj registrácia a zrušenie registrácie udalostí a zverejňovanie udalostí. Má tiež výraz SpEL používaný ako predvolený výraz v našej vlastnej anotácii na definovanie, ktorý z nich EventBus chceme využiť.

3.2. Anotácia vlastnej značky

Ďalej definujeme vlastnú anotáciu značky, ktorú použije BeanPostProcessor na identifikáciu fazule na automatickú registráciu / zrušenie registrácie udalostí:

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

3.3. BeanPostProcessor

Teraz definujeme BeanPostProcessor ktorá skontroluje každú fazuľu na Predplatiteľ anotácia. Táto trieda je tiež a DestructionAwareBeanPostProcessor, čo je rozhranie Spring, ktoré pridáva spätné volanie pred zničením BeanPostProcessor. Ak je anotácia k dispozícii, zaregistrujeme ju u EventBus identifikované výrazom SpEL anotácie pri inicializácii fazule a zrušiť registráciu pri deštrukcii fazule:

verejná trieda GuavaEventBusBeanPostProcessor implementuje DestructionAwareBeanPostProcessor {Logger logger = LoggerFactory.getLogger (this.getClass ()); SpelExpressionParser expressionParser = nový SpelExpressionParser (); @Override public void postProcessBeforeDestruction (objekt bean, reťazec beanName) hodí BeansException {this.process (bean, EventBus :: unregister, "destruction"); } @Override public boolean requiresDestruction (Object bean) {return true; } @Override public Object postProcessBeforeInitialization (Object bean, String beanName) hodí BeansException {return bean; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) hodí BeansException {this.process (bean, EventBus :: register, "initialization"); spätná fazuľa; } proces private void (objekt bean, spotrebiteľ BiConsumer, akcia String) {// Pozri implementáciu nižšie}}

Vyššie uvedený kód vezme každú fazuľu a spustí ju cez procesu metóda definovaná nižšie. Spracuje ho po inicializácii fazule a pred jej zničením. The vyžadujeDestrukciu metóda štandardne vráti hodnotu true a toto správanie tu ponecháme, keď kontrolujeme existenciu súboru @ Predplatiteľ anotácia v postProcessBeforeDestruction zavolaj späť.

Poďme sa teraz pozrieť na procesu metóda:

private void process (Object bean, BiConsumer consumer, String action) {Object proxy = this.getTargetObject (bean); Anotácia účastníka = AnnotationUtils.getAnnotation (proxy.getClass (), Subscriber.class); if (annotation == null) return; this.logger.info ("{}: spracovanie fazule typu {} počas {}", this.getClass (). getSimpleName (), proxy.getClass (). getName (), akcia); Reťazec annotationValue = annotation.value (); skúsiť {Expression expression = this.expressionParser.parseExpression (annotationValue); Hodnota objektu = expression.getValue (); if (! (hodnota instanceof EventBus)) {this.logger.error ("{}: výraz {} sa nevyhodnotil pre inštanciu EventBus pre fazuľu typu {}", this.getClass (). getSimpleName (), annotationValue , proxy.getClass (). getSimpleName ()); návrat; } EventBus eventBus = (EventBus) hodnota; consumer.accept (eventBus, proxy); } catch (ExpressionException ex) {this.logger.error ("{}: nedokáže analyzovať / vyhodnotiť výraz {} pre fazuľu typu {}", this.getClass (). getSimpleName (), annotationValue, proxy.getClass () .getName ()); }}

Tento kód kontroluje existenciu našej vlastnej anotácie značky s názvom Predplatiteľ a ak je prítomný, načíta výraz SpEL z jeho hodnotu nehnuteľnosť. Potom je výraz vyhodnotený do objektu. Ak je to príklad EventBus, aplikujeme BiConsumer parameter funkcie do fazule. The BiConsumer sa používa na registráciu a zrušenie registrácie fazule z EventBus.

Implementácia metódy getTargetObject je nasledujúci:

private Object getTargetObject (Object proxy) hodí BeansException {if (AopUtils.isJdkDynamicProxy (proxy)) {try {return ((Advised) proxy) .getTargetSource (). getTarget (); } catch (Výnimka e) {hodiť novú FatalBeanException ("Chyba pri získavaní cieľa servera JDK proxy", e); }} vrátiť proxy; }

3.4. StockTrade Modelový objekt

Ďalej definujme naše StockTrade modelový objekt:

public class StockTrade {private String symbol; súkromné ​​množstvo; dvojitá súkromná cena; private Date tradeDate; // konštruktor}

3.5. StockTradePublisher Prijímač udalostí

Potom definujme triedu poslucháčov, ktorá nás upozorní na prijatie obchodu, aby sme mohli napísať test:

@FunctionalInterface verejné rozhranie StockTradeListener {void stockTradePublished (obchod StockTrade); }

Nakoniec definujeme prijímač pre nový StockTrade diania:

@Subscriber verejná trieda StockTradePublisher {Nastaviť stockTradeListeners = nový HashSet (); public void addStockTradeListener (StockTradeListener poslucháč) {synchronized (this.stockTradeListeners) {this.stockTradeListeners.add (listener); }} public void removeStockTradeListener (poslucháč StockTradeListener) {synchronizované (this.stockTradeListeners) {this.stockTradeListeners.remove (poslucháč); }} @Subscribe @AllowConcurrentEvents zrušiť handleNewStockTradeEvent (obchod StockTrade) {// publikovať do DB, poslať na PubNub, ... Nastaviť poslucháčov; synchronized (this.stockTradeListeners) {listeners = new HashSet (this.stockTradeListeners); } listeners.forEach (li -> li.stockTradePublished (obchod)); }}

Vyššie uvedený kód označuje túto triedu ako a Predplatiteľ Guava EventBus udalosti a Guava @ Prihlásiť sa na odber anotácia označuje metódu handleNewStockTradeEvent ako prijímač udalostí. Typ udalostí, ktoré dostane, je založený na triede jediného parametra metódy; v takom prípade dostaneme udalosti typu StockTrade.

The @AllowConcurrentEvents anotácia umožňuje súčasné vyvolanie tejto metódy. Akonáhle dostaneme obchod, urobíme akékoľvek spracovanie, ktoré si prajeme, a potom informujeme všetkých poslucháčov.

3.6. Testovanie

Teraz zhrňme naše kódovanie integračným testom na overenie BeanPostProcessor funguje správne. Najprv budeme potrebovať jarný kontext:

@Configuration verejná trieda PostProcessorConfiguration {@Bean public GlobalEventBus eventBus () {return GlobalEventBus.getInstance (); } @Bean public GuavaEventBusBeanPostProcessor eventBusBeanPostProcessor () {vrátiť nový GuavaEventBusBeanPostProcessor (); } @Bean public StockTradePublisher stockTradePublisher () {vrátiť nový StockTradePublisher (); }}

Teraz môžeme implementovať náš test:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = PostProcessorConfiguration.class) verejná trieda StockTradeIntegrationTest {@Autowired StockTradePublisher stockTradePublisher; @Test public void givenValidConfig_whenTradePublished_thenTradeReceived () {Date tradeDate = nový dátum (); StockTrade stockTrade = nový StockTrade ("AMZN", 100, 2483,52d, tradeDate); AtomicBoolean assertionsPassed = nový AtomicBoolean (false); StockTradeListener listener = trade -> assertionsPassed .set (this.verifyExact (stockTrade, trade)); this.stockTradePublisher.addStockTradeListener (poslucháč); vyskúšajte {GlobalEventBus.post (stockTrade); await (). atMost (Duration.ofSeconds (2L)) .untilAsserted (() -> assertThat (assertionsPassed.get ()). isTrue ()); } konečne {this.stockTradePublisher.removeStockTradeListener (poslucháč); }} 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 (); }}

Vyššie uvedený testovací kód vygeneruje obchod s akciami a zaúčtuje ho na server GlobalEventBus. Čakáme nanajvýš dve sekundy, kým sa akcia dokončí a na oznámenie, že obchod bol prijatý StockTradePublisher. Ďalej overujeme, že prijatý obchod nebol pri tranzite upravený.

4. Záver

Na záver jarné BeanPostProcessor umožňuje nám to prispôsobiť fazuľa sami, ktorý nám poskytuje prostriedky na automatizáciu akcií fazule, ktoré by sme inak museli robiť ručne.

Ako vždy, zdrojový kód je k dispozícii na GitHub.