前言

Spring 从 3.x 开始支持事件机制。在 Spring 的事件机制中,我们可以令一个事件类继承 ApplicationEvent 类,然后将实现了 ApplicationListenerBean 注册到 spring 容器,最后向 ApplicationEventPublisher 推送事件对象即可令所有订阅者收到事件。在 4.2 以后,甚至不需要实现 ApplicationListener 接口,仅需在 Bean 中方法标记 @EventListener 注解即可。

笔者将基于 Spring 源码的 5.2.x 分支,分析该功能是如何实现的。

本文是其中的第一篇文章,将分析广播器与监听的是如何被初始化,并完成注解流程的。

在开始前,推荐先阅读前文了解 Spring 容器的初始化过程与 BeanFactoryBean 的创建,如果可能,还可以了解一点 Spring 的注解机制,这将更有利于流程与一些代码的理解。

相关文章:

一、广播器的创建

在前文,我们知道容器的初始化是通过 AbstractApplicationContext.refresh() 方法完成的,事件机制的相关组件同样也离不开容器,因此事件系统的初始化也通过该方法完成。

AbstractApplicationContext.initApplicationEventMulticaster() 是第一步,它的作用很简单:

如果当前 BeanFactory 有名为 “applicationEventMulticaster”ApplicationEventMulticaster,就把它设置为当前上下文的事件广播器,否则就创建并在 BeanFactory 中注册一个SimpleApplicationEventMulticaster 实例作为当前上下文的事件广播器。

protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 是否存在“applicationEventMulticaster”这个Bean
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
// 如果存在就把它设置为当前上下文的事件广播器
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
// 没有就创建一个SimpleApplicationEventMulticaster作为当前上下文的事件广播器
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() + "]");
}
}
}

二、编程式监听器的注册

4.2 及以前版本,监听器需要显式的实现 ApplicationListener 接口,我们管这种监听器叫做编程式监听器。

编程式监听器在 AbstractApplicationContext.registerListeners() 这个方法的调用过程中被注册到注册广播器中,这一块代码逻辑也很简单:

  • 向事件广播器注册已经被注册的 BeanFactroy 中,且实现了 ApplicationListener 接口的监听器;
  • 向事件广播器注册还没有被实例化的监听器的 BeanName
  • 发布一些早期事件;
protected void registerListeners() {
// 向事件广播器注册已经被注册的上下文中的监听器
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
} // 向事件广播器注册指定的监听器,不过这里只注册BeanName,
// 因为有些监听器Bean是由FactoryBean生产的,而在这里FactoryBean实际上还没被生成出来
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
} // 发布一些早期事件
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}

我们需要注意的是,在这一步,虽然向广播器注册了监听器,但是实际上这只是一种关系,真正的监听器实例不一定有被创建出来

不过在如果上下文中存在“早期事件”,则会触发广播,此时调用 ApplicationEventMulticaster.multicastEvent() 将会提前触发广播器中那些监听器的初始化,否则按正常情况这些将等到上下文主动初始化 BeanFactory 中全部非懒加载 Bean 的时候才会一并初始化。

三、注解式监听器的注册

4.2 版本以后,我们可以通过在成员方法上添加 @EventListener 或者 @TransactionalEventListener 注解的方法声明一个监听器,我们管这种监听器叫做注解式监听器。

实际上,由于注解式监听器的类上没有注解或接口作为标识,因此无法直接从 BeanFactory 中查找,所以它的注册显然不能与编程式监听器一样,在 AbstractApplicationContext.registerListeners() 通过从 BeanFactory 中直接找到然后注册。

3.0 以后支持的一些注解式配置的原理一样,@EventListener 是通过 EventListenerMethodProcessor 这个特殊的后置处理器完成注册的。

EventListenerMethodProcessor 实现的接口如下:

public class EventListenerMethodProcessor
implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {
}

其中, SmartInitializingSingletonBeanFactoryPostProcessor 接口非常直观的告诉了我们它被调用的时机:

  • BeanFactoryPostProcessor:在上下文初始化的时候,通过 AbstractApplicationContext.invokeBeanFactoryPostProcessors 这个方法跟其他 BeanFactory 的后置处理器被一起集中调用;
  • SmartInitializingSingleton:在这个 Bean 完成初始化的时候;

接下来我们通过处理器分析注解式监听器的注册流程。

1、监听器方法处理器的注册

根据前文,我们知道容器在初始化过程中,通过 AbstarctApplicationContext.obtainFreshBeanFactory 创建新 BeanFactory 的时候,最终会一路绕到 AbstractRefreshableApplicationContext.loadBeanDefinitions 这个方法上,通过这个方法上下文会为自己的 BeanFactory 提前加载好 BeanDefinition

而这个抽象方法在不同的上下文会有不同的实现,但是基本都要通过不同的 BeanDefinitionReader 去完成这个过程。

支持注解式配置的上下文会用 AnnotatedBeanDefinitionReader 去读取配置的时候,会通过 AnnotationConfigBeanDefinitionParser 将配置信息解析为具体的 BeanDefinition 。而 Spring 就在这一步将默认配置的一些 BeanBeanDefinition 给加上了。

实际上,不止 EventListenerMethodProcessor ,几乎所有针对 Spring 注解的后置处理器都是通过这种方式注册到 BeanFactory 的。

具体代码参见 AnnotationConfigUtils.registerAnnotationConfigProcessors 方法:

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 其他的一些注解处理器,比如 @Configuration,@Autowrite 之类的注解处理器... ... // 注册 EventListenerMethodProcessor
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));
} // 注册名为“org.springframework.context.event.internalEventListenerFactory” EventListenerFactory
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;
}

2、获取监听器工厂

EventListenerMethodProcessor 被作为一个 BeanFactoryPostProcessor 被调用时,它会从 BeanFactory 中收集所有实现了 EventListenerFactory 接口的 Bean,然后记录在成员变量 eventListenerFactories 中:

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;
}

而监听器工厂这个类作用也显而易见,他用于把被注解的方法适配为监听器对象:

public interface EventListenerFactory {
// 是否支持处理该方法
boolean supportsMethod(Method method);
// 将bean中带有@EventListener注解的方法转为ApplicationListener
ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method);
}

值得一提的是,由于注册 EventListenerMethodProcessor 的时候也会默认支持一个名为 :"org.springframework.context.event.internalEventListenerFactory"DefaultEventListenerFactory,这保证至少有一个保底的监听器工厂。

EventListenerFactory 提供两个默认的实现:

  • DefaultEventListenerFactory:默认的实现,支持处理所有被 @EventListener 注解的方法,

    会将方法适配成类型为 ApplicationListenerMethodAdapter 的监听器;

  • TransactionalEventListenerFactory:支持 Spring 事务机制的监听器的工厂, 用于处理被 @TransactionalEventListener 注解的方法,

    会将方法适配成类型为 ApplicationListenerMethodTransactionalAdapter 的监听器;

3、将方法适配为监听器

EventListenerMethodProcessor 作为一个 SmartInitializingSingleton 被调用的时候:

public void afterSingletonsInstantiated() {
ConfigurableListableBeanFactory beanFactory = this.beanFactory;
Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
// 获取工厂中的全部 Bean
String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
if (!ScopedProxyUtils.isScopedTarget(beanName)) {
Class<?> type = null;
try {
// 如果是代理对象,则获取原始对象的类型
type = AutoProxyUtils.determineTargetClass(beanFactory, beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
if (type != null) {
// 实现了ScopedObject接口
if (ScopedObject.class.isAssignableFrom(type)) {
try {
// 获取原始的Bean对象
Class<?> targetClass = AutoProxyUtils.determineTargetClass(
beanFactory, ScopedProxyUtils.getTargetBeanName(beanName));
if (targetClass != null) {
type = targetClass;
}
}
catch (Throwable ex) {
// An invalid scoped proxy arrangement - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex);
}
}
}
try {
// 处理 Bean
processBean(beanName, type);
}
catch (Throwable ex) {
throw new BeanInitializationException("Failed to process @EventListener " +
"annotation on bean with name '" + beanName + "'", ex);
}
}
}
}
}

抛开对代理对象的一些检验和处理,我们直接看看 processBean 方法:

private void processBean(final String beanName, final Class<?> targetType) {
if (!this.nonAnnotatedClasses.contains(targetType) &&
// targetType类名不以“java.”开头,且不为Ordered接口
AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
// 是未被@Component注解的Spring内部类
!isSpringContainerClass(targetType)) { Map<Method, EventListener> annotatedMethods = null;
try {
// 查找直接或间接带有@EventListener注解的方法
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
}
catch (Throwable ex) {
// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
}
} // 如果该类没有直接或间接带有@EventListener注解的方法,则记录并在下次查询时跳过
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)) {
// 将方法包装为ApplicationListener
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
// 如果监听器类型为ApplicationListenerMethodAdapter,则需要传入专门的SpEL表达式解析器EventExpressionEvaluator用于支持@EventListener.condition属性
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
// 将监听器加入中的
context.addApplicationListener(applicationListener);
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
beanName + "': " + annotatedMethods);
}
}
}
} @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);
}

Spring 在这一步主要趁着 EventListenerMethodProcessorBeanFactory 中初始化的时候干了两件事:

  • 检查 BeanFactory 中的所有的 Bean,筛选出其中有成员方法直接或间接带有 @EventListener 注解的 Bean
  • 将此类 Bean 的方法通过 EventListenerFactory 封装为 ApplicationListener 对象;
  • 然后将这些转换后得到的 ApplicationListener 注册到上下文中的广播器中;

此外,这里有一个比较有意思的细节,就是由于 @EventListener 注解是支持在 condition 中通过 SpEL 表达式进行一些判断的,因此在这一步,针对默认的监听适配器实现 ApplicationListenerMethodAdapter ,提供了一个 init 方法用于把 SpEL 表达式解析器塞进去:

if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}

换而言之,如果我们希望让 @EventListener.condition 支持更多功能,就可以在这个地方动点手脚,比如向 SpEL 表达式上下文注册更多变量。

4、监听器的注册

上一节中提到,在 EventListenerMethodProcessor.processBean 将方法转换为 ApplicationListener 后会将其注入广播器:

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);
}

AbstractApplicationContext 会将该方法代理到内部持有的广播器实例的 ApplicationEventMulticaster.addApplicationListener 方法:

public void addApplicationListener(ApplicationListener<?> listener) {
synchronized (this.defaultRetriever) {
// 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();
}
}

该方法最终将监听器添加到广播器持有的 DefaultListenerRetriever 对象实例中,跟已经注册到其中的编程式监听器一起,以待后续使用。

四、监听器工厂

通过上文,我们知道注解式监听器依赖监听器工厂 EventListenerFactoryBean 中的注解方法转为 ApplicationListener 实例。

实际上,我们知道 spring 除了支持 @EventListener 注解外,还提供了 @TransactionalEventListener 注解,用于注册支持事务的注解式监听器,因此 EventListenerFactory 实际上也提供了两类工厂分别用于支持这两种实现:

  • DefaultEventListenerFactory:默认的实现,支持处理所有被 @EventListener 注解的方法,

    会将方法适配成类型为 ApplicationListenerMethodAdapter 的监听器;

  • TransactionalEventListenerFactory:支持 Spring 事务机制的监听器的工厂, 用于处理被 @TransactionalEventListener 注解的方法,

    会将方法适配成类型为 ApplicationListenerMethodTransactionalAdapter 的监听器;

1、通用监听器工厂

通用监听器工厂的代码及其简单,它的特点如下:

  • 支持处理任何方法:supportsMethod 方法固定返回 true
  • 总是最晚被执行:getOrder 默认返回 Ordered.LOWEST_PRECEDENCE
  • 总是将注解方法适配为 ApplicationListenerMethodAdapter 类型的监听器;
public class DefaultEventListenerFactory implements EventListenerFactory, Ordered {

    private int order = LOWEST_PRECEDENCE;

    public void setOrder(int order) {
this.order = order;
} @Override
public int getOrder() {
return this.order;
} @Override
public boolean supportsMethod(Method method) {
return true;
} @Override
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
return new ApplicationListenerMethodAdapter(beanName, type, method);
} }

2、事务监听器工厂

事件监听器工厂代码也并不复杂,相比 DefaultEventListenerFactory,它的特点如下:

  • 默认比 DefaultEventListenerFactory 更先被调用:getOrder 默认返回 50,比 DefaultEventListenerFactory 返回的 Ordered.LOWEST_PRECEDENCE 值更小;
  • 仅支持处理直接或间接被 @TransactionalEventListener 注解的方法;
  • 总是将注解方法适配为 ApplicationListenerMethodTransactionalAdapter 类型的监听器;
public class TransactionalEventListenerFactory implements EventListenerFactory, Ordered {

    private int order = 50;

    public void setOrder(int order) {
this.order = order;
} @Override
public int getOrder() {
return this.order;
} @Override
public boolean supportsMethod(Method method) {
return AnnotatedElementUtils.hasAnnotation(method, TransactionalEventListener.class);
} @Override
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method);
} }

总结

当 Spring 容器启动,上下文调用 AbstractApplicationContext.refresh 方法对其进行初始化时,Spring 事件机制的两个核心组件:广播器、监听器也在该过程完成初始化。

  • AbstractApplicationContext.initApplicationEventMulticaster 这一步,Spring 为上下文创建并挂载了广播器 ApplicationEventMulticaster 的实例;

  • AbstractApplicationContext.registerListeners 这一步,Spring 将容器中所有实现了 ApplicationListener 接口的 Bean 注册到广播器中;

  • 而在 AbstractApplicationContext.finishBeanFactoryInitialization 这一步,Spring 会初始化容器中所有非懒加载的 Bean,此时会一并实例化 EventListenerMethodProcessor 这个 Bean 后置处理器:

    1. 这个 Bean 由上下文在准备 BeanFactory 时,调用 loadBeanDefinitions 方法时注册到容器;

    2. 由于 EventListenerMethodProcessor 本身实现了 SmartInitializingSingleton 接口,因此实例化后会触发其回调函数,此时 EventListenerMethodProcessor 会把容器中所有的 Bean 都拿出来解析;

    3. Bean 中存在直接或间接被 @EventListener 注解的方法,则会将其取出,并通过 EventListenerFactory 将其适配为对应的 ApplicationListener 实例,然后再将适配完的监听器注册到容器中;

    4. 由于 Spring 除了 @EventListener 注解还提供了支持事务的@TransactionalEventListener 注解,因此提供了两类监听器工厂:

      DefaultEventListenerFactory:默认的实现,支持处理所有被 @EventListener 注解的方法;

      TransactionalEventListenerFactory:支持 Spring 事务机制的监听器的工厂, 用于处理被 @TransactionalEventListener 注解的方法;

至此,当 AbstractApplicationContext.refresh 执行完毕,即上下文初始化完成后,广播器与所有编程式或注解式监听器皆初始化完毕,并且完成了注册。

深入理解Spring事件机制(一):广播器与监听器的初始化的更多相关文章

  1. Spring事件机制详解

    一.前言 说来惭愧,对应Spring事件机制之前只知道实现 ApplicationListener 接口,就可以基于Spring自带的事件做一些事情(如ContextRefreshedEvent),但 ...

  2. 搞清楚Spring事件机制后:Spring的源码看起来简单多了

    本文主讲Spring的事件机制,意图说清楚: 什么是观察者模式? 自己实现事件驱动编程,对标Spring的事件机制 彻底搞懂Spring中的事件机制,从而让大家 本文内容较长,代码干货较多,建议收藏后 ...

  3. 深入理解Spring注解机制(一):注解的搜索与处理机制

    前言 众所周知,spring 从 2.5 版本以后开始支持使用注解代替繁琐的 xml 配置,到了 springboot 更是全面拥抱了注解式配置.平时在使用的时候,点开一些常见的等注解,会发现往往在一 ...

  4. spring事件机制

    前置知识补充: 程序里面所谓的“上下文”就是程序的执行环境,打个比方:你就相当于web程序,你的房子就相当于web程序的上下文,你可以在家里放东西,也可以取东西,你的衣食住行都依赖这个房子,这个房子就 ...

  5. 观察者模式之spring事件机制

    ddsspring中的事件机制使用到设计模式中的观察者模式 ,观察者模式有两个概念,1.观察者.被观察者.2.被观察者做出相应得动作,观察者能接收到.不分析设计模式,学习下spring中的事件机制实际 ...

  6. 深入理解DOM事件机制系列第四篇——事件模拟

    × 目录 [1]引入 [2]模拟机制 [3]自定义事件 前面的话 事件是网页中某个特别的瞬间,经常由用户操作或通过其他浏览器功能来触发.但实际上,也可以使用javascript在任意时刻来触发特定的事 ...

  7. Spring 事件机制

    通过模拟邮件的发送,说明Spring的事件监听机制 事件类 package org.zln.module_chapter2.event; import org.springframework.cont ...

  8. 深入理解DOM事件机制系列第三篇——事件对象

    × 目录 [1]获取 [2]事件类型 [3]事件目标[4]事件代理[5]事件冒泡[6]事件流[7]默认行为 前面的话 在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事 ...

  9. 深入理解DOM事件机制系列第二篇——事件处理程序

    × 目录 [1]HTML [2]DOM0级 [3]DOM2级[4]IE[5]总结 前面的话 事件处理程序又叫事件侦听器,实际上就是事件的绑定函数.事件发生时会执行函数中相应代码.事件处理程序有HTML ...

随机推荐

  1. 测试open

    // 此处,返回的 undefined 是 JS 中的一个值 return undefined } // 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同 const add ...

  2. static关键字——JavaSE基础

    static关键字 由于static跟随类被加载,因此静态代码块.构造方法.匿名代码块的执行顺序为静态代码块→匿名代码块→构造方法 public class Demo01 { public stati ...

  3. 2021.02.27【NOIP提高B组】模拟 总结

    T1 欧拉筛质数时若 \(i\) 是质数且没有被用过就顺便用于计算结果,复杂度 \(O(n)\) #include<bits/stdc++.h> using namespace std; ...

  4. 聊聊 C++ 和 C# 中的 lambda 玩法

    这几天在看 C++ 的 lambda 表达式,挺有意思,这个标准是在 C11标准 加进去的,也就是 2011 年,相比 C# 2007 还晚了个 4 年, Lambda 这东西非常好用,会上瘾,今天我 ...

  5. node.js 创建 wss服务

    var https=require('https'); var ws=require('ws'); var fs=require('fs'); var keypath=process.cwd()+'/ ...

  6. TypeScript(4)接口

    介绍 TypeScript 的核心原则之一是对值所具有的结构进行类型检查.我们使用接口(Interfaces)来定义对象的类型.接口是对象的状态(属性)和行为(方法)的抽象(描述) 接口初探 声明接口 ...

  7. Windows 2008R2 IIS环境配置(靶机)

    一.Windows 2008 R2系统安装 VMware Workstation 15安装包 链接:https://pan.baidu.com/s/11sYcZTYPqIV-pyvzo7pWLQ 提取 ...

  8. Sentiment analysis in nlp

    Sentiment analysis in nlp The goal of the program is to analysis the article title is Sarcasm or not ...

  9. 使用.NetCore自带的后台作业,出入队简单模拟生产者消费者处理请求响应的数据

    环境:Core:3.1的项目 说明:由于该方案为个人测试项目,重启时队列中的部分数据很可能会丢失, 对数据有要求的该方案不适用,不能照搬需要持久化处理, 另外发布到Linux Docker中通常不会自 ...

  10. this关键字、static关键字、方法的调用

    1.带有static关键字的方法,不可使用this关键字.因为其调用方法为类名.方法名(建议这种方式,调用不需要对象的参与),不存在对象. 2.实例方法调用必须有对象的存在,先创建对象,通过引用.的方 ...