分類: 3C資訊

  • 大摩:特斯拉Model 3自駕系統車禍降低90%

    大摩:特斯拉Model 3自駕系統車禍降低90%

    電動車大廠特斯拉(Tesla Inc.)要價35,000美元的平價車種「Model 3」預計2017年稍晚就能開賣,摩根士丹利(通稱大摩)認為,這款電動車的安全度會是一般車輛的10倍之多,發生死亡車禍的機率有望比其他車種低90%。

    根據網友最近在YouTube貼出影片,自駕系統「Autopilot」能提前好幾步預測到車禍。()

    MarketWatch、Business Insider等外電報導,大摩分析師Adam Jonas 23日發表研究報告指出,特斯拉為每台車安裝超級電腦後,車子安全性提升至其他車輛兩倍已經不夠看,他相信Model 3的安全度會是其他車輛的十倍之多,這會讓死亡車禍的發生機會降低90%。

    Jonas認為,缺少特斯拉駕車輔助科技的二手車價值將因而猛掉,未來甚至會被禁止上路。特斯拉蒐集資料的能力超群,還能將先進的安全輔助技術應用到電動車,還未推出類似科技的傳統車廠,競爭力堪虞。

    假如Model 3大獲成功、數百萬輛擁有自駕功能的車輛上路,那麼對行車安全的統計資料應有影響,這會凸顯其科技的優異程度,並迫使主管機關下令要求所有車輛都必須配備類似的系統。

    特斯拉23日終場下跌0.09%、收254.78美元;年初迄今已大漲19.23%。

    不過,最近有人仔細挖掘特斯拉向美國證管會(SEC)呈交的最新10-K報告,發現該公司已在內文悄悄坦承,Model 3 Beta版的原型車,至今尚無蹤影。

    美國權威汽車雜誌《Car and Driver》記者Anton Wahlman 3月2日報導(),特斯拉在3月1日發布的10-K報告中表示,已在測試Model 3的設計和製程時開發出多種版本,可作為Model 3 Beta版原型車的候選選項;在董事會選定要用哪一款作為Beta版的原型車之後,特斯拉下一階段的工作里程碑就能順利達陣。

    Wahlman認為,這意味著特斯拉在該份公告涵蓋的期間內(截至2016年12月31日為止),仍舊還未完成Model 3 Beta版的原型車。不過,這份文件是在2017年3月1日發布,這或許也代表至3月初為止,董事會還未對要用哪款原型車拍板定案。

      (本文內容由授權使用。圖片出處:Public Domain)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 騰訊入股Tesla,可望加速Model 3生產

    騰訊入股Tesla,可望加速Model 3生產

    騰訊於2017年3月29日宣布以17.8億美金買下Tesla 5%股權,此投資顯示騰訊對Elon Musk事業遠見之信心。騰訊資金的投入也有助於預定2017年底上市的Model 3。除此之外,Elon Musk在推特上回應關於太陽能瓦片的上市時間。

    外媒The Australian報導,汽車產業諮詢師Michael Dunne認為,騰訊旗下擁有中國最大的社群軟體微信(WeChat),如此有助於Tesla發展在中國生產製造的產線。Tesla過去曾和中國政府談論在中國建組裝廠一事,Elon Musk也表示若在中國建組裝廠不僅可以省下3分之1之成本,也可以省去進口關稅。

    根據中國的調查機構JL Warren Capital資料顯示,光是2016,中國向Tesla進口了11,839輛電動車,和2015相比成長了近5倍。騰訊與鴻海投資的新創汽車公司知行(FMC)預計在2020生廠自家的電動車,擁有Tesla股份也對FMC未來電動車布局有益。

    (首圖來源:Tesla)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

    ※別再煩惱如何寫文案,掌握八大原則!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

    FB行銷專家,教你從零開始的技巧

  • 中國新能源汽車補貼影響,比亞迪估Q1淨利將下滑

    中國新能源汽車補貼影響,比亞迪估Q1淨利將下滑

    比亞迪於28日晚間11點整公告表示,受新能源汽車補貼政策變動影響、產業短期會有所調整,2017年第1季集團新能源汽車業務也將承受一定壓力、預計新能源汽車銷量和盈利將有所下滑。此外,比亞迪也提到太陽能市場競爭依然激烈、產品價格壓力仍然較大。

    比亞迪表示,2016年底四部委發表的新能源汽車補貼新政將會對新能源汽車產業產生較大影響。由於2017年中國中央政府補貼額度比2016年降低20%、地方財政補貼不得超過中央財政單車補貼額的50%,一些規模較小的廠商及沒有實際競爭力的廠商可能會逐漸退出市場,整個市場的集中度將會進一步提升、龍頭企業將顯著受益,最終將促進新能源行業的持續健康發展。

    比亞迪預估2017年1-3月淨利將年減23.59-35.35%至5.5-6.5億元(人民幣)。

    比亞迪股份29日受上述消息衝擊而大幅走低。截至台北時間29日上午11時10分為止,比亞迪下跌2.81%至43.20港元;開盤迄今最低跌至42.85港元、創2月6日以來新低。

    股神巴菲特(Warren Buffett)掌管的波克夏海瑟威(Berkshire Hathaway)在2008年取得比亞迪10%股權。

    (本文內容由授權使用。圖片來源:比亞迪)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

  • 擴電動車版圖,鴻海投資中國鋰電池廠

    擴電動車版圖,鴻海投資中國鋰電池廠

    鴻海自2014開始將旗下事業版圖擴大至電動車領域,3月29日公告於深圳的子公司富泰華工業以44.7億新台幣買下中國寧德時代新能源1.19%之股權,共擁有7,666,525股。

    母公司為鋰電池製造商新能源科技(ATL),寧德時代(CATL)主要研發生產電池芯、電池管理系統和動力電池系統,產品應用於電動車及儲能領域。

    鴻海董事長郭台銘事業瞄準電動車市場,繼2015年旗下富士康同騰訊及和諧汽車,三方合資成立和諧富騰以生產智慧電動車之後,和諧富騰於2016又投資成立初創公司Future Mobility Corporation(FMC),瞄準高階智慧電動車。

    FMC於2017年1月宣布將斥資116億元人民幣,於南京建高端智能電動車廠,第一期工程預計2019年可以完成。關於此次寧德時代的投資,董事長郭台銘表示為長期投資。

    (首圖來源:寧德時代)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

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

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 積極拓電動車市場,Hyundai推EV車用平台

    積極拓電動車市場,Hyundai推EV車用平台

     

    特斯拉(Tesla)電動車成功引領話題,各大車廠跟紛紛仿效,搶攻此一市場。原本力推燃料電池車的韓國車廠–現代汽車(Hyundai Motor),眼看市場風向轉變,也宣布要研發該公司首款電動車專屬的車用平台(car platform)。

    路透社30日報導,車用平台意指外觀不同的汽車共享相通的設計、工程、生產流程,以及主要零件等,能夠壓低研發成本。現代汽車之前力推燃料電池車,如今轉攻電池車,突顯投資人施壓,要求該公司積極進軍新市場。

    現代汽車的電動車平台,電池將位於汽車底部,以便容納大容量電池,並讓車內空間最大化。現代-起亞環保車主管Lee Ki-sang說,電動車平台初期投資費用高,但是他們必須替未來做準備。分析師說現代別無選擇,必須追隨特斯拉、通用汽車、Daimler AG旗下的賓士(Mercedes-Benz),打造單獨的電動車平台,才能留在此一市場。

    Hi Investment & Securities分析師Ko Tae-bong表示,單獨平台初始時可能會造成虧損,但是現代若不研發長程電動車將落後對手,例如300、500、600公里車款。

    電動車大廠特斯拉(Tesla Inc.)要價35,000美元的平價車種「Model 3」預計2017年稍晚就能開賣,摩根士丹利(通稱大摩)認為,這款電動車的安全度會是一般車輛的10倍之多,發生死亡車禍的機率有望比其他車種低90%。

    MarketWatch、Business Insider等外電報導,大摩分析師Adam Jonas 23日發表研究報告指出,特斯拉為每台車安裝超級電腦後,車子安全性提升至其他車輛兩倍已經不夠看,他相信Model 3的安全度會是其他車輛的十倍之多,這會讓死亡車禍的發生機會降低90%。

    Jonas認為,缺少特斯拉駕車輔助科技的二手車價值將因而猛掉,未來甚至會被禁止上路。特斯拉蒐集資料的能力超群,還能將先進的安全輔助技術應用到電動車,還未推出類似科技的傳統車廠,競爭力堪虞。

    (本文內容由授權使用。圖片出處:Hyundai)  

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    FB行銷專家,教你從零開始的技巧

  • 博弈論——兩人取子遊戲與威佐夫博弈,隱藏在背後的黃金分割

    博弈論——兩人取子遊戲與威佐夫博弈,隱藏在背後的黃金分割

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天是算法和數據結構專題第25篇文章,我們繼續博弈論專題。

    在上一篇文章當中我們了解了最簡單的巴什博奕,今天我們來看看另一個經典的博弈模型——威佐夫博弈。博弈論和機器學習有些類似,數學家們針對場景進行建模,設計出了幾個經典模型。然後我們在面臨具體問題的時候,對問題進行深入分析,尋找最合適的模型應用來解決它。

    石子問題

    我們來看一道經典的例題,有兩堆石子,有兩個絕頂聰明的人在玩一個遊戲。每次每個人可以從其中一堆石子當中取走任意數量的石子,或者是從兩堆當中同時取走相同數量的石子。無法取石子的人落敗,請問,在告知兩堆石子數量的情況下,這兩個人當中哪一方會獲勝?

    我們簡單分析一下,會發現一些局面是先手必敗的。比如說(0, 0),再比如(1, 2)。我們簡單分析一下(1, 2),先手有4種策略,首先他可以取走第一堆,那麼後手可以取完第二堆,顯然後手獲勝。他也可以在第二堆當中取1個,這時剩下(1, 1),後手會同時取完,同樣是後手獲勝。第三種是他取走第二堆,後手可以取完第一堆,後手獲勝。第四種是他在第一堆和第二堆當中同時取走一個,這時第二堆剩下一個,後手勝。

    那麼,這些必敗的狀態之間有什麼規律呢?我們怎麼找到這個規律,並且找到解呢?

    分析

    我們可以枚舉幾個必敗的狀態:(0, 0), (1, 2), (3, 5), (4, 7)…

    我們觀察一下這些狀態,可以找到兩條規律。我們假設從小到大排的第k個必敗狀態是(x, y),並且x < y。我們可以發現y = x + k。也就是說必敗狀態兩個數的差值是遞增的,這也說明了每一個必敗狀態的差值都各不相同。

    其實這是很容易證明的,我們用反證法,假設(a, a+k), (b, b+k)都是必敗狀態,並且a < b。那麼先手在面臨(b, b+k)的時候,只需要在兩堆當中同時取走b-a個石子,那麼給後手的局面就是(a, a+k)。對於後手來說,這是一個必敗的局面,這就和(b, b+k)先手必敗矛盾,所以不存在兩個必敗局面的差值相等

    我們也可以作圖分析,我們把兩堆石子的數量看成是坐標軸上的一個點。所以遊戲就變成了:棋盤上有一個點,每次每個人可以將它向下、向左或者向左下移動若干個格子,不能移動的人輸。終止節點顯然是原點,一步就能移動到原點的點顯然是必勝點,假設我們給這些所有必勝點都染色的話,剩下的的沒當中橫縱坐標和最小的點就是下一個必敗點。因為它不論如何移動,都會給對手留下一個必勝點。

    我們根據上面的邏輯把必敗點都染色,可以得到下面這張圖:

    從這張圖可以看出,必敗點之間不能通過一次移動得到,換句話說可以一次移動到必敗點的點都是必勝點,從圖上可以看出,除了必敗點的之外的點都是必勝點,並且每一個自然數都必然只會被包含在一個必敗狀態當中。

    到這裏,我們距離解法已經很接近了,現在剩下的問題是,我們如何根據x和y的取值快速判斷它們是否構成一個必敗局面呢?也就是說我們能不能找出一個通項公式,對於第k個必敗局面,它的坐標是(\(x_k, y_k\))呢?

    求解

    為了寫出通項公式,我們需要引入Betty定理

    設a和b是兩個正無理數,並且\(\frac{1}{a} + \frac{1}{b} = 1\)

    記P={[\(a_n\)], \(n \in N^+\)}, Q={[\(b_n\)], \(n \in N^+\)},則\(P \cap Q = \varnothing\)\(P\cup Q = N^+\)

    證明

    \(P\cap Q = \varnothing\)

    反證,我們假設存在\(k \in P\)並且\(k \in Q\),即存在正整數n, m滿足 k < an, bm < k+1。

    也就是:\(\frac{n}{k} > \frac{1}{a} > \frac{n}{k+1}, \frac{m}{k} > \frac{1}{b} > \frac{m}{k+1}\)兩個式子相加可以得到:\(\frac{m+n}{k} > 1 > \frac{m+n}{k}\)

    \(k < n+m < k+ 1\),這與n,m,k都是正整數矛盾

    \(P \cup Q = N^+\)

    反證,假設存在\(k \notin P\)\(k \notin Q\),即存在正整數n,m滿足\(an < k < a(n+1)-1, bm < k < b(m+1)-1\)

    即:\(\frac{n}{k} < \frac{1}{a} < \frac{n+1}{k+1}, \frac{m}{k} < \frac{1}{b} < \frac{m+1}{k+1}\)

    相加,可以得到:\(\frac{m+n}{k} < 1 < \frac{n+m+2}{k+1}\)

    即:n + m < k < n + m + 1,這與n,m,k均為正整數矛盾

    我們花了這麼大力氣來證明Betty定理就是為了用的,因為我們發現必敗狀態的通項和Betty定理序列很像。我們不妨假設存在這樣的a, b同時滿足Betty定理與必敗狀態的性質:

    \[[an] + n = [bn], \frac{1}{a} + \frac{1}{b} = 1 \]

    \[[an] + n = [an + n] = [(a+1)n] = [bn] \]

    代入可以得到:

    \[\frac{1}{a} + \frac{1}{a+1} = 1 \]

    解這個方程,可以得到\(a = \frac{1 + \sqrt{5}}{2}\approx 1.618\),熟悉數學的同學相信一下就看出來了,這個數是黃金分割的比例,這是巧合嗎,還是藏着更深的道理呢?

    至少,求出了a之後,我們就可以非常簡單地判斷必敗狀態了:

    import math
    def lose_or_win(a, b):
        if a > b:
            a, b = b, a
        
        k = b - a
        # 根據差值k求出第k個必敗狀態,判斷是否相等
        return not (int(k * (math.sqrt(5)+1) / 2)) == a
    

    總結

    和之前介紹的巴什博奕相比,威佐夫博弈的推導過程要複雜得多,但是雖然推導過程依然複雜,但是仍然擋不住最後實現的代碼非常簡單。

    另外,在推導的過程當中,我們用到了Betty定理,這個定理的推導和證明雖然不難,但是如果不是數學專業的同學,可能大概率都沒有接觸過。這其實體現了博弈論本身和數學的關係是非常緊密的。一個看起來非常簡單的問題,引申出了一系列眼花繚亂的推導和證明,怎麼樣,大家看得還過癮嗎?

    今天的文章到這裏就結束了,如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

    【其他文章推薦】

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

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

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

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

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

    ※教你寫出一流的銷售文案?

  • Task.Result跟 Task.GetAwaiter.GetResult()相同嗎?怎麼選?

    前幾天在用線程池執行一些任務時運到一種情形,就是回調方法中使用到了異步方法,但是回調方法貌似不支持async await的寫法。這時候我應該如何處理呢?是使用Task.Result來獲取返回結果,還是使用GetAwaiter.GetResult()呢?本文就來探討下吧。

    作者:依樂祝

    原文地址:https://www.cnblogs.com/yilezhu/p/13168337.html

    這裏先上我這種場景的偽代碼:

    ThreadPool.QueueUserWorkItem(ExcuteScanProcess, node);
    

    ExcuteScanProcess這個回調方法中

    private void ExcuteScanProcess(object state)
    {
        ……其他處理……
        repository.UpdateAsync(node).ConfigureAwait(false).GetAwaiter().GetResult();
        ……其他處理……
    }
    

    如上圖所示repository.UpdateAsync(node)屬於一部方法,這時候我想要等待它異步執行完成之後再執行後續的邏輯。這時候我有兩種選擇,是直接

    repository.UpdateAsync(node).ConfigureAwait(false).GetAwaiter().GetResult();
    

    好呢,還是

    repository.UpdateAsync(node).ConfigureAwait(false).Result;
    

    好呢?

    為此我查找了相關的資料,對它倆的區別做一個簡單的總結:

    其實這兩個使用方式是差不多的。不過,還是有一點小小的區別的:如果任務失敗,Task.GetAwaiter().GetResult()會直接拋出異常,而Task.Result則會把異常包裝在AggregateException中。從這個角度說Task.GetAwaiter().GetResult()要優於Task.Result。畢竟它少了異常的包裝操作,即直接拋出異常,而不是把異常包裝在AggregateException中。

    下面的引言解釋了為什麼Task.Result不僅僅包含Task.GetAwaiter().GetResult()(由於“非常高的兼容性”)的異常傳播行為。

    如前所述,我們有一個非常高的兼容性標準,因此我們避免了改動。因此,Task.Wait保留了始終包裝的原始行為。但是,您可能會發現自己處在某些高級情況下,這些情況下您想要的行為類似於所採用的同步阻塞Task.Wait,但是您希望將原始異常展開而不是傳播,而不是將其封裝在AggregateException中。為此,您可以直接定位任務的等待者。當您編寫“ await task;”時,編譯器Task.GetAwaiter()會將其轉換為方法的用法,這將返回具有GetResult()方法的實例。當用於有故障的任務時,GetResult()將傳播原始異常(這是“ await task;” 如何獲得其行為)。因此,您可以使用“task.GetAwaiter().GetResult()如果您想直接調用此傳播邏輯。

    https://blogs.msdn.microsoft.com/pfxteam/2011/09/28/task-exception-handling-in-net-4-5/

    GetResult”實際上表示“檢查任務是否有錯誤”

    通常,我會儘力避免對異步任務進行同步阻塞。但是,在少數情況下,我確實違反了該準則。在那些罕見的情況下,我的首選方法是GetAwaiter().GetResult()因為它保留任務異常,而不是將它們包裝在中AggregateException

    總結

    通過上述內容的闡述,因此在那些必須對異步任務進行同步阻塞的場景中,我選擇使用GetAwaiter().GetResult()

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

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※推薦評價好的iphone維修中心

  • 這一次搞懂Spring Web零xml配置原理以及父子容器關係

    這一次搞懂Spring Web零xml配置原理以及父子容器關係

    前言

    在使用Spring和SpringMVC的老版本進行開發時,我們需要配置很多的xml文件,非常的繁瑣,總是讓用戶自行選擇配置也是非常不好的。基於約定大於配置的規定,Spring提供了很多註解幫助我們簡化了大量的xml配置;但是在使用SpringMVC時,我們還會使用到WEB-INF/web.xml,但實際上我們是完全可以使用Java類來取代xml配置的,這也是後來SpringBoott的實現原理。本篇就來看看Spring是如何實現完全的零XML配置。

    正文

    先來看一下原始的web.xml配置:

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
          <!--加載spring配置-->
          classpath:spring.xml
        </param-value>
      </context-param>
      <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>ServicePlatform.root</param-value>
      </context-param>
    
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        <!--<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>-->
      </listener>
    
      <servlet>
        <servlet-name>spring-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <!--springmvc的配置文件-->
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:spring-dispatcher.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>spring-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    </web-app>
    

    這裏各個配置的作用簡單說下,context-param是加載我們主的sping.xml配置,比如一些bean的配置和開啟註解掃描等;listener是配置監聽器,Tomcat啟動會觸發監聽器調用;servlet則是配置我們自定義的Servlet實現,比如DispatcherServlet。還有其它很多配置就不一一說明了,在這裏主要看到記住context-paramservlet配置,這是SpringIOC父子容器的體現。在之前的I文章中講過IOC容器是以父子關係組織的,但估計大部分人都不能理解,除了看到複雜的繼承體系,並沒有看到父容器作用的體現,稍後來分析。
    了解了配置,我們就需要思考如何替換掉這些繁瑣的配置。實際上Tomcat提供了一個規範,有一個ServletContainerInitializer接口:

    public interface ServletContainerInitializer {
        void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
    }
    

    Tomcat啟動時會調用該接口實現類的onStartup方法,這個方法有兩個參數,第二個不用說,主要是第一個參數什麼?從哪裡來?另外我們自定義的實現類又怎麼讓Tomcat調用呢?
    首先解答最後一個問題,這裏也是利用SPI來實現的,因此我們實現了該接口后,還需要在META-INF.services下配置。其次,這裏傳入的第一個參數也是我們自定義的擴展接口的實現類,我們可以通過我們自定義的接口實現很多需要在啟動時做的事,比如加載Servlet,但是Tomcat又是怎麼知道我們自定義的接口是哪個呢?這就需要用到@HandlesTypes註解,該註解就是標註在ServletContainerInitializer的實現類上,其值就是我們擴展的接口,這樣Tomcat就知道需要傳入哪個接口實現類到這個onStartup方法了。來看一個簡單的實現:

    @HandlesTypes(LoadServlet.class)
    public class MyServletContainerInitializer implements ServletContainerInitializer {
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            Iterator var4;
            if (set != null) {
                var4 = set.iterator();
                while (var4.hasNext()) {
                    Class<?> clazz = (Class<?>) var4.next();
                    if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && LoadServlet.class.isAssignableFrom(clazz)) {
                        try {
                            ((LoadServlet) clazz.newInstance()).loadOnstarp(servletContext);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
    
    public interface LoadServlet {
    
        void loadOnstarp(ServletContext servletContext);
    }
    
    public class LoadServletImpl implements LoadServlet {
        @Override
        public void loadOnstarp(ServletContext servletContext) {
            ServletRegistration.Dynamic initServlet = servletContext.addServlet("initServlet", "org.springframework.web.servlet.DispatcherServlet");
            initServlet.setLoadOnStartup(1);
            initServlet.addMapping("/init");
    	}
    }
    

    這就是Tomcat給我們提供的規範,通過這個規範我們就能實現Spring的零xml配置啟動,直接來看Spring是如何做的。
    根據上面所說我們可以在spring-web工程下找到META-INF/services/javax.servlet.ServletContainerInitializer配置:

    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    	@Override
    	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
    			throws ServletException {
    
    		List<WebApplicationInitializer> initializers = new LinkedList<>();
    
    		if (webAppInitializerClasses != null) {
    			for (Class<?> waiClass : webAppInitializerClasses) {
    				// Be defensive: Some servlet containers provide us with invalid classes,
    				// no matter what @HandlesTypes says...
    				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
    						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
    					try {
    						initializers.add((WebApplicationInitializer)
    								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
    					}
    					catch (Throwable ex) {
    						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
    					}
    				}
    			}
    		}
    
    		if (initializers.isEmpty()) {
    			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
    			return;
    		}
    
    		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
    		AnnotationAwareOrderComparator.sort(initializers);
    		for (WebApplicationInitializer initializer : initializers) {
    			initializer.onStartup(servletContext);
    		}
    	}
    
    }
    
    

    核心的實現就是WebApplicationInitializer,先看看其繼承體系

    AbstractReactiveWebInitializer不用管,主要看另外一邊,但是都是抽象類,也就是說真的實例也是由我們自己實現,但需要我們實現什麼呢?我們一般直接繼承AbstractAnnotationConfigDispatcherServletInitializer類,有四個抽象方法需要我們實現:

        //父容器
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class<?>[]{SpringContainer.class};
        }
    
        //SpringMVC配置子容器
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class<?>[]{MvcContainer.class};
        }
    
        //獲取DispatcherServlet的映射信息
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    
    	// filter配置
        @Override
        protected Filter[] getServletFilters() {
            MyFilter myFilter = new MyFilter();
            CorsFilter corsFilter = new CorsFilter();
            return new Filter[]{myFilter,corsFilter};
        }
    

    這裏主要注意getRootConfigClassesgetServletConfigClasses方法,分別加載父、子容器:

    @ComponentScan(value = "com.dark",excludeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
    })
    public class SpringContainer {
    }
    
    @ComponentScan(value = "com.dark",includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
    },useDefaultFilters = false)
    public class MvcContainer {
    }
    

    看到這兩個類上的註解應該不陌生了吧,父容器掃描裝載了所有不帶@Controller註解的類,子容器則相反,但需要對象時首先從當前容器中找,如果沒有則從父容器中獲取,為什麼要這麼設計呢?直接放到一個容器中不行么?先思考下, 稍後解答。
    回到onStartup方法中,直接回調用到AbstractDispatcherServletInitializer類:

    	public void onStartup(ServletContext servletContext) throws ServletException {
    		super.onStartup(servletContext);
    		//註冊DispatcherServlet
    		registerDispatcherServlet(servletContext);
    	}
    

    先是調用父類:

    	public void onStartup(ServletContext servletContext) throws ServletException {
    		registerContextLoaderListener(servletContext);
    	}
    
    	protected void registerContextLoaderListener(ServletContext servletContext) {
    
    		//創建spring上下文,註冊了SpringContainer
    		WebApplicationContext rootAppContext = createRootApplicationContext();
    		if (rootAppContext != null) {
    			//創建監聽器
    			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
    			listener.setContextInitializers(getRootApplicationContextInitializers());
    			servletContext.addListener(listener);
    		}
    	}
    

    然後調用createRootApplicationContext創建父容器:

    	protected WebApplicationContext createRootApplicationContext() {
    		Class<?>[] configClasses = getRootConfigClasses();
    		if (!ObjectUtils.isEmpty(configClasses)) {
    			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    			context.register(configClasses);
    			return context;
    		}
    		else {
    			return null;
    		}
    	}
    

    可以看到就是創建了一個AnnotationConfigWebApplicationContext對象,並將我們的配置類SpringContainer註冊了進去。接着創建Tomcat啟動加載監聽器ContextLoaderListener,該監聽器有一個contextInitialized方法,會在Tomcat啟動時調用。

    	public void contextInitialized(ServletContextEvent event) {
    		initWebApplicationContext(event.getServletContext());
    	}
    
    	 */
    	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    		long startTime = System.currentTimeMillis();
    		try {
    			// Store context in local instance variable, to guarantee that
    			// it is available on ServletContext shutdown.
    			if (this.context == null) {
    				this.context = createWebApplicationContext(servletContext);
    			}
    			if (this.context instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    				if (!cwac.isActive()) {
    					// The context has not yet been refreshed -> provide services such as
    					// setting the parent context, setting the application context id, etc
    					if (cwac.getParent() == null) {
    						// The context instance was injected without an explicit parent ->
    						// determine parent for root web application context, if any.
    						ApplicationContext parent = loadParentContext(servletContext);
    						cwac.setParent(parent);
    					}
    					configureAndRefreshWebApplicationContext(cwac, servletContext);
    				}
    			}
    			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    
    			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    			if (ccl == ContextLoader.class.getClassLoader()) {
    				currentContext = this.context;
    			}
    			else if (ccl != null) {
    				currentContextPerThread.put(ccl, this.context);
    			}
    
    			return this.context;
    		}
    	}
    

    可以看到就是去初始化容器,這個和之前分析xml解析是一樣的,主要注意這裏封裝了ServletContext對象,並將父容器設置到了該對象中。
    父容器創建完成后自然就是子容器的創建,來到registerDispatcherServlet方法:

    	protected void registerDispatcherServlet(ServletContext servletContext) {
    		String servletName = getServletName();
    		Assert.hasLength(servletName, "getServletName() must not return null or empty");
    
    		//創建springmvc的上下文,註冊了MvcContainer類
    		WebApplicationContext servletAppContext = createServletApplicationContext();
    		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
    
    		//創建DispatcherServlet
    		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
    		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
    
    		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    		if (registration == null) {
    			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
    					"Check if there is another servlet registered under the same name.");
    		}
    
    		/*
    		* 如果該元素的值為負數或者沒有設置,則容器會當Servlet被請求時再加載。
    			如果值為正整數或者0時,表示容器在應用啟動時就加載並初始化這個servlet,
    			值越小,servlet的優先級越高,就越先被加載
    		* */
    		registration.setLoadOnStartup(1);
    		registration.addMapping(getServletMappings());
    		registration.setAsyncSupported(isAsyncSupported());
    
    		Filter[] filters = getServletFilters();
    		if (!ObjectUtils.isEmpty(filters)) {
    			for (Filter filter : filters) {
    				registerServletFilter(servletContext, filter);
    			}
    		}
    
    		customizeRegistration(registration);
    	}
    
    	protected WebApplicationContext createServletApplicationContext() {
    		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    		Class<?>[] configClasses = getServletConfigClasses();
    		if (!ObjectUtils.isEmpty(configClasses)) {
    			context.register(configClasses);
    		}
    		return context;
    	}
    
    

    這裏也是創建了一個AnnotationConfigWebApplicationContext對象,不同的只是這裏註冊的配置類就是我們的Servlet配置了。然後創建了DispatcherServlet對象,並將上下文對象設置了進去。看到這你可能會疑惑,既然父子容器創建的都是相同類的對象,何來的父子容器之說?別急,這個在初始化該上文時就明白了。但是這裏的初始化入口在哪呢?沒有看到任何監聽器的創建和調用。實際上這裏的上下文對象初始化是在Servlet初始化時實現的,即init方法,直接來到HttpServletBeaninit方法(分析SpringMVC源碼時講過):

    	public final void init() throws ServletException {
    		...省略
    		
    		// Let subclasses do whatever initialization they like.
    		initServletBean();
    	}
    
    	protected final void initServletBean() throws ServletException {
    		try {
    			this.webApplicationContext = initWebApplicationContext();
    			initFrameworkServlet();
    		}
    	}
    
    	protected WebApplicationContext initWebApplicationContext() {
    		//這裡會從servletContext中獲取到父容器,就是通過監聽器加載的容器
    		WebApplicationContext rootContext =
    				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    		WebApplicationContext wac = null;
    
    		if (this.webApplicationContext != null) {
    			// A context instance was injected at construction time -> use it
    			wac = this.webApplicationContext;
    			if (wac instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
    				if (!cwac.isActive()) {
    					if (cwac.getParent() == null) {
    						cwac.setParent(rootContext);
    					}
    					//容器加載
    					configureAndRefreshWebApplicationContext(cwac);
    				}
    			}
    		}
    		if (wac == null) {
    			wac = findWebApplicationContext();
    		}
    		if (wac == null) {
    			wac = createWebApplicationContext(rootContext);
    		}
    
    		if (!this.refreshEventReceived) {
    			synchronized (this.onRefreshMonitor) {
    				onRefresh(wac);
    			}
    		}
    
    		if (this.publishContext) {
    			// Publish the context as a servlet context attribute.
    			String attrName = getServletContextAttributeName();
    			getServletContext().setAttribute(attrName, wac);
    		}
    
    		return wac;
    	}
    

    看到這裏想你也應該明白了,首先從ServletContext中拿到父容器,然後設置到當前容器的parent中,實現了父子容器的組織,而這樣設計好處我想也是很清楚的,子容器目前裝載的都是MVC的配置和Bean,簡單點說就是Controller,父容器中都是Service,Controller是依賴於Service的,如果不構建這樣的層級關係並優先實例化父容器,你怎麼實現Controller層的依賴注入成功呢?

    總結

    本篇結合之前的文章,分析了SpringMVC零XML配置的實現原理,也補充了之前未分析到父子容器關係,讓我們能從細節上更加全面的理解SpringIOC的實現原理,相信看完本篇對於SpringBoot的實現你也會有自己的想法。

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※回頭車貨運收費標準

  • mysql大表在不停機的情況下增加字段該怎麼處理

    mysql大表在不停機的情況下增加字段該怎麼處理

    MySQL中給一張千萬甚至更大量級的表添加字段一直是比較頭疼的問題,遇到此情況通常該如果處理?本文通過常見的三種場景進行案例說明。

    1、 環境準備

    數據庫版本: 5.7.25-28(Percona 分支)

    服務器配置:  3台centos 7虛擬機,配置均為2CPU  2G內存

    數據庫架構: 1主2從的MHA架構(為了方便主從切換場景的演示,如開啟GTID,則兩節點即可),關於MHA搭建可參考此文 MySQL高可用之MHA集群部署

    準備測試表:  創建一張2kw記錄的表,快速創建的方法可以參考快速創建連續數

    本次對存儲過程稍作修改,多添加幾個字段,存儲過程如下:

    DELIMITER $$
    CREATE  PROCEDURE `sp_createNum`(cnt INT )
    BEGIN
        DECLARE i INT  DEFAULT 1;
        DROP TABLE  if exists  tb_add_columns;
        CREATE TABLE if not exists tb_add_columns(id int primary key,col1 int,col2 varchar(32));
        INSERT INTO tb_add_columns(id,col1,col2) SELECT i  as id ,i%7 as col1,md5(i) as col2;
        
        WHILE i < cnt DO
          BEGIN
            INSERT INTO tb_add_columns(id,col1,col2) SELECT id + i   as id ,( id + i) %7 as col1,md5( id + i) as col2  FROM tb_add_columns WHERE id <=cnt - i ;
            SET i = i*2;
          END;
        END WHILE;
    END$$
    DELIMITER ;

    調用存儲過程,完成測試表及測試數據的創建。

    mysql> call sp_createNum(20000000);

     2.  直接添加字段

    使用場景: 在系統不繁忙或者該表訪問不多的情況下,如符合ONLINE DDL的情況下,可以直接添加。

    模擬場景: 創建一個測試腳本,每10s訪問該表隨機一條記錄,然後給該表添加字段

    訪問腳本如下

    #!/bin/bash
    # gjc
    
    for i in  {1..1000000000}                    # 訪問次數1000000000,按需調整即可
    do
        id=$RANDOM                          #生成隨機數    
        mysql -uroot -p'123456' --socket=/data/mysql3306/tmp/mysql.sock  -e "select  a.*,now() from  testdb.tb_add_columns a where id = "$id     # 訪問數據
        sleep 10                            #  暫停10s
    done

    運行腳本

    sh  test.sh

     給表添加字段

    mysql> alter table  testdb.tb_add_columns add col3 int;

      此時,訪問正常。

     附ONLINE DDL的場景如下,建議DBA們必須弄清楚

    (圖片轉載於https://blog.csdn.net/finalkof1983/article/details/88355314)

     

     (圖片轉載於https://blog.csdn.net/finalkof1983/article/details/88355314)

    3.   使用工具在線添加

    雖然Online DDL添加字段時,表依舊可以讀寫,但是生產環境使用場景中對大表操作使用最多的還是使用工具pt-osc或gh-ost添加。

    本文主要介紹 pt-osc(pt-online-schema-change) 來添加字段,該命令是Percona Toolkit工具中的使用頻率最高的一種

    關於Percona Toolkit的安裝及主要使用可以參考  五分鐘學會Percona Toolkit 安裝及使用

    添加字段

    root@mha1 ~]# pt-online-schema-change --alter "ADD COLUMN  col4  int" h=localhost,P=3306,p=123456,u=root,D=testdb,t=tb_add_columns,S=/data/mysql3306/tmp/mysql.sock  --charset=utf8mb4 --execute

    主要過程如下:

    1> Cannot connect to A=utf8mb4,P=3306,S=/data/mysql3306/tmp/mysql.sock,h=192.168.28.132,p=...,u=root
    1> Cannot connect to A=utf8mb4,P=3306,S=/data/mysql3306/tmp/mysql.sock,h=192.168.28.131,p=...,u=root
    No slaves found.  See --recursion-method if host mha1 has slaves.  #  因為使用的是socket方式連接數據庫 且未配置root遠程連接賬號,所以會有此提示
    
    # A software update is available:
    Operation, tries, wait:
      analyze_table, 10, 1                                     
      copy_rows, 10, 0.25                                       
      create_triggers, 10, 1                      
      drop_triggers, 10, 1
      swap_tables, 10, 1
      update_foreign_keys, 10, 1
    Altering `testdb`.`tb_add_columns`...
    Creating new table...                                     #  創建中間表,表名為"_原表名_new"
    Created new table testdb._tb_add_columns_new OK.           
    Altering new table...                                     #  修改表,也就是在新表上添加字段,因新表無數據,因此很快加完
    Altered `testdb`.`_tb_add_columns_new` OK.                  
    2020-06-20T12:23:43 Creating triggers...                  #  創建觸發器,用於在原表拷貝到新表的過程中原表有數據的變動(新增、修改、刪除)時,也會自動同步至新表中
    2020-06-20T12:23:43 Created triggers OK.
    2020-06-20T12:23:43 Copying approximately 19920500 rows... # 拷貝數據,數據庫量是統計信息里的,不準確
    Copying `testdb`.`tb_add_columns`:  11% 03:50 remain       #  分批拷貝數據(根據表的size切分每批拷貝多少數據),拷貝過程中可以用show processlist看到對應的sql
    Copying `testdb`.`tb_add_columns`:  22% 03:22 remain
    Copying `testdb`.`tb_add_columns`:  32% 03:10 remain
    Copying `testdb`.`tb_add_columns`:  42% 02:45 remain
    Copying `testdb`.`tb_add_columns`:  51% 02:21 remain
    Copying `testdb`.`tb_add_columns`:  62% 01:48 remain
    Copying `testdb`.`tb_add_columns`:  72% 01:21 remain
    Copying `testdb`.`tb_add_columns`:  81% 00:53 remain
    Copying `testdb`.`tb_add_columns`:  91% 00:24 remain
    2020-06-20T12:28:40 Copied rows OK.                       # 拷貝數據完成
    2020-06-20T12:28:40 Analyzing new table...                # 優化新表
    2020-06-20T12:28:40 Swapping tables...                    # 交換表名,將原表改為"_原表名_old",然後把新表表名改為原表名
    2020-06-20T12:28:41 Swapped original and new tables OK.    
    2020-06-20T12:28:41 Dropping old table...                 #  刪除舊錶(也可以添加參數不刪除舊錶)
    2020-06-20T12:28:41 Dropped old table `testdb`.`_tb_add_columns_old` OK.
    2020-06-20T12:28:41 Dropping triggers...                  # 刪除觸發器
    2020-06-20T12:28:41 Dropped triggers OK.
    Successfully altered `testdb`.`tb_add_columns`.            # 完成

    修改過程中,讀寫均不受影響,大家可以寫個程序包含讀寫的

    注:  無論是直接添加字段還是用pt-osc添加字段,首先都得拿到該表的元數據鎖,然後才能添加(包括pt-osc在創建觸發器和最後交換表名時都涉及),因此,如果一張表是熱表,讀寫特別頻繁或者添加時被其他會話佔用,則無法添加。

    例如: 鎖住一條記錄

    用pt-osc添加字段,會發現一直卡在創建觸發器那一步

     此時查看對應的SQL正在等待獲取元數據鎖

    換成直接添加也一樣,例如

     當達到鎖等待后將會報錯放棄添加字段

    mysql> alter table  testdb.tb_add_columns add col5 int;
    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

    對於此情況,需等待系統不繁忙情況下添加,或者使用後續的在從庫創建再進行主從切換

    4  先在從庫修改,再進行主從切換

    使用場景: 如果遇到上例中一張表數據量大且是熱表(讀寫特別頻繁),則可以考慮先在從庫添加,再進行主從切換,切換后再將其他幾個節點上添加字段。

    先在從庫添加(本文在備選節點添加)

    mysql> alter table  testdb.tb_add_columns add col5 int;
    Query OK, 0 rows affected (1 min 1.91 sec)
    Records: 0  Duplicates: 0  Warnings: 0

    進行主從切換

    使用MHA腳本進行在線切換

    masterha_master_switch  --conf=/etc/masterha/app1.conf --master_state=alive  --orig_master_is_new_slave --new_master_host=192.168.28.131  --new_master_port=3306

    切換完成后再對其他節點添加字段

    /* 原主庫上添加192.168.28.128  */
    mysql>  alter table  testdb.tb_add_columns add col5 int;
    Query OK, 0 rows affected (1 min 8.36 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    /* 另一個從庫上添加192.168.28.132  */
    mysql>  alter table  testdb.tb_add_columns add col5 int;
    Query OK, 0 rows affected (1 min 8.64 sec)
    Records: 0  Duplicates: 0  Warnings: 0

    這樣就完成了字段添加。

    5.  小結

    生產環境MySQL添加或修改字段主要通過如下三種方式進行,實際使用中還有很多注意事項,大家要多多總結。

    • 直接添加

    如果該表讀寫不頻繁,數據量較小(通常1G以內或百萬以內),直接添加即可(可以了解一下online ddl的知識)

    •  使用pt_osc添加

    如果表較大 但是讀寫不是太大,且想盡量不影響原表的讀寫,可以用percona tools進行添加,相當於新建一張添加了字段的新表,再降原表的數據複製到新表中,複製歷史數據期間的數據也會同步至新表,最後刪除原表,將新表重命名為原表表名,實現字段添加

    •  先在從庫添加 再進行主從切換

    如果一張表數據量大且是熱表(讀寫特別頻繁),則可以考慮先在從庫添加,再進行主從切換,切換后再將其他幾個節點上添加字段

     

     

     

     

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 流產、死胎層出不窮 南蘇丹隱匿石油業環境報告 犧牲者至今未受保障

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

    【其他文章推薦】

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

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

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

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

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

    ※教你寫出一流的銷售文案?