能源轉型委員會說,到2050年中國人均經濟增長和生活水平將比目前提高兩倍

能源轉型委員會說,到2050年中國人均經濟增長和生活水平將比目前提高兩倍,最終能源需求將減少27%,這需要減少使用鋼鐵和水泥,更多地循環使用塑料等材料,以及採取節能措施,包括運輸電氣化。

該報告其他研究結果包括:到2050年,因發電過程中能量損耗減少,中國一次能源​​總需求可能減少45%;化石能源需求可能下降90%以上,而非化石能源需求將增加至3.4倍;在電網具有靈活性且能儲存電力的情況下,近70%的發電量將由風能和太陽能提供。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

無塵擦拭布去污效果如何?

新北市轉軸選用參考標準?

※影響示波器測試準確度的五大因素

※何謂電解水飲水機全自動飲水機 ? 與一般飲水機差異在哪?

噴霧洗滌塔實際應用案例分享

日本鹿兒島市28日傳出,「新日本科學」公司鹿兒島市動物實驗設施內,一名技術員被診斷出患有「皰疹B病毒感染症」

摘錄自2019年11月29日ETtoday綜合報導

日本鹿兒島市28日傳出,「新日本科學」公司鹿兒島市動物實驗設施內,一名技術員被診斷出患有「皰疹B病毒感染症」,是日本境內第一例,而這個相當罕見的病症,在全球僅有約50例確診。

根據台灣衛生福利部疾病管制署網站解釋,皰疹B病毒為人畜共通傳染性病原體,會對感染者的中樞神經系統進行破壞。皰疹B病毒於1932年在美國出現第一例,研究人員B博士(Dr. B)當時被一隻看起來相當健康的恆河猴咬傷,15天後便因急性腦脊髓炎死亡,病毒的名稱也採B博士名字命名為「皰疹B病毒」。皰疹B病毒並無特別季節變化,一年四季的傳染能力同樣活躍。

日本《讀賣新聞》、《中央社》報導,受託開發醫藥品的「新日本科學公司」總社位於東京,並在鹿兒島市設有一處研究設施;一名實驗業務助理在對猴子進行安全性調查時,不慎感染了皰疹B病毒感染症(Herpesvirus B Infection),今年2月出現頭痛、發燒等症狀,但直到8月底轉願接受基因檢查,才在11月上旬被驗出確診。

這類流行於獼猴、日本獼猴等獼猴屬猿猴間的病毒不會經由空氣傳染,主要透過抓、咬受傷後進入人體,潛伏期約2至5週,發病會出現發燒、麻痺等症狀。

鹿兒島市和新日本科學公司並未對外公佈實驗業務助理的姓名,也未透露身體狀況、性別和年齡。報導指出,該名技術員平常直接接觸猿猴的機會很少,就診時也沒有被猿猴抓、咬的痕跡,目前正進一步調查染病途徑。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

廢氣洗滌塔,叫得動, 找得到的專業廠商‎

※選擇示波器的10 項考量因素

無塵擦拭紙各大品牌廠商販售比價網!

飲水機品牌迷思?? 有牌代表最好,錯! 必須先了解用途才能買對開水機!

※什麼規格的隔熱紙能有效提升冷房效果?

※快速搞懂「塑膠射出原理」

日本東北電力公司(Tohoku Electric Power)在27日表示,已經獲得日本核監管局的初步批准

摘錄自2019年11月29日ETtoday報導

日本東北電力公司(Tohoku Electric Power)在27日表示,已經獲得日本核監管局的初步批准,重新啟動女川核電廠(Onagawa nuclear powerplant)2號反應爐。日本2011年大地震時,女川核電廠是最接近震央的一座核電廠,當時受到地震及海嘯的破壞而關閉,目前還需要當地居民的同意才能重新啟動。

女川核電廠在311地震時被海嘯淹沒,但裡面的冷卻系統倖存下來,使反應爐免遭受類似於東京電力福島第一核電廠發生的慘況。

東北電力公司預計為女川核電廠的安全升級投資3,400億日元(約新台幣950億元),將電廠防波堤的海拔高度提高到29公尺,這將會是日本各核電廠中最高的一座防波堤。若重新啟動2號反應爐,每年將為公用事業節省350億日元(約新台幣98億元)的燃料成本。

日本在福島核災之前有54座核反應爐在營運,提供日本三分之一的電力,災難突顯了營運和監管方面的缺陷之後,所有的核反應爐若要重啟,都必須按照新的標準許可。

日本的核能安全問題在最近才又被提起,天主教教宗方濟各在上週末訪問日本時表示,除非可以真正保障人民的安全,否則不該再使用核能。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

AVX代理商NICHICON代理商授權有哪幾家?

※怎麼檢測NBR手套規格是否有符合國家認証規定?

大樓隔熱紙施工分享說明,教你如何善用空間裝潢、設計 !

※買不起高檔茶葉,精緻包裝茶葉罐,也能撐場面!

塑膠射出模具射出成型過程大公開 !

暴雨今天(27日)襲擊洛杉磯北部地區,讓與野火奮鬥的救災人員得以舒緩

摘錄自2019年11月28日中央通訊社綜合報導

暴雨今天(27日)襲擊洛杉磯北部地區,讓與野火奮鬥的救災人員得以舒緩,但也帶來新的泥石流危險,迫使當局向17.5平方公里地區內的居民發布疏散警告。

「華爾街日報」(Wall Street Journal)報導,根據與消防員合作的氣象學家,190毫米的大雨凌晨1時開始襲擊加州聖巴巴拉(Santa Barbara),預料將降下更多雨勢。

因可能發生泥石流,聖巴巴拉郡警察局向凱夫大火(Cave Fire)及太平洋中間17.5平方公里內的居民發布疏散警告。泥石流可能發生在大雨過後、泥沙土石鬆軟的野火區域,並以危險的速度向下滑。

當局表示,本週到今天下午沒有發生泥石流,但威脅可能持續整個冬天,直到灌木叢重新開始在火災過後的地區生長。

聖巴巴拉郡消防局的蓋裡(Tim Gailey)警告消防員,在路上保持警惕,特別是許多火災撤離者今天獲準返家時。他在大雨中表示:「會產生逕流、泥濘、砂石與樹木之類的東西,一起流到路上。」

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※找工作! 想知道堆高機駕駛日薪是多少嗎? 哪裡有職缺?幫你快速媒合

※隨時健康喝好水,高品質飲水機推薦,優質安全有把關

新北市轉軸代工廠商詢價平台?

防爆隔熱紙規格資訊說明

※好的茗茶,更需要密封性高的茶葉罐,才能留住香氣!

※【找人才】台北塑膠射出廠徵選技師,薪資優,福利佳

歐洲議會27日在法國史特拉斯堡表決,以壓倒性票數通過前德國國防部長馮德萊恩(Ursula von der Leyen)領導的新一屆歐盟執行委員會委員名單

摘錄自2019年11月27日聯合新聞網報導

歐洲議會27日在法國史特拉斯堡表決,以壓倒性票數通過前德國國防部長馮德萊恩(Ursula von der Leyen)領導的新一屆歐盟執行委員會委員名單。新一屆歐盟執委會將於12月1日就職,任期5年。

馮德萊恩是德國總理梅克爾的親密盟友,她先前7月份已經獲得歐洲議會多數議員支持,準備接替即將卸任的現任執委會主席容克(Jean-Claude Juncker)。歐洲議會27日再針對新一屆歐盟執委會名單進行表決,結果以461票同意、157票反對和89票棄權的結果壓倒性通過。

馮德萊恩致詞時表示,新一屆執委會的首要任務是面對氣候變遷、難民等問題。她表示,未來目標是讓歐盟成為「全球標準的製定者」;同時承諾要在就職100天內,針對「2050年碳中和」目標與各國達成協議。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※有廠商專門客製化橡膠製品嗎?

噴霧洗滌塔實際應用案例分享

※票選推薦煮婦最愛手壓封口機,省荷包不犧牲品質

※選購空壓機需注意八大事項 !

※DIY自行施工建築隔熱紙,簡易教學大公開 !

※選擇好的茶葉罐,有效地保持茶葉的鮮度與風味!

深具影響力的「時尚雜誌」(Vogue)總編輯溫圖爾(Anna Wintour)表示

摘錄自2019年11月28日中央通訊社綜合報導

深具影響力的「時尚雜誌」(Vogue)總編輯溫圖爾(Anna Wintour)表示,應該珍惜衣服、重覆穿著,甚至傳遞給下一代。她呼籲時尚界多關注永續性,少一些丟棄文化。

溫圖爾是公認時尚界最具影響力人物之一,她接受路透社專訪時並表示,這產業在追求多元化與包融性方面「是有點晚了」,雖然網紅迅速崛起,但時尚雜時仍是時尚達人的標竿。

產業界因為帶起「用後即丟」文化而受批評檢驗時,許多品牌試圖加強他們的環保綠色標籤驗證,並嘗試吸引年輕的環保型消費族群。

掌舵美國版時尚雜誌逾30年的溫圖爾說,時尚達人應呵護自己的衣物,甚至將衣物傳遞下去。「我認為對我們所有人,這代表更重視衣物的手藝和創意,少關注服裝用後即丟的概念,那些你只用過一次就會丟棄的東西。」

「我們想和閱聽眾和讀者表達的是,保留你擁有的衣物,珍惜它們,一次又一次地穿著,有可能就將它們傳給女兒或兒子,視情況而定。」

管理顧問公司麥肯錫(McKinsey & Company)一項2016年報告指出,在2000年至2014年間,全球服裝產出數量增加了一倍,每人每年服裝採購量增加60%。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

示波器鮮為人知的使用技巧?

※各大百貨每波促銷贈品活動,限量知名LOGOL型資料夾,獨家販售中!!

飲水機皆有含淨水功能嗎?

※選購空壓機需注意八大事項 !

真空封口機該不該買?使用心得分享

大樓隔熱紙施工分享說明,教你如何善用空間裝潢、設計 !

南韓能源部今天(28日)表示,為減少空氣污染,將把境內1/4燃煤發電廠於今年12月到明年2月停止運轉

摘錄自2019年11月28日中央通訊社綜合報導

南韓能源部今天(28日)表示,為減少空氣污染,將把境內1/4燃煤發電廠於今年12月到明年2月停止運轉,估計其餘3/4應足夠供應冬季所需用電。南韓的總統委員會今年9月建議於12月到明年2月之間關閉多達14座燃煤火力發電廠,到明年3月要關閉27座,以提升全國反污染措施。

南韓是亞洲第4大經濟體,約有60座燃煤發電廠,發電量佔全國電力的40%,但專家表示,燃煤導致全國空氣品質惡化。能源部在新聞稿中表示,未關閉的燃煤發電廠在今年12月到明年2月間會持續運作,負荷量不到80%。

南韓於今年3月到6月暫停幾座老舊燃煤發電廠的運作;恢復運作後,有空氣污染氣象報告發布時,就把這些電廠的運作負荷量上限設在80%。

能源部預料今年冬天的用電需求將於明年一月第4週達到高峰,約8萬8600百萬瓦特(MW),若有突然來襲的極端寒流,可能達到9萬1800百萬瓦特,由於12月到2月供電盈餘超過1萬1350百萬瓦特,應可滿足用電需求。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專業客製化禮物、贈品設計,辦公用品常見【L夾】搖身一變大受好評!!

示波器探測執行效能最佳化的8大秘訣

※選用哪種桶裝水,外宿露營超方便?

※(全省)堆高機租賃保養一覽表

※DIY自行施工建築隔熱紙,簡易教學大公開 !

※票選推薦煮婦最愛手壓封口機,省荷包不犧牲品質

據《日本時報》報導,到本世紀中葉,中國可能成為一個完全發達的經濟體

摘錄自2019年11月27日北京新浪網報導

據《日本時報》報導,到本世紀中葉,中國可能成為一個完全發達的經濟體,不會產生任何碳排放,其高儲蓄率和高投資率使其能夠支持和實現這項目標。

該報導是根據能源轉型委員會的一份報告,該委員會是由能源領域企業高管為致力推行《巴黎協定》而組成的全球聯盟組織。

報導稱,中國這全球第二大經濟體朝著零排放的目標邁進,這對於世界應對氣候變化至關重要,但儘管中國在清潔能源方面的投入超過其他任何國家,它仍是污染最嚴重的燃料之一——煤炭的最大消耗國。

能源轉型委員會說,到2050年中國人均經濟增長和生活水平將比目前提高兩倍,最終能源需求將減少27%,這需要減少使用鋼鐵和水泥,更多地循環使用塑料等材料,以及採取節能措施,包括運輸電氣化。

該報告其他研究結果包括:到2050年,因發電過程中能量損耗減少,中國一次能源​​總需求可能減少45%;化石能源需求可能下降90%以上,而非化石能源需求將增加至3.4倍;在電網具有靈活性且能儲存電力的情況下,近70%的發電量將由風能和太陽能提供。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※影響示波器測試準確度的五大因素

※無毒橡膠墊片哪裡買的到?

※飲用桶裝水到底安不安全? 破解錯誤迷思!

※十大封口機人氣排行榜-烘焙必備幫手!

連續封口機購物網-不怕你比價,就怕你買貴!

※買不起高檔茶葉,精緻包裝茶葉罐,也能撐場面!

ThreadLocal 源碼解析

在activeJDBC框架內部的實現中看到了 ThreadLocal 這個類,記錄下了每個線程獨有的連接

private static final ThreadLocal<HashMap<String, Connection>> connectionsTL = new ThreadLocal<>();

感覺是個知識點,就打開源碼看看了。先看一下源碼里的解釋

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

這個鳥文,瞎翻譯一下,就是:

這個類提供了供線程專享的變量。這些變量不同與其它普通的變量,它是每個線程都有一個自己的獨立初始化的變量(通過get和set方法實現)。這個類的實例常用於類的私有靜態字段,以實現每個線程都有自己的狀態(例如userId,事務ID等)。

先跑一下用法吧,

package com.test.threadlocal;

public class TestController {
    
    private static int index = 0;
    private static String str = "這個字符串是每個線程共享的";
    // 這個變量,看似是一個類的靜態屬性,實則是每個線程有自己獨有的區域
    private static ThreadLocal<String> threadStr = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "main線程專享";
        }
    };      
    
    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i < 3; i++) {
            Thread t = new MyThread();
            t.start();
            t.join();
        }
        
        System.out.println(str);
        System.out.println(threadStr.get());
    }
    
    static class MyThread extends Thread{

        @Override
        public void run() {
            index++;
            str = "第" + index + "個str";
            threadStr.set("第" + index + "個threadStr");
        }
        
        
    }

}

這個例子中,從str和threadStr變量的打印結果可以看出來。str被所有的線程讀和寫,threadStr在每個線程內部開闢了一塊線程專享的區域。接下來,我們看一下具體實現。
先看一下構造函數

     /**
     * ThreadLocals rely on per-thread linear-probe hash maps attached
     * to each thread (Thread.threadLocals and
     * inheritableThreadLocals).  The ThreadLocal objects act as keys,
     * searched via threadLocalHashCode.  This is a custom hash code
     * (useful only within ThreadLocalMaps) that eliminates collisions
     * in the common case where consecutively constructed ThreadLocals
     * are used by the same threads, while remaining well-behaved in
     * less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();
     /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

構造函數是空的,但是,該類有一個私有整型常量threadLocalHashCodenextHashCode()方法我們就不看了,省的一如源碼深似海。看鳥文的話,大概就是每new一個ThreadLocal變量的時候,就會生成一個散列碼,該碼非極端情況下與某個整數取模后不容易衝突(這句話有點迷吧,其實我也不懂)
然後看一下set方法

/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

容易看出,這個方法設置每個線程自己的value,相當於當前線程是key,然後得出一個ThreadLocalMap。顯然,這個map用來保存線程內部的值,既然是map當然每個線程可以保存多個數值了,該map的value我們猜一下就是我要保存的具體的值,估計是用Object類聲明的。那key是什麼呢?我們看下ThreadLocalMap類的構造方法。

/**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
                
/**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

我的天!這個類沒有繼承我們想象中的HashMap,或者是ConcurrentMap,但是看過,Map的內部實現的同學應該可以發現,這個Map的實現和HashMap的實現簡直就是小巫見大巫,有沒有。它在構造函數中做了如下幾步:

  1. 初始化一個大小為16的Entry數組
  2. 通過上面說過的很迷的HashCode散列值與15取模得到將要存儲在數組中的索引值
  3. 構造Entry,然後保存進去
  4. 長度設置為1
  5. 設置要擴容的限制大小為16的2/3

我們看到這個不就是用數組實現的Map嘛,看過HashMap實現的我們,覺得洒洒水啦。
Map的set和get方法就不分析了。ThreadLocal的get方法我們還是要貼出來的,畢竟是我們主要分析的東西

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

可見,是獲取到當前線程,用作key獲取到Map,然後用當前this獲取到Entry實體。最後當然獲取到了存儲的value。

我編碼,我快樂~

本文由博客一文多發平台 發布!

我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=19l4zaaz7g6ct

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

從EFCore上下文的使用到深入剖析DI的生命周期最後實現自動屬性注入

故事背景

最近在把自己的一個老項目從Framework遷移到.Net Core 3.0,數據訪問這塊選擇的是EFCore+Mysql。使用EF的話不可避免要和DbContext打交道,在Core中的常規用法一般是:創建一個XXXContext類繼承自DbContext,實現一個擁有DbContextOptions參數的構造器,在啟動類StartUp中的ConfigureServices方法里調用IServiceCollection的擴展方法AddDbContext,把上下文注入到DI容器中,然後在使用的地方通過構造函數的參數獲取實例。OK,沒任何毛病,官方示例也都是這麼來用的。但是,通過構造函數這種方式來獲取上下文實例其實很不方便,比如在Attribute或者靜態類中,又或者是系統啟動時初始化一些數據,更多的是如下一種場景:

    public class BaseController : Controller
    {
        public BloggingContext _dbContext;
        public BaseController(BloggingContext dbContext)
        {
            _dbContext = dbContext;
        }

        public bool BlogExist(int id)
        {
            return _dbContext.Blogs.Any(x => x.BlogId == id);
        }
    }

    public class BlogsController : BaseController
    {
        public BlogsController(BloggingContext dbContext) : base(dbContext) { }
    }

從上面的代碼可以看到,任何要繼承BaseController的類都要寫一個“多餘”的構造函數,如果參數再多幾個,這將是無法忍受的(就算只有一個參數我也忍受不了)。那麼怎樣才能更優雅的獲取數據庫上下文實例呢,我想到以下幾種辦法。


DbContext從哪來

1、  直接開new

回歸原始,既然要創建實例,沒有比直接new一個更好的辦法了,在Framework中沒有DI的時候也差不多都這麼干。但在EFCore中不同的是,DbContext不再提供無參構造函數,取而代之的是必須傳入一個DbContextOptions類型的參數,這個參數通常是做一些上下文選項配置例如使用什麼類型數據庫連接字符串是多少。

        public BloggingContext(DbContextOptions<BloggingContext> options) : base(options)
        {
        }

默認情況下,我們已經在StartUp中註冊上下文的時候做了配置,DI容器會自動幫我們把options傳進來。如果要手動new一個上下文,那豈不是每次都要自己傳?不行,這太痛苦了。那有沒有辦法不傳這個參數?肯定也是有的。我們可以去掉有參構造函數,然後重寫DbContext中的OnConfiguring方法,在這個方法中做數據庫配置: 

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Filename=./efcoredemo.db");
        }

即使是這樣,依然有不夠優雅的地方,那就是連接字符串被硬編碼在代碼中,不能做到從配置文件讀取。反正我忍受不了,只能再尋找其他方案。

2、  從DI容器手動獲取

既然前面已經在啟動類中註冊了上下文,那麼從DI容器中獲取實例肯定是沒問題的。於是我寫了這樣一句測試代碼用來驗證猜想:

    var context = app.ApplicationServices.GetService<BloggingContext>();

不過很遺憾拋出了異常:

報錯信息說的很明確,不能從root provider中獲取這個服務。我從G站下載了DI框架的源碼(地址是),拿報錯信息進行反向追溯,發現異常來自於CallSiteValidator類的ValidateResolution方法:

        public void ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
        {
            if (ReferenceEquals(scope, rootScope)
                && _scopedServices.TryGetValue(serviceType, out var scopedService))
            {
                if (serviceType == scopedService)
                {
                    throw new InvalidOperationException(
                        Resources.FormatDirectScopedResolvedFromRootException(serviceType,
                            nameof(ServiceLifetime.Scoped).ToLowerInvariant()));
                }

                throw new InvalidOperationException(
                    Resources.FormatScopedResolvedFromRootException(
                        serviceType,
                        scopedService,
                        nameof(ServiceLifetime.Scoped).ToLowerInvariant()));
            }
        }

View Code

繼續往上,看到了GetService方法的實現:

        internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
        {
            if (_disposed)
            {
                ThrowHelper.ThrowObjectDisposedException();
            }

            var realizedService = RealizedServices.GetOrAdd(serviceType, _createServiceAccessor);
            _callback?.OnResolve(serviceType, serviceProviderEngineScope);
            DependencyInjectionEventSource.Log.ServiceResolved(serviceType);
            return realizedService.Invoke(serviceProviderEngineScope);
        }

View Code

可以看到,_callback在為空的情況下是不會做驗證的,於是猜想有參數能對它進行配置。把追溯對象換成_callback繼續往上翻,在DI框架的核心類ServiceProvider中找到如下方法:

        internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
        {
            IServiceProviderEngineCallback callback = null;
            if (options.ValidateScopes)
            {
                callback = this;
                _callSiteValidator = new CallSiteValidator();
            }
            //省略....
        }    

說明我的猜想沒錯,驗證是受ValidateScopes控制的。這樣來看,把ValidateScopes設置成False就可以解決了,這也是網上普遍的解決方案:

      .UseDefaultServiceProvider(options =>
       {
              options.ValidateScopes = false;
       })

但這樣做是極其危險的。

為什麼危險?到底什麼是root provider?那就要從原生DI的生命周期說起。我們知道,DI容器被封裝成一個IServiceProvider對象,服務都是從這裏來獲取。不過這並不是一個單一對象,它是具有層級結構的,最頂層的即前面提到的root provider,可以理解為僅屬於系統層面的DI控制中心。在Asp.Net Core中,內置的DI有3種服務模式,分別是SingletonTransientScoped,Singleton服務實例是保存在root provider中的,所以它才能做到全局單例。相對應的Scoped,是保存在某一個provider中的,它能保證在這個provider中是單例的,而Transient服務則是隨時需要隨時創建,用完就丟棄。由此可知,除非是在root provider中獲取一個單例服務,否則必須要指定一個服務範圍(Scope),這個驗證是通過ServiceProviderOptionsValidateScopes來控制的。默認情況下,Asp.Net Core框架在創建HostBuilder的時候會判定當前是否開發環境,在開發環境下會開啟這個驗證:

所以前面那種關閉驗證的方式是錯誤的。這是因為,root provider只有一個,如果恰好有某個singleton服務引用了一個scope服務,這會導致這個scope服務也變成singleton,仔細看一下註冊DbContext的擴展方法,它實際上提供的是scope服務:

如果發生這種情況,數據庫連接會一直得不到釋放,至於有什麼後果大家應該都明白。

所以前面的測試代碼應該這樣寫:

     using (var serviceScope = app.ApplicationServices.CreateScope())
     {
         var context = serviceScope.ServiceProvider.GetService<BloggingContext>();
     }

與之相關的還有一個ValidateOnBuild屬性,也就是說在構建IServiceProvider的時候就會做驗證,從源碼中也能體現出來:

            if (options.ValidateOnBuild)
            {
                List<Exception> exceptions = null;
                foreach (var serviceDescriptor in serviceDescriptors)
                {
                    try
                    {
                        _engine.ValidateService(serviceDescriptor);
                    }
                    catch (Exception e)
                    {
                        exceptions = exceptions ?? new List<Exception>();
                        exceptions.Add(e);
                    }
                }

                if (exceptions != null)
                {
                    throw new AggregateException("Some services are not able to be constructed", exceptions.ToArray());
                }
            }

View Code

正因為如此,Asp.Net Core在設計的時候為每個請求創建獨立的Scope,這個Scope的provider被封裝在HttpContext.RequestServices中。

 [小插曲]

通過代碼提示可以看到,IServiceProvider提供了2種獲取service的方式:

這2個有什麼區別呢?分別查看各自的方法摘要可以看到,通過GetService獲取一個沒有註冊的服務時會返回null,而GetRequiredService會拋出一個InvalidOperationException,僅此而已。

        // 返回結果:
        //     A service object of type T or null if there is no such service.
        public static T GetService<T>(this IServiceProvider provider);

        // 返回結果:
        //     A service object of type T.
        //
        // 異常:
        //   T:System.InvalidOperationException:
        //     There is no service of type T.
        public static T GetRequiredService<T>(this IServiceProvider provider);

 

終極大招

到現在為止,儘管找到了一種看起來合理的方案,但還是不夠優雅,使用過其他第三方DI框架的朋友應該知道,屬性注入的快感無可比擬。那原生DI有沒有實現這個功能呢,我滿心歡喜上G站搜Issue,看到這樣一個回復():

官方明確表示沒有開發屬性注入的計劃,沒辦法,只能靠自己了。

我的思路大概是:創建一個自定義標籤(Attribute),用來給需要注入的屬性打標籤,然後寫一個服務激活類,用來解析給定實例需要注入的屬性並賦值,在某個類型被創建實例的時候也就是構造函數中調用這個激活方法實現屬性注入。這裡有個核心點要注意的是,從DI容器獲取實例的時候一定要保證是和當前請求是同一個Scope,也就是說,必須要從當前的HttpContext中拿到這個IServiceProvider

先創建一個自定義標籤:

    [AttributeUsage(AttributeTargets.Property)]
    public class AutowiredAttribute : Attribute
    {

    }

解析屬性的方法:

        public void PropertyActivate(object service, IServiceProvider provider)
        {
            var serviceType = service.GetType();
            var properties = serviceType.GetProperties().AsEnumerable().Where(x => x.Name.StartsWith("_"));
            foreach (PropertyInfo property in properties)
            {
                var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>();
                if (autowiredAttr != null)
                {
                    //從DI容器獲取實例
                    var innerService = provider.GetService(property.PropertyType);
                    if (innerService != null)
                    {
                        //遞歸解決服務嵌套問題
                        PropertyActivate(innerService, provider);
                        //屬性賦值
                        property.SetValue(service, innerService);
                    }
                }
            }
        }

然後在控制器中激活屬性:

        [Autowired]
        public IAccountService _accountService { get; set; }

        public LoginController(IHttpContextAccessor httpContextAccessor)
        {
            var pro = new AutowiredServiceProvider();
            pro.PropertyActivate(this, httpContextAccessor.HttpContext.RequestServices);
        }

這樣子下來,雖然功能實現了,但是裏面存着幾個問題。第一個是由於控制器的構造函數中不能直接使用ControllerBaseHttpContext屬性,所以必須要通過注入IHttpContextAccessor對象來獲取,貌似問題又回到原點。第二個是每個構造函數中都要寫這麼一堆代碼,不能忍。於是想有沒有辦法在控制器被激活的時候做一些操作?沒考慮引入AOP框架,感覺為了這一個功能引入AOP有點重。經過網上搜索,發現Asp.Net Core框架激活控制器是通過IControllerActivator接口實現的,它的默認實現是DefaultControllerActivator):

       /// <inheritdoc />
        public object Create(ControllerContext controllerContext)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException(nameof(controllerContext));
            }

            if (controllerContext.ActionDescriptor == null)
            {
                throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
                    nameof(ControllerContext.ActionDescriptor),
                    nameof(ControllerContext)));
            }

            var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo;

            if (controllerTypeInfo == null)
            {
                throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
                    nameof(controllerContext.ActionDescriptor.ControllerTypeInfo),
                    nameof(ControllerContext.ActionDescriptor)));
            }

            var serviceProvider = controllerContext.HttpContext.RequestServices;
            return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType());
        }

View Code

這樣一來,我自己實現一個Controller激活器不就可以接管控制器激活了,於是有如下這個類:

    public class HosControllerActivator : IControllerActivator
    {
        public object Create(ControllerContext actionContext)
        {
            var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType();
            var instance = actionContext.HttpContext.RequestServices.GetRequiredService(controllerType);
            PropertyActivate(instance, actionContext.HttpContext.RequestServices);
            return instance;
        }

        public virtual void Release(ControllerContext context, object controller)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (controller == null)
            {
                throw new ArgumentNullException(nameof(controller));
            }
            if (controller is IDisposable disposable)
            {
                disposable.Dispose();
            }
        }

        private void PropertyActivate(object service, IServiceProvider provider)
        {
            var serviceType = service.GetType();
            var properties = serviceType.GetProperties().AsEnumerable().Where(x => x.Name.StartsWith("_"));
            foreach (PropertyInfo property in properties)
            {
                var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>();
                if (autowiredAttr != null)
                {
                    //從DI容器獲取實例
                    var innerService = provider.GetService(property.PropertyType);
                    if (innerService != null)
                    {
                        //遞歸解決服務嵌套問題
                        PropertyActivate(innerService, provider);
                        //屬性賦值
                        property.SetValue(service, innerService);
                    }
                }
            }
        }
    }

View Code

需要注意的是,DefaultControllerActivator中的控制器實例是從TypeActivatorCache獲取的,而自己的激活器是從DI獲取的,所以必須額外把系統所有控制器註冊到DI中,封裝成如下的擴展方法:

        /// <summary>
        /// 自定義控制器激活,並手動註冊所有控制器
        /// </summary>
        /// <param name="services"></param>
        /// <param name="obj"></param>
        public static void AddHosControllers(this IServiceCollection services, object obj)
        {
            services.Replace(ServiceDescriptor.Transient<IControllerActivator, HosControllerActivator>());
            var assembly = obj.GetType().GetTypeInfo().Assembly;
            var manager = new ApplicationPartManager();
            manager.ApplicationParts.Add(new AssemblyPart(assembly));
            manager.FeatureProviders.Add(new ControllerFeatureProvider());
            var feature = new ControllerFeature();
            manager.PopulateFeature(feature);
            feature.Controllers.Select(ti => ti.AsType()).ToList().ForEach(t =>
            {
                services.AddTransient(t);
            });
        }

View Code

ConfigureServices中調用:

services.AddHosControllers(this);

到此,大功告成!可以愉快的繼續CRUD了。

 

結尾

市面上好用的DI框架一堆一堆的,集成到Core裏面也很簡單,為啥還要這麼折騰?沒辦法,這不就是造輪子的樂趣嘛。上面這些東西從頭到尾也折騰了不少時間,屬性注入那裡也還有優化的空間,歡迎探討。

推薦閱讀:

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?