Proljetni BeanPostProcessor

1. Pregled

Dakle, u nizu drugih vodiča smo razgovarali o tome BeanPostProcessor. U ovom vodiču stavit ćemo ih na primjeru iz stvarnog svijeta koristeći Guava-inu EventBus.

Proljetni BeanPostProcessor daje nam kukice u životnom ciklusu proljetnog graha kako bismo izmijenili njegovu konfiguraciju.

BeanPostProcessor omogućuje izravnu modifikaciju samih graha.

U ovom uputstvu ćemo pogledati konkretan primjer ovih klasa koje integriraju Guavinu EventBus.

2. Postavljanje

Prvo, moramo postaviti svoje okruženje. Dodajmo ovisnosti Spring Context, Spring Expression i Guava pom.xml:

 org.springframework spring-context 5.2.6.RELEASE org.springframework spring-expression 5.2.6.RELEASE com.google.guava guava 29.0-jre 

Dalje, razgovarajmo o našim ciljevima.

3. Ciljevi i provedba

Kao prvi cilj želimo koristiti Guava EventBus za asinkrono prosljeđivanje poruka kroz različite aspekte sustava.

Dalje, želimo automatski registrirati i odjaviti objekte za događaje prilikom stvaranja / uništavanja graha, umjesto da koristimo ručnu metodu koju pruža EventBus.

Dakle, sada smo spremni za početak kodiranja!

Naša implementacija sastojat će se od klase omota za Guavu EventBus, prilagođena oznaka markera, a BeanPostProcessor, objekt modela i grah za primanje događaja trgovine dionicama od EventBus. Osim toga, stvorit ćemo testni slučaj kako bismo provjerili željenu funkcionalnost.

3.1. EventBus Omot

Da bismo bili s, definirat ćemo EventBus omot za pružanje nekih statičnih metoda za lako registriranje i odjavu graha za događaje koje će koristiti BeanPostProcessor:

javna završna klasa GlobalEventBus {javni statički završni niz GLOBAL_EVENT_BUS_EXPRESSION = "T (com.baeldung.postprocessor.GlobalEventBus) .getEventBus ()"; privatni statički završni niz IDENTIFIER = "sabirnica globalnih događaja"; privatni statički konačni GlobalEventBus GLOBAL_EVENT_BUS = novi GlobalEventBus (); privatni konačni EventBus eventBus = novi AsyncEventBus (IDENTIFIER, Executors.newCachedThreadPool ()); private GlobalEventBus () {} public static GlobalEventBus getInstance () {return GlobalEventBus.GLOBAL_EVENT_BUS; } javni statički EventBus getEventBus () {return GlobalEventBus.GLOBAL_EVENT_BUS.eventBus; } javna statička void pretplata (objekt obj) {getEventBus (). register (obj); } javna statička void odjava (objekt obj) {getEventBus (). odjava (obj); } javni statični void post (događaj objekta) {getEventBus (). post (event); }}

Ovaj kôd pruža statičke metode za pristup GlobalEventBus i temeljni EventBus kao i registracija i odjava za događaje i objavljivanje događaja. Također ima SpEL izraz koji se koristi kao zadani izraz u našoj prilagođenoj bilješci da definira koji EventBus želimo iskoristiti.

3.2. Bilješka prilagođenog markera

Dalje, definirajmo prilagođenu bilješku markera koju će koristiti BeanPostProcessor za prepoznavanje graha za automatsku registraciju / poništavanje registracije za događaje:

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

3.3. BeanPostProcessor

Sada ćemo definirati BeanPostProcessor koji će provjeriti svaki grah za Pretplatnik bilješka. Ovaj je razred također a DestructionAwareBeanPostProcessor, što je sučelje Spring koje dodaje povratni poziv prije uništenja BeanPostProcessor. Ako je napomena prisutna, registrirat ćemo je kod EventBus identificiran SpEL izrazom napomene pri inicijalizaciji graha i odjavite ga prilikom uništavanja graha:

javna klasa GuavaEventBusBeanPostProcessor implementira DestructionAwareBeanPostProcessor {Logger logger = LoggerFactory.getLogger (this.getClass ()); SpelExpressionParser expressionParser = novi SpelExpressionParser (); @Override public void postProcessBeforeDestruction (Object bean, String beanName) baca BeansException {this.process (bean, EventBus :: odjavite se, "uništenje"); } @Override public boolean requiresDestruction (Object bean) {return true; } @Override public Object postProcessBeforeInitialization (Object bean, String beanName) baca BeansException {return bean; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) baca BeansException {this.process (bean, EventBus :: register, "initialization"); grah za povratak; } postupak privatne void (objekt objekta, potrošač BiConsumer, radnja niza) {// pogledajte implementaciju u nastavku}}

Gornji kod uzima svaki grah i provodi ga kroz postupak metoda, definirana u nastavku. Obrađuje ga nakon što je grah inicijaliziran i prije nego što je uništen. The zahtijevaUništenje metoda vraća true po defaultu i to ponašanje zadržavamo ovdje dok provjeravamo postoji li @Pretplatnik bilješka u postProcessBeforeDestruction uzvratiti poziv.

Pogledajmo sada postupak metoda:

privatni void postupak (Object bean, BiConsumer consumer, String action) {Object proxy = this.getTargetObject (bean); Napomena pretplatnika = AnnotationUtils.getAnnotation (proxy.getClass (), Subscriber.class); if (anotacija == null) return; this.logger.info ("{}: obrada graha tipa {} tijekom {}", this.getClass (). getSimpleName (), proxy.getClass (). getName (), action); Niz annotationValue = annotation.value (); isprobajte {Expression expression = this.expressionParser.parseExpression (annotationValue); Vrijednost objekta = expression.getValue (); if (! (value instanceof EventBus)) {this.logger.error ("{}: izraz {} nije procijenio na instancu EventBus za grah tipa {}", this.getClass (). getSimpleName (), annotationValue , proxy.getClass (). getSimpleName ()); povratak; } EventBus eventBus = (EventBus) vrijednost; consumer.accept (eventBus, proxy); } catch (ExpressionException ex) {this.logger.error ("{}: nije moguće raščlaniti / procijeniti izraz {} za grah tipa {}", this.getClass (). getSimpleName (), annotationValue, proxy.getClass () .getName ()); }}

Ovaj kôd provjerava postoji li naša prilagođena oznaka oznake s imenom Pretplatnik i, ako je prisutan, čita izraz SpEL iz svog vrijednost imovine. Zatim se izraz ocjenjuje u objekt. Ako se radi o primjerku EventBus, primjenjujemo BiConsumer parametar funkcije na grah. The BiConsumer koristi se za registraciju i odjavu graha iz EventBus.

Provedba metode getTargetObject je kako slijedi:

private Object getTargetObject (Object proxy) baca BeansException {if (AopUtils.isJdkDynamicProxy (proxy)) {try {return ((Savjetovan) proxy) .getTargetSource (). getTarget (); } catch (Exception e) {throw new FatalBeanException ("Pogreška pri dohvaćanju cilja JDK proxyja", e); }} return proxy; }

3.4. StockTrade Model objekta

Dalje, definirajmo naše StockTrade objekt objekta:

javna klasa StockTrade {simbol privatnog niza; privatna int količina; privatna dvostruka cijena; privatni Datum tradeDate; // konstruktor}

3.5. StockTradePublisher Prijemnik događaja

Zatim, definirajmo klasu slušatelja koja će nas obavijestiti da je primljena trgovina kako bismo mogli napisati svoj test:

@FunctionalInterface javno sučelje StockTradeListener {void stockTradePublished (trgovina StockTrade); }

Na kraju ćemo definirati prijemnik za novi StockTrade događaji:

@Subscriber javna klasa StockTradePublisher {Postavi stockTradeListeners = novi HashSet (); javna void addStockTradeListener (slušatelj StockTradeListener) {sinkronizirano (this.stockTradeListeners) {this.stockTradeListeners.add (slušatelj); }} javna praznina removeStockTradeListener (slušatelj StockTradeListener) {sinkronizirano (this.stockTradeListeners) {this.stockTradeListeners.remove (slušatelj); }} @Subscribe @AllowConcurrentEvents void handleNewStockTradeEvent (StockTrade trgovina) {// objaviti u DB, poslati na PubNub, ... Postaviti slušatelje; sinkronizirano (this.stockTradeListeners) {listeners = novi HashSet (this.stockTradeListeners); } listeners.forEach (li -> li.stockTradePublished (trgovina)); }}

Gornji kod označava ovu klasu kao Pretplatnik od Guave EventBus događaja i Guava @ Pretplatite se anotacija označava metodu handleNewStockTradeEvent kao primatelj događaja. Vrsta događaja koje će primiti temelji se na klasi pojedinog parametra metode; u ovom ćemo slučaju primiti događaje tipa StockTrade.

The @AllowConcurrentEvents napomena omogućuje istodobno pozivanje ove metode. Jednom kad primimo trgovinu, obavimo bilo koju obradu koju želimo, a zatim obavijestimo slušatelje.

3.6. Testiranje

Sada završimo naše kodiranje testom integracije kako bismo provjerili BeanPostProcessor radi ispravno. Prvo, trebat će nam proljetni kontekst:

@Configuration javna klasa PostProcessorConfiguration {@Bean public GlobalEventBus eventBus () {return GlobalEventBus.getInstance (); } @Bean public GuavaEventBusBeanPostProcessor eventBusBeanPostProcessor () {return new GuavaEventBusBeanPostProcessor (); } @Bean public StockTradePublisher stockTradePublisher () {return new StockTradePublisher (); }}

Sada možemo provesti naš test:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = PostProcessorConfiguration.class) javna klasa StockTradeIntegrationTest {@Autowired StockTradePublisher stockTradePublisher; @Test javna praznina givenValidConfig_whenTradePublished_thenTradeReceived () {Datum tradeDate = novi datum (); StockTrade stockTrade = novi StockTrade ("AMZN", 100, 2483.52d, tradeDate); AtomicBoolean assertionsPassed = novo AtomicBoolean (netačno); StockTradeListener slušatelj = trade -> assertionsPassed .set (this.verifyExact (stockTrade, trgovina)); this.stockTradePublisher.addStockTradeListener (slušatelj); isprobajte {GlobalEventBus.post (stockTrade); await (). atMost (Duration.ofSeconds (2L)) .untilAsserted (() -> assertThat (assertionsPassed.get ()). isTee ()); } napokon {this.stockTradePublisher.removeStockTradeListener (slušatelj); }} boolean verifyExact (StockTrade stockTrade, StockTrade trgovina) {return Objects.equals (stockTrade.getSymbol (), trade.getSymbol ()) && Objects.equals (stockTrade.getTradeDate (), trade.getTradeDate ()) && stockTrade.getQuantity () == trade.getQuantity () && stockTrade.getPrice () == trade.getPrice (); }}

Gornji testni kod generira trgovanje dionicama i objavljuje ga na GlobalEventBus. Čekamo najviše dvije sekunde da se akcija dovrši i da nas obavijeste da je trgovina primila zamjenu stockTradePublisher. Nadalje, potvrđujemo da primljena trgovina nije modificirana u tranzitu.

4. Zaključak

Zaključno, Spring's BeanPostProcessor omogućuje nam da prilagodite sami grah, pružajući nam sredstvo za automatizaciju graha koje bismo inače morali raditi ručno.

Kao i uvijek, izvorni kod dostupan je na GitHub-u.