欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot事件發(fā)布與監(jiān)聽超詳細講解

 更新時間:2022年11月28日 15:42:01   作者:喜羊羊sk  
今天去官網(wǎng)查看spring boot資料時,在特性中看見了系統(tǒng)的事件及監(jiān)聽章節(jié),所以下面這篇文章主要給大家介紹了關于SpringBoot事件發(fā)布和監(jiān)聽的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下

應用場景

比如: 我們現(xiàn)在的應用是Maven聚合工程的多模塊方式開發(fā)。現(xiàn)在有biz模塊、bill模塊、pay模塊。biz模塊有一個用戶注冊功能,bill模塊有一個發(fā)生郵件的功能,每次用戶注冊都需要調(diào)用bill模塊的發(fā)送郵件功能。我們只需要在pom引入bill模塊的依賴,注入bill模塊發(fā)送郵件的service接口,即可以完成操作。

現(xiàn)在業(yè)務有調(diào)整,pay模塊也需要完成支付業(yè)務發(fā)送郵件,也需要和上面操作一樣,操作會很費時費力。有沒有一種辦法可以把它們解耦出來?

答案是有的,我們可以使用事件的方式,biz模塊只要注冊成功,發(fā)布一個用戶注冊成功的事件,pay模塊支付成功,發(fā)布一個支付成功的事件。讓bill模塊監(jiān)聽了此事件的去做剩下的發(fā)送郵件事件就好了。對于事件發(fā)布者而言,不需要關心誰監(jiān)聽了該事件,以此來解耦業(yè)務。注意:必須在同一個Spring容器內(nèi),不能完成跨容器的通信,跨容器的通信可以考慮中間件消息隊列

Spring的事件應該是在3.x版本就發(fā)布的功能了,并越來越完善,其為bean和bean之間的消息通信提供了支持。

概述

ApplicationEvent以及Listener是Spring為我們提供的一個事件監(jiān)聽、訂閱的實現(xiàn),內(nèi)部實現(xiàn)原理是觀察者設計模式,設計初衷也是為了系統(tǒng)業(yè)務邏輯之間的解耦,提高可擴展性以及可維護性。

  • ApplicationEvent就是Spring的事件接口
  • ApplicationListener就是Spring的事件監(jiān)聽器接口,所有的監(jiān)聽器都實現(xiàn)該接口
  • ApplicationEventPublisher是Spring的事件發(fā)布接口,ApplicationContext實現(xiàn)了該接口

其執(zhí)行的流程大致為:

當一個事件源產(chǎn)生事件時,它通過事件發(fā)布器ApplicationEventPublisher發(fā)布事件,然后事件廣播器ApplicationEventMulticaster會去事件注冊表ApplicationContext中找到事件監(jiān)聽器ApplicationListnener,并且逐個執(zhí)行監(jiān)聽器的onApplicationEvent方法,從而完成事件監(jiān)聽器的邏輯。

自定義事件發(fā)布和監(jiān)聽

pom

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

自定義事件源和實體

Spring中,事件源不強迫繼承ApplicationEvent接口的,也就是可以直接發(fā)布任意一個對象類(實體類,Map,List,String等任意對象類)。但內(nèi)部其實是使用PayloadApplicationEvent類進行包裝了一層。

@TableName("user")
@Data
public class User {
    @TableId(type = IdType.ASSIGN_ID,value = "cate_id")
    private String cateId;
    @TableField("cate_code")
    private String cateCode;
    @TableField("user_name")
    private String username;
    @TableField("age")
    private Integer age;
    @TableField("sex")
    private String sex;
}
public class SendEmailEvent extends ApplicationEvent {
	private User user;
	public SendEmailEvent(Object source, User user) {
	    super(source);
	    this.user = user;
	}
	public User getUser() {
	      return user;
	}
	public void setUser(User user) {
	    this.user = user;
	}
	@Override
	public String toString() {
	    return "SendEmailEvent{" +
	            "user=" + user +
	            '}';
	}
}

發(fā)布事件

這里我手動處理的事務,也可以使用@Transactional處理。這里我主要模擬發(fā)布事件發(fā)生異常和監(jiān)聽事件中發(fā)生異常的情況。

默認情況下,發(fā)布事件和監(jiān)聽事件都是在一個線程中同步執(zhí)行的。業(yè)務發(fā)布事件類中,當try代碼塊int a = 1 / 0;模擬保存用戶異常,會進入到catch塊中回滾保存事務,不會發(fā)布事件。

當業(yè)務類保存用戶,并執(zhí)行事件發(fā)布邏輯成功,使用@EventListener的監(jiān)聽類中發(fā)生異常,也會到發(fā)布事件的catch塊中回滾保存的事務。即證明了發(fā)布事件和監(jiān)聽事件都是在一個線程中同步執(zhí)行的。事務是一致性的。

@Service
@Slf4j
//@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Resource
    private DataSourceTransactionManager transactionManager;
    @Override
    public Boolean saveUser(User user) {
        // 手動開啟事務  start
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus transaction = transactionManager.getTransaction(definition);
        // 手動開啟事務  end
        log.info("[UserService] start insert saveUser");
        // 保存用戶
        Boolean result = false;
        try {
            if (Objects.nonNull(user)) {
                // 保存用戶
                result = this.save(user);
                // 模擬異?;貪L
                // int a = 1 / 0;
            }
            // 1、發(fā)布ApplicationEvent對象
			eventPublisher.publishEvent(new SendEmailEvent(this,user));
            // 2、發(fā)送郵件  (發(fā)布任意一個對象類)
            eventPublisher.publishEvent(user.getUsername());
            log.info("[UserService] finish  insert saveUser");
            // 手動提交事務  end
            transactionManager.commit(transaction);
            // 手動提交事務  start
        }
        // 異?;貪L
        catch (Exception e) {
            log.error("saveUser異常 fail:{}", e);
            // 手動回滾事務  start
            transactionManager.rollback(transaction);
            // 手動回滾事務  end
        }
        return result;
    }
}

監(jiān)聽類使用ApplicationListener方式

@Component
@Slf4j
public class CustomListener implements ApplicationListener<SendEmailEvent> {
    @Override
    public void onApplicationEvent(SendEmailEvent event) {
        send(event.getUser().getUsername());
    }
	public void send(String username) {
		String code = UUID.randomUUID().toString().replace("_", "").substring(0, 4);
		log.info("生成驗證碼: {}",code);
		// 可能由于網(wǎng)絡帶寬,發(fā)送驗證碼異常,模擬監(jiān)聽事件異常
		int i = 1/0;
		log.info("【{}】本次登陸驗證碼為:{},請在5分鐘內(nèi)完成驗證,請勿將驗證碼泄露給他人。",username,code);
	}
} 

監(jiān)聽類使用@EventListener方式

使用@EventListener的condition可以實現(xiàn)更加精細的事件監(jiān)聽,condition支持SpEL表達式,可根據(jù)事件源的參數(shù)來判斷是否監(jiān)聽。事件源為對象,可以使用對象.屬性。如: @EventListener(condition = "#customEvent.billType=='pay'")

@EventListener(condition = "#username.equals('風雨')")
public void sendEmail(String username) {
    log.info("transactionEventListener start");
    // 發(fā)生驗證碼
    send(username);
    log.info("transactionEventListener finish");
}
public void send(String username) {
   String code = UUID.randomUUID().toString().replace("_", "").substring(0, 4);
   log.info("生成驗證碼: {}",code);
   // 可能由于網(wǎng)絡帶寬,發(fā)送驗證碼異常,模擬監(jiān)聽事件異常
   int i = 1/0;
   log.info("【{}】本次登陸驗證碼為:{},請在5分鐘內(nèi)完成驗證,請勿將驗證碼泄露給他人。",username,code);
}

Spring事件最佳實踐

通用泛型類事件

事件類可以不繼承ApplicationEvent類,定義通用泛型類事件對象,其他事件對象繼承該對象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GenericEvent<T> {
    /**
     * 業(yè)務類型
     */
    private String billType;
    private T data;
}

發(fā)送銀行成功事件

public class SendBankSuccessEvent extends GenericEvent<SendBank>{
    public SendBankSuccessEvent(String billType, SendBank data) {
        super(billType, data);
    }
}

發(fā)送銀行失敗事件

public class SendBankFailEvent extends GenericEvent<SendBank>{
    public SendBankFailEvent(String billType, SendBank data) {
        super(billType, data);
    }
}

發(fā)送銀行退回事件

public class SendBankGoBackEvent extends GenericEvent<Map>{
    public SendBankGoBackEvent(String billType, Map data) {
        super(billType, data);
    }
}
@Data
public class SendBank {
    private String username;
    private String paymentAccount;
    private String money;
}

發(fā)布事件類

發(fā)送事件類一般可以直接是Service層的實現(xiàn)類

@Service
public class SendBankServiceImpl {
    @Resource
    private ApplicationContext applicationContext;
    public void sendBank(String event, String billType) {
        SendBank sendBank = new SendBank();
        sendBank.setUsername("風雨修");
        sendBank.setPaymentAccount("中國建設銀行");
        sendBank.setMoney("2000");
        if ("success".equals(event)) {
            if ("DYZC".equals(billType)) {
                applicationContext.publishEvent(new SendBankSuccessEvent("DYZC", sendBank));
            }
            if ("ZYSL".equals(billType)) {
                applicationContext.publishEvent(new SendBankSuccessEvent("ZYSL", sendBank));
            }
            if ("SLHZ".equals(billType)) {
                applicationContext.publishEvent(new SendBankSuccessEvent("SLHZ", sendBank));
            }
        } else if ("fail".equals(event)){

            applicationContext.publishEvent(new SendBankFailEvent("ZYSL", sendBank));
        } else if ("goBack".equals(event)){
            HashMap<Object, Object> map = Maps.newHashMap();
            map.put("username", "喜羊羊sk");
            map.put("age", 23);
            map.put("sex","男");
            map.put("hobby","hello world");
            applicationContext.publishEvent(new SendBankGoBackEvent("SLHZ", map));
        }
    }
}

事件監(jiān)聽類

@EventListener注解屬性

  • classes: 此偵 聽器處理的事件類。
  • condition:用于使事件處理成為條件的SpEL表達式,如果表達式的計算結果為布爾值true,則將處理該事件

一個事件可以有很多個監(jiān)聽者,可以通過classes屬性指定具體監(jiān)聽事件類,通過condition可以指定事件類的屬性值滿足條件才生效執(zhí)行

@Component
@Slf4j
public class GenericEventListener {
    /**
     *  同一種事件,不同業(yè)務類型。實現(xiàn)不同的監(jiān)聽處理
     *  通過 @EventListener注解屬性
     *    classes: 此 偵聽器處理的事件類。
     *    condition:用于使事件處理成為條件的SpEL表達式,如果表達式的計算結果為布爾值true,則將處理該事件
     */
    @EventListener(classes = SendBankSuccessEvent.class,condition = "#event.billType.equalsIgnoreCase('dyzc')")
    public void DYZCsendBankSuccessListener(GenericEvent<SendBank> event) {
        log.info("event: {}", event);
        log.info("data: {}", event.getData());
    }
    @EventListener(classes = SendBankSuccessEvent.class,condition = "#event.billType.equalsIgnoreCase('zysl')")
    public void ZYSLsendBankSuccessListener(GenericEvent<SendBank> event) {
        log.info("event: {}", event);
        log.info("data: {}", event.getData());
    }
    @EventListener(classes = SendBankSuccessEvent.class,condition = "#event.billType.equalsIgnoreCase('slhz')")
    public void SLHZsendBankSuccessListener(GenericEvent<SendBank> event) {
        log.info("event: {}", event);
        log.info("data: {}", event.getData());
    }
    /**
     *  end 
     * 
     */
    @EventListener(classes = SendBankFailEvent.class)
    public void sendBankFailListener(GenericEvent<SendBank> event) {
        log.info("event: {}", event);
        log.info("data: {}", event.getData());
    }
    @EventListener(classes = SendBankGoBackEvent.class)
    public void sendGoBackListener(GenericEvent<Map> event) {
        log.info("event: {}", event);
        log.info("data: {}", event.getData());
    }
}

異步監(jiān)聽處理

默認情況下,發(fā)布事件和監(jiān)聽事件都是同步執(zhí)行,在一個線程中的。在需要異步處理時,可以在方法上加上@Async進行異步化操作。此時,可以定義一個線程池,同時開啟異步功能,加入@EnableAsync。

@Async
@EventListener
public void sendEmail(String username) {
    log.info("transactionEventListener start");
    // 發(fā)生驗證碼
    send(username);
    log.info("transactionEventListener finish");
}

事務綁定事件

當一些場景下,比如在用戶注冊成功后,即數(shù)據(jù)庫事務提交了,之后再異步發(fā)送郵件等,不然會發(fā)生數(shù)據(jù)庫插入失敗,但事件卻發(fā)布了,也就是郵件發(fā)送成功了的情況。此時,我們可以使用@TransactionalEventListener注解或者TransactionSynchronizationManager類來解決此類問題,也就是:事務成功提交后,再執(zhí)行事件。

TransactionSynchronizationManager類

@EventListener
public void afterRegisterSendMail(SendEmailEvent event) {
    // Spring 4.2 之前
    TransactionSynchronizationManager.registerSynchronization(
            new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    send(username);
                }
            }
    );
}

上面的代碼將在事務提交后執(zhí)行.如果在非事務context中將拋出java.lang.IllegalStateException: Transaction synchronization is not active

@EventListener
public void afterRegisterSendMail(SendEmailEvent event) {
    // Spring 4.2 之前
    if (TransactionSynchronizationManager.isActualTransactionActive()) {
        TransactionSynchronizationManager.registerSynchronization(
                new TransactionSynchronizationAdapter() {
                    @Override
                    public void afterCommit() {
                        send(event);
                    }
                });
    } else {
        send(username);
    }
}

這樣無論是否有事務都能兼容啦.

@TransactionalEventListener

Spring 4.2除了EventListener之外,額外提供了新的注解@TransactionalEventListener

@TransactionalEventListener
public void sendEmail(String username) {
    log.info("transactionEventListener start");
    // 發(fā)生驗證碼
    send(username);
    log.info("transactionEventListener finish");
}

這個注解的強大之處在于可一直控制事務的 before/after commit, after rollback ,after completion (commit或 rollback). 默認情況下,在事務中的Event將會被執(zhí)行,

到此這篇關于SpringBoot事件發(fā)布與監(jiān)聽超詳細講解的文章就介紹到這了,更多相關SpringBoot事件發(fā)布與監(jiān)聽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring?Boot?@Autowired?@Resource屬性賦值時機探究

    Spring?Boot?@Autowired?@Resource屬性賦值時機探究

    這篇文章主要為大家介紹了Spring?Boot?@Autowired?@Resource屬性賦值時機,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • spring單元測試之@RunWith的使用詳解

    spring單元測試之@RunWith的使用詳解

    這篇文章主要介紹了spring單元測試之@RunWith的使用詳解,@RunWith 就是一個運行器,@RunWith(JUnit4.class) 就是指用JUnit4來運行,
    @RunWith(SpringJUnit4ClassRunner.class),讓測試運行于Spring測試環(huán)境,需要的朋友可以參考下
    2023-12-12
  • SpringBoot使用WebSocket的方法實例詳解

    SpringBoot使用WebSocket的方法實例詳解

    WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協(xié)議。這篇文章主要介紹了SpringBoot使用WebSocket,需要的朋友可以參考下
    2019-06-06
  • OpenTelemetry初識及調(diào)用鏈Trace詳解

    OpenTelemetry初識及調(diào)用鏈Trace詳解

    這篇文章主要為為大家介紹了OpenTelemetry初識及調(diào)用鏈Trace詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • 深入理解Java8雙冒號::的使用

    深入理解Java8雙冒號::的使用

    這篇文章主要介紹了深入理解Java8雙冒號::的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • Java并發(fā)之原子性 有序性 可見性及Happen Before原則

    Java并發(fā)之原子性 有序性 可見性及Happen Before原則

    一提到happens-before原則,就讓人有點“丈二和尚摸不著頭腦”。這個涵蓋了整個JMM中可見性原則的規(guī)則,究竟如何理解,把我個人一些理解記錄下來。下面可以和小編一起學習Java 并發(fā)四個原則
    2021-09-09
  • SpringBoot整合Gson 整合Fastjson的實例詳解

    SpringBoot整合Gson 整合Fastjson的實例詳解

    這篇文章主要介紹了SpringBoot整合Gson 整合Fastjson的實例詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • 一篇文章教你如何用Java自定義一個參數(shù)校驗器

    一篇文章教你如何用Java自定義一個參數(shù)校驗器

    這篇文章主要介紹了使用java自定義一個參數(shù)校驗器,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習
    2021-09-09
  • java實現(xiàn)發(fā)送email小案例

    java實現(xiàn)發(fā)送email小案例

    這篇文章主要為大家詳細介紹了java實現(xiàn)發(fā)送email小案例,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • spring-data-jpa中findOne與getOne的區(qū)別說明

    spring-data-jpa中findOne與getOne的區(qū)別說明

    這篇文章主要介紹了spring-data-jpa中findOne與getOne的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11

最新評論