spring-boot-2.0.3不一樣系列之源碼篇 - run方法(二)之prepareEnvironment,絕對有值得你看的地方

来源:https://www.cnblogs.com/youzhibing/archive/2018/09/17/9622441.html
-Advertisement-
Play Games

前言 此系列是針對springboot的啟動,旨在於和大家一起來看看springboot啟動的過程中到底做了一些什麼事。如果大家對springboot的源碼有所研究,可以挑些自己感興趣或者對自己有幫助的看;但是如果大家沒有研究過springboot的源碼,不知道springboot在啟動過程中做了些 ...


前言

  此系列是針對springboot的啟動,旨在於和大家一起來看看springboot啟動的過程中到底做了一些什麼事。如果大家對springboot的源碼有所研究,可以挑些自己感興趣或者對自己有幫助的看;但是如果大家沒有研究過springboot的源碼,不知道springboot在啟動過程中做了些什麼,那麼我建議大家從頭開始一篇一篇按順序讀該系列,不至於從中途插入,看的有些懵懂。當然,文中講的不對的地方也歡迎大家指出,有待改善的地方也希望大家不吝賜教。老規矩:一周至少一更,中途會不定期的更新一些其他的博客,可能是springboot的源碼,也可能是其他的源碼解析,也有可能是其他的。

  路漫漫其修遠兮,吾將上下而求索!

  github:https://github.com/youzhibing

  碼雲(gitee):https://gitee.com/youzhibing

前情回顧

  大家還記得上篇博文講了什麼嗎,或者說大家知道上篇博文講了什麼嗎。這裡幫大家做個簡單回顧,主要是兩個方法

  1、getApplicationListeners

    過濾出於與ApplicationStartingEvent匹配的監聽器,過濾出的結果是:LoggingApplicationListener、BackgroundPreinitializer、DelegatingApplicationListener、LiquibaseServiceLocatorApplicationListener、EnableEncryptablePropertiesBeanFactoryPostProcessor五種類型的實例

  2、invokeListener

    調用getApplicationListeners過濾出的五個實例的onApplicationEvent方法,5個onApplicationEvent都做了啥,大體如下

      LoggingApplicationListener:檢測正在使用的日誌系統,預設是logback,支持3種,優先順序從高到低:logback > log4j > javalog。此時日誌系統還沒有初始化

      BackgroundPreinitializer:另起一個後臺線程觸發那些耗時的初始化,包括驗證器、消息轉換器等等,具體是哪些初始化見下代碼,有興趣的朋友可去跟下

private void performPreinitialization() {
    try {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                runSafely(new ConversionServiceInitializer());
                runSafely(new ValidationInitializer());
                runSafely(new MessageConverterInitializer());
                runSafely(new MBeanFactoryInitializer());
                runSafely(new JacksonInitializer());
                runSafely(new CharsetInitializer());
                preinitializationComplete.countDown();
            }

            public void runSafely(Runnable runnable) {
                try {
                    runnable.run();
                }
                catch (Throwable ex) {
                    // Ignore
                }
            }

        }, "background-preinit");
        thread.start();
    }
    catch (Exception ex) {
        // This will fail on GAE where creating threads is prohibited. We can safely
        // continue but startup will be slightly slower as the initialization will now
        // happen on the main thread.
        preinitializationComplete.countDown();
    }
}
View Code

      DelegatingApplicationListener:此時什麼也沒做

      LiquibaseServiceLocatorApplicationListener:此時什麼也沒做

      EnableEncryptablePropertiesBeanFactoryPostProcessor:此時僅僅列印了一句日誌,其他什麼也沒做

  簡單點來說,就是檢測正在使用的日誌系統、另起一個後臺線程執行耗時的初始化

prepareEnvironment

  講prepareEnvironment之前,我們先來看看我們的戰績,我們對run方法完成了多少源碼解讀

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
    // 秒錶,用於記錄啟動時間;記錄每個任務的時間,最後會輸出每個任務的總費時
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // spring應用上下文,也就是我們所說的spring根容器
    ConfigurableApplicationContext context = null;
    // 自定義SpringApplication啟動錯誤的回調介面
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 設置jdk系統屬性java.awt.headless,預設情況為true即開啟;更多java.awt.headless信息大家可以去查閱資料,這不是本文重點
    configureHeadlessProperty();
    // 獲取啟動時監聽器(EventPublishingRunListener實例)
    SpringApplicationRunListeners listeners = getRunListeners(args)
    // 觸發啟動事件,啟動監聽器會被調用,一共5個監聽器被調用
    listeners.starting(); 
    try {
        // 參數封裝,也就是在命令行下啟動應用帶的參數,如--server.port=9000
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // 準備環境,這是本文的重點
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}
View Code

  如果光從run方法的源代碼比例來看,我們已經完成了將近一半的源碼解析了,可真是這樣的嗎?我們先別急著否定,就當我們快完成一半的解析了(笑而不語)。既然我們都快完成了一半了,那麼我們加把勁,今天來完成“這一半”。

  prepareEnvironment按字面意思就是準備環境,那到底準備什麼環境呢?我們一起來慢慢看,其源代碼如下

// 準備環境
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment 創建和配置環境

    // 獲取或創建環境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置環境:配置PropertySources和activeProfiles
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // listeners環境準備(就是廣播ApplicationEnvironmentPreparedEvent事件)。還記得這個listeners怎麼來的嗎?
    listeners.environmentPrepared(environment);
    // 將環境綁定到SpringApplication
    bindToSpringApplication(environment);
    // 如果是非web環境,將環境轉換成StandardEnvironment
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    // 配置PropertySources對它自己的遞歸依賴
    ConfigurationPropertySources.attach(environment);
    return environment;
}
View Code

  內容不多,我們就一行一行的來跟源代碼

  getOrCreateEnvironment

    從字面上看,這個方法的作用就是獲取或創建環境,應該就是存在就直接返回,不存在則創建一個並返回。

// 獲取或創建Environment,很顯然我們這裡是創建StandardServletEnvironment
private ConfigurableEnvironment getOrCreateEnvironment() {
    // 存在則直接返回
    if (this.environment != null) {
        return this.environment;
    }
    // 根據webApplicationType創建對應的Environment
    // webApplicationType的值還記得在哪獲取到的嗎?不知道的請去看我的springboot源碼一
    if (this.webApplicationType == WebApplicationType.SERVLET) {
        return new StandardServletEnvironment();    // 標準的Servlet環境,也就是我們說的web環境
    }
    return new StandardEnvironment();                // 標準環境,非web環境
}
View Code

    還記得this.webApplicationType的值是什麼嗎,不記得的點這裡尋找答案,在我的案例中其值就是WebApplicationType.SERVLET,那麼很顯然創建一個StandardServletEnvironment對象返回。

    StandardServletEnvironment類圖

      StandardServletEnvironment繼承自StandardEnvironment,也就是web環境是特殊的非web環境,有點類似正方形是特殊的長方形一樣。AbstractEnvironment的構造方法調用了customizePropertySources方法,也就說StandardServletEnvironment在實例化的時候,他的customizePropertySources會被調用,customizePropertySources源代碼如下

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
    }
    super.customizePropertySources(propertySources);
}
View Code

      從上圖中可以看出StandardServletEnvironment的customizePropertySources方法只是往propertySources中添加了兩個名字叫servletConfigInitParams、servletContextInitParams的StubPropertySource對象,沒更多的操作;而StandardEnvironment的customizePropertySources方法則往propertySources中添加了兩個包含java系統屬性和操作系統環境變數的兩個對象:MapPropertySource和SystemEnvironmentPropertySource。

    總結下,getOrCreateEnvironment方法創建並返回了一個環境:StandardServletEnvironment,該環境目前包含的內容如下

  configureEnvironment

protected void configureEnvironment(ConfigurableEnvironment environment,
    String[] args) {
    // 配置PropertySources
    configurePropertySources(environment, args);
    // 配置Profiles
    configureProfiles(environment, args);
}
View Code

    從源碼看,將配置任務按順序委托給configurePropertySources和configureProfiles,那麼我們來看看這兩個方法

    configurePropertySources

protected void configurePropertySources(ConfigurableEnvironment environment,
        String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    // 此時defaultProperties還是null,可能後續過程會初始化,具體詳情請期待後續的博文
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        // 存在的話將其放到最後位置
        sources.addLast(
                new MapPropertySource("defaultProperties", this.defaultProperties));
    }
    // 存在命令行參數,則解析它並封裝進SimpleCommandLinePropertySource對象,同時將此對象放到sources的第一位置(優先順序最高)
    if (this.addCommandLineProperties && args.length > 0) {
        String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
        if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(new SimpleCommandLinePropertySource(
                    "springApplicationCommandLineArgs", args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        }
        else {
            // 將其放到第一位置
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }
}
View Code

      註釋說明是增加、移除或者重排序應用環境中的PropertySource。就目前而言,如果有命令行參數則新增封裝命令行參數的PropertySource,並將它放到sources的第一位置。

    configureProfiles

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    // 保證environment的activeProfiles屬性被初始化了。從PropertySources中查找spring.profiles.active屬性
    // 存在則將其值添加activeProfiles集合中。我們可以通過命令行參數指定該參數,但我們沒有指定
    environment.getActiveProfiles(); // ensure they are initialized
    // But these ones should go first (last wins in a property key clash)
    // 如果存在其他的Profiles,則將這些Profiles放到第一的位置。此時沒有,後面有沒有後面再說
    Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
View Code

      配置應用環境中的哪些配置文件處於激活狀態(或預設激活)。可以通過spring.profiles.active屬性在配置文件處理期間激活其他配置文件。說的簡單點就是設置哪些Profiles是激活的。

    這3個方法都是protected,也就說鼓勵被重寫。重寫configureEnvironment可以完全控制自定義環境,或者重寫configurePropertySources或configureProfiles,進行更細粒度控制。

  listeners.environmentPrepared(environment)

public void environmentPrepared(ConfigurableEnvironment environment) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.environmentPrepared(environment);
    }
}
View Code

    這個代碼有沒有很熟悉?,不清楚的點這裡,查看其中的listeners.starting()。上次廣播的是ApplicationStartingEvent事件,而這次廣播的是ApplicationEnvironmentPreparedEvent事件。這裡就不和大家一起跟源代碼了,大家自行去跟哦。我在這總結下:

    過濾出的與ApplicationEnvironmentPreparedEvent相匹配的監聽器列表如下,他們的onApplicationEvent會被調用,大致做了以下事情:  

      ConfigFileApplicationListener
        1、載入EnvironmentPostProcessor列表,仍然是從META-INF/spring.factories中載入(在SpringApplication實例化的時候已經載入了,這次是從緩存中讀取),然後實例化;
        2、將自己也加入EnvironmentPostProcessor列表;ConfigFileApplicationListener實現了EnvironmentPostProcessor介面,可以看它的類圖。
        3、對EnvironmentPostProcessor列表進行排序;排序之後,EnvironmentPostProcessor列表圖如下:
        4、遍歷EnvironmentPostProcessor列表,調用每個EnvironmentPostProcessor的postProcessEnvironment方法

          SystemEnvironmentPropertySourceEnvironmentPostProcessor

            將propertySourceList中名為systemEnvironment的SystemEnvironmentPropertySource對象替換成OriginAwareSystemEnvironmentPropertySource對象,source未變,還是SystemEnvironmentPropertySource對象的source;OriginAwareSystemEnvironmentPropertySource是SystemEnvironmentPropertySourceEnvironmentPostProcessor的靜態內部類,且繼承自SystemEnvironmentPropertySource。具體這麼替換出於什麼目的,便於原點查找?暫時還未知

          SpringApplicationJsonEnvironmentPostProcessor

            spring.application.json(或SPRING_APPLICATION_JSON)是設置在系統屬性或系統環境中;

            如果spring.application.json(或SPRING_APPLICATION_JSON)有配置,那麼給environment的propertySourceList增加JsonPropertySource,並將JsonPropertySource放到名叫systemProperties的PropertySource前;目前沒有配置,那麼此環境後處理器相當於什麼也沒做。

          CloudFoundryVcapEnvironmentPostProcessor

            雲平臺是否激活,激活了則給environment的propertySourceList增加名為vcap的PropertiesPropertySource對象,並將此對象放到命令行參數PropertySource(名叫commandLineArgs)後。很顯然,我們沒有激活雲平臺,那麼此環境後處理器相當於什麼也沒做。

          ConfigFileApplicationListener

            添加名叫random的RandomValuePropertySource到名叫systemEnvironment的PropertySource後;

            並初始化Profiles;初始化PropertiesPropertySourceLoader和YamlPropertySourceLoader這兩個載入器從file:./config/,file:./,classpath:/config/,classpath:/路徑下載入配置文件,PropertiesPropertySourceLoader載入配置文件application.xml和application.properties,YamlPropertySourceLoader載入配置文件application.yml和application.yaml。目前我們之後classpath:/路徑下有個application.yml配置文件,將其屬性配置封裝進了一個名叫applicationConfig:[]的OriginTrackedMapPropertySource中,並將此對象放到了propertySourceList的最後。

      AnsiOutputApplicationListener

        設置ansi輸出,將AnsiOutput的屬性enabled設置成ALWAYS,即允許ANSI-colored輸出

      LoggingApplicationListener

        初始化日誌系統
      ClasspathLoggingApplicationListener:沒開啟調試,所以什麼也沒做
      BackgroundPreinitializer:此時什麼也沒做
      DelegatingApplicationListener:此時什麼也沒做,因為環境中沒有配置context.listener.classes屬性
      FileEncodingApplicationListener:此時什麼也沒做,環境中沒有spring.mandatory-file-encoding屬性

      EnableEncryptablePropertiesBeanFactoryPostProcessor:此時什麼也沒有做

    environmentPrepared方法會觸發所有監聽了ApplicationEnvironmentPreparedEvent事件的監聽器,這些監聽器目前主要新增了兩個PropertySource:RandomValuePropertySource和OriginTrackedMapPropertySource,這個OriginTrackedMapPropertySource一般就是我們應用的配置文件application.yml(application.properties)。

  bindToSpringApplication(environment)

/**
 * Bind the environment to the {@link SpringApplication}.
 * @param environment the environment to bind
 */
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
    try {
        Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
    }
    catch (Exception ex) {
        throw new IllegalStateException("Cannot bind to SpringApplication", ex);
    }
}
View Code

    代碼比較簡單,應該就是將environment綁定到SpringApplication,可我跟進去發現沒有將environment綁定到SpringApplication,執行完bindToSpringApplication方法後,SpringApplication的屬性environment仍是null,這我就有點懵圈了,那這個方法到底有什麼用,有知道的朋友嗎

  ConfigurationPropertySources.attach(environment)

public static void attach(Environment environment) {
    // 判斷environment是否是ConfigurableEnvironment的實例
    Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
    // 從environment獲取PropertySources
    MutablePropertySources sources = ((ConfigurableEnvironment) environment)
            .getPropertySources();
    PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
    if (attached != null && attached.getSource() != sources) {
        sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
        attached = null;
    }
    if (attached == null) {
        // 將sources封裝成ConfigurationPropertySourcesPropertySource對象,並把這個對象放到sources的第一位置
        sources.addFirst(new ConfigurationPropertySourcesPropertySource(
                ATTACHED_PROPERTY_SOURCE_NAME,
                new SpringConfigurationPropertySources(sources)));
    }
}
View Code

    將sources封裝成了一個名叫configurationProperties的ConfigurationPropertySourcesPropertySource對象,並把這個對象放到了sources的第一個位置。SpringConfigurationPropertySources是一個將MutablePropertySources轉換成ConfigurationPropertySources的適配器。這就相當於sources的第一個元素是它自己,形成了一個自己對自己的遞歸依賴,這麼做的目的是什麼,暫時還不得而知,也許後面會有所體現,這裡先當做一個疑問留著

  prepareEnvironment執行完後,此時environment中的內容如下:(重點看下propertySourceList)

總結

  1、profile

    直譯的意思總感覺不對(其作用就是指定激活的配置文件,可以區分環境來載入不同的配置),所以文中沒有對其進行翻譯,直接採用的原單詞。有更好理解的小伙伴可以在評論區提供翻譯。

  2、資源文件

    載入外部化配置的資源到environment,Spring Boot設計了一個非常特別的PropertySource順序,以允許對屬性值進行合理的覆蓋。具體有哪些外部化配置,以及他們的優先順序情況可以參考《Spring Boot Reference Guide》的第24章節

  3、prepareEnvironment方法到底做了什麼

    載入外部化配置資源到environment,包括命令行參數、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties)等;

    初始化日誌系統。

參考

  Spring Boot Reference Guide


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 第一步先安裝nmp 在node.js的官網下載即可。 第二步 直接安裝剛下載好的node.js即可,(這裡建議不要修改node.js的安裝路徑),傻瓜式直接下一步即可 檢測是否安裝成功: 在cmd的控制台直接輸入node -v 如果出現這樣的界面恭喜你node.js安裝成功 (利用 win + r ...
  • 開發者的javascript造詣取決於對【動態】和【非同步】這兩個詞的理解水平。 一. 一道考察非同步知識的面試題 題目是這樣的,要求寫出下麵代碼的輸出: 如果沒有詳細鑽研過非同步隊列,答對的可能性很低。題目的考察點很明確,就是 中最核心的特點之一的【非同步】,瞭解了原理以後,你就會明白 中聲稱的“無阻塞” ...
  • 首發鏈接:https://bbs.huaweicloud.com/blogs/70f69ca4953111e89fc57ca23e93a89f 《一統江湖的大前端》系列是自己的前端學習筆記,旨在介紹javascript在非網頁開發領域的應用案例和發現各類好玩的js庫,不定期更新。如果你對前端的理解還 ...
  • 很精彩的一次內部分享,介紹了大部分的GC演算法理論知識,JVM博大精深,本篇文章只是結合本次內部分享總結的一些理論知識,如果有大佬有疑問,歡迎留言指出! Concurrent:併發,程式一邊運行一邊做GC Parallel:並行,一塊區域,一個人做清掃,需要100s,但是把區域分成兩塊,用兩個人掃,時 ...
  • 電腦端登錄公眾號管理後臺,【添加功能插件】開通客服功能,輸入"人工客服"接入客服熱線 底部有我的微信二維碼,如有問題,可加好友進行技術交流! ​ ​ ​ ​ ​ ​ ​ weixin-java-mp集成微信公眾號自帶客服功能代碼 增加TextBuilder.java文件 內容如下: public c ...
  • 博客園首頁是需要分享乾貨的地方,今天早上寫的《HRMS(人力資源管理系統)-從單機應用到SaaS應用-系統介紹》內容下架了,所以我就按照相關規定,只分享乾貨,我把之前寫完的架構設計相關知識的內容整理髮布上來。這次主要分享一下在架構設計過程中涉及的基礎知識,主要是涵蓋系統架構方法、架構模式及設計模式,... ...
  • 上周發佈的《2018,全新出發(全力推動實現住有所居》文章,其中記錄了個人在這5年過程中的成長和收穫,有幸認識了不少博客園的朋友,大家一起學習交流,在這個過程當中好多朋友提出SaaS系統如何設計,架構方面如何下手,在這5年的過程中我參與規劃設計了很多的SaaS系統其中有不少的坑和痛苦的經驗,特別是在... ...
  • RabbitMQ實戰教程(一) : 安裝及相關概念介紹 由於本人只在 安裝 服務 ,其他系統安裝暫時沒有涉及,如果有需要請自行搜索安裝教程. . . 1 . Windows 安裝 安裝需要先安裝 ,再安裝 第一步:安裝 ,由於 是用 編寫的,所以在安裝 之前要先安裝 下載地址: 下載最新版本即可,例 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...