Spring中如何自定義監(jiān)聽(tīng)器
前言
通過(guò)一個(gè)簡(jiǎn)單的自定義的監(jiān)聽(tīng)器,從源碼的角度分一下Spring中監(jiān)聽(tīng)的整個(gè)過(guò)程,分析監(jiān)聽(tīng)的作用。
一、自定義監(jiān)聽(tīng)案例
1.1定義事件
package com.lazy.snail;
?
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
?
/**
* @ClassName UserRegisteredEvent
* @Description TODO
* @Author lazysnail
* @Date 2024/11/8 10:37
* @Version 1.0
*/
@Getter
public class UserRegisteredEvent extends ApplicationEvent {
private final String username;
?
public UserRegisteredEvent(Object source, String username) {
super(source);
this.username = username;
}
}
1.2定義監(jiān)聽(tīng)
package com.lazy.snail;
?
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
?
/**
* @ClassName UserRegisteredListener
* @Description TODO
* @Author lazysnail
* @Date 2024/11/8 10:36
* @Version 1.0
*/
@Component
public class UserRegisteredListener {
@EventListener
public void handleUserRegisterEvent(UserRegisteredEvent event) {
System.out.println("用戶(hù)注冊(cè)成功,發(fā)送郵件通知");
}
}
1.3定義用戶(hù)服務(wù)(發(fā)布事件)
package com.lazy.snail;
?
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
?
/**
* @ClassName UserService
* @Description TODO
* @Author lazysnail
* @Date 2024/11/8 10:37
* @Version 1.0
*/
@Service
public class UserService {
private final ApplicationEventPublisher eventPublisher;
?
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
?
public void registerUser(String username) {
// 用戶(hù)注冊(cè)邏輯
System.out.println("Registering user: " + username);
?
// 發(fā)布用戶(hù)注冊(cè)事件
eventPublisher.publishEvent(new UserRegisteredEvent(this, username));
}
}
1.4測(cè)試類(lèi)
package com.lazy.snail;
?
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
?
@Slf4j
public class SpringTest {
?
@Test
void test() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.getBean(UserService.class).registerUser("lazysnail");
}
}
1.5測(cè)試結(jié)果

二、事件監(jiān)聽(tīng)流程
2.1容器啟動(dòng)階段
2.1.1事件監(jiān)聽(tīng)方法處理器及默認(rèn)事件監(jiān)聽(tīng)工廠
事件監(jiān)聽(tīng)方法處理器及默認(rèn)事件監(jiān)聽(tīng)工廠的bean定義信息注冊(cè)
事件監(jiān)聽(tīng)方法處理器會(huì)在后續(xù)用于處理自定義監(jiān)聽(tīng)中的@EventListener注解
默認(rèn)事件監(jiān)聽(tīng)工廠會(huì)用于將自定義監(jiān)聽(tīng)封裝為ApplicationListenerMethodAdapter
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 省略部分代碼...
// 事件監(jiān)聽(tīng)方法處理器
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
// 默認(rèn)事件監(jiān)聽(tīng)工廠
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
}
?
return beanDefs;
}
事件監(jiān)聽(tīng)方法處理器及默認(rèn)事件監(jiān)聽(tīng)工廠的實(shí)例化
refresh方法中,invokeBeanFactoryPostProcessors處理BeanFactoryPostProcessor(EventListenerMethodProcessor實(shí)現(xiàn)了BeanFactoryPostProcessor)
實(shí)例化EventListenerMethodProcessor
調(diào)用EventListenerMethodProcessor的postProcessBeanFactory實(shí)例化DefaultEventListenerFactory

// EventListenerMethodProcessor
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
?
Map<String, EventListenerFactory> beans = beanFactory.getBeansOfType(EventListenerFactory.class, false, false);
List<EventListenerFactory> factories = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(factories);
this.eventListenerFactories = factories;
}
2.1.3應(yīng)用事件廣播器創(chuàng)建
容器刷新時(shí),initApplicationEventMulticaster創(chuàng)建SimpleApplicationEventMulticaster
注冊(cè)單例到容器
// AbstractApplicationContext
public void refresh() throws BeansException, IllegalStateException {
// 為容器初始化事件廣播器
initApplicationEventMulticaster();
}
// AbstractApplicationContext
protected void initApplicationEventMulticaster() {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
SimpleApplicationEventMulticaster從AbstractApplicationEventMulticaster繼承過(guò)來(lái)一個(gè)defaultRetriever對(duì)象
defaultRetriever中封裝了監(jiān)聽(tīng)器集合
private class DefaultListenerRetriever {
?
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
?
public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
}
監(jiān)聽(tīng)集合中的監(jiān)聽(tīng)是何時(shí)添加的
提前實(shí)例化單例后EventListenerMethodProcessor對(duì)容器中所有監(jiān)聽(tīng)處理時(shí)添加
// DefaultListableBeanFactory
public void preInstantiateSingletons() throws BeansException {
// 省略部分代碼...
// EventListenerMethodProcessor
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
} else {
// 單例實(shí)例化后處理
smartSingleton.afterSingletonsInstantiated();
}
smartInitialize.end();
}
}
}
監(jiān)聽(tīng)器的創(chuàng)建
// EventListenerMethodProcessor
public void afterSingletonsInstantiated() {
ConfigurableListableBeanFactory beanFactory = this.beanFactory;
Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
// 處理UserRegisteredListener
for (String beanName : beanNames) {
// 省略部分代碼...
processBean(beanName, type);
}
}
?
private void processBean(final String beanName, final Class<?> targetType) {
if (!this.nonAnnotatedClasses.contains(targetType) &&
AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
!isSpringContainerClass(targetType)) {
?
Map<Method, EventListener> annotatedMethods = null;
// 省略部分代碼...
// @EventListener注解的方法(注解上的屬性)
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
?
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
if (logger.isTraceEnabled()) {
logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
}
} else {
// Non-empty set of methods
ConfigurableApplicationContext context = this.applicationContext;
Assert.state(context != null, "No ApplicationContext set");
List<EventListenerFactory> factories = this.eventListenerFactories;
Assert.state(factories != null, "EventListenerFactory List not initialized");
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
// 事件監(jiān)聽(tīng)工廠創(chuàng)建應(yīng)用監(jiān)聽(tīng)器 ApplicationListenerMethodAdapter
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
// 添加到應(yīng)用上下文
context.addApplicationListener(applicationListener);
break;
}
}
}
}
}
}
2.2客戶(hù)端調(diào)用階段
發(fā)布事件
// AbstractApplicationContext
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
拿到內(nèi)部應(yīng)用事件廣播器(SimpleApplicationEventMulticaster)
廣播器廣播事件
// SimpleApplicationEventMulticaster
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
invokeListener(listener, event);
}
}
}
獲取監(jiān)聽(tīng)
檢索應(yīng)用監(jiān)聽(tīng)器
直接從檢索器(defaultRetriever)中取出監(jiān)聽(tīng)
/**
* 根據(jù)給定的事件、源(我理解是容器)檢索監(jiān)聽(tīng)器
*
*/
// AbstractApplicationEventMulticaster
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
?
List<ApplicationListener<?>> allListeners = new ArrayList<>();
Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);
?
Set<ApplicationListener<?>> listeners;
Set<String> listenerBeans;
synchronized (this.defaultRetriever) {
// 默認(rèn)檢索器中獲取應(yīng)用監(jiān)聽(tīng),監(jiān)聽(tīng)已經(jīng)在Spring啟動(dòng)階段注冊(cè)完成
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
}
?
// 省略部分代碼...
?
AnnotationAwareOrderComparator.sort(allListeners);
if (retriever != null) {
if (filteredListenerBeans.isEmpty()) {
retriever.applicationListeners = new LinkedHashSet<>(allListeners);
retriever.applicationListenerBeans = filteredListenerBeans;
}
else {
retriever.applicationListeners = filteredListeners;
retriever.applicationListenerBeans = filteredListenerBeans;
}
}
return allListeners;
}
調(diào)用監(jiān)聽(tīng)
invokeListener

三、總結(jié)
個(gè)人理解:
事件發(fā)布是一個(gè)抽象的概念,真正將事件發(fā)布出去的是SimpleApplicationEventMulticaster,發(fā)布事件實(shí)際做的事情,找到監(jiān)聽(tīng)器,過(guò)濾出能夠處理這個(gè)事件的監(jiān)聽(tīng)器,然后執(zhí)行監(jiān)聽(tīng)器中針對(duì)這個(gè)事件的業(yè)務(wù)邏輯。
3.1監(jiān)聽(tīng)流程總結(jié)
3.1.1. Spring 容器啟動(dòng)
- 在 Spring 啟動(dòng)過(guò)程中,
ApplicationContext被初始化,它作為核心容器,提供了事件發(fā)布和監(jiān)聽(tīng)的機(jī)制。 - Spring 使用
ApplicationEventPublisher作為事件發(fā)布的核心接口,事件的發(fā)布與處理都在ApplicationContext內(nèi)部實(shí)現(xiàn)。
3.1.2. 監(jiān)聽(tīng)器注冊(cè)
在 Spring 中,可以通過(guò)以下幾種方式注冊(cè)監(jiān)聽(tīng)器:
- 實(shí)現(xiàn) ApplicationListener 接口:將實(shí)現(xiàn)類(lèi)作為 Spring Bean 注冊(cè),Spring 會(huì)自動(dòng)將它識(shí)別為事件監(jiān)聽(tīng)器。
- 通過(guò) XML 配置:在 XML 文件中配置
<bean class="com.example.MyEventListener"/>,將監(jiān)聽(tīng)器注冊(cè)到ApplicationContext。 - 注解方式:使用@EventListener注解
監(jiān)聽(tīng)器的作用:每當(dāng)發(fā)布的事件類(lèi)型與監(jiān)聽(tīng)器泛型參數(shù)中的事件類(lèi)型匹配時(shí),監(jiān)聽(tīng)器的 onApplicationEvent 方法就會(huì)被調(diào)用。
3.1.3. 事件發(fā)布
發(fā)布者:Spring 中的任何組件都可以通過(guò) ApplicationEventPublisher 發(fā)布事件。通常,ApplicationContext 本身實(shí)現(xiàn)了 ApplicationEventPublisher,可以直接調(diào)用 publishEvent() 發(fā)布事件。
事件傳播器:默認(rèn)情況下,Spring 使用 SimpleApplicationEventMulticaster 作為事件傳播器,它負(fù)責(zé)查找符合條件的監(jiān)聽(tīng)器并將事件分發(fā)給它們。
發(fā)布事件的方法:通過(guò) applicationContext.publishEvent(new CustomEvent(this)) 來(lái)發(fā)布事件。
3.1.4. 事件廣播給監(jiān)聽(tīng)器
- 篩選監(jiān)聽(tīng)器:
SimpleApplicationEventMulticaster會(huì)檢查所有注冊(cè)的監(jiān)聽(tīng)器,篩選出對(duì)當(dāng)前事件感興趣的監(jiān)聽(tīng)器(基于事件類(lèi)型的匹配)。 - 同步與異步:在 Spring 環(huán)境中,默認(rèn)情況下事件是同步傳遞的,所有監(jiān)聽(tīng)器在主線(xiàn)程中執(zhí)行。如果需要異步,可以通過(guò)自定義
SimpleApplicationEventMulticaster并配置線(xiàn)程池。
3.1.5. 監(jiān)聽(tīng)器處理事件
- 監(jiān)聽(tīng)邏輯執(zhí)行:每個(gè)匹配的監(jiān)聽(tīng)器會(huì)調(diào)用
onApplicationEvent()方法,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯。 - 異常處理:如果監(jiān)聽(tīng)器拋出異常,
SimpleApplicationEventMulticaster會(huì)捕獲并記錄日志,但不會(huì)影響其他監(jiān)聽(tīng)器的執(zhí)行。
3.1.6. 事件傳播的擴(kuò)展
在某些場(chǎng)景中,一個(gè)事件的監(jiān)聽(tīng)器可能會(huì)發(fā)布新的事件,這會(huì)形成事件鏈。Spring 容器會(huì)遞歸地將這些新事件廣播給感興趣的監(jiān)聽(tīng)器。
3.2應(yīng)用場(chǎng)景
3.2.1. 解耦業(yè)務(wù)邏輯
- 場(chǎng)景描述:在業(yè)務(wù)流程中,常常需要在某個(gè)操作完成后執(zhí)行附加邏輯,比如用戶(hù)注冊(cè)后發(fā)送歡迎郵件、推送通知、或更新統(tǒng)計(jì)數(shù)據(jù)。
- 實(shí)現(xiàn)方式:通過(guò)監(jiān)聽(tīng)器監(jiān)聽(tīng)用戶(hù)注冊(cè)事件,執(zhí)行后續(xù)的附加操作。這樣,核心業(yè)務(wù)邏輯與附加邏輯可以解耦,各自獨(dú)立管理。
- 示例:用戶(hù)注冊(cè)成功后觸發(fā)
UserRegistrationEvent,監(jiān)聽(tīng)器接收事件后完成發(fā)送郵件或通知的任務(wù)。
3.2.2. 事務(wù)性事件
- 場(chǎng)景描述:在某些情況下,需要確保只有當(dāng)事務(wù)成功提交后,才會(huì)發(fā)布事件。比如在訂單創(chuàng)建后,確保庫(kù)存減少或通知支付系統(tǒng)。
- 實(shí)現(xiàn)方式:通過(guò) @TransactionalEventListener 監(jiān)聽(tīng)事務(wù)性事件,確保事件只有在事務(wù)提交成功時(shí)才會(huì)觸發(fā)。
- 示例:訂單創(chuàng)建完成并且數(shù)據(jù)庫(kù)事務(wù)成功提交后,觸發(fā)
OrderCreatedEvent,通知庫(kù)存系統(tǒng)減少庫(kù)存。
3.2.3. 異步處理任務(wù)
- 場(chǎng)景描述:對(duì)于不需要實(shí)時(shí)完成的任務(wù),可以通過(guò)異步監(jiān)聽(tīng)器來(lái)解放主線(xiàn)程,避免阻塞。
- 實(shí)現(xiàn)方式:在事件監(jiān)聽(tīng)器方法上使用 @Async,使其在獨(dú)立線(xiàn)程中執(zhí)行異步任務(wù)。
- 示例:用戶(hù)在系統(tǒng)中上傳文件,文件處理邏輯通過(guò)事件異步執(zhí)行,以保證上傳接口的快速響應(yīng)。
3.2.4. 應(yīng)用啟動(dòng)或關(guān)閉事件
- 場(chǎng)景描述:在應(yīng)用啟動(dòng)或關(guān)閉時(shí),通常需要執(zhí)行一些初始化或清理操作,比如加載配置、檢查依賴(lài)服務(wù)、關(guān)閉資源等。
- 實(shí)現(xiàn)方式:通過(guò)監(jiān)聽(tīng) ApplicationReadyEvent、ContextClosedEvent 等應(yīng)用上下文事件,實(shí)現(xiàn)啟動(dòng)和關(guān)閉時(shí)的操作。
- 示例:在應(yīng)用啟動(dòng)完成后加載配置文件,或在應(yīng)用關(guān)閉時(shí)清理緩存或關(guān)閉數(shù)據(jù)庫(kù)連接。
3.2.5. 狀態(tài)變化或監(jiān)控
- 場(chǎng)景描述:在系統(tǒng)中監(jiān)控某些狀態(tài)的變化,比如監(jiān)控服務(wù)狀態(tài)、資源使用情況、流量變化等。
- 實(shí)現(xiàn)方式:使用自定義事件來(lái)捕獲和廣播狀態(tài)變化,監(jiān)聽(tīng)器實(shí)時(shí)響應(yīng)狀態(tài)變化,執(zhí)行對(duì)應(yīng)操作。
- 示例:當(dāng)服務(wù)發(fā)現(xiàn)高負(fù)載時(shí),發(fā)布
HighLoadEvent,監(jiān)聽(tīng)器響應(yīng)并調(diào)整系統(tǒng)參數(shù)或生成告警。
3.2.6. 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)中的事件處理
- 場(chǎng)景描述:在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中,事件驅(qū)動(dòng)架構(gòu)常用于處理不同領(lǐng)域的事件交互,比如訂單模塊的事件會(huì)影響到支付、物流等模塊。
- 實(shí)現(xiàn)方式:通過(guò)領(lǐng)域事件(如訂單支付事件、庫(kù)存更新事件)來(lái)實(shí)現(xiàn)模塊間的松耦合通信,避免模塊之間的直接依賴(lài)。
- 示例:在電商系統(tǒng)中,用戶(hù)下單后觸發(fā)
OrderPlacedEvent,物流模塊監(jiān)聽(tīng)該事件并安排發(fā)貨。
3.2.7. 跨服務(wù)通信
- 場(chǎng)景描述:在微服務(wù)架構(gòu)中,服務(wù)之間往往需要基于事件進(jìn)行異步通信,降低耦合度。
- 實(shí)現(xiàn)方式:通過(guò)發(fā)布事件到消息中間件(如 Kafka、RabbitMQ),各服務(wù)監(jiān)聽(tīng)感興趣的事件。
- 示例:支付服務(wù)完成支付后觸發(fā)
PaymentCompletedEvent,訂單服務(wù)監(jiān)聽(tīng)該事件并更新訂單狀態(tài)。
3.2.8. 監(jiān)聽(tīng)?wèi)?yīng)用配置變化
- 場(chǎng)景描述:在應(yīng)用運(yùn)行期間,可能需要?jiǎng)討B(tài)刷新配置,比如數(shù)據(jù)庫(kù)連接、緩存配置等。
- 實(shí)現(xiàn)方式:通過(guò)監(jiān)聽(tīng)配置中心的配置更新事件,觸發(fā)配置的刷新。
- 示例:當(dāng)配置中心檢測(cè)到 Redis 緩存配置更新后觸發(fā)
CacheConfigUpdateEvent,應(yīng)用的緩存配置自動(dòng)刷新。
3.2.9. 處理安全或認(rèn)證事件
- 場(chǎng)景描述:在用戶(hù)認(rèn)證、權(quán)限驗(yàn)證等過(guò)程中,可以發(fā)布事件來(lái)處理安全相關(guān)操作。
- 實(shí)現(xiàn)方式:監(jiān)聽(tīng)認(rèn)證成功、認(rèn)證失敗等事件,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯,比如記錄日志、鎖定賬戶(hù)。
- 示例:用戶(hù)多次登錄失敗后觸發(fā)
AuthenticationFailureEvent,監(jiān)聽(tīng)器響應(yīng)后鎖定用戶(hù)賬戶(hù)并生成告警。
以上就是Spring中如何自定義監(jiān)聽(tīng)器的詳細(xì)內(nèi)容,更多關(guān)于Spring自定義監(jiān)聽(tīng)器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot接收與響應(yīng)xml報(bào)文請(qǐng)求的實(shí)現(xiàn)
我們?cè)谶M(jìn)行接口對(duì)接時(shí),會(huì)出現(xiàn)報(bào)文形式的信息傳遞,這篇文章主要給大家介紹了關(guān)于SpringBoot接收與響應(yīng)xml報(bào)文請(qǐng)求的相關(guān)資料,需要的朋友可以參考下2023-06-06
Spring Cloud 系列之服務(wù)調(diào)用 OpenFeign的實(shí)現(xiàn)
這篇文章主要介紹了Spring Cloud 系列之服務(wù)調(diào)用 OpenFeign的實(shí)現(xiàn),需要的朋友可以參考下2020-11-11
一文掌握Spring中循環(huán)依賴(lài)與三級(jí)緩存
這篇文章主要介紹了Spring中循環(huán)依賴(lài)與三級(jí)緩存,Spring通過(guò)三級(jí)緩存解決了循環(huán)依賴(lài),其中一級(jí)緩存為單例池,二級(jí)緩存為早期曝光對(duì)象earlySingletonObjects,三級(jí)緩存為早期曝光對(duì)象工廠(singletonFactories),本文結(jié)合實(shí)例代碼介紹的非常詳細(xì),需要的朋友參考下吧2023-09-09
Java自動(dòng)添加重寫(xiě)的toString方法詳解
在本篇文章里小編給大家整理了關(guān)于Java自動(dòng)添加重寫(xiě)的toString方法總結(jié),需要的朋友們學(xué)習(xí)下。2019-07-07
java使用http實(shí)現(xiàn)文件下載學(xué)習(xí)示例
這篇文章主要介紹了java使用http實(shí)現(xiàn)文件下載學(xué)習(xí)示例,需要的朋友可以參考下2014-04-04
Java并發(fā)系列之CyclicBarrier源碼分析
這篇文章主要為大家詳細(xì)分析了Java并發(fā)系列之CyclicBarrier源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
SpringBoot學(xué)習(xí)篇之@Valid與@Validated的區(qū)別
@Valid是使用Hibernate?validation的時(shí)候使用,@Validated是只用Spring?Validator校驗(yàn)機(jī)制使用,下面這篇文章主要給大家介紹了關(guān)于SpringBoot學(xué)習(xí)篇之@Valid與@Validated區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-11-11

