溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

關(guān)于JAVA Spring中的事件機(jī)制分析

發(fā)布時(shí)間:2020-07-01 16:22:25 來源:億速云 閱讀:143 作者:清晨 欄目:開發(fā)技術(shù)

小編給大家分享一下關(guān)于JAVA Spring中的事件機(jī)制分析,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討方法吧!

說到事件機(jī)制,可能腦海中最先浮現(xiàn)的就是日常使用的各種 listener,listener去監(jiān)聽事件源,如果被監(jiān)聽的事件有變化就會(huì)通知listener,從而針對(duì)變化做相應(yīng)的動(dòng)作。這些listener是怎么實(shí)現(xiàn)的呢?說listener之前,我們先從設(shè)計(jì)模式開始講起。

觀察者模式

觀察者模式一般包含以下幾個(gè)對(duì)象:

  • Subject:被觀察的對(duì)象。它提供一系列方法來增加和刪除觀察者對(duì)象,同時(shí)它定義了通知方法notify()。目標(biāo)類可以是接口,也可以是抽象類或具體類。
  • ConcreteSubject:具體的觀察對(duì)象。Subject的具體實(shí)現(xiàn)類,在這里實(shí)現(xiàn)通知事件。
  • Observer:觀察者。這里是抽象的觀察者,觀察者有一個(gè)或者多個(gè)。
  • ConcreteObserver:具體的觀察者。在這里維護(hù)觀察對(duì)象的具體操作。
     

按照觀察者對(duì)象,我們來寫一個(gè)簡(jiǎn)單的觀察者示例,定義一個(gè)氣象中心,發(fā)布?xì)庀笮畔?,觀察者是各個(gè)電視臺(tái),訂閱氣象中心的信息,有新增的氣象信息發(fā)布的時(shí)候,及時(shí)播報(bào)。

定義氣象中心:

public interface WeatherCenter {

  void publishWeatherInfo();

}

定義觀察者對(duì)象:

public interface Observer {

  void sendWeatherWarning();
}

定義具體的觀察者:

public class BeijingTvObserver implements Observer {
  
  @Override
  public void sendWeatherWarning(){
    System.out.println("北京衛(wèi)視天氣預(yù)報(bào)開始了");
  }

}

中央電視臺(tái):

public class CCTVObserver implements Observer {

  @Override
  public void sendWeatherWarning(){
    System.out.println("中央電視臺(tái)天氣預(yù)報(bào)開始了");
  }

}

現(xiàn)在發(fā)布北京的氣象信息:

public class BeijingWeather implements WeatherCenter {

  private List<Observer> observerArrayList = new ArrayList<>();

  @Override
  public void publishWeatherInfo() {
    for(Observer observer : observerArrayList) {
      observer.sendWeatherWarning();
    }
  }
}

這時(shí)候給所有的訂閱者推送一條氣象發(fā)布消息,那么他們就收到最新的氣象預(yù)報(bào)。

總結(jié)一下觀察者模式的核心就是:事件中心持有所有的訂閱者,每當(dāng)事件發(fā)生時(shí)循環(huán)通知所有的訂閱者。

當(dāng)然上面我寫的比較簡(jiǎn)單,你也可以在事件中心寫一個(gè)注冊(cè)訂閱者的方法,每當(dāng)有新的訂閱者加入就調(diào)用該方法注冊(cè)。

Java 中的事件機(jī)制

Java中提供了基本的事件處理基類:

  1. EventObject:所有事件狀態(tài)對(duì)象都將從其派生的根類;
  2. EventListener:所有事件偵聽器接口必須擴(kuò)展的標(biāo)記接口;
     

具體使用方式可以用一個(gè)非常經(jīng)典的開門案例來講解:

首先創(chuàng)建一個(gè)開/關(guān)門事件:

import java.util.EventObject;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc TODO
 */
public class DoorEvent extends EventObject {

  private Integer doorStatus;

  public DoorEvent(Object source) {
    super(source);
  }

  public DoorEvent(Object source, Integer status) {
    super(source);
    this.doorStatus = status;
  }

  public void setStatus(Integer status) {
    this.doorStatus = status;
  }

  public Integer getStatus() {
    return doorStatus;
  }

}

所有的事件都繼承 EventObject。

然后創(chuàng)建監(jiān)聽器:

public interface DoorListener extends EventListener {

  void DoorEvent(DoorEvent doorEvent);
}

所有的監(jiān)聽器都要實(shí)現(xiàn) EventListener。

繼續(xù)創(chuàng)建具體的開門/關(guān)門的監(jiān)聽器:

public class CloseDoorListener implements DoorListener {

  @Override
  public void DoorEvent(DoorEvent doorEvent) {
    Integer openStatus = doorEvent.getStatus();
    if(0 == openStatus) {
      System.out.println("the door is close");
    }
  }
}

開門:

public class OpenDoorListener implements DoorListener {
  @Override
  public void DoorEvent(DoorEvent doorEvent) {
    Integer openStatus = doorEvent.getStatus();
    if(1 == openStatus) {
      System.out.println("the door is open");
    }
  }
}

有了監(jiān)聽器和事件之后,下一步就是用上他們。還記得上面的觀察者模式嘛,同樣的使用方式:

		/**
   * 將所有的listener保存起來
   *
   * @return
   */
public static List<DoorListener> getAllListener() {
 List<DoorListener> list = Lists.newArrayList();
 list.add(new OpenDoorListener());
 list.add(new CloseDoorListener());
 return list;
}

public static void main(String[] args) {
 DoorEvent open = new DoorEvent("open", 1);
 List<DoorListener> listeners = getAllListener();
 for (DoorListener listener : listeners) {
  listener.DoorEvent(open);
 }
}

Spring 中的事件機(jī)制

在 Spring 容器中通過 ApplicationEvent 類和 ApplicationListener 接口來處理事件,如果某個(gè) bean實(shí)現(xiàn) ApplicationListener 接口并被部署到容器中,那么每次對(duì)應(yīng)的 ApplicationEvent 被發(fā)布到容器中都會(huì)通知該 bean ,這是典型的觀察者模式。

Spring 的事件默認(rèn)是同步的,即調(diào)用 publishEvent 方法發(fā)布事件后,它會(huì)處于阻塞狀態(tài),直到 onApplicationEvent 接收到事件并處理返回之后才繼續(xù)執(zhí)行下去,這種單線程同步的好處是可以進(jìn)行事務(wù)管理。

先展示一下使用方式,我們拿用戶登錄來舉例。首先來創(chuàng)建一個(gè)事件:

import org.springframework.context.ApplicationEvent;

/**
 * @author rickiyang
 * @date 2019-12-04
 * @Desc TODO
 */
public class UserRegisterEvent extends ApplicationEvent {

  public UserRegisterEvent(Object source) {
    super(source);
  }
}

然后創(chuàng)建監(jiān)聽器去監(jiān)聽這個(gè)事件:

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc 插入用戶信息
 */
@Component
public class UserInsertListener implements ApplicationListener<UserRegisterEvent> {


  @Override
  public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
    String source = (String)userRegisterEvent.getSource();
    User user = JSON.parseObject(source, User.class);
    //insert db
  }
}

創(chuàng)建一個(gè)用戶注冊(cè)成功之后插入用戶信息的監(jiān)聽器。

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc 用戶注冊(cè)成功發(fā)送短信
 */
@Component
public class NotifyUserListener implements ApplicationListener<UserRegisterEvent> {


  @Override
  public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
    String source = (String)userRegisterEvent.getSource();
    User user = JSON.parseObject(source, User.class);
    //send sms
  }
}

創(chuàng)建注冊(cè)成功發(fā)送通知短信的監(jiān)聽器。

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc 用戶注冊(cè)成功給用戶生成推薦商品
 */
@Component
public class RecommendListener implements ApplicationListener<UserRegisterEvent> {


  @Override
  public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
    String source = (String)userRegisterEvent.getSource();
    User user = JSON.parseObject(source, User.class);
    // generate recommend commodity
  }
}

創(chuàng)建用戶注冊(cè)成功之后給用戶推薦商品的事件。

用戶注冊(cè)事件的監(jiān)聽器創(chuàng)建完畢,那么接下來就發(fā)布事件等待監(jiān)聽器監(jiān)聽就行。在Spring中提供了 ApplicationEventPublisherAware 接口,從名稱上看就知道是 ApplicationEventPublisher 的適配器類,用法就是你在業(yè)務(wù)類中實(shí)現(xiàn)該接口,然后使用 ApplicationEventPublisher#publishEvent發(fā)布你的事件即可。

package com.rickiyang.learn.controller.test;

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/**
 * @author rickiyang
 * @date 2019-12-04
 * @Desc TODO
 */
@Service
public class UserRegisterPublisherService implements ApplicationEventPublisherAware {

  private ApplicationEventPublisher applicationEventPublisher;

  @Override
  public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
    this.applicationEventPublisher = applicationEventPublisher;
  }

  public void insert(User user){
    UserRegisterEvent event = new UserRegisterEvent(JSON.toJSONString(user));
    applicationEventPublisher.publishEvent(event);
  }
}

調(diào)用insert方法就可以發(fā)布事件,寫一個(gè)test測(cè)試一下:

import com.rickiyang.learn.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRegisterPublisherServiceTest {

  @Resource
  private UserRegisterPublisherService userRegisterPublisherService;


  @Test
  public void test1() {
    User build = User.builder().name("1").sex(1).phone("123456789").build();
    userRegisterPublisherService.insert(build);
  }

}

可以看到3個(gè)監(jiān)聽器都打印出來了:

發(fā)送短信
商品推薦
插入用戶

有個(gè)問題不知道大家發(fā)現(xiàn)沒,監(jiān)聽器的發(fā)布順序是按照 bean 自然裝載的順序執(zhí)行的,如果我們的bean是有序的應(yīng)該怎么辦呢?別怕,Spring自然考慮到這個(gè)問題。

SmartApplicationListener實(shí)現(xiàn)有序的監(jiān)聽

SmartApplicationListener 接口繼承了 ApplicationListener,使用全局的 ApplicationEvent 作為監(jiān)聽的事件對(duì)象。之所以 能提供順序性,是因?yàn)槔^承了 Ordered 類,實(shí)現(xiàn)了排序的邏輯。另外添加了兩個(gè)方法#supportsEventType、#supportsSourceType 來作為區(qū)分是否是我們監(jiān)聽的事件,只有這兩個(gè)方法同時(shí)返回true時(shí)才會(huì)執(zhí)行onApplicationEvent方法。

package com.rickiyang.learn.controller.test;

import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc TODO
 */
@Component
public class UserInsert1Listener implements SmartApplicationListener {

  @Override
  public boolean supportsEventType(Class<&#63; extends ApplicationEvent> aClass) {
    return aClass == UserRegisterEvent.class;
  }

  @Override
  public boolean supportsSourceType(Class<&#63;> sourceType) {
    return sourceType == User.class;
  }

  /**
   * 數(shù)字越小優(yōu)先級(jí)越高
   * 默認(rèn)值為 2147483647
   * @return
   */
  @Override
  public int getOrder() {
    return 8;
  }

  @Override
  public void onApplicationEvent(ApplicationEvent applicationEvent) {
    UserRegisterEvent event = (UserRegisterEvent)applicationEvent;
    // insert to db
  }
}

如果你有對(duì)多個(gè)監(jiān)聽器做排序的需求,那么你只用在 getOrder 方法中指定當(dāng)前的排序級(jí)別即可。數(shù)字越大優(yōu)先級(jí)越低,默認(rèn)的排序級(jí)別是2147483647,你可以自己調(diào)整。

Spring 對(duì)事件監(jiān)聽機(jī)制的注解支持

Spring4.2之后,ApplicationEventPublisher 自動(dòng)被注入到容器中,不再需要顯示實(shí)現(xiàn)Aware接口。

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author rickiyang
 * @date 2019-12-04
 * @Desc TODO
 */
@Service
public class UserRegisterPublisher1Service {

  @Resource
  private ApplicationEventPublisher applicationEventPublisher;

  public void insert(User user){
    UserRegisterEvent event = new UserRegisterEvent(JSON.toJSONString(user));
    applicationEventPublisher.publishEvent(event);
  }
}

創(chuàng)建listener也就不需要顯式的繼承 ApplicationListener 或者 SmartApplicationListener,使用 @EventListener 注解即可:

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

/**
 * @author rickiyang
 * @date 2019-12-07
 * @Desc TODO
 */
@Service
public class UserInfoCheckListener {

  @Order(8)
  @EventListener(classes = UserRegisterEvent.class)
  public void checkUserInfo(UserRegisterEvent event) {
    String source = (String) event.getSource();
    User user = JSON.parseObject(source, User.class);
    //todo check user info
  }
}

如果你想使用順序性的listener,那么只需要使用 @Order注解就可以了。

異步事件的支持

上面說過 Spring 事件機(jī)制默認(rèn)是同步阻塞的,如果 ApplicationEventPublisher 發(fā)布事件之后他會(huì)一直阻塞等待listener 響應(yīng),多個(gè) listener 的情況下前面的沒有執(zhí)行完后面的一直被阻塞。如果我們的應(yīng)用場(chǎng)景是:用戶訂單完成之后異步發(fā)貨,檢查快遞信息,這些操作是沒有必要返回結(jié)果給用戶的。

這種情況下,我們是不是想到可以使用異步線程的方式來處理。你可以把listener中的處理流程做一個(gè)異步線程,或者利用 Spring 提供的線程池注解 @Async 來實(shí)現(xiàn)異步線程。

要使用 @Async 之前需要先開啟線程池,在 啟動(dòng)類上添加 @EnableAsync 注解即可。線程池支持配置模式,如果你不想使用默認(rèn)的線程池配置,可以手動(dòng)指定:

package com.rickiyang.learn.controller.test;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.*;

/**
 * @author rickiyang
 * @date 2019-12-07
 * @Desc TODO
 */
@Configuration
@EnableAsync
public class AsyncConfig {

  @Bean("userInfoPool")
  public Executor getExecutor() {
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("consumer-queue-thread-%d").build();
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    // 線程池維護(hù)線程的最少數(shù)量
    executor.setCorePoolSize(5);
    // 線程池維護(hù)線程的最大數(shù)量
    executor.setMaxPoolSize(10);
    // 緩存隊(duì)列
    executor.setQueueCapacity(25);
    //線程名
    executor.setThreadFactory(namedThreadFactory);
    // 線程池初始化
    executor.initialize();
    return executor;
  }
}

手動(dòng)配置一個(gè) bean name 為 userInfoPool 的線程池,接下來使用@Async注解使用線程池:

package com.rickiyang.learn.controller.test;

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @author rickiyang
 * @date 2019-12-07
 * @Desc TODO
 */
@Service
public class UserInfoCheckListener {

  @Async("userInfoPool")
  @Order(8)
  @EventListener(classes = UserRegisterEvent.class)
  public void checkUserInfo(UserRegisterEvent event) {
    String source = (String) event.getSource();
    User user = JSON.parseObject(source, User.class);
    System.out.println("async deel");
    //todo check user info
  }
}

這樣我們就把 UserInfoCheckListener 變成了異步任務(wù)。

Spring中的事件機(jī)制分析

上面從基本的發(fā)布訂閱設(shè)計(jì)模式到 Java 提供的基本的事件處理基類,再拓展到 Spring 中如何使用事件機(jī)制來拓展代碼,整條線還是很清晰。講完了我們應(yīng)該如何在業(yè)務(wù)代碼中使用發(fā)布訂閱模式,我們也來分析一下Spring是如何實(shí)現(xiàn)發(fā)布訂閱模式的,看看人家的代碼功底。

在Spring 中提供了Event 的基類:ApplicationEvent,如果事件要想被Spring監(jiān)聽那么就必須繼承該類,同樣該類也繼承了 Java 中的事件基類:EventObject。

有了事件源,我們要定義事件監(jiān)聽者用于處理事件,所有的事件監(jiān)聽者都要繼承 org.springframework.context.ApplicationListener 接口:

/**
 * Interface to be implemented by application event listeners.
 * Based on the standard {@code java.util.EventListener} interface
 * for the Observer design pattern.
 *
 * <p>As of Spring 3.0, an ApplicationListener can generically declare the event type
 * that it is interested in. When registered with a Spring ApplicationContext, events
 * will be filtered accordingly, with the listener getting invoked for matching event
 * objects only.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @param <E> the specific ApplicationEvent subclass to listen to
 * @see org.springframework.context.event.ApplicationEventMulticaster
 */
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

}

ApplicationListener 提供了 一個(gè)基于 ApplicationEvent 的泛型,所以你指定了某個(gè)類的監(jiān)聽者只會(huì)處理該類型的event。

上面我們說了 Spring 是基于 ApplicationEventPublisher 來發(fā)布事件,那么監(jiān)聽器是如何獲取到事件呢?

注意到 ApplicationListener 上面的注釋寫到:@param <E> the specific ApplicationEvent subclass to listen to ApplicationEventMulticaster,從名稱上看這個(gè)類的作用應(yīng)該是用于事件廣播。

ApplicationEventMulticaster是一個(gè)接口,提供了如下方法:

  • addApplicationListener(ApplicationListener<&#63;> listener) :新增一個(gè)listener;
  • addApplicationListenerBean(String listenerBeanName):新增一個(gè)listener,參數(shù)為bean name;
  • removeApplicationListener(ApplicationListener<&#63;> listener):刪除listener;
  • removeApplicationListenerBean(String listenerBeanName):根據(jù)bean name 刪除listener;
  • multicastEvent(ApplicationEvent event):廣播事件;
  • multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType):廣播事件,指定事件的source類型。
     

從接口的方法看,該類的作用就是添加監(jiān)聽器然后對(duì)所有監(jiān)聽器或者指定監(jiān)聽器發(fā)送事件進(jìn)行處理。

ApplicationEventMulticaster 有兩個(gè)實(shí)現(xiàn)類:

  • SimpleApplicationEventMulticaster
  • AbstractApplicationEventMulticaster
     

因?yàn)?AbstractApplicationEventMulticaster 是一個(gè)抽象類,并且 SimpleApplicationEventMulticaster 也繼承了了 SimpleApplicationEventMulticaster ,所以我們直接看 SimpleApplicationEventMulticaster:

public abstract class AbstractApplicationEventMulticaster
		implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {

	private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);

	final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);

 
 @Override
	public void addApplicationListener(ApplicationListener<&#63;> listener) {
		synchronized (this.retrievalMutex) {
			// Explicitly remove target for a proxy, if registered already,
			// in order to avoid double invocations of the same listener.
			Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
			if (singletonTarget instanceof ApplicationListener) {
				this.defaultRetriever.applicationListeners.remove(singletonTarget);
			}
			this.defaultRetriever.applicationListeners.add(listener);
			this.retrieverCache.clear();
		}
	}
 
 ......
 ......


}

#addApplicationListener 方法用于新增監(jiān)聽器,新增的邏輯主要在這一句:

defaultRetriever.applicationListeners.add(listener);

繼續(xù)看 ListenerRetriever 的實(shí)現(xiàn):

private class ListenerRetriever {

 public final Set<ApplicationListener<&#63;>> applicationListeners = new LinkedHashSet<>();

 public final Set<String> applicationListenerBeans = new LinkedHashSet<>();

 private final boolean preFiltered;

 public ListenerRetriever(boolean preFiltered) {
  this.preFiltered = preFiltered;
 }

 public Collection<ApplicationListener<&#63;>> getApplicationListeners() {
  List<ApplicationListener<&#63;>> allListeners = new ArrayList<>(
   this.applicationListeners.size() + this.applicationListenerBeans.size());
  allListeners.addAll(this.applicationListeners);
  if (!this.applicationListenerBeans.isEmpty()) {
   BeanFactory beanFactory = getBeanFactory();
   for (String listenerBeanName : this.applicationListenerBeans) {
    try {
     ApplicationListener<&#63;> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
     if (this.preFiltered || !allListeners.contains(listener)) {
      allListeners.add(listener);
     }
    }
    catch (NoSuchBeanDefinitionException ex) {
     // Singleton listener instance (without backing bean definition) disappeared -
     // probably in the middle of the destruction phase
    }
   }
  }
  if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) {
   AnnotationAwareOrderComparator.sort(allListeners);
  }
  return allListeners;
 }
}

看到?jīng)],最終還是 持有了一個(gè) applicationListeners 的集合,跟我們的發(fā)布訂閱設(shè)計(jì)模式一樣。

剩下的邏輯就好去解釋,順著咱們前面講過的發(fā)布訂閱模式的使用套路擼下去就行,事件廣播的方法#multicastEvent不外乎就是遍歷所有的監(jiān)聽器進(jìn)行匹配。

總結(jié)

這一篇講的發(fā)布訂閱模式以及在Spring中的使用在日常開發(fā)中只要稍加注意你就會(huì)發(fā)現(xiàn)對(duì)改善代碼流程的影響還是挺大。寫代碼有90%的時(shí)間我們都是在寫同步代碼,因?yàn)椴挥脛?dòng)腦子,順著該有的流程擼就完事。這樣帶來的后果就是你真的只是在搬磚!

有的時(shí)候停下來,從業(yè)務(wù)邏輯跳出來拿半個(gè)小時(shí)想想你應(yīng)該如何讓這這一次搬磚有點(diǎn)技術(shù)含量。或許從此刻開始,搬磚也會(huì)與眾不同。

看完了這篇文章,相信你對(duì)關(guān)于JAVA Spring中的事件機(jī)制分析有了一定的了解,想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI