SpringBoot源码解析(七):应用上下文结构体系
SpringBoot源码系列文章
SpringBoot源码解析(一):SpringApplication构造方法
SpringBoot源码解析(二):引导上下文DefaultBootstrapContext
SpringBoot源码解析(三):启动开始阶段
SpringBoot源码解析(四):解析应用参数args
SpringBoot源码解析(五):准备应用环境
SpringBoot源码解析(六):打印Banner
SpringBoot源码解析(七):应用上下文结构体系
目录
- 前言
- 一、AnnotationConfigServletWebServerApplicationContext类图
- 二、AnnotationConfigServletWebServerApplicationContext组件功能解析
- 1、BeanFactory(Bean管理核心接口)
- 2、HierarchicalBeanFactory(支持父子容器)
- 3、ListableBeanFactory(批量获取Bean)
- 4、MessageSource(国际化消息支持)
- 5、EnvironmentCapable(环境变量访问能力)
- 6、ResourcePatternResolver(资源路径解析器)
- 7、ApplicationEventPublisher(发布事件)
- 8、ApplicationContext(应用上下文核心接口)
- 9、Lifecycle(Bean生命周期管理接口)
- 10、ConfigurableApplicationContext(可配置应用上下文)
- 11、WebApplicationContext(web应用上下文)
- 12、ConfigurableWebApplicationContext(可配置web应用上下文)
- 13、WebServerApplicationContext(web服务应用上下文)
- 14、ConfigurableWebServerApplicationContext(可配置web服务应用上下文)
- 15、AbstractApplicationContext(抽象应用上下文)
- 16、BeanDefinitionRegistry(Bean定义注册接口)
- 17、GenericApplicationContext(通用应用上下文)
- 18、GenericWebApplicationContext(web通用应用上下文)
- 19、ServletWebServerApplicationContext(web服务通用上下文)
- 20、AnnotationConfigRegistry(注解配置注册器)
- 21、AnnotationConfigServletWebServerApplicationContext(注解驱动web服务应用上下文)
- 总结
前言
在解析SpringBoot源码之前,需要对其使用的应用上下文(ApplicationContext)
相关组件有清晰的了解。SpringBoot的核心在于基于Spring的扩展,其自动配置机制、嵌入式Web容器等特性都依赖于一套规范的上下文组件。因此,提前理清这些核心组件的功能和职责,可以显著提高源码阅读的效率,避免频繁地在代码中跳转而迷失方向。
SpringBoot在调用SpringApplication.run()
方法启动时,根据应用类型(如 Servlet 类型)通过createApplicationContext()
方法推断并创建AnnotationConfigServletWebServerApplicationContext
,用于初始化和管理 Web 应用的上下文环境。
一、AnnotationConfigServletWebServerApplicationContext类图
类图如下(摆这个图用我好长时间🙇♂️),每个类的作用将在下面进行简要分析。
二、AnnotationConfigServletWebServerApplicationContext组件功能解析
1、BeanFactory(Bean管理核心接口)
BeanFactory
是Spring框架中最基础的IOC
(Inversion of Control,控制反转)容器接口,它负责创建
、管理
和配置
应用中的Bean(对象)
,并处理Bean的依赖注入
与生命周期管理
。BeanFactory支持延迟加载(懒加载)
,即在首次请求时才实例化Bean,适用于轻量级应用或资源受限的环境。
public interface BeanFactory {// bean名称前加&,返回的FactoryBean工厂实例,否则返回实际创建类型实例String FACTORY_BEAN_PREFIX = "&";// 根据指定的名称获取Bean实例// 返回的实例可能是单例模式下的共享实例,也可能是每次请求都会创建的新实例,具体取决于 Bean 的配置// 如果容器中不存在该名称的 Bean,则会抛出异常Object getBean(String name) throws BeansException;// 根据指定的名称和类型获取Bean实例// 如果名称对应的 Bean 类型与指定的类型不匹配,则会抛出异常<T> T getBean(String name, Class<T> requiredType) throws BeansException;// 根据指定的名称和参数获取Bean实例Object getBean(String name, Object... args) throws BeansException;// 根据指定的类型获取唯一匹配的Bean实例// 如果容器中不存在该类型的 Bean,或者存在多个该类型的 Bean,则会抛出异常 <T> T getBean(Class<T> requiredType) throws BeansException;// 根据指定的类型和参数获取Bean实例<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;// 返回一个可以提供指定类型Bean的ObjectProvider对象// ObjectProvider不会立即实例化Bean,只有在调用其方法时才会加载或创建Bean<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);// 通过ResolvableType,可以获取复杂类型或泛型Bean// 可以定义复杂的泛型结构,如 List<MyBean>、Map<String, MyBean> 等<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);// 检查容器中是否存在指定名称的 Beanboolean containsBean(String name);// 检查指定名称的Bean是否为单例(一个类只能有一个实例)boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 检查指定名称的Bean是否为原型(每次获取返回一个新实例)boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 检查指定名称的Bean是否匹配某个ResolvableType// 例如,检查一个Bean是否是List<String>类型boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;// 检查指定名称的Bean是否匹配某个Class类型// 这是基础的类型匹配检查,不支持泛型,但速度更快boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;// 返回Bean的Class类型(不考虑FactoryBean情况)@NullableClass<?> getType(String name) throws NoSuchBeanDefinitionException;// 返回Bean的Class类型// allowFactoryBeanInit=true,初始化FactoryBean返回实际类型@NullableClass<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;// 获取Bean的所有别名String[] getAliases(String name);
}
2、HierarchicalBeanFactory(支持父子容器)
HierarchicalBeanFactory
是Spring框架中定义的一个接口,它扩展了BeanFactory,用于支持Bean工厂的层次化结构管理。它的设计目的是允许一个Bean工厂拥有父级Bean工厂
,并能够在当前Bean工厂和父级工厂之间协调Bean的管理和查找。
public interface HierarchicalBeanFactory extends BeanFactory {// 返回父级Bean工厂,如果没有父级,则返回null@NullableBeanFactory getParentBeanFactory();// 判断本地 Bean 工厂是否包含指定名称的 Bean,忽略父级上下文中定义的 Beanboolean containsLocalBean(String name);
}
3、ListableBeanFactory(批量获取Bean)
ListableBeanFactory
是Spring框架中的一个接口,它扩展了BeanFactory
,提供了按照类型、名称等多种方式列出Bean
的功能。它是Spring应用上下文中一个核心接口,用于管理和访问Bean。
// "只考虑当前工厂":检查范围限定为当前工厂的直接定义部分
// "不考虑层次结构":不会递归查询父工厂或祖先工厂中的定义
public interface ListableBeanFactory extends BeanFactory { // 检查此 Bean 工厂是否包含具有给定名称的 Bean 定义boolean containsBeanDefinition(String beanName);// 返回工厂中定义的 Bean 数量int getBeanDefinitionCount();// 返回此工厂中定义的所有 Bean 的名称String[] getBeanDefinitionNames();// 返回一个可以提供指定类型Bean的ObjectProvider对象,可选择是否延迟初始化<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType, boolean allowEagerInit);// 返回一个可以提供复杂指定类型Bean的ObjectProvider对象,可选择是否延迟初始化<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType, boolean allowEagerInit);// 返回与给定类型(包括给定类型子类)匹配的 Bean 名称String[] getBeanNamesForType(ResolvableType type);// 返回与给定复杂类型(包括给定子类)匹配的 Bean 名称// 并允许对非单例 Bean 和延迟加载 Bean 进行控制String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit);// 返回与给定类型(包括子类)匹配的 Bean 名称String[] getBeanNamesForType(@Nullable Class<?> type);// 返回与给定类型(包括子类)匹配的 Bean 名称// 并允许对非单例 Bean 和延迟加载 Bean 进行控制String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);// 返回一个 Map,包含匹配的 Bean 名称和对应的 Bean 实例<T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;// 返回一个 Map,包含匹配的 Bean 名称和对应的 Bean 实例// 并允许对非单例 Bean 和延迟加载 Bean 进行控制<T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)throws BeansException;// 查找带有指定注解的所有 Bean 名称String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);// 一个 Map,包含匹配的 Bean 名称和对应带指定注解的Bean实例Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;// 用于查找指定 Bean 上的特定类型注解,并返回该注解的实例@Nullable<A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)throws NoSuchBeanDefinitionException;// 用于查找指定 Bean 上的特定注解,并提供一个参数来控制是否允许初始化 FactoryBean@Nullable<A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType, boolean allowFactoryBeanInit)throws NoSuchBeanDefinitionException;
}
4、MessageSource(国际化消息支持)
MessageSource
是SpringFramework中用于国际化的接口,主要用来处理应用程序中的多语言消息。它允许你根据用户的语言环境(Locale)来动态加载和显示相应的消息。这对于支持多语言的应用程序非常重要。
// 消息解析的策略接口,支持消息的参数化和国际化
public interface MessageSource {// 尝试解析指定代码的消息,如果找不到则返回默认消息@NullableString getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);// 尝试解析消息。如果未找到消息,视为错误并抛出异常String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;// 根据传入的 MessageSourceResolvable 参数解析消息,若未找到对应消息,抛出异常String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}
5、EnvironmentCapable(环境变量访问能力)
EnvironmentCapable
是SpringFramework中的一个接口,它定义了获取Environment的能力。Environment
是Spring的核心抽象之一,用于表示当前应用程序的环境信息,比如操作系统属性
、配置属性
、环境变量
等。
public interface EnvironmentCapable {// 返回一个 Environment 对象,Environment 提供了多个方法来获取配置信息Environment getEnvironment();
}
6、ResourcePatternResolver(资源路径解析器)
ResourcePatternResolver
是Spring框架中的一个接口,用于将路径模式(如通配符 * 和 **)
解析为资源(Resource)对象
。简单来说,它提供了一种机制,可以通过路径模式加载符合条件的资源文件,比如从类路径、文件系统或 JAR 文件中加载多个资源。
public interface ResourcePatternResolver extends ResourceLoader {// 用于从类路径中匹配所有资源的伪 URL 前缀:"classpath*:"String CLASSPATH_ALL_URL_PREFIX = "classpath*:";// 解析给定路径的资源,解析为Resource对象Resource[] getResources(String locationPattern) throws IOException;
}
7、ApplicationEventPublisher(发布事件)
ApplicationEventPublisher
是SpringFramework中的一个接口,用于将事件发布给监听器
。它是Spring事件驱动编程模型的核心组件之一,能够实现组件之间的解耦通信。之前文章Spring发布-订阅模式:解耦与异步通信的高效实现对于此类有详细介绍
// 一个封装事件发布功能的接口
@FunctionalInterface
public interface ApplicationEventPublisher {// 默认方法,最终还是调用publishEvent(Object event)方法default void publishEvent(ApplicationEvent event) {publishEvent((Object) event);}// 发布事件void publishEvent(Object event);
}
8、ApplicationContext(应用上下文核心接口)
ApplicationContext
是Spring框架中的一个中央接口,它提供了对Bean工厂、事件发布、消息解析等核心功能的统一访问。
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,MessageSource, ApplicationEventPublisher, ResourcePatternResolver {// 返回此应用程序上下文的唯一标识符,没有返回null@NullableString getId();// 返回该上下文所属的已部署应用程序的名称,默认为空字符串String getApplicationName();// 返回此上下文的友好名称(显示名称),不会为nullString getDisplayName();// 返回此上下文第一次加载时的时间戳(以毫秒为单位)long getStartupDate();// 父上下文,如果没有则返回null@NullableApplicationContext getParent();// 这个方法的作用是让开发者能够访问底层的 BeanFactory,从而手动管理和操作 Bean 的生命周期和依赖注入AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
9、Lifecycle(Bean生命周期管理接口)
Lifecycle
是一个通用的生命周期接口,定义了组件的启动
、停止
和运行状态检查
方法,通常用于控制具有生命周期管理能力的组件(如线程、定时任务、消息监听器等)。
public interface Lifecycle {// 启动组件,对于容器,它会将启动信号传播到容器内所有相关的子组件void start();// 停止组件,通常是同步操作,确保组件在方法返回时完全停止void stop();// 检查组件是否正在运行,只有当所有相关的子组件都运行时,才返回 trueboolean isRunning();
}
10、ConfigurableApplicationContext(可配置应用上下文)
ConfigurableApplicationContext
是Spring应用上下文接口的扩展,定义了可配置的应用上下文
,主要用于管理、配置和控制Spring 应用上下文的生命周期。它在 ApplicationContext 的基础上增加了许多高级功能,例如上下文刷新、关闭、父上下文设置等。
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {// 配置路径中多个配置路径之间的分隔符字符// 可以是逗号 (`,`)、分号 (`;`)、空格 (` `)、制表符 (`\t`) 或换行符 (`\n`)String CONFIG_LOCATION_DELIMITERS = ",; \t\n";// BeanFactory 中 ConversionService bean 的名称String CONVERSION_SERVICE_BEAN_NAME = "conversionService";// BeanFactory 中 LoadTimeWeaver bean 的名称String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver";// BeanFactory 中 Environment bean 的名称String ENVIRONMENT_BEAN_NAME = "environment";// BeanFactory 中系统属性的 Bean 名称String SYSTEM_PROPERTIES_BEAN_NAME = "systemProperties";// BeanFactory 中系统环境变量的 Bean 名称String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment";// BeanFactory 中 ApplicationStartup bean 的名称// 收集和记录应用程序启动过程中的性能指标,省略不细讲String APPLICATION_STARTUP_BEAN_NAME = "applicationStartup";// JVM 中关闭钩子线程的默认名称String SHUTDOWN_HOOK_THREAD_NAME = "SpringContextShutdownHook";// 设置此应用程序上下文的唯一 IDvoid setId(String id);// 设置此应用程序上下文的父上下文void setParent(@Nullable ApplicationContext parent);// 设置此应用程序上下文的Environmentvoid setEnvironment(ConfigurableEnvironment environment);// 返回此应用程序上下文的Environment@OverrideConfigurableEnvironment getEnvironment();// 添加一个工厂处理器BeanFactoryPostProcessor,该处理器将在刷新时应用于内部 bean 工厂void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);// 添加一个新的 ApplicationListener,该监听器将在上下文事件(如刷新或关闭)时被通知void addApplicationListener(ApplicationListener<?> listener);// 加载或刷新配置的持久化表示,例如基于 Java 配置、XML 文件、属性文件等void refresh() throws BeansException, IllegalStateException;// 向 JVM 注册一个关闭钩子,在 JVM 关闭时关闭此上下文void registerShutdownHook();// 关闭此应用程序上下文,释放所有资源并销毁所有缓存的单例 bean@Overridevoid close();// 确定此应用程序上下文是否处于活动状态boolean isActive();// 返回此应用程序上下文的内部 BeanFactoryConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}
11、WebApplicationContext(web应用上下文)
WebApplicationContext
是Spring框架中专门为Web应用设计的上下文接口
。它扩展了ApplicationContext,增加了与Servlet API
的集成功能,例如访问 ServletContext,支持Web特定的作用域(如request和session),并且通过分层结构实现了灵活的上下文管理。
public interface WebApplicationContext extends ApplicationContext {// 常量,表示根 WebApplicationContext 在启动过程中绑定到的属性名称// 用于在 Web 应用程序中查找根上下文,例如通过 WebApplicationContextUtils 工具类String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";// 分别对应请求范围(request)、会话范围(session)、全局应用范围(application)String SCOPE_REQUEST = "request";String SCOPE_SESSION = "session";String SCOPE_APPLICATION = "application";// 定义了 ServletContext 在 Spring 容器中的 Bean 名称String SERVLET_CONTEXT_BEAN_NAME = "servletContext";// 定义了 ServletContext 初始化参数(init-params)在 Spring 容器中的 Bean 名称。// 如果参数在 ServletConfig 和 ServletContext 中同名,则 ServletConfig 参数优先。String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";// 定义了 ServletContext 属性(attributes)在 Spring 容器中的 Bean 名称String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";// 返回当前应用程序的 ServletContext 对象。// 可用于直接访问与 Web 应用程序相关的上下文信息@NullableServletContext getServletContext();
}
12、ConfigurableWebApplicationContext(可配置web应用上下文)
ConfigurableWebApplicationContext
是一个用于管理和配置Web应用上下文的接口,支持与Servlet环境集成、动态加载配置文件以及命名空间管理。
// 此接口主要用于管理基于 Web 的应用程序上下文,并提供特定于 Web 环境的配置方法
public interface ConfigurableWebApplicationContext extends WebApplicationContext, ConfigurableApplicationContext {// 用于引用上下文路径和/或 Servlet 名称的 ApplicationContext id 前缀String APPLICATION_CONTEXT_ID_PREFIX = WebApplicationContext.class.getName() + ":";// 在工厂中表示 ServletConfig 环境 Bean 的名称String SERVLET_CONFIG_BEAN_NAME = "servletConfig";// 设置此 Web 应用程序上下文的 ServletContextvoid setServletContext(@Nullable ServletContext servletContext);// 设置此 Web 应用程序上下文的 ServletConfigvoid setServletConfig(@Nullable ServletConfig servletConfig);// 返回此 Web 应用程序上下文的 ServletConfig(如果有)@NullableServletConfig getServletConfig();// 设置此 Web 应用程序上下文的命名空间void setNamespace(@Nullable String namespace);// 返回此 Web 应用程序上下文的命名空间(如果有)@NullableString getNamespace();// 以 init-param 样式设置此 Web 应用程序上下文的配置位置void setConfigLocation(String configLocation);// 设置此 Web 应用程序上下文的配置位置void setConfigLocations(String... configLocations);// 返回此 Web 应用程序上下文的配置位置@NullableString[] getConfigLocations();
}
13、WebServerApplicationContext(web服务应用上下文)
WebServerApplicationContext
专门用于管理嵌入式Web服务器(如Tomcat、Jetty 或 Undertow
)的生命周期。它提供了与Web服务器交互的抽象,支持启动、停止和运行时管理Web服务器的状态
。
public interface WebServerApplicationContext extends ApplicationContext {// 返回由上下文创建的内嵌的 Web 服务器实例,如果服务器尚未创建,则返回nullWebServer getWebServer();// 返回Web服务器应用程序上下文的命名空间,如果未设置命名空间,则返回nullString getServerNamespace();// 判断指定的上下文是否为WebServerApplicationContext,且具有匹配的服务器命名空间static boolean hasServerNamespace(ApplicationContext context, String serverNamespace) {return (context instanceof WebServerApplicationContext) && ObjectUtils.nullSafeEquals(((WebServerApplicationContext) context).getServerNamespace(), serverNamespace);}// 如果指定的上下文是WebServerApplicationContext,则返回其服务器命名空间static String getServerNamespace(ApplicationContext context) {return (context instanceof WebServerApplicationContext)? ((WebServerApplicationContext) context).getServerNamespace() : null;}}// 简单的接口,表示一个完全配置的 Web 服务器(例如 Tomcat、Jetty 或 Netty)
public interface WebServer {// 启动 Web 服务器void start() throws WebServerException;// 停止 Web 服务器。void stop() throws WebServerException;// 返回监听的端口号,如果未分配端口则返回 -1int getPort();// 允许在不影响现有请求的情况下优雅地关闭服务器// 默认实现是直接返回立即关闭的结果,可以被覆盖实现实际的关闭逻辑default void shutDownGracefully(GracefulShutdownCallback callback) {// 默认行为:直接调用回调函数,立即完成关闭(不实际执行关闭逻辑)callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);}
}
14、ConfigurableWebServerApplicationContext(可配置web服务应用上下文)
ConfigurableWebServerApplicationContext
是一个用于配置和管理嵌入式Web服务器的Spring应用上下文接口,支持动态配置、服务器命名空间设置以及上下文生命周期管理。
// 通过此接口,开发者可以在 Web 服务器应用上下文中进行更灵活的配置和管理
public interface ConfigurableWebServerApplicationContextextends ConfigurableApplicationContext, WebServerApplicationContext {// 设置上下文的服务器命名空间。// 服务器命名空间通常用于区分不同的 Web 服务器实例或模块,尤其在复杂的微服务架构中非常有用。void setServerNamespace(String serverNamespace);
}
15、AbstractApplicationContext(抽象应用上下文)
AbstractApplicationContext
是Spring应用上下文的抽象实现类,负责提供通用的上下文生命周期管理
、事件发布
、资源加载
以及BeanFactory的初始化
等核心功能。
所有具体的应用上下文(如 ClassPathXmlApplicationContext 和 AnnotationConfigApplicationContext)都继承自它。
public abstract class AbstractApplicationContext extends DefaultResourceLoaderimplements ConfigurableApplicationContext {... // 内容很多,这里只介绍下核心内容/*** 核心属性*/// bean工厂后置处理器// 在容器刷新时,允许在 Bean 定义加载完成但 Bean 实例化之前,修改 Bean 定义private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors = new ArrayList<>();// 生命周期处理器,用于管理应用上下文中所有实现了 Lifecycle 接口的组件的启动和停止// 容器刷新时(onRefresh() 方法),启动所有实现了 Lifecycle 或 SmartLifecycle 接口的 Bean// 容器关闭时(onClose() 方法),停止所有实现了 Lifecycle 或 SmartLifecycle 接口的 Bean@Nullableprivate LifecycleProcessor lifecycleProcessor;// 事件广播器,负责将发布的事件分发给所有注册的监听器@Nullableprivate ApplicationEventMulticaster applicationEventMulticaster;// 用于存储在上下文刷新之前显式注册的 ApplicationListener// 这些监听器在容器初始化时注册,并在事件发布时被调用private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();/*** 核心方法*/// 核心方法,用于刷新上下文的配置和状态@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 1、刷新前预处理prepareRefresh();// 2、获取最新的bean工厂ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 3、注册bean工厂的类加载器和部分后置处理器prepareBeanFactory(beanFactory);try {// 4、bean工厂的后置处理器(子类实现)postProcessBeanFactory(beanFactory);// 5、执行bean工厂的后置处理器invokeBeanFactoryPostProcessors(beanFactory);// 6、注册所有的后置处理器registerBeanPostProcessors(beanFactory);// 7、初始化国际化组件initMessageSource();// 8、初始化事件多播器(用来广播事件)initApplicationEventMulticaster();// 9、子类实现,扩展其他beanonRefresh();// 10、注册监听器registerListeners();// 11、实例化,初始化所有的非懒加载的单例beanfinishBeanFactoryInitialization(beanFactory);// 12、最后刷新,发布相应事件finishRefresh();}catch (BeansException ex) {...}}}// 发布事件到所有监听器。如果事件多播器尚未初始化,将事件存储以供后续处理// ApplicationEventPublisher接口的实现@Overridepublic void publishEvent(ApplicationEvent event) {...}// 注册 JVM 关闭钩子,确保上下文在 JVM 关闭时也能被正确关闭@Overridepublic void registerShutdownHook() {...}// 关闭上下文,销毁所有单例 Bean,并发布关闭事件@Overridepublic void close() {...}}
16、BeanDefinitionRegistry(Bean定义注册接口)
BeanDefinitionRegistry
是Spring容器中用于管理Bean定义的核心接口,支持动态注册
、移除
、查询
和别名管理
,常用于扩展和动态操作容器内的 Bean 定义。
// 此接口是 Bean 定义注册的核心,用于动态管理 Bean 定义(注册、移除、查询等)
public interface BeanDefinitionRegistry extends AliasRegistry {// 向注册表中注册一个新的 BeanDefinitionvoid registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException;// 移除给定名称的 BeanDefinitionvoid removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;// 返回给定名称的 BeanDefinitionBeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;// 检查此注册表是否包含具有给定名称的 BeanDefinition。boolean containsBeanDefinition(String beanName);// 返回此注册表中定义的所有 Bean 的名称String[] getBeanDefinitionNames();// 返回注册表中定义的 Bean 的数量。int getBeanDefinitionCount();// 确定给定的 Bean 名称是否已经在此注册表中使用(即是否有本地 Bean 或别名注册了此名称)boolean isBeanNameInUse(String beanName);
}/*** 管理别名的通用接口(提供了注册、删除、查询别名的方法)** 别名是一种机制,用于为已有的名称提供额外的标识,* 通常用于配置中增加灵活性,例如为同一个 Bean 定义多个名称*/
public interface AliasRegistry {// 为给定的名称注册一个别名void registerAlias(String name, String alias);// 从注册表中删除指定的别名void removeAlias(String alias);// 确定给定的名称是否被定义为别名(而不是实际注册的组件名称)boolean isAlias(String name);// 返回给定名称的所有别名(如果定义了别名)String[] getAliases(String name);
}
17、GenericApplicationContext(通用应用上下文)
GenericApplicationContext
是一个通用的Spring应用上下文容器
,支持以编程方式注册Bean
和灵活配置应用上下文
DefaultListableBeanFactory
内部组件较为复杂,下篇文章将单独对其内部组件进行详细解析。
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {... // 内容很多,这里只介绍下核心内容// 用于注册、管理和创建 Bean 定义,支持依赖注入和动态 Bean 管理private final DefaultListableBeanFactory beanFactory;// 用于统一加载文件、类路径、URL 等各种类型的资源@Nullableprivate ResourceLoader resourceLoader;// 根据给定路径加载单个资源,支持自定义协议解析和默认资源加载器@Overridepublic Resource getResource(String location) {...}// 根据路径模式加载多个资源,优先使用资源加载器的路径解析能力@Overridepublic Resource[] getResources(String locationPattern) throws IOException {...}@Override// 向底层 Bean 工厂注册一个新的 Bean 定义public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {this.beanFactory.registerBeanDefinition(beanName, beanDefinition);}@Override// 从底层 Bean 工厂中移除指定名称的 Bean 定义public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {this.beanFactory.removeBeanDefinition(beanName);}@Override// 获取指定名称的 Bean 定义public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {return this.beanFactory.getBeanDefinition(beanName);}// registerBean 方法封装了 Bean 注册的逻辑// 允许通过 Class 类型、可选的实例供应器和自定义器动态注册 Bean// 极大简化了 Bean 定义和注册的过程public <T> void registerBean(@Nullable String beanName, Class<T> beanClass,@Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {...}}
18、GenericWebApplicationContext(web通用应用上下文)
GenericWebApplicationContext
是一个通用的Spring Web应用上下文
,结合动态Bean注册功能与Web特定特性(如ServletContext
),用于轻量化配置和管理 Web 应用环境
public class GenericWebApplicationContext extends GenericApplicationContextimplements ConfigurableWebApplicationContext, ThemeSource {// ServletContext 是 Web 应用的全局上下文// 用于共享数据、加载资源、解析路径和访问应用及容器的配置信息@Nullableprivate ServletContext servletContext;...
}
19、ServletWebServerApplicationContext(web服务通用上下文)
ServletWebServerApplicationContext
是SpringBoot用于启动和管理内嵌Servlet Web服务器(如 Tomcat)的应用上下文,实现Web服务器
与Spring容器
的无缝集成。
public class ServletWebServerApplicationContext extends GenericWebApplicationContextimplements ConfigurableWebServerApplicationContext {// 表示一个Web服务器(例如 Tomcat、Jetty 或 Netty)private volatile WebServer webServer;// ServletConfig 是 Servlet 规范中的接口// 用于向 Servlet 提供初始化参数和上下文信息private ServletConfig servletConfig;// 创建和初始化内嵌 Web 服务器private void createWebServer() {...}
}
20、AnnotationConfigRegistry(注解配置注册器)
AnnotationConfigRegistry
是一个接口,提供通过注册注解配置类
或扫描包路径
来管理Spring组件的功能。
// 主要功能是提供通过注解方式注册组件类或扫描包的能力
public interface AnnotationConfigRegistry {// 注册一个或多个组件类// 对register方法的调用是幂等的;多次添加相同的组件类不会产生额外的效果void register(Class<?>... componentClasses);// 扫描指定的基础包以查找组件类void scan(String... basePackages);
}
21、AnnotationConfigServletWebServerApplicationContext(注解驱动web服务应用上下文)
AnnotationConfigServletWebServerApplicationContext
是一个基于注解配置的 Spring 应用上下文,继承自 ServletWebServerApplicationContext,并支持通过注解注册组件类和包扫描来配置 Web 应用。
// 一个专门用于基于注解配置的 Servlet Web 服务器应用上下文
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContextimplements AnnotationConfigRegistry {// 存储已注册的注解配置类的集合,这些类通常标注有@Configuration注解private final Set<Class<?>> annotatedClasses = new LinkedHashSet<>();// 存储要扫描的基础包路径数组,通过包路径扫描,可以发现并注册 Spring 组件private String[] basePackages;...
}
总结
- SpringBoot的上下文架构基于Spring的扩展,核心是以
ApplicationContext
为中心的组件体系- 通过
BeanFactory
提供Bean的注册、管理和依赖注入功能 - 通过
ApplicationContext
为应用提供高级功能支持,包括国际化消息、访问环境变量、事件发布、资源访问等 - 通过
WebApplicationContext
集成ServletContext,管理Web环境中的Bean和特定作用域(如request和session) - 通过
WebServerApplicationContext
管理嵌入式Web服务器(如 Tomcat、Jetty)的启动、停止和生命周期
- 通过
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=5dxybut99es
相关文章:
SpringBoot源码解析(七):应用上下文结构体系
SpringBoot源码系列文章 SpringBoot源码解析(一):SpringApplication构造方法 SpringBoot源码解析(二):引导上下文DefaultBootstrapContext SpringBoot源码解析(三):启动开始阶段 SpringBoot源码解析(四):解析应用参数args Sp…...
5 分钟复刻你的声音,一键实现 GPT-Sovits 模型部署
想象一下,只需简单几步操作,就能生成逼真的语音效果,无论是为客户服务还是为游戏角色配音,都能轻松实现。GPT-Sovits 模型,其高效的语音生成能力为实现自然、流畅的语音交互提供了强有力的技术支持。本文将详细介绍如何…...
数字化时代,传统代理模式的变革之路
在数字化飞速发展的今天,线上线下融合(O2O)成了商业领域的大趋势。这股潮流,正猛烈冲击着传统代理模式,给它带来了新的改变。 咱们先看看线上线下融合现在啥情况。线上渠道那是越来越多,企业纷纷在电商平台…...
python爬虫爬取淘宝商品比价||淘宝商品详情API接口
最近在学习北京理工大学的爬虫课程,其中一个实例是讲如何爬取淘宝商品信息,现整理如下: 功能描述:获取淘宝搜索页面的信息,提取其中的商品名称和价格 探讨:淘宝的搜索接口 翻页的处理 技术路线:requests…...
HunyuanVideo 文生视频模型实践
HunyuanVideo 文生视频模型实践 flyfish 运行 HunyuanVideo 模型使用文本生成视频的推荐配置(batch size 1): 模型分辨率(height/width/frame)峰值显存HunyuanVideo720px1280px129f60GHunyuanVideo544px960px129f45G 本项目适用于使用 N…...
CSRF攻击XSS攻击
概述 在 HTML 中,<a>, <form>, <img>, <script>, <iframe>, <link> 等标签以及 Ajax 都可以指向一个资源地址,而所谓的跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样。这里的域指…...
vue3学习日记8 - 一级分类
最近发现职场前端用的框架大多为vue,所以最近也跟着黑马程序员vue3的课程进行学习,以下是我的学习记录 视频网址: Day2-17.Layout-Pinia优化重复请求_哔哩哔哩_bilibili 学习日记: vue3学习日记1 - 环境搭建-CSDN博客 vue3学…...
Notepad++移除所有空格
1.打开Notepad。 2.打开你想要编辑的文件。 3.按下 Ctrl H 打开查找和替换对话框,并选择 “正则表达式”。 4.在 “查找目标” 框中输入 \s。 5.在 “替换为” 框中留空,不填写任何内容。 6.点击 “全部替换” 按钮。...
JavaSE第八天
一、继承之super关键字 super关键字: 一个引用变量,用于引用父类对象 父类和子类都具有相同的命名方法,要调用父类方法时使用 父类和子类都具有相同的命名属性,要调用父类中的属性时使用 super也是父类的构造函数,…...
ideal jdk报错如何解决
例如: 可能一:环境变量中未配置 请在Path中加入并将要使用的最好置顶,如 可能二:项目结构中语言级别错误: 可能三:Maven工程中,对于模块要单独设置jdk: 如: 未设置则为默认,在博主本次展示中为:...
嵌入式Linux ntpclient的使用
ntpclient是一个用于与NTP(Network Time Protocol,网络时间协议)服务器通信并测量系统时间的工具。我这里用的是"ntpclient_2024_132"。下载源码编译后会得到一个ntpclient程序。 下面是对ntpclient每个选项的解释: -…...
25/1/15 嵌入式笔记 初学STM32F108
GPIO初始化函数 GPIO_Ini:初始化GPIO引脚的模式,速度和引脚号 GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA的引脚0 GPIO输出控制函数 GPIO_SetBits:将指定的GPIO引脚设置为高电平 GPIO_SetBits(GPIOA, GPIO_Pin_0); // 将GPIO…...
【练习】力扣热题100 字符串解码
题目 给定一个经过编码的字符串,返回它解码后的字符串。 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k保证为正整数。 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,…...
1.快慢指针-力扣-283-移动零
题目描述 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 用例 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nu…...
5. 使用springboot做一个音乐播放器软件项目【业务逻辑开发】
#万物oop 上一章文章 我们做了音乐播放器 数据表的创建。参加地址: https://blog.csdn.net/Drug_/article/details/145093705 今天分享的这篇文章就是根据数据表 来写 业务逻辑 。 今天我们主要是实现管理后台的功能。 对于这篇文章 的理解 需要小伙伴有 springbo…...
配置正确spring-boot工程启动的时候报错dynamic-datasource Please check the setting of primary
一个两年没有碰的spring-boot工程,启动时报错。因为用了baomidou的多源数据库配置,因此启动时报错primary没有正确配置。经过检查,确定配置文件配置正确。 报错原因是没有读到正确的配置文件。pom文件里的resources标签重定义,把…...
Freeswitch使用media_bug能力实现回铃音检测
利用freeswitch的media bug能力来在智能外呼时通过websocket对接智能中心的声音检测接口,来实现回铃音检测,来判断用户当前是否已响应,拒接,关机等。 1.回铃音处理流程 2.模块源码目录结构 首先新建一个freeswitch的源码的src/a…...
Kubernetes(k8s)和Docker Compose本质区别
Kubernetes(简称 k8s)和 Docker Compose 是容器编排领域的两大重要工具,虽然它们都用于管理和编排容器化应用,但在设计目标、功能特性、使用场景和复杂度上存在显著差异。以下将从多个方面详细探讨 Kubernetes 和 Docker Compose …...
OSI七层协议——分层网络协议
OSI七层协议,顾名思义,分为七层,实际上七层是不存在的,是人为的进行划分,让人更好的理解 七层协议包括,物理层(我),数据链路层(据),网络层(网),传输层(传输),会话层(会),表示层(表),应用层(用)(记忆口诀->我会用表…...
RabbitMQ 客户端 连接、发送、接收处理消息
RabbitMQ 客户端 连接、发送、接收处理消息 一. RabbitMQ 的机制跟 Tcp、Udp、Http 这种还不太一样 RabbitMQ 服务,不是像其他服务器一样,负责逻辑处理,然后转发给客户端 而是所有客户端想要向 RabbitMQ服务发送消息, 第一步&a…...
SQL ON与WHERE区别
在 MySQL 中,ON 和 WHERE 都用于过滤数据,但它们的使用场景和作用有所不同,尤其是在涉及 JOIN 操作时。下面通过具体的例子来说明它们的区别。 1. ON 的作用 ON 用于指定表之间的连接条件,决定哪些行应该被连接在一起。它在 JOI…...
[创业之路-254]:《华为数字化转型之道》-1-华为是一个由客户需求牵引、高度数字化、高度智能化、由无数个闭环流程组成的价值创造、评估、分配系统。
前言: 华为是一个由客户需求牵引、高度数字化、高度智能化、由无数个闭环流程组成的价值创造、评估、分配系统。华为的流程大到战略,小到日常工作,是由无数个自我调节自我优化的数字化闭环控制流程组成,大闭环套小闭环࿰…...
免费为企业IT规划WSUS:Windows Server 更新服务 (WSUS) 之快速入门教程(一)
哈喽大家好,欢迎来到虚拟化时代君(XNHCYL),收不到通知请将我点击星标!“ 大家好,我是虚拟化时代君,一位潜心于互联网的技术宅男。这里每天为你分享各种你感兴趣的技术、教程、软件、资源、福利…...
异步任务与定时任务
一、异步任务 基于TaskExecutionAutoConfiguration配置类中,注册的ThreadPoolTaskExecutor线程池对象进行异步任务执行。 (一)手动执行异步任务 在yml中配置线程池参数 spring: task:execution:pool:core-size: 5 # 核心线程数max-size: 20 # 最大线…...
大模型WebUI:Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(5)
大模型WebUI:Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(5) 前言本篇摘要11. Chatbot:融合大模型的多模态聊天机器人11.5 Chatbot的特殊Events11.5.1 各事件总演示11.5.2 详解.undo、.retry、.like和.edit…...
32单片机综合应用案例——基于GPS的车辆追踪器(三)(内附详细代码讲解!!!)
困难不会永远存在,只要你勇于面对,坚持努力,就一定能够战胜一切困难。每一次挑战都是一次成长的机会,不要害怕失败,失败是成功之母。只有经历过失败,你才能更加明白自己的不足,并不断改进自己&a…...
扣除价格因素与剔除季节性因素:统计数据中的“真实”增长(中英双语)
扣除价格因素与剔除季节性因素:统计数据中的“真实”增长 在经济统计分析中,我们经常会听到“扣除价格因素”和“剔除季节性因素”这两个概念。这两者都是为了排除外部干扰因素,真实反映经济活动的增长情况。它们分别针对价格波动和季节性波…...
网卡接收报文的过程
网卡接收报文的过程通常包括以下几个关键步骤: 1. 硬件接收: • 网卡硬件首先接收到从网络传输过来的数据包。网络接口卡(NIC)负责将接收到的电信号转换为数字信号,并存储到一个硬件缓冲区中。 2. DMA传输ÿ…...
Windows图形界面(GUI)-QT-C/C++ - QT 对话窗口
公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 模态对话框 非模态对话框 文件对话框 基本概念 静态函数 常见属性 颜色对话框 基本概念 静态函数 常见属性 字体对话框 基本概念 静态函数 常见属性 输入对话框 基本概念 …...
bypass--2025春秋杯冬季赛
漏洞点 题目不难,这个循环赋值的结束条件是s[i]0,并且s和key再栈上的位置是挨着的 那么很容易想到,第二次循环赋值的时候,有一个溢出,溢出部分的值是第一次写入的key的值。 那么基本思路就是,利用这段溢出…...
学习微信小程序的下拉列表控件-picker
1、创建一个空白工程 2、index.wxml中写上picker布局: <!--index.wxml--> <view class"container"><picker mode"selector" range"{{array}}" bindchange"bindPickerChange"><view class"pick…...
【17】Word:林楚楠-供应链❗
目录 题目 NO1.2 NO3 NO4 NO5 NO6 NO7 NO89 题目 NO1.2 另存为:文件→另存为→文档→文件名/考生文件夹F12/FnF12→文件名/考生文件夹 插入→分节符→文本框→输入文件→排版_居中对齐→间距/回车去掉文本框的边框→选中文本框→格式:形状轮廓…...
父子盒子滑动事件穿透问题
问题描述 当父子盒子都有滚动条时,在子盒子内滚动时,父盒子滚动子盒子无法滚动,直到父盒子滚动到底部,子盒子才滚动 解决 如果是vue的项目,直接在子盒子上添加 wheel.stop""...
vue-amap、leaflet、融汇 离线地图瓦片使用情况分析
vue-amap: vue3写的,使用文档 -> 文档地址 <-的离线jsApi里的demo,发现 tile-url不能读取本地项目文件夹里的瓦片,文档里写的其实还是要互联网读取的高德瓦片......... 作者在git库回复tile-url要么放项目里使用绝对路…...
leetcode - 1055. Shortest Way to Form String
Description A subsequence of a string is a new string that is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (i.e., “ace” is a subsequence of “abcd…...
【HarmonyOS之旅】基于ArkTS开发(二) -> UI开发三
目录 1 -> 绘制图形 1.1 -> 绘制基本几何图形 1.2 -> 绘制自定义几何图形 2 -> 添加动画效果 2.1 -> animateTo实现闪屏动画 2.2 -> 页面转场动画 3 -> 常见组件说明 1 -> 绘制图形 绘制能力主要是通过框架提供的绘制组件来支撑,支…...
RabbitMQ 进阶
文章目录 一、发送者的可靠性 1.1 生产者重试机制:1.2 生产者确认机制: 1.2.1 开启生产者确认:1.2.2 定义 ReturnCallback:1.2.3 定义 ConfirmCallback: 二、MQ 的可靠性 2.1 数据持久化: 2.1.1 交换机持…...
RabbitMQ---TTL与死信
(一)TTL 1.TTL概念 TTL又叫过期时间 RabbitMQ可以对队列和消息设置TTL,当消息到达过期时间还没有被消费时就会自动删除 注:这里我们说的对队列设置TTL,是对队列上的消息设置TTL并不是对队列本身,不是说队列过期时间…...
uniapp(小程序、app、微信公众号、H5)预览下载文件(pdf)
1. 小程序、app 在uniapp开发小程序环境或者app环境中,都可以使用以下方式预览文件 之前其实写过一篇,就是使用uniapp官网提供文件下载、文件保存、文件打开的API, uniapp文件下载 感兴趣也可以去看下 uni.downloadFile({// baseURL 是...
Spring Boot经典面试题及答案
一、Spring Boot基础知识 什么是Spring Boot? 答案: Spring Boot是Spring开源组织下的子项目,是Spring组件一站式解决方案。它简化了Spring应用程序的初始化和开发过程,通过“约定大于配置”的原则,减少了手动配置的繁…...
usb通过hdc连接鸿蒙next的常用指令
参考官方 注册报名https://www.hiascend.com/developer/activities/details/44de441ef599450596131c8cb52f7f8c/signup?channelCodeS1&recommended496144 hdc-调试命令-调测调优-系统 - 华为HarmonyOS开发者https://developer.huawei.com/consumer/cn/doc/harmonyos-guid…...
FPGA:Quartus软件与操作系统版本对照表
文章目录 1.软件概述2.软件版本3.设计流程4.支持的设备5.新特性6.版本对照 1.软件概述 Quartus软件是由英特尔(Intel)公司开发的一款功能强大的FPGA(现场可编程逻辑门阵列)设计工具,广泛应用于数字电路设计、仿真、综…...
RustDesk ID更新脚本
RustDesk ID更新脚本 此PowerShell脚本自动更新RustDesk ID和密码,并将信息安全地存储在Bitwarden中。 特点 使用以下选项更新RustDesk ID: 使用系统主机名生成一个随机的9位数输入自定义值 为RustDesk生成新的随机密码将RustDesk ID和密码安全地存储…...
[C语言]字符串分离
题目 从标准输入流(控制台)中获取一行字符串 str,字符串中可能会存在空格,现在需要将字符串进行分离,规则如下: (1)将 str 中位于 偶数下标 的元素放置在字符串 str1 之中 (2)将 str 中位于 奇数下标 的…...
-bash: /java: cannot execute binary file
在linux安装jdk报错 -bash: /java: cannot execute binary file 原因是jdk安装包和linux的不一致 程序员的面试宝典,一个免费的刷题平台...
Python绘制数据地图-GeoPandas入门
使用GeoPandas绘制数据地图是一种非常直观且强大的数据可视化方法。GeoPandas是一个Python库,专门用于处理地理空间数据,它建立在Pandas和Shapely库之上,并集成了matplotlib、seaborn等绘图库的功能。 下面是一个简单的入门教程,…...
CVPR 2024 图像处理方向总汇(图像去噪、图像增强、图像分割和图像恢复等)
1、Image Progress(图像处理) 去鬼影 Generating Content for HDR Deghosting from Frequency View去阴影 HomoFormer: Homogenized Transformer for Image Shadow Removal去模糊 Unsupervised Blind Image Deblurring Based on Self-EnhancementLatency Correction for E…...
c++ string
1 sting 基本概念 string 基本概念 本质:string是c风格的字符串,而string 本质上是一个类string 和char* 的区别: char * 是一个指针 string 是一个类,类内部封装了char*,管理这个字符串,是一个char* 数组…...
tomcat文件目录讲解
目录的用处 bin:tomcat的可执行命令,比如:tomcat的启动停止命令,也包含其他命令以及.bat(Windows执行的命令)和.sh(Linux操作系统执行的命令)文件config:关于tomcat的配置࿰…...
博客搭建 — GitHub Pages 部署
关于 GitHub Pages GitHub Pages 是一项静态站点托管服务,它直接从 GitHub 上的仓库获取 HTML、CSS 和 JavaScript 文件,通过构建过程运行文件,然后发布网站。 本文最终效果是搭建出一个域名为 https://<user>.github.io 的网站 创建…...