当前位置: 首页 > news >正文

Spring事件详解,Spring-Event源码详解,一文搞透Spring事件管理

文章目录

  • 一、Java中事件/监听器编程模型
    • 1、Java中Observable/Observer事件监听
      • (1)代码实例
      • (2)Java9新的事件监听
    • 2、面向接口的事件/监听器设计模式
    • 3、面向注解的事件/监听器设计模式
  • 二、Spring事件
    • 1、Spring标准事件-ApplicationEvent
    • 2、基于接口的 Spring 事件监听器
      • 代码实例
    • 3、基于注解的 Spring 事件监听器
      • 代码实例
    • 4、注册Spring ApplicationListener
      • 方法一:ApplicationListener 作为 Spring Bean 注册
      • 方法二:通过 ConfigurableApplicationContext API 注册
      • 方法三:直接使用注解@EventListener
      • 三种方式的执行顺序
    • 5、Spring 事件发布器
      • (1)源码分析
      • (2)依赖注入获取ApplicationEventPublisher
      • (3)依赖查找 ApplicationEventMulticaster
      • (4)ApplicationEventPublisher 发布事件
      • (5)ApplicationEventPublisher与ApplicationEventMulticaster的关联
    • 6、Spring 层次性上下文事件传播
      • 代码实例
      • 源码分析
      • 事件重复处理
    • 7、Spring 内建事件
      • 源码分析
    • 8、Spring 4.2 Payload 事件
      • 代码实例及分析
    • 9、自定义 Spring 事件
    • 10、同步和异步 Spring 事件广播
      • (1)基于实现类 - org.springframework.context.event.SimpleApplicationEventMulticaster
      • (2)基于注解 - @org.springframework.context.event.EventListener
      • (3)小tips
    • 11、Spring 4.1 事件异常处理
      • 源码分析
      • 代码实例
    • 12、Spring 事件/监听器实现原理
      • 源码分析
      • 基于注解的监听器如何注册
  • 三、扩展-Spring Boot 、Spring Cloud事件
    • 1、Spring Boot 事件
    • 2、Spring Cloud 事件
  • 参考资料

一、Java中事件/监听器编程模型

1、Java中Observable/Observer事件监听

设计模式 - 观察者模式扩展
• 可观者对象(消息发送者) - java.util.Observable
• 观察者 - java.util.Observer

标准化接口
• 事件对象 - java.util.EventObject
• 事件监听器 - java.util.EventListener

Observable/Observer 是观察者模型在 JDK 中的实现,而 EventObject 和 EventListener 是事件驱动的接口,这里有涉及实现,实现可以利用 Observable/Observer 或者扩展来完成。

Observable/Observer 是jdk中观察者模式的实现标准,有具体实现。但是事件驱动只是建议基于EventObject/EventListener来拓展,并没有具体实现。Spring事件并没有用到Observable/Observer ,并且Observable/Observer 在 jdk 9 被推荐转为java.util.concurrent.Flow实现。

JDK 中的 Observable/Observer 只是一个参考,它的执行顺序和插入顺序是相反,也即是它是 Stack 的方式,无法实现自定义顺序,并且它使用了 Vector 线程安全的集合容器,无法提升高性能,所以 Spring 没有必要用到它。

(1)代码实例

import java.util.EventListener;
import java.util.EventObject;
import java.util.Observable;
import java.util.Observer;

/**
 * Java 事件监听示例
 * {@link Observer} 示例
 */
public class ObserverDemo {

    public static void main(String[] args) {
        EventObservable observable = new EventObservable();
        // 添加观察者(监听者)
        observable.addObserver(new EventObserver());
        // 发布消息(事件)
        observable.notifyObservers("Hello,World");
    }

    static class EventObservable extends Observable {

        public void setChanged() {
            super.setChanged();
        }

        public void notifyObservers(Object arg) {
            setChanged(); // 需要手动打开开关
            super.notifyObservers(new EventObject(arg));
            clearChanged();
        }
    }

    static class EventObserver implements Observer, EventListener {

        @Override
        public void update(Observable o, Object event) {
            EventObject eventObject = (EventObject) event;
            System.out.println("收到事件 :" + eventObject);
        }
    }
}

(2)Java9新的事件监听

Observer和Observable在JDK9以后被废弃,因为观察者和可观察对象支持的事件模型是非常有限的,可观察对象传递的通知的顺序无法指定,状态变化与通知不能一一对应。

为了在线程之间可靠而有序地传递消息,可以考虑在java.util.concurrent包中使用一种并发数据结构。对于响应式流风格的编程,javaDOC提示考虑使用java.util.concurrent.Flow

2、面向接口的事件/监听器设计模式

事件/监听器场景举例:

Java 技术规范事件接口监听器接口
JavaBeansjava.beans.PropertyChangeEventjava.beans.PropertyChangeListener
Java AWTjava.awt.event.MouseEventjava.awt.event.MouseListener
Java Swingjavax.swing.event.MenuEventjavax.swing.event.MenuListener
Java Preferencejava.util.prefs.PreferenceChangeEventjava.util.prefs.PreferenceChangeListener

通过上面列举的类的源码我们可以看到,Event都是继承了EventObject;Listener都是继承了EventListener,这基本是一个约定俗成的事情,Spring也不例外。

3、面向注解的事件/监听器设计模式

事件/监听器注解场景举例:

Java 技术规范事件注解监听器注解
Servlet 3.0+@javax.servlet.annotation.WebListener
JPA 1.0+@javax.persistence.PostPersist
Java Common@PostConstruct
EJB 3.0+@javax.ejb.PrePassivate
JSF 2.0+@javax.faces.event.ListenerFor

二、Spring事件

1、Spring标准事件-ApplicationEvent

上面我们提到过,Java 标准事件 java.util.EventObject 扩展的扩展特性就是事件发生事件戳。

而Spring 应用上下文 ApplicationEvent 扩展就是 ApplicationContextEvent,Spring 应用上下文(ApplicationContext)作为事件源。
具体实现:
• org.springframework.context.event.ContextClosedEvent
• org.springframework.context.event.ContextRefreshedEvent
• org.springframework.context.event.ContextStartedEvent
• org.springframework.context.event.ContextStoppedEvent

分析源码我们可以看出,ApplicationEvent抽象类继承了jdk的EventObject,ApplicationContextEvent抽象类又继承了ApplicationEvent。

ApplicationContextEvent就是Spring应用上下文的一个事件源,提供了获取ApplicationContext方法,事件的source就是ApplicationContext。

ApplicationContextEvent有以上四个实现:ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent。

2、基于接口的 Spring 事件监听器

Java 标准事件监听器 java.util.EventListener 扩展
• 扩展接口 - org.springframework.context.ApplicationListener
• 设计特点:单一类型事件处理
• 处理方法:onApplicationEvent(ApplicationEvent)
• 事件类型:org.springframework.context.ApplicationEvent

代码实例

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.GenericApplicationContext;

GenericApplicationContext context = new GenericApplicationContext();

// 向 Spring 应用上下文注册事件
context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println(("ApplicationListener - 接收到 Spring 事件:" + event));
    }
});

// 启动 Spring 应用上下文
context.refresh(); // ContextRefreshedEvent
// 启动 Spring 上下文(通常,ConfigurableApplicationContext#start() 方法是为了发布 ContextStartedEvent 事件,而真正的启动方法是 refresh() 方法。)
context.start();  // ContextStartedEvent
// 停止 Spring 上下文
context.stop();  // ContextStoppedEvent
// 关闭 Spring 应用上下文
context.close(); // ContextClosedEvent

我们通过该实例可以看出,Spring的四个事件通过Spring应用上下文的几个方法来触发的。

3、基于注解的 Spring 事件监听器

Spring 注解 - @org.springframework.context.event.EventListener

特性说明
设计特点支持多 ApplicationEvent 类型,无需接口约束
注解目标方法
是否支持异步执行支持
是否支持泛型类型事件支持
是指支持顺序控制支持,配合 @Order 注解控制

代码实例

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.event.*;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync // 激活异步
public class MyApplicationListenerDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 将引导类 MyApplicationListenerDemo 作为 Configuration Class
        context.register(MyApplicationListenerDemo.class);

        // 启动 Spring 应用上下文
        context.refresh(); // ContextRefreshedEvent
        // 启动 Spring 上下文
        context.start();  // ContextStartedEvent
        // 停止 Spring 上下文
        context.stop();  // ContextStoppedEvent
        // 关闭 Spring 应用上下文
        context.close(); // ContextClosedEvent
    }

    @EventListener
    @Async // 需要@EnableAsync
    public void onApplicationEventAsync(ContextRefreshedEvent event) {
        println("@EventListener(异步) - 接收到 Spring ContextRefreshedEvent");
    }

    @EventListener
    @Order(2)
    public void onApplicationEvent(ContextStartedEvent event) {
        println("@EventListener - 接收到 Spring ContextStartedEvent1");
    }
    @EventListener
    @Order(1) // 可以使用@Order控制顺序,数字越小越先执行
    public void onApplicationEvent2(ContextStartedEvent event) {
        println("@EventListener - 接收到 Spring ContextStartedEvent2");
    }

    @EventListener
    public void onApplicationEvent(ContextClosedEvent event) {
        println("@EventListener - 接收到 Spring ContextClosedEvent");
    }

    @EventListener
    public void onApplicationEvent(ContextStoppedEvent event) {
        println("@EventListener - 接收到 Spring ContextStoppedEvent");
    }

    private static void println(Object printable) {
        System.out.printf("[线程:%s] : %s\n", Thread.currentThread().getName(), printable);
    }
}

执行结果:

[线程:SimpleAsyncTaskExecutor-1] : @EventListener(异步) - 接收到 Spring ContextRefreshedEvent
[线程:main] : @EventListener - 接收到 Spring ContextStartedEvent2
[线程:main] : @EventListener - 接收到 Spring ContextStartedEvent1
[线程:main] : @EventListener - 接收到 Spring ContextStoppedEvent
[线程:main] : @EventListener - 接收到 Spring ContextClosedEvent

使用注解监听Spring事件相对而言比较简单,而且可以手动实现异步、进行排序。

4、注册Spring ApplicationListener

方法一:ApplicationListener 作为 Spring Bean 注册

// 基于 ApplicationListener 注册为 Spring Bean
// 通过 Configuration Class 来注册
context.register(MyApplicationListener.class);

public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        println("MyApplicationListener - 接收到 Spring 事件:" + event);
    }
}

方法二:通过 ConfigurableApplicationContext API 注册

以上通过接口、注解的方式其实就是使用方法二的方式进行注册的

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

// 将引导类 ApplicationListenerDemo 作为 Configuration Class
context.register(ApplicationListenerDemo.class);

// 基于 Spring 接口:向 Spring 应用上下文注册事件
// 基于 ConfigurableApplicationContext API 实现
context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        println("ApplicationListener - 接收到 Spring 事件:" + event);
    }
});

方法三:直接使用注解@EventListener

上面介绍过了

三种方式的执行顺序

通过运行代码我们发现,基于注解的Listener先执行,而后是基于接口的,最后才是注册为Bean的。

后面我们会通过分析源码来分析这个执行顺序的原因。

5、Spring 事件发布器

方法一:通过 ApplicationEventPublisher 发布 Spring 事件(事件发布)
• 获取 ApplicationEventPublisher:依赖注入

方法二:通过 ApplicationEventMulticaster 发布 Spring 事件(事件广播)
• 获取 ApplicationEventMulticaster:依赖注入、依赖查找

(1)源码分析

ApplicationEventPublisher接口有两个方法,其中有个方法是Spring4.2引入的。

@FunctionalInterface
public interface ApplicationEventPublisher {

	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}

	void publishEvent(Object event);
}

ApplicationEventMulticaster接口会在ApplicationEventPublisher接口的基础上增加很多东西,包括对ApplicationListener的增删改查,multicastEvent方法就是事件广播方法。

public interface ApplicationEventMulticaster {

	void addApplicationListener(ApplicationListener<?> listener);

	void addApplicationListenerBean(String listenerBeanName);

	void removeApplicationListener(ApplicationListener<?> listener);
	
	void removeApplicationListenerBean(String listenerBeanName);

	void removeAllListeners();

	void multicastEvent(ApplicationEvent event);

	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

(2)依赖注入获取ApplicationEventPublisher


import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.annotation.PostConstruct;

/**
 * 注入 {@link ApplicationEventPublisher} 示例
 */
public class InjectingApplicationEventPublisherDemo implements ApplicationEventPublisherAware,
        ApplicationContextAware, BeanPostProcessor {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        //# 3
        applicationEventPublisher.publishEvent(new MySpringEvent("The event from @Autowired ApplicationEventPublisher"));
        // #4
        applicationContext.publishEvent(new MySpringEvent("The event from @Autowired ApplicationContext"));
    }

    public static void main(String[] args) {

        // 创建注解驱动 Spring 应用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 注册 Configuration Class
        context.register(InjectingApplicationEventPublisherDemo.class);

        // 增加 Spring 事件监听器
        context.addApplicationListener(new MySpringEventListener());

        // 启动 Spring 应用上下文
        context.refresh();

        // 关闭 Spring 应用上下文
        context.close();
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { // #1
        applicationEventPublisher.publishEvent(new MySpringEvent("The event from ApplicationEventPublisherAware"));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // #2
        // ApplicationContext
        applicationEventPublisher.publishEvent(new MySpringEvent("The event from ApplicationContextAware"));
    }
}

通过以上实例我们可以看出,获取ApplicationEventPublisher 有两种方式,一种是通过 ApplicationEventPublisherAware 回调接口;一种是通过 @Autowired ApplicationEventPublisher。

而ApplicationContext也是继承了ApplicationEventPublisher,所以我们认为它也是一种ApplicationEventPublisher,也具有事件发布的能力。

(3)依赖查找 ApplicationEventMulticaster

查找条件
• Bean 名称:“applicationEventMulticaster”
• Bean 类型:org.springframework.context.event.ApplicationEventMulticaster

我们进行一下源码分析:

事实上,我们调用context.addApplicationListener()方法时,会委派给applicationEventMulticaster调用它的addApplicationListener方法。

// org.springframework.context.support.AbstractApplicationContext#addApplicationListener
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
	Assert.notNull(listener, "ApplicationListener must not be null");
	if (this.applicationEventMulticaster != null) {
		this.applicationEventMulticaster.addApplicationListener(listener);
	}
	this.applicationListeners.add(listener);
}

而此处的applicationEventMulticaster初始化方法:

// org.springframework.context.support.AbstractApplicationContext#initApplicationEventMulticaster
protected void initApplicationEventMulticaster() {
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { // applicationEventMulticaster
		this.applicationEventMulticaster =
				beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
		}
	}
	else {
		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() + "]");
		}
	}
}

initApplicationEventMulticaster这个方法是在springIOC容器refresh时被调用的。

而实际上ApplicationEventPublisher 发布事件的底层实现,也是通过getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); 实现的。ApplicationEventPublisher 没有直接实现。

(4)ApplicationEventPublisher 发布事件

通过以下实例我们可以看出,实现了ApplicationEventPublisherAware 接口可以获取到ApplicationEventPublisher ,从而直接发送事件(这种事件貌似只能通过addApplicationListener添加的监听器接收到,通过注解的方式是接收不到的,具体原因尚不明确)。

import org.springframework.context.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyApplicationListenerDemo implements ApplicationEventPublisherAware {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 将引导类 MyApplicationListenerDemo 作为 Configuration Class
        context.register(MyApplicationListenerDemo.class);
        context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println(("ApplicationListener - 接收到 Spring 事件:" + event));
            }
        });

        // 启动 Spring 应用上下文
        context.refresh(); // ContextRefreshedEvent
        // 启动 Spring 上下文
        context.start();  // ContextStartedEvent
        // 停止 Spring 上下文
        context.stop();  // ContextStoppedEvent
        // 关闭 Spring 应用上下文
        context.close(); // ContextClosedEvent
    }


    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        applicationEventPublisher.publishEvent(new ApplicationEvent("Hello,World"){});

        // 发送 PayloadApplicationEvent
        applicationEventPublisher.publishEvent("Hello,World2");
    }

}

也可以通过通过 @Autowired ApplicationEventPublisher 来依赖注入ApplicationEventPublisher 。

(5)ApplicationEventPublisher与ApplicationEventMulticaster的关联

底层实现:
• 接口:org.springframework.context.event.ApplicationEventMulticaster
• 抽象类:org.springframework.context.event.AbstractApplicationEventMulticaster
• 实现类:org.springframework.context.event.SimpleApplicationEventMulticaster

实际上,ApplicationEventPublisher与ApplicationEventMulticaster之间是没有接口依赖关系的。

那么他们两个是怎么关联的呢?

我们通过context调用publishEvent方法,里面的逻辑我们再看一下:

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
	Assert.notNull(event, "Event must not be null");

	// Decorate event as an ApplicationEvent if necessary
	ApplicationEvent applicationEvent;
	if (event instanceof ApplicationEvent) {
		applicationEvent = (ApplicationEvent) event;
	}
	else {
		applicationEvent = new PayloadApplicationEvent<>(this, event);
		if (eventType == null) {
			eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
		}
	}

	// Multicast right now if possible - or lazily once the multicaster is initialized
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else {
		// 关键逻辑!获取ApplicationEventMulticaster
		getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
	}

	// Publish event via parent context as well...
	if (this.parent != null) {
		if (this.parent instanceof AbstractApplicationContext) {
			((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
		}
		else {
			this.parent.publishEvent(event);
		}
	}
}

我们可以看到,ApplicationEventPublisher的publishEvent方法底层其实是调用了ApplicationEventMulticaster的multicastEvent方法,而SimpleApplicationEventMulticaster作为ApplicationEventMulticaster的一个默认实现,是支持异步事件发送的:

// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
@Override
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);
		}
	}
}

话又说回来,earlyApplicationEvents 是什么呢?
其实这是Spring4开始支持的,就是早期的Event。Spring3其实有个bug,如果一个类同时实现ApplicationEventPublisherAware、BeanPostProcessor或者BeanFactoryPostProcessor,就会出问题。

SpringIOC在初始化时执行的refresh方法:

// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();

我们发现,如果一个bean实现了BeanPostProcessor,会在initApplicationEventMulticaster初始化ApplicationEventMulticaster之前初始化,所以当此时发布事件的时候,ApplicationEventMulticaster还未初始化,导致报错,所以使用一个earlyApplicationEvents 临时存储,当registerListeners()方法执行完之后,此时ApplicationEventMulticaster已经完全初始化了,这时候取出所有存储的earlyApplicationEvents ,进行挨个事件发布。

所以,这相当于一个Spring的BUG,只不过在后期修复了。

6、Spring 层次性上下文事件传播

发生说明:
当 Spring 应用出现多层次 Spring 应用上下文(ApplicationContext)时,如 Spring WebMVC、Spring Boot或 Spring Cloud 场景下,由子 ApplicationContext 发起 Spring 事件可能会传递到其 Parent ApplicationContext(直到 Root)的过程。

如何避免:
定位 Spring 事件源(ApplicationContext)进行过滤处理。

代码实例

import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;

import java.util.LinkedHashSet;
import java.util.Set;

/**
 * 层次性 Spring 事件传播实例
 */
public class HierarchicalSpringEventPropagateDemo {

    public static void main(String[] args) {
        // 1. 创建 parent Spring 应用上下文
        AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();
        parentContext.setId("parent-context");
        // 注册 MyListener 到 parent Spring 应用上下文
        parentContext.register(MyListener.class);

        // 2. 创建 current Spring 应用上下文
        AnnotationConfigApplicationContext currentContext = new AnnotationConfigApplicationContext();
        currentContext.setId("current-context");
        // 3. current -> parent
        currentContext.setParent(parentContext);
        // 注册 MyListener 到 current Spring 应用上下文
        currentContext.register(MyListener.class);

        // 4.启动 parent Spring 应用上下文
        parentContext.refresh();

        // 5.启动 current Spring 应用上下文
        currentContext.refresh();

        // 关闭所有 Spring 应用上下文
        currentContext.close();
        parentContext.close();
    }

    static class MyListener implements ApplicationListener<ApplicationContextEvent> {

        private
        //static
        Set<ApplicationContextEvent> processedEvents = new LinkedHashSet<>();

        @Override
        public void onApplicationEvent(ApplicationContextEvent event) {
            if (processedEvents.add(event)) {
                System.out.printf("监听到 Spring 应用上下文[ ID : %s ] 事件 :%s\n", event.getApplicationContext().getId(),
                        event.getClass().getSimpleName());
            }
        }
    }
}

执行结果:

监听到 Spring 应用上下文[ ID : parent-context ] 事件 :ContextRefreshedEvent
监听到 Spring 应用上下文[ ID : current-context ] 事件 :ContextRefreshedEvent
监听到 Spring 应用上下文[ ID : current-context ] 事件 :ContextRefreshedEvent
监听到 Spring 应用上下文[ ID : current-context ] 事件 :ContextClosedEvent
监听到 Spring 应用上下文[ ID : current-context ] 事件 :ContextClosedEvent
监听到 Spring 应用上下文[ ID : parent-context ] 事件 :ContextClosedEvent

我们可以看出,每个事件会执行三次,因为current执行完毕之后,还会在其parent中执行一次。

源码分析

我们可以看一下context的refresh方法触发的finishRefresh方法中publishEvent触发的事件逻辑:

// org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
	Assert.notNull(event, "Event must not be null");

	// Decorate event as an ApplicationEvent if necessary
	ApplicationEvent applicationEvent;
	if (event instanceof ApplicationEvent) {
		applicationEvent = (ApplicationEvent) event;
	}
	else {
		applicationEvent = new PayloadApplicationEvent<>(this, event);
		if (eventType == null) {
			eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
		}
	}

	// Multicast right now if possible - or lazily once the multicaster is initialized
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else {
		getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
	}

	// Publish event via parent context as well...
	if (this.parent != null) { // 递归,如果有parent的话,就传递给parent进行事件发布
		if (this.parent instanceof AbstractApplicationContext) {
			((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
		}
		else {
			this.parent.publishEvent(event);
		}
	}
}

事件重复处理

比较简单的方式就是定义一个全局变量,如上我们的代码实例,定义了一个static的set做去重处理,同一个事件保证只能监听一次。

也可以注入ApplicationContext,判断事件的上下文对象是否是当前的上下文对象。从而进行过滤。

7、Spring 内建事件

ApplicationContextEvent 派生事件
• ContextRefreshedEvent :Spring 应用上下文就绪事件
• ContextStartedEvent :Spring 应用上下文启动事件
• ContextStoppedEvent :Spring 应用上下文停止事件
• ContextClosedEvent :Spring 应用上下文关闭事件

start 和 stop 方法是一种辅助的特性,通常使用不多。

源码分析

我们来分析一下context的refresh方法:

refresh方法最终会调用finishRefresh方法,会调用publishEvent方法发送ContextRefreshedEvent事件:

// org.springframework.context.support.AbstractApplicationContext#finishRefresh
protected void finishRefresh() {
	// Clear context-level resource caches (such as ASM metadata from scanning).
	clearResourceCaches();

	// Initialize lifecycle processor for this context.
	initLifecycleProcessor();

	// Propagate refresh to lifecycle processor first.
	getLifecycleProcessor().onRefresh();

	// Publish the final event.
	publishEvent(new ContextRefreshedEvent(this));

	// Participate in LiveBeansView MBean, if active.
	LiveBeansView.registerApplicationContext(this);
}

实际上,AbstractApplicationContext实现了ApplicationContext,ApplicationContext继承了ApplicationEventPublisher,所以在底层是可以发布事件的。

其他几个事件发送同理。

8、Spring 4.2 Payload 事件

Spring Payload 事件 - org.springframework.context.PayloadApplicationEvent

使用场景:简化 Spring 事件发送,关注事件源主体

发送方法:ApplicationEventPublisher#publishEvent(java.lang.Object)

代码实例及分析


import org.springframework.context.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyApplicationListenerDemo implements ApplicationEventPublisherAware {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 将引导类 MyApplicationListenerDemo 作为 Configuration Class
        context.register(MyApplicationListenerDemo.class);
        context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println(("ApplicationListener - 接收到 Spring 事件:" + event));
            }
        });

        // 启动 Spring 应用上下文
        context.refresh(); // ContextRefreshedEvent
        // 启动 Spring 上下文
        context.start();  // ContextStartedEvent
        // 停止 Spring 上下文
        context.stop();  // ContextStoppedEvent
        // 关闭 Spring 应用上下文
        context.close(); // ContextClosedEvent
    }


    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        applicationEventPublisher.publishEvent(new ApplicationEvent("Hello,World"){});

        // 发送 PayloadApplicationEvent
        applicationEventPublisher.publishEvent("Hello,World2");
        applicationEventPublisher.publishEvent(new HelloPayloadApplicationEvent(this, "Hello,World3"));
    }

    static class HelloPayloadApplicationEvent extends PayloadApplicationEvent<String> {

        public HelloPayloadApplicationEvent(Object source, String payload) {
            super(source, payload);
        }
    }
}

通过运行我们可以看出,使用下面这种方式发送PayloadApplicationEvent会报错:

applicationEventPublisher.publishEvent(new HelloPayloadApplicationEvent(this, "Hello,World3"));

错误提示:

Mismatched number of generics specified

当我们把HelloPayloadApplicationEvent类改一下:

static class HelloPayloadApplicationEvent<String> extends PayloadApplicationEvent<String> {

    public HelloPayloadApplicationEvent(Object source, String payload) {
        super(source, payload);
    }
}

此时就不会报错了,正常发送我们的事件。

实际上,Spring一直没有处理过这个问题,在Payload事件中Bean的泛型具体化并没有处理好。

关于Spring泛型处理:Spring泛型处理源码详解,Java泛型处理

所以,想要发送PayloadApplicationEvent事件,只需要使用上面的那种方式即可,不需要再继承、new一个Event了:

// 发送 PayloadApplicationEvent
applicationEventPublisher.publishEvent("Hello,World2");// (√)
applicationEventPublisher.publishEvent(new HelloPayloadApplicationEvent(this, "Hello,World3")); // (×)

9、自定义 Spring 事件

(1)扩展 org.springframework.context.ApplicationEvent

import org.springframework.context.ApplicationEvent;

/**
 * 自定义 Spring 事件
 */
public class MySpringEvent extends ApplicationEvent {

    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param message 事件消息
     */
    public MySpringEvent(String message) {
        super(message);
    }

    @Override
    public String getSource() {
        return (String) super.getSource();
    }

    public String getMessage() {
        return getSource();
    }
}

(2)实现 org.springframework.context.ApplicationListener

import org.springframework.context.ApplicationListener;

/**
 * 自定义 Spring 事件监听器
 */
public class MySpringEventListener implements ApplicationListener<MySpringEvent> {

    @Override
    public void onApplicationEvent(MySpringEvent event) {
        System.out.printf("[线程 : %s] 监听到事件 : %s\n", Thread.currentThread().getName(), event);
    }
}

(3)注册 org.springframework.context.ApplicationListener

GenericApplicationContext context = new GenericApplicationContext();

// 1.添加自定义 Spring 事件监听器
context.addApplicationListener(new MySpringEventListener());

// 2.启动 Spring 应用上下文
context.refresh();

// 3. 发布自定义 Spring 事件
context.publishEvent(new MySpringEvent("Hello,World"));

// 4. 关闭 Spring 应用上下文
context.close();

10、同步和异步 Spring 事件广播

(1)基于实现类 - org.springframework.context.event.SimpleApplicationEventMulticaster

模式切换:setTaskExecutor(java.util.concurrent.Executor) 方法
• 默认模式:同步
• 异步模式:如 java.util.concurrent.ThreadPoolExecutor

设计缺陷:非基于接口契约编程

源码分析:
ApplicationEventMulticaster的一个默认实现就是SimpleApplicationEventMulticaster,它的multicastEvent如下:

// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
@Override
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);
		}
	}
}

如果手动设置(setTaskExecutor)了taskExecutor,就是异步执行。

代码实例:


import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 异步事件处理器示例
 */
public class AsyncEventHandlerDemo {

    public static void main(String[] args) {

        GenericApplicationContext context = new GenericApplicationContext();

        // 1.添加自定义 Spring 事件监听器
        context.addApplicationListener(new MySpringEventListener());

        // 2.启动 Spring 应用上下文
        context.refresh(); // 初始化 ApplicationEventMulticaster

        // 依赖查找 ApplicationEventMulticaster
        ApplicationEventMulticaster applicationEventMulticaster =
                context.getBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);

        // 判断当前 ApplicationEventMulticaster 是否为 SimpleApplicationEventMulticaster
        if (applicationEventMulticaster instanceof SimpleApplicationEventMulticaster) {
            SimpleApplicationEventMulticaster simpleApplicationEventMulticaster =
                    (SimpleApplicationEventMulticaster) applicationEventMulticaster;
            // 切换 taskExecutor
            ExecutorService taskExecutor = Executors.newSingleThreadExecutor(new CustomizableThreadFactory("my-spring-event-thread-pool"));
            // 同步 -> 异步
            simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor);

            // 添加 ContextClosedEvent 事件处理,优雅关闭线程池
            applicationEventMulticaster.addApplicationListener(new ApplicationListener<ContextClosedEvent>() {
                @Override
                public void onApplicationEvent(ContextClosedEvent event) {
                    if (!taskExecutor.isShutdown()) {
                        taskExecutor.shutdown();
                    }
                }
            });

        }

        // 3. 发布自定义 Spring 事件
        context.publishEvent(new MySpringEvent("Hello,World"));

        // 4. 关闭 Spring 应用上下文(ContextClosedEvent)
        context.close();

    }
}

import org.springframework.context.ApplicationListener;

/**
 * 自定义 Spring 事件监听器
 */
public class MySpringEventListener implements ApplicationListener<MySpringEvent> {

    @Override
    public void onApplicationEvent(MySpringEvent event) {
        System.out.printf("[线程 : %s] 监听到事件 : %s\n", Thread.currentThread().getName(), event);
    }
}


(2)基于注解 - @org.springframework.context.event.EventListener

模式切换
• 默认模式:同步
• 异步模式:标注 @org.springframework.scheduling.annotation.Async

实现限制:无法直接实现同步/异步动态切换

事实上注解方式实现异步,上面我们已经介绍过了。

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;

import static java.util.concurrent.Executors.newSingleThreadExecutor;

/**
 * 注解驱动异步事件处理器示例
 */
@EnableAsync // 激活 Spring 异步特性
public class AnnotatedAsyncEventHandlerDemo {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 1. 注册当前类作为 Configuration Class
        context.register(AnnotatedAsyncEventHandlerDemo.class);

        // 2.启动 Spring 应用上下文
        context.refresh(); // 初始化 ApplicationEventMulticaster

        // 3. 发布自定义 Spring 事件
        context.publishEvent(new MySpringEvent("Hello,World"));

        // 4. 关闭 Spring 应用上下文(ContextClosedEvent)
        context.close();

    }

    @Async // 同步 -> 异步
    @EventListener
    public void onEvent(MySpringEvent event) {
        System.out.printf("[线程 : %s] onEvent方法监听到事件 : %s\n", Thread.currentThread().getName(), event);
    }

    @Bean
    public Executor taskExecutor() {
        ExecutorService taskExecutor = newSingleThreadExecutor(new CustomizableThreadFactory("my-spring-event-thread-pool-a"));
        return taskExecutor;
    }
}

(3)小tips

上面的例子我们可以看出,基于实现类SimpleApplicationEventMulticaster手动设置线程池的方式来实现异步,其实是全局设置。

而使用注解的方式,可以指定方法异步。

11、Spring 4.1 事件异常处理

Spring 3.0 错误处理接口 - org.springframework.util.ErrorHandler

使用场景:
• Spring 事件(Events):SimpleApplicationEventMulticaster Spring 4.1 开始支持
• Spring 本地调度(Scheduling):org.springframework.scheduling.concurrent.ConcurrentTaskScheduler和org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler

源码分析

上面我们分析到,SimpleApplicationEventMulticaster发布事件执行multicastEvent方法,最终会调用invokeListener来调用监听器:

// org.springframework.context.event.SimpleApplicationEventMulticaster#invokeListener
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
	ErrorHandler errorHandler = getErrorHandler();
	if (errorHandler != null) { // 如果有ErrorHandler 
		try {
			doInvokeListener(listener, event);
		}
		catch (Throwable err) {
			errorHandler.handleError(err);
		}
	}
	else { // 没有ErrorHandler 就直接执行
		doInvokeListener(listener, event);
	}
}

代码实例

public class AsyncEventHandlerDemo {

    public static void main(String[] args) {

        GenericApplicationContext context = new GenericApplicationContext();

        // 启动 Spring 应用上下文
        context.refresh(); // 初始化 ApplicationEventMulticaster

        // 依赖查找 ApplicationEventMulticaster
        ApplicationEventMulticaster applicationEventMulticaster =
                context.getBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);

        // 判断当前 ApplicationEventMulticaster 是否为 SimpleApplicationEventMulticaster
        if (applicationEventMulticaster instanceof SimpleApplicationEventMulticaster) {
            SimpleApplicationEventMulticaster simpleApplicationEventMulticaster =
                    (SimpleApplicationEventMulticaster) applicationEventMulticaster;

            simpleApplicationEventMulticaster.setErrorHandler(e -> {
                System.err.println("当 Spring 事件异常时,原因:" + e.getMessage());
            });
        }

        context.addApplicationListener(new ApplicationListener<MySpringEvent>() {
            @Override
            public void onApplicationEvent(MySpringEvent event) {
                System.out.printf("[线程 : %s] 监听到事件 : %s\n", Thread.currentThread().getName(), event);
                throw new RuntimeException("故意抛出异常");
            }
        });

        // 3. 发布自定义 Spring 事件
        context.publishEvent(new MySpringEvent("Hello,World"));

        // 4. 关闭 Spring 应用上下文(ContextClosedEvent)
        context.close();

    }
}

12、Spring 事件/监听器实现原理

通过上面我们的分析与实践,大致基本也了解了Spring 事件/监听器实现原理,在此我们进一步做一下总结。

核心类 - org.springframework.context.event.SimpleApplicationEventMulticaster

设计模式:观察者模式扩展
• 观察者 - org.springframework.context.ApplicationListener:API 添加、依赖查找
• 通知对象 - org.springframework.context.ApplicationEvent

执行模式:同步/异步

异常处理:org.springframework.util.ErrorHandler

泛型处理:org.springframework.core.ResolvableType

源码分析

SimpleApplicationEventMulticaster先继承了AbstractApplicationEventMulticaster,
AbstractApplicationEventMulticaster又实现了ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware。

在addApplicationListener时,实际上发生了以下事情:

// org.springframework.context.event.AbstractApplicationEventMulticaster#addApplicationListener
@Override
public void addApplicationListener(ApplicationListener<?> 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();
	}
}

defaultRetriever就是ListenerRetriever一个内部类,保存着ApplicationListener的集合:

public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

在AbstractApplicationEventMulticaster定义了一个

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

ListenerCacheKey就是将ApplicationEvent的泛型,里面存着ResolvableType。

实际上,我们上面分析到,调用multicastEvent方法时,会获取所有的ApplicationListener:

// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
@Override
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);
		}
	}
}
// org.springframework.context.event.AbstractApplicationEventMulticaster#getApplicationListeners(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
protected Collection<ApplicationListener<?>> getApplicationListeners(
		ApplicationEvent event, ResolvableType eventType) {

	Object source = event.getSource();
	Class<?> sourceType = (source != null ? source.getClass() : null);
	ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

	// Quick check for existing entry on ConcurrentHashMap...
	ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
	if (retriever != null) {
		return retriever.getApplicationListeners(); // 一个类型关联多个监听器
	}

	if (this.beanClassLoader == null ||
			(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
					(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
		// Fully synchronized building and caching of a ListenerRetriever
		synchronized (this.retrievalMutex) {
			retriever = this.retrieverCache.get(cacheKey);
			if (retriever != null) {
				return retriever.getApplicationListeners();
			}
			retriever = new ListenerRetriever(true);
			Collection<ApplicationListener<?>> listeners =
					retrieveApplicationListeners(eventType, sourceType, retriever);
			this.retrieverCache.put(cacheKey, retriever);
			return listeners;
		}
	}
	else {
		// No ListenerRetriever caching -> no synchronization necessary
		return retrieveApplicationListeners(eventType, sourceType, null);
	}
}

在获取所有的Listener时,会首先通过泛型构造一个ListenerCacheKey,来确定查找监听指定类型的监听器,迭代这些监听器挨个进行通知。

这也是可以解释了,为什么可以单独监听指定事件的类型了:

context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        println("ApplicationListener - 接收到 Spring 事件:" + event);
    }
});

context.addApplicationListener(new ApplicationListener<ContextRefreshedEvent>() {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        println("ApplicationListener - 接收到 Spring 事件:" + event);
    }
});

基于注解的监听器如何注册

1.org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry) 注册所有相关注解的post processor到beanRegistry里面,其中就包括监 听器相关的EventListenerMethodProcessor

2.该类的这个方法org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated会在bean实例化的中被调用。

3.org.springframework.context.event.EventListenerMethodProcessor#processBean 最终在这个方法找到相关注解并不listener注册到ApplicationContext中

其实spring的很多功能都是通过扩展post processor来完成的。

三、扩展-Spring Boot 、Spring Cloud事件

1、Spring Boot 事件

事件类型发生时机
ApplicationStartingEvent当 Spring Boot 应用已启动时
ApplicationStartedEvent当 Spring Boot 应用已启动时
ApplicationEnvironmentPreparedEvent当 Spring Boot Environment 实例已准备时
ApplicationPreparedEvent当 Spring Boot 应用预备时
ApplicationReadyEvent当 Spring Boot 应用完全可用时
ApplicationFailedEvent当 Spring Boot 应用启动失败时

实际上,Spring Boot事件基本上还是使用Spring事件那一套-ApplicationEvent。

也是用的ApplicationEventPublisher来发布的事件。

关于SpringBoot事件详细用法请移步:
Springboot启动之后立即执行某些方法可以怎么做?Springboot监听器,Springboot生命周期钩子函数总结大全

2、Spring Cloud 事件

事件类型发生时机
EnvironmentChangeEvent当 Environment 示例配置属性发生变化时
HeartbeatEvent当 DiscoveryClient 客户端发送心跳时
InstancePreRegisteredEvent当服务实例注册前
InstanceRegisteredEvent当服务实例注册后
RefreshEvent当 RefreshEndpoint 被调用时
RefreshScopeRefreshedEvent当 Refresh Scope Bean 刷新后

Spring Cloud 事件和Spring事件也都差不多,Spring Cloud 事件会分到不同的功能模块里面。

参考资料

极客时间-《小马哥讲 Spring 核心编程思想》

相关文章:

  • 【C语言进阶】柔性数组
  • 2023​史上最全软件测试工程师常见的面试题总结​ 备战金三银四
  • Day12【元宇宙的实践构想01】—— 元宇宙概念和发展历程
  • 《从0开始学大数据》之如何自己开发一个大数据SQL引擎
  • websoket是干么的如何基于websoket实现一个简单的消息通信。
  • nacos 服务发现获取列表源码分析
  • 【MySQL】过年没有回老家,在出租屋里整理了一些思维导图
  • 《流浪地球 2》 Deepfake 小试牛刀,45+ 吴京「被」年轻,变身 21 岁小鲜肉
  • C++工程实践必备技能
  • GitHub访问问题与FastGithub下载及使用(详细篇)
  • <使用Python自定义生成简易二维码>——《Python项目实战》
  • Spring Boot 热部署(热加载)
  • 又一个开源工具搞完了,工作效率直接翻倍
  • 入职-环境安装篇
  • 自动驾驶感知——毫米波雷达
  • (三)从jvm层面了解线程的启动和停止
  • 【159天】尚学堂高琪Java300集视频精华笔记(128)
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • 10个确保微服务与容器安全的最佳实践
  • CSS 三角实现
  • eclipse(luna)创建web工程
  • Fundebug计费标准解释:事件数是如何定义的?
  • IndexedDB
  • javascript数组去重/查找/插入/删除
  • Vue.js-Day01
  • 飞驰在Mesos的涡轮引擎上
  • 前端之Sass/Scss实战笔记
  • 思维导图—你不知道的JavaScript中卷
  • 算法-图和图算法
  • 通信类
  • 走向全栈之MongoDB的使用
  • Nginx实现动静分离
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ​比特币大跌的 2 个原因
  • #NOIP 2014#Day.2 T3 解方程
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • (3)(3.5) 遥测无线电区域条例
  • (4)事件处理——(2)在页面加载的时候执行任务(Performing tasks on page load)...
  • (pojstep1.1.2)2654(直叙式模拟)
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (WSI分类)WSI分类文献小综述 2024
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (利用IDEA+Maven)定制属于自己的jar包
  • (一)基于IDEA的JAVA基础12
  • .Net - 类的介绍
  • .Net 8.0 新的变化
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET Core 通过 Ef Core 操作 Mysql
  • .xml 下拉列表_RecyclerView嵌套recyclerview实现二级下拉列表,包含自定义IOS对话框...
  • /deep/和 >>>以及 ::v-deep 三者的区别
  • @取消转义
  • [C/C++]关于C++11中的std::move和std::forward
  • [i.MX]飞思卡尔IMX6处理器的GPIO-IOMUX_PAD说明