目录

一、概要

二、观察者模式

三、Spring事件监听相关角色

四、Spring事件监听案例

五、Spring事件监听源码剖析

六、总结


一、概要

Spring提供了一套完整的事件发布和监听机制,它在JDK事件监听的基础上进行了一些扩展,并使用IOC容器来管理相关组件,如监听器、事件发布器等,使得用户不需要关心事件发布和监听的具体细节,降低了开发难度。

如果项目中有这样的场景,当一个 Bean 处理完一个任务之后,希望另一个 Bean 知道并能做相应的处理逻辑,这时我们就可以使用Spring的事件发布-监听机制,实现bean监听另外一个bean的功能。

二、观察者模式

Spring的事件监听机制可以说是在典型观察者模式基础上的进一步抽象和改进,所以我们先来回顾一下观察者模式。

观察者模式是一个典型的订阅-发布模型,其主要涉及到四种角色:

  • 抽象被观察者角色:内部持有所有观察者角色的引用,并对外提供新增、移除观察者角色、通知所有观察者的功能;
  • 具体被观察者角色:当状态变更时,会通知到所有的观察者角色;
  • 抽象观察者角色:抽象具体观察者角色的一些共性方法,如状态变更方法;
  • 具体观察者角色:实现抽象观察者角色的方法;

UML图大体如下:

下面通过一个案例回顾观察者模式的用法。

  • (一)、定义抽象观察者
/*** 抽象观察者角色*/
public abstract class AbstractObserver {public abstract void receiveMsg(String context);
}
  • (二)、定义抽象被观察者
/*** 抽象主题(抽象被观察者角色)*/
public abstract class AbstractSubject {/*** 持有所有抽象观察者角色的集合引用*/private List<AbstractObserver> observerList = new ArrayList<>();/*** 添加一个观察者** @param observer*/public void addObserver(AbstractObserver observer) {observerList.add(observer);}/*** 移除一个观察者** @param observer*/public void removeObserver(AbstractObserver observer) {observerList.remove(observer);}/*** 通知所有的观察者,执行观察者更新方法*/public void notifyAllObserver(String context) {observerList.forEach(observer -> observer.receiveMsg(context));}}
  • (三)、定义具体被观察者
/*** 具体被观察者角色*/
public class ConcreteSubject extends AbstractSubject {private String context;public String getContext() {return context;}public void sendMsg(String context) {this.context = context;System.out.println("具体被观察者角色发送消息: " + context);// 通知到所有观察者角色,更新super.notifyAllObserver(context);}}
  • (四)、定义具体观察者
/*** 具体观察者角色*/
public class ConcreteObserver extends AbstractObserver {private String context;public String getContext() {return context;}@Overridepublic void receiveMsg(String context) {this.context = context;System.out.println("具体观察者角色接收消息: " + context);}
}
  • (五)、测试类
public class Test {public static void main(String[] args) {ConcreteSubject concreteSubject = new ConcreteSubject();concreteSubject.addObserver(new ConcreteObserver());// 被观察者发出消息concreteSubject.sendMsg("hello world!");}
}
  • (六)、输出结果
具体被观察者角色发送消息: hello world!
具体观察者角色接收消息: hello world!

从控制台输出结果,可以看到,当具体被观察者的发出消息后,具体的观察者马上就接收到消息了,也就是说具体观察者的状态是会随着具体被观察者的状态变化而变化的。

三、Spring事件监听相关角色

了解观察者模式的用法之后,我们再来看下Spring事件监听机制涉及的几种角色:

  • (一)、事件(ApplicationEvent)

事件(ApplicationEvent)可以类比为观察者模式中的具体被观察者角色,它继承自JDK中的EventObject类。其定义如下:

public abstract class ApplicationEvent extends EventObject {/** use serialVersionUID from Spring 1.2 for interoperability. */private static final long serialVersionUID = 7099057708183571937L;/** System time when the event happened. */private final long timestamp;/*** Create a new ApplicationEvent.* @param source the object on which the event initially occurred (never {@code null})*/public ApplicationEvent(Object source) {super(source);this.timestamp = System.currentTimeMillis();}/*** Return the system time in milliseconds when the event happened.*/public final long getTimestamp() {return this.timestamp;}}

在Spring容器中已经内置了一些常用的事件,如IOC容器刷新开始、结束、关闭等事件,如下图:

  • (二)、事件发布器(ApplicationEventPublisher)

通过实现ApplicationEventPublisher接口,并重写publishEvent()方法,我们可以自定义事件发布的逻辑。其定义如下:

@FunctionalInterface
public interface ApplicationEventPublisher {default void publishEvent(ApplicationEvent event) {publishEvent((Object) event);}void publishEvent(Object event);}

我们既可以自定义ApplicationEventPublisher自定义事件发布逻辑,也可以直接使用ApplicationContext上下文类进行事件发布。

如上图,可以看到,ApplicationContext继承了ApplicationEventPublisher接口,因此,我们可以通过实现ApplicationContextAware接口,注入ApplicationContext,然后通过ApplicationContext的publishEvent()方法来实现事件发布功能。

注意,ApplicationContext容器本身仅仅是对外提供了事件发布的接口仅仅是对外提供了事件发布的接口publishEvent(),真正的工作其实是委托给了具体容器内部一个ApplicationEventMulticaster对象:

// AbstractApplicationContext#applicationEventMulticaster
private ApplicationEventMulticaster applicationEventMulticaster;

所以,真正的事件发布器其实是ApplicationEventMulticaster,ApplicationEventMulticaster可以类比成观察者模式的抽象被观察者角色,因为它具体以下功能:持有所有观察者集合的引用、动态添加、移除观察者角色。

  • (三)、事件监听器(ApplicationListener)

事件监听器(ApplicationListener)对应于观察者模式中的具体观察者角色,当事件发布之后,就会执行事件监听器的逻辑。通过实现ApplicationListener接口,并重写onApplicationEvent()方法,我们可以监听到事件发布器发布的事件。

Spring定义了一个ApplicationListener接口作为为事件监听器的抽象,其定义如下:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {// 监听具体的事件void onApplicationEvent(E event);}

四、Spring事件监听案例

前面介绍了Spring事件监听机制涉及的三种角色,要实现事件监听功能,无非就是定义前面涉及的三种角色。

  • (一)、自定义ApplicationEvent事件对象

自定义事件对象必须继承ApplicationEvent类,并提供对应的构造方法。

import org.springframework.context.ApplicationEvent;/*** 自定义事件*/
public class CustomEvent extends ApplicationEvent {/*** Create a new ApplicationEvent.** @param source the object on which the event initially occurred (never {@code null})*/public CustomEvent(Object source) {super(source);}private String context;public CustomEvent(Object source, String context) {super(source);this.context = context;}public String getContext() {return context;}public void setContext(String context) {this.context = context;}
}
  • (二)、自定义ApplicationListener事件监听器

前面提到Spring提供了一个抽象监听器接口ApplicationListener,我们可以通过实现此接口定义事件监听器:

import org.springframework.context.ApplicationListener;/*** 自定义事件监听器*/
public class CustomListener implements ApplicationListener<CustomEvent> {@Overridepublic void onApplicationEvent(CustomEvent event) {// 监听事件,处理具体逻辑System.out.println("event.getContext() = " + event.getContext());}
}

除了实现ApplicationListener接口这种方式之外,onApplicationEvent()监听方法的实现也可以使用@EventListener注解。

  • (三)、测试类
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test {public static void main(String[] args) throws InterruptedException {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-config.xml");// 直接使用applicationContext发布自定义事件applicationContext.publishEvent(new CustomEvent("customEvent", "hello world"));Thread.sleep(2000);applicationContext.publishEvent(new CustomEvent("customEvent", "hello, spring listener!!"));}
}
  • (四)、运行结果
event.getContext() = hello world
event.getContext() = hello, spring listener!!

从输出结果看,我们成功监听到事件多播器发布的customEvent事件,然后Spring就会回调自定义事件监听器CustomListener的onApplicationEvent()方法,这里只是简单输出,读者可自行扩展。

五、Spring事件监听源码剖析

Spring事件监听机制离不开容器IOC特性提供的支持,比如容器自动帮我们初始化事件发布器,自动扫描用户注册的监听器并注册到IOC容器中,在特定的事件发布后会找到对应的事件监听器并对其监听方法进行回调。Spring帮助用户屏蔽了关于事件监听机制背后的很多细节,使用户可以专注于业务层面进行自定义事件开发。

要想更深入地理解Spring的事件监听原理,还是得分析一下对应的源码。Spring事件监听机制源码主要涉及三个方面:

  • (一)、初始化事件发布器的流程
  • (二)、注册事件监听器的流程
  • (三)、容器事件发布的流程

下面依次分析上述几个关键步骤的处理逻辑。

  • (一)、初始化事件发布器的流程

经过前面分析,我们知道真正发布事件的对象其实是ApplicationEventMulticaster对象,Spring初始化ApplicationEventMulticaster是发生在IOC容器初始化阶段,也就是AbstractApplicationContext#refresh()方法的第八步:

/*** 8、初始化事件多播器*/
initApplicationEventMulticaster();
// 初始化事件多播器
protected void initApplicationEventMulticaster() {// 获取到当前bean工厂ConfigurableListableBeanFactory beanFactory = getBeanFactory();// 1、如果用户自定义了事件广播器,那么使用用户自定义的事件广播器(实现ApplicationEventMulticaster接口)// 判断bean工厂中是否存在id为applicationEventMulticaster的beanif (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {// 如果存在,则通过getBean获取对应的beanthis.applicationEventMulticaster =beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);if (logger.isTraceEnabled()) {logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");}}else {// 2、如果用户没有自定义事件广播器,则使用默认的事件广播器:SimpleApplicationEventMulticaster// 如果不存在,则新建一个简单的SimpleApplicationEventMulticaster事件多播器this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);// 作为单例bean注册到bean工厂,存入一级缓存singletonObjects中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,然后放入IOC容器中。

  • (二)、注册事件监听器的流程

Spring注册事件监听器也是发生在IOC容器初始化阶段,在初始化完事件多播器后面,也就是AbstractApplicationContext#refresh()方法的第十步:

/*** 10、注册监听器*/
registerListeners();
// 注册监听器
protected void registerListeners() {// 1. 获取静态指定的监听器集合,循环添加到事件多播器中,事件多播器在前面一个步骤initApplicationEventMulticaster()中已经初始化// getApplicationListeners()就是获取的AbstractApplicationContext#applicationListeners成员属性的值,即通过硬编码调用addApplicationListener方法添加的监听器for (ApplicationListener<?> listener : getApplicationListeners()) {// 把手动注册的监听器添加到事件多播器中,这里直接添加的是类型是ApplicationListenergetApplicationEventMulticaster().addApplicationListener(listener);}// Do not initialize FactoryBeans here: We need to leave all regular beans// uninitialized to let post-processors apply to them!// 2. 从bean工厂中找出所有实现了ApplicationListener接口的bean名称,循环添加到事件多播器中// 通过配置文件或注解注入的监听器String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {// 由于实现了ApplicationListener接口的类此时还没有实例化,这里添加的是监听器对应的bean的名称,在获取所有的事件监听器方法AbstractApplicationEventMulticaster.DefaultListenerRetriever#getApplicationListeners中,会通过beanFactory.getBean(listenerBeanName, ApplicationListener.class)获取到对应的监听器beangetApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}// 3. 如果存在早期应用事件, 则直接发布早期的应用程序事件到相应的监听器,并将earlyApplicationEvents早期事件置为空//早期应用程序事件this.earlyApplicationEvents在AbstractApplicationContext#prepareRefresh()方法中已经初始化Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;// 清空早期应用程序事件this.earlyApplicationEvents = null;// 如果早期应用程序事件不为空,则直接执行multicastEvent()方法发布事件if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {for (ApplicationEvent earlyEvent : earlyEventsToProcess) {getApplicationEventMulticaster().multicastEvent(earlyEvent);}}
}

简述一下registerListeners()注册监听器的流程:

  • 1、获取静态指定的监听器集合(通过硬编码调用addApplicationListener方法添加的监听器),循环添加到事件多播器中,事件多播器在前面一个步骤initApplicationEventMulticaster()中已经初始化;
  • 2. 从bean工厂中找出所有实现了ApplicationListener接口的bean名称(通过配置文件或注解注入的监听器),循环添加到事件多播器中;
  • 3. 如果存在早期应用事件, 则直接发布早期的应用程序事件到相应的监听器,并将earlyApplicationEvents早期事件置为空;

对于硬编码方式注入的监视器,Spring直接通过addApplicationListener()添加到applicationListeners集合中。

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

对于通过配置文件或者注解方式注入的监视器,Spring通过addApplicationListenerBean()将监视器的beanName保存到applicationListenerBeans集合中。代码如下:

// org.springframework.context.event.AbstractApplicationEventMulticaster#addApplicationListenerBean
public void addApplicationListenerBean(String listenerBeanName) {synchronized (this.retrievalMutex) {// 保存所有监听器的beanName// public final Set<String> applicationListenerBeans = new LinkedHashSet<>();this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);// 清除事件和监听器映射关系的缓存this.retrieverCache.clear();}
}

从上面的代码可以看到,这里只是将所有监听器的beanName保存到了事件发布器ApplicationEventMulticaster中,那么真正的事件监听器实例是何时被创建并被加入到事件发布器中的?

其实在AbstractApplicationContext#refresh()方法,执行prepareBeanFactory(beanFactory)准备Bean工厂这一步骤的时候,Spring往容器中注入了一些重要的组件,其中就包括一个特殊的BeanPostProcessor后置处理器:ApplicationListenerDetector,翻译过来叫“应用监视器探测器”。如下:

beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

ApplicationListenerDetector实现了DestructionAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor接口,这两个接口都是继承自BeanPostProcessor。BeanPostProcessor是Spring提供的一个扩展点,在Bean初始化之前(回调postProcessBeforeInitialization()方法)、之后(回调postProcessAfterInitialization()方法)可以执行一些用户自定义逻辑。

ApplicationListenerDetector也重写了BeanPostProcessor定义的两个抽象方法,具体实现如下:

class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {private static final Log logger = LogFactory.getLog(ApplicationListenerDetector.class);private final transient AbstractApplicationContext applicationContext;private final transient Map<String, Boolean> singletonNames = new ConcurrentHashMap<>(256);public ApplicationListenerDetector(AbstractApplicationContext applicationContext) {this.applicationContext = applicationContext;}@Overridepublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {this.singletonNames.put(beanName, beanDefinition.isSingleton());}// bean初始化之前执行@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {return bean;}// bean初始化之后执行@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {// 1.判断bean是否实现了ApplicationListener接口if (bean instanceof ApplicationListener) {// potentially not detected as a listener by getBeanNamesForType retrieval// singletonNames集合中的值是在ApplicationListenerDetector#postProcessMergedBeanDefinition()方法中加入的Boolean flag = this.singletonNames.get(beanName);if (Boolean.TRUE.equals(flag)) {// singleton bean (top-level or inner): register on the fly// 将bean注册到事件发布器applicationEventMulticaster中this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);}else if (Boolean.FALSE.equals(flag)) {if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {// inner bean with other scope - can't reliably process eventslogger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +"but is not reachable for event multicasting by its containing ApplicationContext " +"because it does not have singleton scope. Only top-level listener beans are allowed " +"to be of non-singleton scope.");}this.singletonNames.remove(beanName);}}return bean;}@Overridepublic void postProcessBeforeDestruction(Object bean, String beanName) {if (bean instanceof ApplicationListener) {try {ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster();multicaster.removeApplicationListener((ApplicationListener<?>) bean);multicaster.removeApplicationListenerBean(beanName);}catch (IllegalStateException ex) {// ApplicationEventMulticaster not initialized yet - no need to remove a listener}}}@Overridepublic boolean requiresDestruction(Object bean) {return (bean instanceof ApplicationListener);}@Overridepublic boolean equals(Object other) {return (this == other || (other instanceof ApplicationListenerDetector &&this.applicationContext == ((ApplicationListenerDetector) other).applicationContext));}@Overridepublic int hashCode() {return ObjectUtils.nullSafeHashCode(this.applicationContext);}}

在初始化所有的bean后,ApplicationListenerDetector的postProcessAfterInitialization(Object bean, String beanName)方法会被作用在每一个bean上,通过判断传入的bean是否实现了ApplicationListener接口,然后将找到的事件监听器bean注册到事件发布器中。

  • (三)、容器事件发布的流程

Spring发布事件是通过multicastEvent()方法,具体代码如下:

// org.springframework.context.event.ApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent)
void multicastEvent(ApplicationEvent event);// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent)
public void multicastEvent(ApplicationEvent event) {// 通过resolveDefaultEventType()方法获取事件类型multicastEvent(event, resolveDefaultEventType(event));
}// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
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);}}
}

可以看到,Spring事件监听器允许我们自己指定执行器Executor,以实现异步方式执行监听器逻辑。

getApplicationListeners(event, type)方法是根据事件类型找到匹配的所有事件监听器,具体的匹配逻辑是在AbstractApplicationEventMulticaster#supportsEvent()方法:

// 确定给定的监听器是否支持给定事件
protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));// 确定监听器是否支持给定的事件类型 && 确定监听器是否支持给定源类型return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

真正执行事件监听器是在SimpleApplicationEventMulticaster#invokeListener()方法,其实就是回调事件监听器的onApplicationEvent()方法。

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {// 回调事件监听器的onApplicationEvent()方法listener.onApplicationEvent(event);}catch (ClassCastException ex) {String msg = ex.getMessage();if (msg == null || matchesClassCastMessage(msg, event.getClass())) {// Possibly a lambda-defined listener which we could not resolve the generic event type for// -> let's suppress the exception and just log a debug message.Log logger = LogFactory.getLog(getClass());if (logger.isTraceEnabled()) {logger.trace("Non-matching event type for listener: " + listener, ex);}}else {throw ex;}}
}

六、总结

本篇文章主要是梳理了Spring的容器内事件监听机制并对其对应的源码做了简单的剖析,记住使用Spring事件监听,主要就是下面三个步骤:

  • 自定义事件,通过继承ApplicationEvent
  • 自定义应用监视器,通过实现ApplicationListener接口,并重写onApplicationEvent();
  • 调用ApplicationContext的multicastEvent()发布事件或者自定义事件发布器实现事件发布;

Spring事件监听机制相关推荐

  1. Spring5源码 - 13 Spring事件监听机制_@EventListener源码解析

    文章目录 Pre 概览 开天辟地的时候初始化的处理器 @EventListener EventListenerMethodProcessor afterSingletonsInstantiated 小 ...

  2. Spring5源码 - 12 Spring事件监听机制_异步事件监听应用及源码解析

    文章目录 Pre 实现原理 应用 配置类 Event事件 事件监听 EventListener 发布事件 publishEvent 源码解析 (反推) Spring默认的事件广播器 SimpleApp ...

  3. Spring5源码 - 11 Spring事件监听机制_源码篇

    文章目录 pre 事件监听机制的实现原理[观察者模式] 事件 ApplicationEvent 事件监听者 ApplicationEvent 事件发布者 ApplicationEventMultica ...

  4. Spring5源码 - 10 Spring事件监听机制_应用篇

    文章目录 Spring事件概览 事件 自定义事件 事件监听器 基于接口 基于注解 事件广播器 Spring事件概览 Spring事件体系包括三个组件:事件,事件监听器,事件广播器 事件 Spring的 ...

  5. spring 扫描所有_自定义Spring事件监听机制

    开头提醒一下大家: 尽管我简化了Spring源码搞了个精简版的Spring事件机制,但是没接触过Spring源码的朋友阅读起来还是有很大难度,请复制代码到本地,边Debug边看 既然要简化代码,所以不 ...

  6. Springboot事件监听机制:工作原理

    目录 前言 1.观察者模式 1.1观察者模式的核心元素 1.2观察者模式的工作流程 2.springboot事件监听机制的基本工作原理 2.1事件发布器是什么时候在哪里产生的呢? 2.2事件监听器是什 ...

  7. spring中的事件监听机制

    Spring event listener 介绍 example 简单原理解释 自定义事件.监听和发布 事件 监听器 发布者 测试 更加一般的事件 @EventListener原理 介绍 exampl ...

  8. Spring容器的事件监听机制(简单明了的介绍)

    文章目录 前言 事件 1. 定义事件 2. 定义监听器 3. 定义发布器 Spring容器的事件监听机制 1.事件的继承类图 监听器的继承类图 总结 前言 上一篇我们介绍了SpringFactorie ...

  9. 事件监听机制(一)Java事件监听

    事件监听机制(一)Java事件监听 事件监听实现流程 事件对象: 继承自java.util.EventObject对象,由开发者自行定义实现. 事件源: 就是触发事件的源头,不同的事件源会触发不同的事 ...

  10. springBoot启动事件监听机制

    springBoot启动之事件监听机制源码解析 1. Java的事件监听机制 在进行正式的分析之前,先介绍一下Java的事件监听机制.参考05–SpringBoot启动之事件监听机制 Java事件监听 ...

最新文章

  1. Select、Poll、Epoll IO复用技术
  2. 三十五、深入Java中的泛型(下篇)
  3. Rotate String
  4. IntelliJ IDEA开发环境应用
  5. 用计算机进行服装设计,电脑服装设计(10制版1班)
  6. Java ClassLoader findClass()方法与示例
  7. TCP/IP,HTTP,Socket的区别与联系
  8. react打包服务器文件,react项目搭建及打包发布
  9. 赛锐信息:基于SAP ERP系统的企业内部审计介绍
  10. 机器学习算法总结之支持向量机(四)
  11. 《SQL注入攻击与防御》读书笔记
  12. java进度条_Java实现进度条开发过程
  13. 微信抽奖小程序怎么做怎么弄?微信抽奖小程序制作方法详细介绍
  14. 干货 | 携程平台化常态化数据治理之路
  15. 股票预测pythonlstm_LSTM预测股票涨跌--结合技术分析视角(一)
  16. bzoj 2827 千山鸟飞绝(treap)
  17. 推荐系统:CTR模型学习总结--LR、FM、FFM、Wide and Deep、DeepFM
  18. CC BY-SA 4.0知识共享许可协议
  19. matlab图像进行变换
  20. 读过的laravel文章

热门文章

  1. kotlin和python哪个好_对比 Go 语言,Kotlin 有什么优势和劣势?
  2. 算法:874. 模拟行走机器人
  3. android公交车代码,android实现查询公交车还有几站的功能
  4. 多元线性回归实现代码
  5. python字典用法总结
  6. 求一个容器的最值的索引_初中几何最值——瓜豆原理模型分析
  7. Python入门:Dataframe的索引模式
  8. 读论文 + 总结 + 笔记
  9. Boost Thread 编程指南、Boost线程入门教程
  10. 【SPOJ - DQUERY】D-query【主席树 —— 区间中不同数的个数】