大家好,我是三友~~

今天来扒一扒Spring在启动过程中核心的12个步骤

之所以来写这篇文章,主要是来填坑的

之前在三万字盘点Spring 9大核心基础功能这篇文章的末尾中给自己挖了一个坑,提了一嘴有机会要写这么一篇文章

但是由于Spring启动过程并不复杂,所以后面就没写了

不过,好巧不巧,刚刚好有兄弟来催更了,那么此时这个机会就来了,这篇文章也就有了

公众号:三友的java日记

前言

Spring启动时候整个入口是这么一个方法

AbstractApplicationContext#refresh

总共有12个方法,也就是启动时的核心步骤

AbstractApplicationContext有众多实现,这里我选择SpringBoot Web应用默认的实现来讲

AnnotationConfigServletWebServerApplicationContext


AnnotationConfigServletWebServerApplicationContext类图

对应的SpringBoot版本为 2.2.5.RELEASE

高版本refresh方法会多一些日志相关的代码,这里为了方便讲解,就使用这个版本

所以后面本文提到的所有子类的方法实现、重写都是指AnnotationConfigServletWebServerApplicationContext及其父类

本文主要是讲一些启动的步骤,具体的很多技术实现细节、技术点这里就不过多赘述

如果有疑问的,可以查看三万字盘点Spring 9大核心基础功能这篇文章或者公众号菜单栏中关于Spring的文章,基本上都能找到答案

prepareRefresh

prepareRefresh整个刷新的一个步骤,这个步骤是做启动的一个准备操作

ApplicationContext刚创建出来,什么也没有,所以需要做一些准备

首先是做一些状态位的变更,表明开始启动了或者刷新了

后面一行

initPropertySources

initPropertySources是一个模板方法,本身是一个空实现,是给子类用的

我们的这个子类就重写了initPropertySources方法

会将Servlet相关的配置加入到Environment中,这样我们就能从Environment中获取到Servlet相关的配置了

再后面一行

getEnvironment().validateRequiredProperties()

这行代码就是校验一些必要的配置属性,我们可以通过ConfigurableEnvironment来设置哪些属性是必要的,默认是没有必要的

所以prepareRefresh就是做了一些前置操作,准备好一些属性配置相关的东西,后面的其它环节,比如说生成Bean时可能需要用到这些配置

obtainFreshBeanFactory

这一步骤是刷新BeanFactory并且获取BeanFactory

refreshBeanFactory() 和 getBeanFactory() 都是抽象方法,由子类来实现的

而子类的实现其实很简单,就是给beanFactory设置一个id和返回beanFactory

beanFactory就是下面这个玩意

并且创建对象的ApplicationContext对象的时候就创建了,类型为

DefaultListableBeanFactory

所以从这就可以看出来,虽然说BeanFactory是一个接口,有非常多的实现

但是实际情况下,真正使用的就是DefaultListableBeanFactory

并且DefaultListableBeanFactory其实算是BeanFactory唯一真正的实现

除此之外,还可以得出一个结论,ApplicationContext中有一个BeanFactory(DefaultListableBeanFactory)

prepareBeanFactory

上一步骤获取到了BeanFactory,但是这个BeanFactory仅仅就是刚刚new出来的,什么也没有

所以当前步骤就是对BeanFactory做一些配置工作

前三行代码

先给BeanFactory设置了一个ClassLoader,因为BeanFactory是用来创建Bean,需要加载Bean class对象

然后设置了一个BeanExpressionResolver,这个是用来解析SpEL表达式的

然后添加了一个PropertyEditorRegistrar,也就是

ResourceEditorRegistrar

这个的作用就是为BeanFactory添加一堆跟资源相关的PropertyEditor


ResourceEditorRegistrar核心实现方法

PropertyEditor之前说过,就是进行类型转换的,将一个字符串转成对应的类型

接下来这几行

这里主要是添加了一个BeanPostProcessor,也就是

ApplicationContextAwareProcessor

BeanPostProcessor我们都知道会在Bean的生命周期阶段进行回调,是Bean的生命周期一个核心的环节

ApplicationContextAwareProcessor这个是用来处理Bean生命周期中的Aware回调有关

当你的Bean实现这些接口的时候,在创建的时候Spring会回调这些接口,传入对应的对象

而后面的这行代码

beanFactory.ignoreDependencyInterface(EnvironmentAware.class);

意思是说,如果你的Bean想注入一个EnvironmentAware对象

@Resource
private EnvironmentAware environmentAware;

这是不允许的

因为很简单,注入一个EnvironmentAware对象,没有实际的意义

后面的其它几行代码也都是这个意思

再接下来这几行

这跟上面的ignoreDependencyInterface作用相反

他是来设置依赖注入时Bean的类型所对应的对象

比如说这行代码

beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);

这行代码的意思就是当你需要注入一个Bean类型为BeanFactory.class类型的时候

@Resource
private BeanFactory beanFactory;

那么实际注入的就是方法第二个参数beanFactory,也就是上面获取的DefaultListableBeanFactory对象

同理,注入ResourceLoader、ApplicationEventPublisher、ApplicationContext时,其实注入的对象都是this,也就是当前的ApplicationContext对象

再再接下来这几行

最开始又添加了一个BeanPostProcessor

ApplicationListenerDetector

这个BeanPostProcessor是跟ApplicationListener有关

他是将单例的ApplicationListener给添加到ApplicationContext中

再后面就是往BeanFactory里面添加一些跟配置属性相关的单例对象,如果有哪里用到,就可以从BeanFactory中获取到了

prepareBeanFactory就完了

正如方法名字的含义一样,就是对BeanFactory做一些配置相关的东西

比如添加一些BeanPostProcessor,注册一些PropertyEditor

为Bean的生成做准备操作

最后画张图来总结一下这个方法的作用

此时BeanFactory状态就是这样的

postProcessBeanFactory

这个方法是一个模板方法

本身是空实现,是交给子类来扩展,子类可以根据不同的特性再对BeanFactory进行一些准备工作

比如我们用的这个Web实现就重写了这个方法,对BeanFactory设置一些Web相关的配置


AnnotationConfigServletWebServerApplicationContext#postProcessBeanFactory

首先调用父类ServletWebServerApplicationContext的postProcessBeanFactory


ServletWebServerApplicationContext#postProcessBeanFactory

前两行代码跟之前说的一样,也是添加Aware接口的回调对应的BeanPostProcessor,只不过这个Aware是跟Servlet相关的东西

接下来调用registerWebApplicationScopes方法,最终会调到下面这个方法

WebApplicationContextUtils#registerWebApplicationScopes

这个方法干了两件事

第一件事就是注册一下Bean在Web环境下的作用域request、session,八股文中的东西

第二个就是注册一些依赖注入时Bean类型和对应的对象,这在日常开发中还是有用的

比如可以直接注入一个ServletRequest

@Resource
private ServletRequest servletRequest;

所以,父类的实现主要还是对BeanFactory进行一些配置,只不过配置的主要是跟Web环境相关的东西

现在来看看AnnotationConfigServletWebServerApplicationContext自身的实现

核心代码就是这两行

this.scanner.scan(this.basePackages);

this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));

scanner和reader就是下面这两个玩意

也就是说,如果这些配置都不是空的话,那么此时就会扫描对应的包的下Bean,生成对应的BeanDenifition,再注册到DefaultListableBeanFactory

至于为什么会存到DefaultListableBeanFactory中,可以看看之前的文章

此时BeanFactory大概是这么一个状态

除此之外,还有一个贼重要的事

AnnotationConfigServletWebServerApplicationContext这个ApplicationContext创建时会去创建AnnotatedBeanDefinitionReader

而AnnotatedBeanDefinitionReader的构造方法最终会调用这么一行代码

AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry registry)

这个方法非常重要,他会去注册一些BeanDefinition到BeanFactory中,这里我称为Spring内部的Bean

这里我说几个常见和重要的

  • ConfigurationClassPostProcessor:这个是用来处理配置类的,非常重要,记住这个类,后面有大用
  • AutowiredAnnotationBeanPostProcessor:处理@Autowired、@Value注解
  • CommonAnnotationBeanPostProcessor:处理@Resource、@PostConstruct等注解

所以除了扫描出来的一些Bean对应的BeanDefinition,还有一些Spring内部的Bean会注册到BeanFactory中

此时BeanFactory的状态就如下图所示

不过,在SpringBoot默认情况下,不会指定包和配置类,也就不会扫描文件,生成BeanDefinition

但是内部创建的BeanDefinition依然存在,并且在ApplicationContext创建的时候就注册到BeanFactory中了

所以总结来说,postProcessBeanFactory这个方法是交给子类对BeanFactory做一些准备操作,并且可能会扫描Bean

invokeBeanFactoryPostProcessors

从这个方法的名字可以看出,是调用BeanFactoryPostProcessor,这个步骤非常重要,而且过程有点绕

前置知识:BeanFactoryPostProcessor及其子接口

BeanFactoryPostProcessor是一个接口,有一个方法,方法参数就是BeanFactory

通过这个方法就可以拿到BeanFactory,然后对BeanFactory做一些自己的调整

比如说,你想关闭循环依赖,你就可以实现这个接口,然后进行调整

他还有一个子接口BeanDefinitionRegistryPostProcessor

这个接口是对BeanDefinitionRegistry进行调整,BeanDefinitionRegistry就是存BeanDefinition的地方,真实的实现就是DefaultListableBeanFactory

所以BeanDefinitionRegistryPostProcessor的作用就是往BeanDefinitionRegistry(DefaultListableBeanFactory)中添加BeanDefinition的

再看invokeBeanFactoryPostProcessors

有了这两个前置知识之后,我们来看看invokeBeanFactoryPostProcessors方法的实现

这个方法最终会调用下面方法来真正的处理

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors;

这个方法比较长,大致分为两件事

  • 调用所有的BeanDefinitionRegistryPostProcessor,解析配置类,注册BeanDefinition到DefaultListableBeanFactory中
  • 从BeanFactory中获取所有的BeanFactoryPostProcessor进行调用,完成对BeanFactory一些其它的扩展
调用BeanDefinitionRegistryPostProcessor

首先第一步,先从BeanFactory中获取到所有的BeanDefinitionRegistryPostProcessor对象,调用它的postProcessBeanDefinitionRegistry方法

还记得上一节在说注册Spring内部的Bean时特地强调的一个类ConfigurationClassPostProcessor不?

他就实现了BeanDefinitionRegistryPostProcessor接口

所以此时获取到的就是ConfigurationClassPostProcessor

获取ConfigurationClassPostProcessor的时候会走Bean的生命周期,也就是会回调前面添加的BeansPostProcessor,但是也没几个

之后会调用他的postProcessBeanDefinitionRegistry方法,来处理此时BeanFactory中的配置类

配置类从哪来,前面一直没提到过

但是看一下ApplicationContext是如何使用的就知道了

比如说,下面这个demo

在创建一个ApplicationContext之后,在注册一个Bean之后再refresh

此时这个注册的Bean就是配置类。

如果你不注册,那是真没有配置类,此时也就没什么意义了。

所以,ApplicationContext一定会有一个配置类,不然没有意义。

在SpringBoot条件下,SpringBoot在启动时就会将启动引导类当做配置类给扔到BeanFactory中。

所以ConfigurationClassPostProcessor最开始处理的时候,就是处理启动引导类

我们可以在ConfigurationClassPostProcessor方法实现上打个断点验证一下

在处理之前可以看见,除了几个spring内部的BeanDefinition之外,还有一个myApplication,就是我的启动引导类

处理的时候它会解析启动引导类的注解,进行自动装配,扫描你写的代码的操作,之后生成BeanDefinition

当处理完成之后我们再看看,DefaultListableBeanFactory有了非常多的BeanDefinition了

所以到第一步就完成了,此时BeanFactory就加载了很多Bean

接下来,由于又新注册了很多BeanDefinition,而这些里面就有可能有BeanDefinitionRegistryPostProcessor接口的实现

所以之后会重复从BeanFactory中获取BeanDefinitionRegistryPostProcessor,调用postProcessBeanDefinitionRegistry

一直会循环下去,直到所有的BeanDefinitionRegistryPostProcessor都被调用为止

由于BeanDefinitionRegistryPostProcessor继承BeanFactoryPostProcessor

所以之后也会调用BeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法

调用BeanFactoryPostProcessor

当调完所有的BeanDefinitionRegistryPostProcessor实现方法

之后就会从BeanFactory获取所有的BeanFactoryPostProcessor(除了BeanDefinitionRegistryPostProcessor实现之外),调用postProcessBeanFactory方法

此时就可以通过BeanFactoryPostProcessor再次对BeanFactory进制扩展

总的来说,这一步骤的核心作用就是完成对BeanFactory自定义扩展,但是由于BeanFactoryPostProcessor都是Bean,所以要第一步先加载Bean,之后才能通过BeanFactoryPostProcessor来扩展

一张图来总结上面主要干的事

这里简化了一些前面提到东西

registerBeanPostProcessors

上面一个步骤已经完成了Bean的扫描和对BeanFactory的扩展

这一节通过方法名就可以看出,是跟BeanPostProcessor相关

不过在这个方法执行之前,我们先来看看此时BeanFactory中已经有了哪些BeanPostProcessor

此时只有4个,前3个前面都提到过,但是像我们熟知的处理@Autowired、@Resource注解的BeanPostProcessor都不在里面

所以这里就有一个非常重要的小细节

在当前这个步骤执行之前如果从BeanFactory中获取Bean的话,虽然会走Bean生命周期的整个过程,但是@Autowired、@Resource注解都不会生效,因为此时BeanFactory中还没有处理这些注解的BeanPostProcessor(CommonAnnotationBeanPostProcessor等)

什么意思呢,举个例子

比如上面一节,在当前步骤执行之前会从BeanFactory中获取BeanFactoryPostProcessor

假设现在你实现了BeanFactoryPostProcessor,想注入一个ApplicationContext对象

此时是注入不成功的,@Resource注解不会生效,就是这个意思。

这时只能通过ApplicationContextAware方式获取,因为有对应的BeanPostProcessor(ApplicationContextAwareProcessor)

接下来我们再来看看registerBeanPostProcessors实现

最终也是调用下面的方法

PostProcessorRegistrationDelegate#registerBeanPostProcessors

这个过程就没上面那个步骤复杂了

其实就是从BeanFactory中获取到所有的BeanPostProcessor,然后添加到BeanFactory中

不过值得注意的是,BeanPostProcessor创建会有优先级,优先级高的会先被创建和添加到BeanFactory中

到这一步其实BeanFactory就算是准备完成了,基本上跟创建Bean相关的前置操作几乎都完成了

最后再来张图总结一下这个方法干的事

initMessageSource

这个方法是处理国际化相关的操作

这个操作比较简单,就是从BeanFactory中看看有没有Bean名称为messageSource的Bean

有的话就使用这个MessageSource,没有的话就用默认的

不过SpringBoot项目下会自动装配一个MessageSource,所以此时容器中是有的

initApplicationEventMulticaster

这个方法跟上面的差不多,也是从BeanFactory找有没有ApplicationEventMulticaster

有就用容器中的,没有就自己创建一个

ApplicationEventMulticaster是真正用来发布事件的,ApplicationEventPublisher最终也是调用他来发布事件

ApplicationEventMulticaster内部会缓存所有的监听器

当通过ApplicationEventMulticaster发布事件的时候,会去找到所有的监听器,然后调用

onRefresh

onRefresh也是一个模板方法,本身也是空实现

子类重写这个方法,会去创建一个Web服务器

registerListeners

这个方法其实也比较简单,就是将监听器给添加到ApplicationEventMulticaster中

finishBeanFactoryInitialization

这个方法首先又是老套路,就是判断容器中有没有ConversionService

ConversionService也是用来做类型转换的,跟前面提到的PropertyEditor作用差不多

如果有,就把ConversionService设置到BeanFactory中

到这一步,BeanFactory才算真的准备完成。。。

之后其实干的事就不太重要了

但是最后一行比较重要

beanFactory.preInstantiateSingletons();

从方法的命名就可以看出,实例化所有的单例对象

因为对于BeanFactory的一些配置在前面都完成了,所以这里就可以来实例化所有的单例对象了

这个方法会做两件事

第一件事就是实例化所有的非懒加载的单例Bean

实际上就是通过getBean方法来的,因为获取Bean,不存在的时候就会创,会走Bean的生命周期

第二件事就是一旦单例Bean实现了SmartInitializingSingleton接口,就会调用SmartInitializingSingleton的afterSingletonsInstantiated方法

这个其实也算是Bean生命周期的一部分。

finishRefresh

这个方法是整个Spring容器刷新的最后一个方法

这个方法就是收尾的操作

清理一下缓存操作

之后就是初始化LifecycleProcessor

都是一样的套路,优先用BeanFactory中的

后面就会调用LifecycleProcessor#onRefresh方法

这个方法的作用就是,如果你的Bean实现了SmartLifecycle的接口,会调start的方法

随后就发布一个ContextRefreshedEvent事件,表明容器已经刷新完成了

在Web环境底下,这个finishRefresh方法被重写了

主要是多干了一件事,那就是启动Web服务器

并且会发布了一个ServletWebServerInitializedEvent事件

这个事件在SpringBoot中用的不多

但是在SpringCloud中却非常重要

在SpringCloud环境底下会有一个类监听这个事件

一旦监听到这个事件,SpringCloud就会将当前的服务的信息自动注册到注册中心上

这就是服务自动注册的原理

总结

这里再来简单回顾一下Spring启动大致的几个过程

最开始的准备操作,这部分就是准备一些配置属性相关的

之后连续好几个方法都是准备BeanFactory的,我把上面那张图拿过来

整个准备BeanFactory过程大致如下:

  • 先配置BeanFactory
  • 通过ConfigurationClassPostProcessor加载Bean到BeanFactory中
  • 从上一步加载的Bean中获取BeanFactoryPostProcessor,完成对BeanFactory做自定义处理
  • 从上一步加载的Bean中获取BeanPostProcessor,添加到BeanFactory中

当这些步骤完成之后,BeanFactory跟Bean创建相关的配置几乎算是配置完成了

之后其实就是一些ApplicationContext内部的一些组价的初始化,比如MessageSource、ApplicationEventMulticaster等等

优先从BeanFactory中获取,没有再用默认的

到这ApplicationContext也算配置完成了,之后就可以实例化单例非懒加载的Bean了

再后面就是一些扫尾的操作,发布一个ContextRefreshedEvent事件,表明容器已经刷新完成了

这时Spring就就算是真正启动完成了。

最后,如果本篇文章对你所有帮助,欢迎转发、点赞、收藏、在看,非常感谢。

最后的最后,再来留个坑,有机会再来扒一扒SpringBoot在启动时都做了哪些事

至于啥时候填,那就等一个有缘人吧。。

往期热门文章推荐

如何去阅读源码,我总结了18条心法

如何写出漂亮代码,我总结了45个小技巧

1.5万字+30张图盘点索引常见的11个知识点

三万字盘点Spring/Boot的那些常用扩展点

三万字盘点Spring 9大核心基础功能

两万字盘点那些被玩烂了的设计模式

扫码或者搜索关注公众号 三友的java日记 ,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习,回复 面试 即可获得一套面试真题。

万字+20张图剖析Spring启动时核心的12个步骤的更多相关文章

  1. 2 万字 + 20张图| 细说 Redis 九种数据类型和应用场景

    作者:小林coding 计算机八股文网(操作系统.计算机网络.计算机组成.MySQL.Redis):https://xiaolincoding.com 大家好,我是小林. 我们都知道 Redis 提供 ...

  2. 一张图了解Spring Cloud微服务架构

    Spring Cloud作为当下主流的微服务框架,可以让我们更简单快捷地实现微服务架构.Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟.经得起实际考验的服务框架组合起来 ...

  3. spring启动时加载字典表数据放入map

    import java.util.HashMap; import java.util.List; import org.springframework.beans.factory.annotation ...

  4. Spring启动时获取自定义注解的属性值

    1.自定义注解 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documen ...

  5. 2 万字 + 30 张图 | 细聊 MySQL undo log、redo log、binlog 有什么用?

    作者:小林coding 计算机八股文网站:https://xiaolincoding.com/ 大家好,我是小林. 从这篇「执行一条 SQL 查询语句,期间发生了什么?」中,我们知道了一条查询语句经历 ...

  6. Nginx源码分析:3张图看懂启动及进程工作原理

    编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享.转载请注明来自高可用架构公众号「ArchNotes」.   导读:很多工程师及架构师都希望了解及掌握高性能服务器 ...

  7. Spring启动时加载数据

    程序中也许有会有许多常用的,不会经常更改的数据,我们可以在程序初始化的时候就把他们加载,就不用频繁的加载或者查询. 以下是几个常用的,有COPY收集的,也有自己弄. 1. 实现BeanPostProc ...

  8. Spring 启动时加载资源

    Spring加载资源文件目前了解三种, @PostConstruct在Context加载完成之后加载.在创建各个Bean对象之前加载. 实现ApplicationRunner的run方法,Bean加载 ...

  9. 两张图概括struts2执行流程核心(经典)

  10. 一张图彻底理解Spring如何解决循环依赖!!

    写在前面 最近,在看Spring源码,看到Spring解决循环依赖问题的源码时,不得不说,源码写的太烂了.像Spring这种顶级的项目源码,竟然存在着这种xxx的代码.看了几次都有点头大,相信很多小伙 ...

随机推荐

  1. Nodejs 应用编译构建提速建议

    编译构建的整体过程 拉取编译镜像 拉取缓存镜像 拉取项目源码 挂载缓存目录 执行编译命令(用户自定义) 持久化缓存 上传编译镜像 为什么在本地构建就快, 但编译机上很慢 在编辑机上每次的构建环境都是全 ...

  2. 逍遥自在学C语言 | 揭开while循环的神秘面纱

    前言 循环是一种重要的控制结构,可以使程序重复执行一段代码,直到满足特定条件为止. 在C语言中,while和do-while是两种常用的循环结构,本文将详细介绍这两种循环的用法. 一.人物简介 第一位 ...

  3. Rust 语言风靡学术界

    AWS 将 Rust 编译器团队负责人收入麾下的新闻让开发者们再次聚焦于这门兼具安全性与高性能的编程语言.近日,著名科学期刊 Nature 刊登了一篇文章,表明 Rust 语言也正在成为学术界最受欢迎 ...

  4. 今天在内部 Galaxy 分析平台操作探针引物设计小工具程序,调用 Ensembl API 获取相关序列和信息时,发现官网 MySQL server 异常,报告问题后当天晚上就收到了回复,并且修......

    本文分享自微信公众号 - 生信科技爱好者(bioitee).如有侵权,请联系 support@oschina.cn 删除.本文参与"OSC源创计划",欢迎正在阅读的你也加入,一起分 ...

  5. shell工具和脚本

    Shell脚本 shell 脚本是一种更加复杂度的工具. 大多数shell都有自己的一套脚本语言,包括变量.控制流和自己的语法.shell脚本 与其他脚本语言不同之处在于,shell 脚本针对 she ...

  6. Grafana系列-GaC-1-Grafana即代码的几种实现方式

    系列文章 Grafana 系列文章 Terraform 系列文章 概述 GaC(Grafana as Code, Grafana 即代码) 很明显是扩展自 IaC(Infrastructure as ...

  7. docker中的mysql中文乱码解决办法

    博主最近在做谷粒商城,因为要使用docker安装mysql,但是由于安装的时候没有指定mysql的数据库的utf8格式,导致插入的时候就出现了中文是问号的情况,到处百度终于解决,于是打算记录一下自己的 ...

  8. Java判断一个数是不是质数

    判断一个数是不是质数 做这个题之前我们需要先进行了解什么是质数 质数:只能被1和它本身整除的数 举一个简单的例子:数字5是不是质数呢? 我们可以进行分析: 解题思路:5可以分为1 2 3 4 5,我们 ...

  9. java.lang.reflect.UndeclaredThrowableException

    org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.Persiste ...

  10. Hugging News #0703: 在浏览器中运行 Whisper 模型、WAIC 分论坛活动邀请报名

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...