Model S爆出第三起電池起火事件 Tesla股價大跌

美國知名電動車大廠特斯拉 (Tesla)熱門車款 Model S,週三 (6 日) 傳出近日第三起電池起火事件,引發外界對於爭議性高的電動車輛安全再掀疑慮。Tesla 週三股價才因前日盤後發布第三季車輛銷售不如預期而下挫,週四股價再因上述消息大跌 7.53%,收 139.77 美元。

這起 Tesla 電池起火事件發生在美國田納西州的 Murfreesboro 市,駕駛者 Juris Shibayama 週三下午開著 Model S 駛過路上遺落的拖車掛鉤,因刺破了車輛的電池部位而造成車輛起火。所幸駕駛者在起火之前逃出,並未受傷。

據CNBC報導,這已是過去6 週 Model S 傳出的第 3 起電池起火事件。第 1 次事件發生在西雅圖,當時也是車輛駛過路上一大塊金屬物體,導致電池部分被刺穿而起火。美國國家公路交通安全管理局 (NHTSA)針對上述事件的立場是,他們正在監視情況發展,但並未展開正式調查。

而最新週三的這次車輛起火事件過後,NHTSA 仍表示目前處於搜集資料階段,將諮詢事發當局來確定事故是否與車輛安全相關。

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

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

英國三季度電動汽車銷量環比增25%

據英國媒體報導,第三季度英國電動汽車的銷量環比增長25%,電動汽車補貼登記數量達到1149台,創下2011年1月以來的最高記錄。

2011年1月,英國政府曾宣佈,在隨後的14個月中,只要用戶購買一輛低碳電動汽車,即將獲得高達5000英鎊的補貼。2012年,政府決定把對電動汽車的補貼措施延長到2015年。

不久前,雷諾—日產公司首席執行官卡洛斯•戈恩表示,2016年底之前,將實現不了150萬輛電動汽車的銷售目標,實現目標有可能是在2020年或2021年。他表示,政府對充電站建設的投入不到位,影響了電動汽車的銷售。

日產公司的純電動汽車「聆風」是政府補貼計畫下第一個受益的主要車型,並避開了英國執政聯盟預算縮減的風潮。

聆風由日產公司桑德蘭工廠生產,今年4月在挪威上市以後一炮打響,成為市場上最暢銷的汽車。當然,這部分得益於挪威慷慨的電動汽車減稅政策。

但是聆風也有不少競爭者,如通用雪佛蘭沃藍達在歐洲大陸的版本Vauxhall Ampera、相對便宜的雷諾Zoe和寶馬i3。此外,還有福特福克斯的電動汽車及特斯拉的豪華電動汽車。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

嘉義縣府為處理流浪犬問題,7月17日起實施精準捕捉專線

嘉義縣府為處理流浪犬問題,7月17日起實施精準捕捉專線,並以「嘉義縣廢棄物清除處理基金」經費委託民間機構代養收容犬隻、增聘六名動物保護管制人員,嘉義縣議會臨時會昨聯席審查通過兩案,預計本週五進行二、三讀後實施。

農委會調查,嘉義縣流浪犬估計有9000多隻,但嘉義縣家畜疾病防治所目前捕捉流浪貓、犬業務人力僅兩人,嘉縣府農業處提案增聘動物保護管制人員擴充至八人,今年度經費122萬元,另以每隻狗每月1000元費用委託民間代養機構收容犬隻,經費396萬元。

嘉義縣家畜疾病防治所長林珮如表示,實施精準捕捉後已受理302件通報,獲報追車咬人部分,派員捕捉29件,捕捉17隻,有傷人、性情凶悍犬隻均採收容處理;緊急救援動物若評估健康安全,或經當地居民同意,則採TNVR(Trap誘捕、Neuter絕育、Vaccinate防疫、Return回置)處理。

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

※哪裡買的到省力省空間,方便攜帶的購物推車?

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

※市面十大品牌封口機!該如何選購?

※塑膠射出成型加工商品有哪些?

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

※各款電動堆高機價格?

※掌握產品行銷策略,帶你認識商品包裝設計基本要素

苗栗縣府針對森林外樹木進行普查,公告59株受保護樹木

苗栗縣府針對森林外樹木進行普查,公告59株受保護樹木,其中銅鑼鄉興隆國小樹齡已逾800年的老樟樹,不僅最高齡,也曾獲選全國最美校樹之一,是地方發展重要精神堡壘。

林務局2015年7月公告增訂森林法樹木保護專章,明定地方主管機關應對轄區森林外樹木進行普查,凡經公告之受保護樹木,應優先加強保護。苗栗縣政府2019年7月首次公告列管59株受保護樹木,多位於校園或伯公廟、機關、社區、民宅、道路旁;樹種多為樟樹、榕樹及茄苳,少數為台灣油杉、淋漓、黃連木及琉球松。

其中,樹齡最老的銅鑼鄉興隆國小老樟樹,估計已逾800歲,樹圍約5.8公尺,至少得5人以上才能環抱,樹高約22公尺,樹形挺拔,且生長狀況良好, 2015年曾獲福田樹木保育基金會「全國最美校樹」特優,近幾年畢業季,校方與雪霸國家公園志工合作舉辦畢業生「攀高敬樹」環境教育活動,別具特色。

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

【其他文章推薦】

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

※如何知道自已的電腦cpu支不支持AVX指令集?

※如何正確使用飲水機?

※攻戰消費者第一視覺,包裝設計很重要!

※滑鼠墊適用各種文宣活動廣告曝光,專業客製服務

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

新竹縣政府推動新竹縣國際AI智慧園區,日前經新竹縣環境影響評估審查委員會審議通過

新竹縣政府推動新竹縣國際AI智慧園區,日前經新竹縣環境影響評估審查委員會審議通過,預計10月完成園區報編、都市計畫發布實施,這也是獲經濟部工業局核定,被列為前瞻基礎建設計畫之一的項目。

新竹縣國際AI智慧園區總面積約為12.61公頃,初步規畫設置8.06公頃的產業專用區,包含興建1棟AI產業願景館,還有國際會議廳、創新育成中心、新創辦公室及產品展示與體驗場域等,另4.56公頃為公共設施用地。

園區用地初步將以出租,即設定地上權為主,規畫引進無煙囪類別產業,如節能、綠色能源研發、設計及後端應用技術服務,以及軟體設計、資訊服務、智慧應用服務等產業。也將促進新竹地區AI專業技術大專學院、研究單位與業者的產學合作,提供新創業者基本設施、儀器與支援服務,以提升產業競爭力並促進產業轉型。

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

【其他文章推薦】

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

※如何正確使用飲水機?

※如何知道自已的電腦cpu支不支持AVX指令集?

※掌握產品行銷策略,帶你認識商品包裝設計基本要素

※商業用玻璃煎台不必開大火也能迅速導熱

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

※空壓機這裡買最划算!

第18次「瀕危野生動植物種國際貿易公約」(華盛頓公約,CITES)會議

第18次「瀕危野生動植物種國際貿易公約」(華盛頓公約,CITES)會議,簡稱COP18,將於8月17日至28日在瑞士日內瓦舉行。138個締約國家將透過決定和決議,在三年一度的會議上擴大和加強全球野生動植物貿易制度。

各國政府共交出,以更新野生動植物國際貿易的保護層級。

整體而言,這56項新提案可能會影響約550種物種,包括非洲象、長頸鹿、許多種蜥蜴、壁虎和蠑螈、陸龜、鯊魚、海參、馬達加斯加猴麵包樹和北印度紅木樹。

其中許多提案目的是納入CITES附錄II 清單、取得貿易許可,確保風險物種貿易的永續性。有些提案欲將瀕危物種標本列入附錄I ,禁止所有商業貿易。也有一些提案提供證據證明族群已經穩定或擴大,可以安全地從附錄I 轉移到附錄II 。

CITES新任秘書長、巴拿馬環境經濟學家易圭羅(Ivonne Higuero)說:「CITES制定野生動植物國際貿易規則,是透過預防和扭轉野生動物族群量下降來確保永續性、因應生物多樣性快速流失(通常稱為第六次大滅絕)的有力工具。」

「今年的會議的重點是加強現有規則和標準,同時將更多受人類活動威脅的植物和動物納入CITES制度。」

COP18提案:大貓、大象、長頸鹿、犀牛、爬行動物、魚類和樹木

非洲和亞洲的大型貓科動物面臨各種存續壓力,包括盜獵和非法貿易。COP18代表將討論是否建立CITES大貓特別工作小組,以加強保護亞洲大貓的行動。會議上還有關於非洲的獵豹、獅子以及南美洲的美洲豹的議程。

納米比亞的長頸鹿。拍攝(CC BY-ND 2.0)

由於棲息地流失和其他壓力,長頸鹿的數量在過去幾十年間急劇下降。中非共和國、查德、肯亞、馬利、尼日和塞內加爾建議將長頸鹿作列入附錄II,以預防該物種持續衰退。

多年來,白犀牛角盜獵持續嚴重。儘管史瓦帝尼(以前稱為史瓦濟蘭)的南方白犀牛(Ceratotherium simum simum)已列在附錄II,但有一條註釋禁止犀牛角的國際貿易。史瓦帝尼建議刪除這個註釋,以便出售330公斤的現有犀牛角庫存,接著每年出售20公斤的非致命性採集的犀牛角。

納米比亞建議將南方白犀牛從附錄I 轉移到II ,註釋限出售活體動物和狩獵戰利品,所有其他標本保留在附錄I 。

COP18將再次討論部分關於非洲象的提案。幾十年的象牙盜獵使族群量大減,非洲象於1989年從CITES附錄II轉移到附錄I 。1997年和2000年,CITES發現部分非洲南部的象群健康且管理良好,同意將波札納、納米比亞、南非和辛巴威族群列入附錄II 。1999年和2008年,這些國家的官方所有像牙登記庫存銷售權授予中國和日本。

現在,尚比亞提議將其大象族群從附錄I 轉到附錄II,以允許出售登記象牙庫存給CITES批准的買家以及部分特定非象牙貿易活動。

波札納、納米比亞、南非和辛巴威希望將已登記象牙庫存賣給CITES驗證的合作夥伴以及一些特定的非象牙貿易活動。他們建議透過修改註釋達成,雖然他們的象群列在附錄II,但目前不允許貿易。

為了客觀評估大象盜獵和象牙非法貿易的趨勢,CITES建立了非法殺害大象監測計畫(MIKE)和大象貿易資訊系統(ETIS)。這些平台的評估結果將作為締約方在大象保護和象牙貿易方面的決策依據。

最後一個象牙問題,也是CITES過去不曾遇過的問題,是以色列要求列出已滅絕物種清單。以國建議在附錄II中加入真猛獁象,以防止大象象牙被蓄意標成猛獁象牙來洗物種。

非洲象, 攝於辛巴威。(CC BY-NC-ND 2.0)

20個提案跟兩棲動物和爬行動物有關。這兩類物種深具異國魅力,在寵物貿易市場越來越受歡迎。

是否將CITES規則應用於高價值魚類和樹種貿易、如何最佳管理非洲象,以及如何處理象牙等仍有爭議。非法殺害犀牛和犀牛角相關貿易也是議程重點。

代表們也將決定是否應該豁免由公約納管樹木製成的珍貴木製樂器。

Mulanje雪松是馬拉威的國樹。因為其黃白色木材能抗真菌、昆蟲,不易腐爛,價值非常高,被非法伐木者過度採伐。馬拉威提案將Mulanje雪松列入附錄II清單。

北印度玫瑰木是COP18中唯一被提案從CITES附錄中刪除的物種。孟加拉國、不丹、印度和尼泊爾建議從附錄II 中刪除這種用於木材、家具、樂器和許多其他用途的樹,因為數量充足且可永續伐採。

生長在巴基斯坦的北印度玫瑰木, 攝(CC BY-SA 3.0)

各國代表將評估每份提案,並以協商方式通過,或在必要時透過表決通過。除了分析提案本身之外,政府代表也可諮詢正式受CITES邀請提出意見的組織。秘書處的任務是評估提案是否符合既有貿易和生物標準,並審查技術和科學因素。

停止物種快速衰退的戰略

今年5月,(IPBES Global Assessment Report on Biodiversity and Ecosystem Services)證實,世界各地的物種和生態係正在迅速劣化。物種減少的主要因素之一是直接過度開發,包括非永續或非法狩獵、捕撈和伐木。

7月,世界自然保育聯盟(IUCN)受脅物種紅皮書的最新更新顯示,過度捕撈已使兩種魟魚瀕危,人為獵補和棲息地流失導致7種靈長類動物瀕危。

該次更新還揭露更多全球淡水魚類存續狀況危急的證據。日本和墨西哥自由流動的河流減少、棲息地劣化、污染和入侵種威脅著大量物種。

除了修訂CITES附錄的56項提案外,COP18議程還力求通過未來幾年的戰略,透過協議的解釋和條款的實施,提高公約的有效性。

會議上也將提出CITES2020後戰略願景文件供討論和批准。該願景草案期望「到2030年,野生動植物的所有國際貿易都要是合法和永續的,符合物種長期保育的目標,有助阻止生物多樣性流失。」

戰略願景還強調CITES在如何促進實現2030年永續發展目標。

CITES的成功跟堅實的法律基礎和有效的合規制度息息相關。如果有證據顯示締約方可能沒有完全履行公約規定的義務,CITES提供技術援助和協助能力建設,使該國遵守規定。

如有必要,CITES還可以採取合規措施,包括建議暫停所列物種的所有貿易,甚至包括所有CITES列出的物種。

野生動物犯罪是議程重點

非法的野生動物國際貿易威脅著許多野生動植物的生存,同時破壞了國家經濟和依賴永續利用野生動植物的人們的生計。組織犯罪集團越來越多,增加了執法調查的困難度,以及國家公園管理員等執法人員的危險。

會議上將討論與網路相關的野生動植物犯罪、法醫學的運用、貪污、西非和中非野生動植物犯罪的威脅評估報告,以及如何透過非法貿易資料的儲存和管理指引決策。

代表們也將關注CITES秘書處、國際刑警組織、聯合國毒品和犯罪問題辦公室、世界銀行和世界海關組織在「國際打擊野生動植物犯罪聯合會」主持下,提供締約方的能力建構和技術支援。

有些締約方認為,應審查非CITES所列物種的貿易,以評估CITES是否可以在管理此類貿易方面發揮作用。

COP18將討論若干目前未納入CITES附錄中的物種和分類群的貿易問題,相關物種包括鳴禽、兩棲動物、海洋觀賞魚,考氏鰭天竺鯛和乳香。公約進行中的關於玫瑰木、鰻魚、鯊魚和魟魚的工作則牽涉已列入和未列入名單的物種。

CITES列出的許多野生動植物物種現在也有人工繁殖以進行貿易,但此種貿易活動不得對野生物種的存活產生不利影響。會議也將審議非野生動植物貿易的廣泛問題。

除了因應這些持續存在的問題外,CITES締約方還創造了許多成功案例,主要是永續利用和保育野生動植物,使農村社區獲得收入或增加糧食安全的案例,像駱馬、尼羅河鱷魚和雪花蓮。

會議上也將討論各種能加強原住民、地方和農村社區參與CITES決策過程的機會,以及如何加強與其他生物多樣性相關公約的合作。

CITES to Update Wildlife Trade Rules for 550 Species GENEVA, Switzerland, August 13, 2019 (ENS)

The 183 countries that are Parties to the Convention on International Trade in Endangered Species, CITES, will adopt decisions and resolutions to expand and strengthen the global wildlife trade regime at CITES’ upcoming triennial World Wildlife Conference. The 18th such meeting, informally known as COP18 , will be held at Palexpo in Geneva from August 17 to 28.

Governments have submitted to change the levels of protection that CITES provides for species of wild animals and plants that are traded internationally.

In total, some 550 species may be affected by the proposed changes. They include the African elephant, the giraffe, a variety of lizards, geckos and newts, tortoises, sharks, sea cucumbers, Grandidier’s baobab and North Indian rosewood trees.

Many of these proposals seek to ensure that trade in at-risk species remains sustainable by requiring trade permits through a CITES Appendix II listing.

Others recommend banning all commercial trade in specimens of species threatened by extinction by listing them on Appendix I.

Still others aim to provide evidence that a population has stabilized or expanded and can be safely transferred from Appendix I to Appendix II.

CITES’ new Secretary-General, environmental economist of Panama, said, “CITES sets the rules for international trade in wild fauna and flora. It is a powerful tool for ensuring sustainability and responding to the rapid loss of biodiversity – often called the sixth extinction crisis – by preventing and reversing declines in wildlife populations.

“This year’s conference will focus on strengthening existing rules and standards while extending the benefits of the CITES regime to additional plants and animals threatened by human activity,” she said.

COP18 Proposals: Cats, Elephants, Giraffes, Rhinos, Reptiles, Fish and Trees

The big cats of Africa and Asia are under threat from a wide range of pressures, including poaching and illegal trade. COP18 delegates will consider establishing a CITES Big Cats Task Force to strengthen action on conserving these high-profile species in Asia. The conference also has agenda items on Africa’s cheetahs and lions and Latin America’s jaguars.

Populations of the giraffe have declined sharply over the past several decades due to habitat loss and other pressures. The Central African Republic, Chad, Kenya, Mali, Niger and Senegal propose that the giraffe be listed on Appendix II as a precautionary measure to help arrest the species’ ongoing decline.

The white rhinoceros has been heavily poached for its horn for many years. Although the population of southern white rhinos, Ceratotherium simum simum, in Eswatini (formerly known as Swaziland) is listed on Appendix II, an annotation currently prevents international trade in rhino horn. Eswatini proposes removing this annotation so that it can sell an existing stock of 330 kg of rhino horn and then 20 kg per year from non-lethal harvesting.

Namibia proposes transferring its population of southern white rhinos from Appendix I to II with an annotation solely for the sale of live animals and for hunting trophies, with all other specimens to remain on Appendix I.

COP18 will once again consider a number of elephant proposals. The African elephant was moved from CITES Appendix II to CITES Appendix I in 1989 after decades of ivory poaching had greatly reduced many populations. In 1997 and 2000, recognizing that some southern African elephant populations were healthy and well managed, CITES agreed to downlist the populations of Botswana, Namibia, South Africa and Zimbabwe to Appendix II. In 1999 and again in 2008, sales of registered stocks of government-owned ivory from these countries were authorized to China and Japan.

Now, Zambia proposes to downlist its elephant population from Appendix I to Appendix II to permit sales of registered ivory stocks to CITES-approved buyers as well as some specified non-ivory trade.

Botswana, Namibia, South Africa and Zimbabwe would like to enable trade in registered ivory stocks to CITES-verified partners as well as some specified non-ivory trade. They propose to do this by amending an annotation that, although their elephant populations are listed in Appendix II, currently disallows trade.

To provide objective assessments of trends in elephant poaching and illegal trade in ivory, CITES established the Monitoring of Illegal Killing of Elephants (MIKE) program and the Elephant Trade Information System (ETIS). Their results inform the decisions taken by Parties on elephant conservation and the ivory trade.

Finally on the ivory issue, and in a first for CITES, Proposal 13 calls for listing a long-extinct species. Israel proposes adding the woolly mammoth to Appendix II, citing the so-called “look-alike provision” aimed at preventing the “laundering” or mislabeling of elephant ivory as mammoth ivory.

Twenty of the listing proposals to be presented at COP18 are inspired by concern over the growing appetite of the exotic pet trade for charismatic amphibians and reptiles.

The trend towards applying CITES rules to trade in high-value fish and tree species continues, as do the debates over how best to manage the African elephant populations and what to do with their ivory tusks. Illegal killing of rhinos and the related trade in rhino horn is also high on the agenda.

Delegates also will decide whether musical instruments made of precious wood from trees regulated by the Convention should be exempted from CITES controls.

The Mulanje cedar is the national tree of Malawi. Because its yellow-white timber is highly resistant to fungal rot, insects and decay, it is highly valued. It has been overexploited by illegal loggers. Malawi proposes an Appendix II listing for this cedar.

The proposal on North Indian rosewood is the only one at COP18 recommending that a species be deleted from the CITES appendices. Bangladesh, Bhutan, India and Nepal propose removing this tree, which is used for timber, furniture, musical instruments and many other purposes, from Appendix II because it is abundant and sustainably harvested.

Governments will evaluate each listing proposal and decide to adopt it by consensus or, if necessary, by vote.

In addition to analyzing the proposals themselves, government representatives can consider the views of the that were formally invited to provide their comments as stakeholders.

The Secretariat itself is tasked with evaluating the proposals for whether they satisfy agreed trade and biological criteria, and reviewing technical and scientific factors.

Strategizing to Halt Rapid Decline in Species

In May, the confirmed that species and ecosystems around the world are in rapid decline. One of the main drivers of species decline is the direct overexploitation of living organisms, including unsustainable or illegal hunting, fishing and logging.

In July, the latest update of the maintained by the International Union for the Conservation of Nature, IUCN, shows that overfishing has pushed two families of rays to the brink of extinction while hunting for bushmeat and habitat loss have led to the decline of seven primate species.

The update also reveals further evidence of the perilous state of freshwater fishes globally. This is shown by high numbers of species threatened by the loss of free-flowing rivers, habitat degradation, pollution and invasive species in Japan and Mexico.

In addition to the 56 proposals for amending the CITES Appendices, the COP18 agenda also seeks to adopt a strategy for the coming years and improve the effectiveness of the Convention through agreements on the interpretation and implementation of its provisions.

Along these lines, the CITES Strategic Vision Post-2020 document will be presented for discussion and adoption. The draft vision foresees that “By 2030, all international trade in wild fauna and flora is legal and sustainable, consistent with the long-term conservation of species, and thereby contributing to halting biodiversity loss.”

The Strategic Vision also highlights CITES’ role in contributing to the achievement of the Sustainable Development Goals of the 2030 Agenda for Sustainable Development.

The success of CITES is based in good part on having a solid legal basis and an effective compliance regime. When there is evidence that a Party may not be fully complying with their obligations under the Convention, CITES provides technical assistance and capacity building to bring the country back into compliance.

If necessary, CITES can also adopt compliance measures which may include a recommendation to suspend all trade in a listed species or even in all CITES-listed species.

Wildlife Crime High on the Agenda

Illegal international trade in wildlife threatens the survival of many wild animals and plants while undermining national economies and the livelihoods of people who rely on the sustainable use of wildlife. The growing involvement of organized crime groups is increasing the complexity of enforcement investigations and the risks faced by enforcement officers such as park rangers.

Among other issues, the conference will address wildlife crime linked to the Internet, the use of forensic applications, corruption, a threat assessment report on wildlife crime in West and Central Africa, and the storage and management of data on illegal trade used to inform decision -making.

Delegates will also focus on capacity building and technical support provided to Parties by the CITES Secretariat, Interpol, the United Nations Office on Drugs and Crime, the World Bank and the World Customs Organization under the auspices of the International Consortium on Combating Wildlife Crime.

Some Parties are of the view that trade in non-CITES-listed species should be examined to assess if CITES could play a role in managing such trade.

COP18 will discuss several documents that describe trade-related concerns for species and taxa currently not included in the CITES Appendices. These involve songbirds, amphibians, marine ornamental fish, Bangai cardinal fish and frankincense. The ongoing work under the Convention on rosewoods, eels as well as sharks and rays touches both on listed and non-listed species.

Many “wild” animal and plant species listed under CITES are now bred in captivity or cultivated for trade, but such trade must not be detrimental to the survival of a species in the wild. The conference will consider the broad issue of trade in animal and plant specimens from non-wild sources.

In addition to tackling these ongoing challenges, CITES Parties have generated many success stories. These often involve enabling rural communities to develop income or increase food security through the sustainable use and conservation of wild animals and plants. Examples include the vicuña, the Nile crocodile and the snowdrop flower.

The conference will also consider opportunities to enhance the role of indigenous, local and rural communities in CITES decision-making processes and how to further strengthen collaboration with other biodiversity-related conventions.

※ 全文及圖片詳見:

※ 本文與 行政院農業委員會林務局 合作刊登

作者

如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。

於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。

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

【其他文章推薦】

※使用真空封口機常見問題?

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

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

※哪裡有合版印刷優惠,尋找L夾客製化印刷廠商?

※如何正確使用飲水機?

※洗滌塔運作原理介紹

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

聯合國教科文組織世界遺產委員會第43屆會議上

聯合國教科文組織世界遺產委員會第43屆會議上,中國申報的良渚古城遺址成功申報世界文化遺產。離杭州市中心約一小時車距的良渚古城遺址代表了中國在5000多年前即有稻作文明,是古城市文明代表。

2007年發現的良渚古城分三重,佔地630多公頃,是在一片濕地上興建的。當時人們先在濕地上堆起墩台,再在上面修建房屋和宮殿,被譽為「中華第一城」。

2015年發現的良渚古城外圍水利工程,為中國最古老的大型水利工程遺址。其間出土的諸多精美玉器,是中國玉文化史前遺產,並顯示階層分化。埋藏地下、儲量可觀的炭化稻米,印證了興旺的稻作農業,和手工業遺存共同成為社會分工複雜化的寫照。北京大學副教授秦嶺博士指出,良渚代表了中國南方地區尤其是長江下游濕地稻作農業文明的發端,這與中原及世界大部分地區以旱作農業為主的經濟模式下產生的文明有著鮮明區別。

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

【其他文章推薦】

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

※如何正確使用飲水機?

※如何知道自已的電腦cpu支不支持AVX指令集?

※掌握產品行銷策略,帶你認識商品包裝設計基本要素

※商業用玻璃煎台不必開大火也能迅速導熱

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

※空壓機這裡買最划算!

野放近四個月的南安小熊「BUNI」

野放近四個月的南安小熊「BUNI」,追蹤用項圈疑似在日前脫落並於6月底尋獲,今有媒體指出,「花蓮林管處人員持悲觀看法,認為小熊存活率不高。」林務局花蓮林區管理處長楊瑞芬得知後嚴重駁斥,並在接受本報記者採訪時指出「樂觀看待小熊回山林!」,副處長王怡靖也出面澄清,「針對本處表達悲觀一事」 ,表示並無此事。



脫落的項圈。照片來源:台灣黑熊保育協會。

備受關注的熊妹妹「南安小熊」,去年7月在花蓮南安瀑布步道被發現,經台灣黑熊保育協會九個月野訓,4月30日搭乘黑鷹直升機抵達花蓮山區野放,事隔約二個月,野放團隊發現繫在小熊脖子上的項圈衛星訊號疑似停滯不動。

楊瑞芬說,野放團隊隨即上山,並依照衛星訊號定位,在6月29日尋獲項圈,確定脫落,因位置在一處隱密的崖峭壁下,且一般人難以到達,團隊均樂觀看待牠是安全的,「應該是躲起來了!」林管處絕無「悲觀」認為小熊存活率不高一事。

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

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

※各種升降平台款式?

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

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

※高價位跟低價位的示波器又有何差異?

※高效率洗滌塔活性碳設備有哪些?

※空壓機這裡買最划算!

Spring AOP學習筆記04:AOP核心實現之創建代理

  上文中,我們分析了對所有增強器的獲取以及獲取匹配的增強器,在本文中我們就來分析一下Spring AOP中另一部分核心邏輯–代理的創建。這部分邏輯的入口是在wrapIfNecessary()方法中緊接着增強器的獲取之後的createProxy():

protected Object createProxy(
        Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

    ProxyFactory proxyFactory = new ProxyFactory();
    // 獲取當前類中相關屬性
    proxyFactory.copyFrom(this);
    // 決定對於給定的bean是否應該使用targetClass而不是它的接口進行代理
    if (!shouldProxyTargetClass(beanClass, beanName)) {
        // Must allow for introductions; can't just set interfaces to
        // the target's interfaces only.
        Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, this.proxyClassLoader);
        for (Class<?> targetInterface : targetInterfaces) {
            // 添加代理接口
            proxyFactory.addInterface(targetInterface);
        }
    }

    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    for (Advisor advisor : advisors) {
        // 加入增強器
        proxyFactory.addAdvisor(advisor);
    }
    // 設置要代理的類
    proxyFactory.setTargetSource(targetSource);
    // 定製代理
    customizeProxyFactory(proxyFactory);
    // 用來控制代理工廠被配置之後,是否還允許修改通知
    // 默認值為false(即在代理被配置之後,不允許修改代理的配置)
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    return proxyFactory.getProxy(this.proxyClassLoader);
}

  對於代理類的創建及處理,Spring委託給了ProxyFactory進行處理,而在上面的函數中主要是對ProxyFactory的初始化操作,為真正的代理創建做準備,初始化包括如下內容:

  • 獲取當前中的屬性;
  • 添加代理接口;
  • 封裝Advisor並加入到ProxyFactory中;
  • 設置要代理的類;
  • 對代理工廠進行定製化處理,供子類實現;
  • 進行獲取代理操作;

  其中封裝Advisor並加入到ProxyFactory中以及創建代理是兩個相對繁瑣的過程,可以通過ProxyFactory提供的addAdvisor方法直接將增強器置入代理創建工廠中,但是將攔截器封裝為增強器還是需要一定的邏輯的。

protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
    // 解析註冊的所有interceptorName
    Advisor[] commonInterceptors = resolveInterceptorNames();

    List<Object> allInterceptors = new ArrayList<Object>();
    if (specificInterceptors != null) {
        // 加入攔截器
        allInterceptors.addAll(Arrays.asList(specificInterceptors));
        if (commonInterceptors != null) {
            if (this.applyCommonInterceptorsFirst) {
                allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
            }
            else {
                allInterceptors.addAll(Arrays.asList(commonInterceptors));
            }
        }
    }
    if (logger.isDebugEnabled()) {
        int nrOfCommonInterceptors = (commonInterceptors != null ? commonInterceptors.length : 0);
        int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);
        logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +
                " common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");
    }

    Advisor[] advisors = new Advisor[allInterceptors.size()];
    for (int i = 0; i < allInterceptors.size(); i++) {
        // 將攔截器進行包裝轉化為Advisor
        advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
    }
    return advisors;
}

public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    // 如果要封裝的對象本身就是Advisor類型的那麼無需再做過多處理
    if (adviceObject instanceof Advisor) {
        return (Advisor) adviceObject;
    }
    // 如果不是Advisor與Advice兩種類型,則拋出異常
    if (!(adviceObject instanceof Advice)) {
        throw new UnknownAdviceTypeException(adviceObject);
    }
    Advice advice = (Advice) adviceObject;
    if (advice instanceof MethodInterceptor) {
        // 如果是MethodInterceptor類型則使用DefaultPointcutAdvisor封裝
        return new DefaultPointcutAdvisor(advice);
    }
    // 如果存在Advisor的適配器那麼也同樣需要進行封裝
    for (AdvisorAdapter adapter : this.adapters) {
        // Check that it is supported.
        if (adapter.supportsAdvice(advice)) {
            return new DefaultPointcutAdvisor(advice);
        }
    }
    throw new UnknownAdviceTypeException(advice);
}

  因為Spring中涉及過多的攔截器、增強器、增強方法等方式來對邏輯進行增強,所以非常有必要將增強器封裝成Advisor來進行代理的創建,完成了增強的封裝過程,那麼接下來就是最重要的一步–代理的創建與獲取。

public Object getProxy(ClassLoader classLoader) {
    return createAopProxy().getProxy(classLoader);
}

1. 創建代理

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    // 創建代理
    return getAopProxyFactory().createAopProxy(this);
}

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface()) {
            return new JdkDynamicAopProxy(config);
        }
        return CglibProxyFactory.createCglibProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

  到這裏已經完成了代理的創建了,不管我們之前是否有閱讀過Spring的源碼,但是應該或多或少都聽說過對於Spring的代理中JDKProxy的實現CglibProxy的實現。Spring是如果選取的呢?現在我們就從源碼的角度分析,看看到底Spring是如何選擇代理方式的。

  從上面代碼中的判斷條件可以看到3個方面影響着Spring的判斷:

  • optimize:用來控制通過CGLIB創建的代理是否使用激進的優化策略。除非完全了解AOP代理如何處理優化,否則不推薦用戶使用這個設置。目前這個屬性僅用於CGLIB代理,對於JDK動態代理(默認代理)無效。
  • proxyTargetClass:這個屬性為true時,目標類本身被代理而不是目標類的接口,並且使用CGLIB方式創建代理,xml文件配置方式為:<aop:aspectj-autoproxy proxy-target-class=”true”/>。
  • hasNoUserSuppliedProxyInterfaces:是否存在代理接口。

  下面是對JDK與Cglib方式的總結:

  • 如果目標對象實現了接口,默認情況下會採用JDK的動態代理實現AOP;
  • 如果目標對象實現了接口,可以強制使用CGLIB實現AOP;
  • 如果目標對象沒有實現接口,則必須採用CGLIB方式實現AOP,Spring會自動切換;

如何強制使用CGLIB實現AOP?

  • 添加CGLIB庫,Spring_HOME/cglib/*.jar
  • 在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>

JDK動態代理和CGLIB字節碼生成的區別?

  • JDK動態代理只能對實現了接口的類生成代理,而不能針對類。
  • CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,因為是繼承,所以該類或方法最好不要聲明成final。

 

2. 獲取代理

  確定了使用哪種代理方式之後便可以進行代理的創建了,Spring中主要使用了兩種方式來實現代理的創建:JDK動態代理、cglib,我們一一來解析。

2.1 JDK動態代理方式

  這裏直接定位到JdkDynamicAopProxy中的getProxy():

public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

  JDK動態代理的使用關鍵是創建自定義的InvocationHandler,而InvocationHandler中包含了需要覆蓋的函數getProxy,這裏其實JdkDynamicAopProxy就是繼承了InvocationHandler的,所以上面的方法正是完成了這個操作,並且我們還可以推斷出,在JdkDynamicAopProxy中一定會有一個invoke函數,並且JdkDynamicAopProxy會把AOP的核心邏輯寫在其中,找一下,一定會有這樣一個函數的:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Class<?> targetClass = null;
    Object target = null;

    try {
        // 處理equals方法
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            return equals(args[0]);
        }
        // 處理hash方法
        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            return hashCode();
        }
        if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;
        // 有時候目標對象內部的自我調用將無法實施切面中的增強,則需要通過此屬性暴露代理
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }

        // 獲取當前方法的攔截器鏈
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        if (chain.isEmpty()) {
            // 如果沒有任何攔截器鏈則直接調用切點方法
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
        }
        else {
            // 將攔截器封裝在ReflectiveMethodInvocation,以便於使用其proceed進行鏈式調用攔截器
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // 執行攔截器鏈
            retVal = invocation.proceed();
        }

        // 返回結果
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

  上面的invoke()函數最主要的工作就是創建了一個攔截器鏈,並使用ReflectiveMethodInvocation類進行了鏈的封裝,而在ReflectiveMethodInvocation類的proceed方法中實現了攔截器的逐一調用,那麼我們就繼續來探究,在proceed方法中是怎麼實現諸如前置增強在目標方法前調用以及後置增強在目標方法后調用的邏輯的。

public Object proceed() throws Throwable {
    //    執行完所有增強后執行切點方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    // 獲取下一個要執行的攔截器
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // 動態匹配
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // 若未匹配則不執行攔截器,調用攔截器鏈中下一個
            return proceed();
        }
    }
    else {
        // 普通攔截器,直接調用。將this作為參數傳入以保證當前實例中調用鏈的執行
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

  ReflectiveMethodInvocation的主要職責是維護一個鏈式調用的計數器,記錄著當前調用鏈的位置,以便鏈可以有序地進行下去。其實在這個方法中並沒有我們設想的維護各種增強的順序,但是細心的讀者可能會發現,這部分工作其實是委託給了各個增強器來實現,前面有說到。

2.2 Cglib方式

  完成CGLIB代理的類是委託給CglibAopProxy類去實現的,我們來一探究竟。根據前面的分析,我們容易判斷出來,CglibAopProxy的入口應該也是在getProxy():

public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
    }

    try {
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

        Class<?> proxySuperClass = rootClass;
        if (ClassUtils.isCglibProxyClass(rootClass)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        // 驗證Class
        validateClassIfNecessary(proxySuperClass);

        // 創建及配置CGLIB Enhancer
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader &&
                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new MemorySafeUndeclaredThrowableStrategy(UndeclaredThrowableException.class));
        enhancer.setInterceptDuringConstruction(false);
        // 設置攔截器
        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);
        enhancer.setCallbacks(callbacks);

        // 生成代理類及創建代理對象
        Object proxy;
        if (this.constructorArgs != null) {
            proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
        }
        else {
            proxy = enhancer.create();
        }

        return proxy;
    }
    catch (CodeGenerationException ex) {
        catch若干異常。。。
    }
}

  上面的函數中就是一個完整創建Enhancer的過程,詳細可以參考Enhancer的文檔,這裏最重要的是通過getCallbacks()方法設置攔截器鏈。

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    // 對於expose-proxy屬性的處理
    boolean exposeProxy = this.advised.isExposeProxy();
    boolean isFrozen = this.advised.isFrozen();
    boolean isStatic = this.advised.getTargetSource().isStatic();

    // 將攔截器封裝在DynamicAdvisedInterceptor中
    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

    // Choose a "straight to target" interceptor. (used for calls that are
    // unadvised but can return this). May be required to expose the proxy.
    Callback targetInterceptor;
    if (exposeProxy) {
        targetInterceptor = isStatic ?
                new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource());
    }
    else {
        targetInterceptor = isStatic ?
                new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedInterceptor(this.advised.getTargetSource());
    }

    // 將攔截器加入到callback中
    Callback targetDispatcher = isStatic ?
            new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp();

    Callback[] mainCallbacks = new Callback[] {
            aopInterceptor,  // for normal advice
            targetInterceptor,  // invoke target without considering advice, if optimized
            new SerializableNoOp(),  // no override for methods mapped to this
            targetDispatcher, this.advisedDispatcher,
            new EqualsInterceptor(this.advised),
            new HashCodeInterceptor(this.advised)
    };

    Callback[] callbacks;

    // If the target is a static one and the advice chain is frozen,
    // then we can make some optimisations by sending the AOP calls
    // direct to the target using the fixed chain for that method.
    if (isStatic && isFrozen) {
        Method[] methods = rootClass.getMethods();
        Callback[] fixedCallbacks = new Callback[methods.length];
        this.fixedInterceptorMap = new HashMap<String, Integer>(methods.length);

        // TODO: small memory optimisation here (can skip creation for methods with no advice)
        for (int x = 0; x < methods.length; x++) {
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
            fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
                    chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
            this.fixedInterceptorMap.put(methods[x].toString(), x);
        }

        // Now copy both the callbacks from mainCallbacks
        // and fixedCallbacks into the callbacks array.
        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
        this.fixedInterceptorOffset = mainCallbacks.length;
    }
    else {
        callbacks = mainCallbacks;
    }
    return callbacks;
}

  在getCallback()中Spring考慮了很多情況,有很多的細節,但是我們閱讀源碼是沒有必要也沒有那麼多精力把每一個細節都弄明白的,重點是抓住主幹即可。這裏只需要理解最常用的,比如將advised屬性封裝在DynamicAdvisedInterceptor並加入在callbacks中,這麼做的目的是什麼呢?在CGLIB中對於方法的攔截是通過將自定義的攔截器(實現了MethodInterceptor接口的類)加入Callback中並在調用代理時直接激活攔截器中的intercept()方法來實現的,而在getCallback()方法中正好有這一部分功能的實現,DynamicAdvisedInterceptor繼承自MethodInterceptor,加入Callback中后,在再次調用代理時會直接調用其intercept()方法,由此推斷,對於CGLIB方式實現的代理,其核心邏輯應該是在DynamicAdvisedInterceptor中的intercept()方法中的:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Class<?> targetClass = null;
    Object target = null;
    try {
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // May be null. Get as late as possible to minimize the time we
        // "own" the target, in case it comes from a pool...
        target = getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        // 獲取攔截器鏈
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // 如果攔截器鏈為空則直接激活原方法
            retVal = methodProxy.invoke(target, args);
        }
        else {
            // 鏈式調用
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        if (target != null) {
            releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

  這裏的實現與JDK動態代理方式實現代理中的invoke方法大同小異,都是首先構造攔截器鏈,然後封裝此鏈進行串聯調用,不同的是在JDK動態代理的方式中是直接構造ReflectiveMethodInvocation,而在cglib中則是使用CglibMethodInvocation,其是繼承自ReflectiveMethodInvocation,但是proceed()方法並沒有重寫。

 

3. 總結

  本文着重分析了Spring AOP實現原理中代理對象的創建過程,在bean的初始化過程中會執行Spring的後置處理器,這裡會去判斷這個bean是否需要增強,如果需要則會根據Aspect中定義的增強信息,對指定bean進行增強,也就是創建一個代理對象。對代理對象的創建有兩種方式,一種是通過JDK動態代理的方式,另一種是通過cglib的方式。

 

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

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

【Flutter實戰】定位裝飾權重組件及柱狀圖案例

老孟導讀:Flutter中有這麼一類組件,用於定位、裝飾、控制子組件,比如 Container (定位、裝飾)、Expanded (擴展)、SizedBox (固定尺寸)、AspectRatio (寬高比)、FractionallySizedBox (占父組件比例)。這些組件的使用頻率非常高,下面一一介紹,最後給出項目中實際案例熟悉其用法。
【Flutter實戰】系列文章地址:http://laomengit.com/guide/introduction/mobile_system.html

Container

Container 是最常用的組件之一,它是單容器類組件,即僅能包含一個子組件,用於裝飾和定位子組件,例如設置背景顏色、形狀等。

最簡單的用法如下:

Container(
	child: Text('老孟'),
 )

子組件不會發生任何外觀上的變化:

設置背景顏色:

Container(
	color: Colors.blue,
    child: Text('老孟'),
)

設置內邊距( padding ) 和 外邊距( margin )

Container(
      color: Colors.blue,
      child: Container(
        margin: EdgeInsets.all(10),
        padding: EdgeInsets.all(20),
        color: Colors.red,
        child: Text('老孟'),
      ),
    )

效果如下:

decoration 屬性設置子組件的背景顏色、形狀等。設置背景為圓形,顏色為藍色:

Container(
  child: Text('老孟,專註分享Flutter技術及應用'),
  decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.blue),
)

默認情況下,圓形的直徑等於 Container 窄邊長度,相當於在矩形內繪製內切圓。

上面的情況明顯不是我們希望看到了,希望背景是圓角矩形:

Container(
        child: Text('老孟,專註分享Flutter技術及應用'),
        padding: EdgeInsets.symmetric(horizontal: 10),
        decoration: BoxDecoration(
            shape: BoxShape.rectangle,
            borderRadius: BorderRadius.all(Radius.circular(20)),
            color: Colors.blue),
      )

除了背景我們可以設置邊框效果,代碼如下:

Container(
        child: Text('老孟,專註分享Flutter技術及應用'),
        padding: EdgeInsets.symmetric(horizontal: 10),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(12),
          border: Border.all(
            color: Colors.blue,
            width: 2,
          ),
        ),
      )

創建圓角圖片和圓形圖片:

Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        image:  DecorationImage(
          image: NetworkImage(
              'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
          fit: BoxFit.cover,
        ),
        border: Border.all(
          color: Colors.blue,
          width: 2,
        ),
        borderRadius: BorderRadius.circular(12),
      ),
    )

修改其形狀為圓形,代碼如下:

Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        image: DecorationImage(
          image: NetworkImage(
              'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
          fit: BoxFit.cover,
        ),
        border: Border.all(
          color: Colors.blue,
          width: 2,
        ),
        shape: BoxShape.circle,
      ),
    )

設置對齊方式為居中,背景色為藍色,代碼如下:

Container(
        color: Colors.blue,
        child: Text('老孟,一個有態度的程序員'),
        alignment: Alignment.center,
      )

注意:設置對齊方式后,Container將會充滿其父控件,相當於Android中 match_parent 。

Alignment 已經封裝了常用的位置,

通過名字就知道其位置,這裏要介紹一下其他的位置,比如在距離左上角1/4處:

Container(
  alignment: Alignment(-.5,-.5),
  child: Text('老孟,專註分享Flutter技術及應用'),
)

所以這裡有一個非常重要的坐標系,Alignment 坐標系如下:

組件的中心為坐標原點。

設置固定的寬高屬性:

Container(
        color: Colors.blue,
        child: Text('老孟,專註分享Flutter技術及應用'),
        alignment: Alignment.center,
        height: 60,
        width: 250,
      )

通過 constraints 屬性設置最大/小寬、高來確定大小,如果不設置,默認最小寬高是0,最大寬高是無限大(double.infinity),約束width代碼如下:

Container(
        color: Colors.blue,
        child: Text('老孟,專註分享Flutter技術及應用'),
        alignment: Alignment.center,
        constraints: BoxConstraints(
          maxHeight: 100,
          maxWidth: 300,
          minHeight: 100,
          minWidth: 100,
        ),
      )

通過transform可以旋轉、平移、縮放Container,旋轉代碼如下:

Container(
        color: Colors.blue,
        child: Text('老孟,專註分享Flutter技術及應用'),
        alignment: Alignment.center,
        height: 60,
        width: 250,
        transform: Matrix4.rotationZ(0.5),
      )

注意:Matrix4.rotationZ()參數的單位是弧度而不是角度

SizedBox

SizedBox 是具有固定寬高的組件,直接指定具體的寬高,用法如下:

SizedBox(
        height: 60,
        width: 200,
        child: Container(
          color: Colors.blue,
          alignment: Alignment.center,
          child: Text('老孟,專註分享Flutter技術及應用'),
        ),
      )

設置尺寸無限大,如下:

SizedBox(
  height: double.infinity,
  width: double.infinity,
  ...
)

雖然設置了無限大,子控件是否會無限長呢?不,不會,子控件依然會受到父組件的約束,會擴展到父組件的尺寸,還有一個便捷的方式設置此方式:

SizedBox.expand(
  child: Text('老孟,專註分享Flutter技術及應用'),
)

SizedBox 可以沒有子組件,但仍然會佔用空間,所以 SizedBox 非常適合控制2個組件之間的空隙,用法如下:

Column(
          children: <Widget>[
            Container(height: 30,color: Colors.blue,),
            SizedBox(height: 30,),
            Container(height: 30,color: Colors.red,),
          ],
        )

AspectRatio

AspectRatio 是固定寬高比的組件,用法如下:

Container(
        height: 300,
        width: 300,
        color: Colors.blue,
        alignment: Alignment.center,
        child: AspectRatio(
          aspectRatio: 2 / 1,
          child: Container(color: Colors.red,),
        ),
      )

aspectRatio 是寬高比,可以直接寫成分數的形式,也可以寫成小數的形式,但建議寫成分數的形式,可讀性更高。效果如下:

FractionallySizedBox

FractionallySizedBox 是一個相對父組件尺寸的組件,比如占父組件的70%:

Container(
  height: 200,
  width: 200,
  color: Colors.blue,
  child: FractionallySizedBox(
    widthFactor: .8,
    heightFactor: .3,
    child: Container(
      color: Colors.red,
    ),
  ),
)

通過 alignment 參數控制子組件显示的位置,默認為居中,用法如下:

FractionallySizedBox(
  alignment: Alignment.center,
  ...
)

權重組件

Expanded、Flexible 和 Spacer 都是具有權重屬性的組件,可以控制 Row、Column、Flex 的子控件如何布局的組件。

Flexible 組件可以控制 Row、Column、Flex 的子控件佔滿父組件,比如,Row 中有3個子組件,兩邊的寬是100,中間的佔滿剩餘的空間,代碼如下:

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Flexible(
            child: Container(
              color: Colors.red,
              height: 50,
            )
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

還是有3個子組件,第一個佔1/6,第二個佔2/6,第三個佔3/6,代碼如下:

Column(
      children: <Widget>[
        Flexible(
          flex: 1,
          child: Container(
            color: Colors.blue,
            alignment: Alignment.center,
            child: Text('1 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
          ),
        ),
        Flexible(
          flex: 2,
          child: Container(
            color: Colors.red,
            alignment: Alignment.center,
            child: Text('2 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
          ),
        ),
        Flexible(
          flex: 3,
          child: Container(
            color: Colors.green,
            alignment: Alignment.center,
            child: Text('3 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
          ),
        ),
      ],
    )

子組件佔比 = 當前子控件 flex / 所有子組件 flex 之和。

Flexible中 fit 參數表示填滿剩餘空間的方式,說明如下:

  • tight:必須(強制)填滿剩餘空間。
  • loose:盡可能大的填滿剩餘空間,但是可以不填滿。

這2個看上去不是很好理解啊,什麼叫盡可能大的填滿剩餘空間?什麼時候填滿?看下面的例子:

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Flexible(
            child: Container(
              color: Colors.red,
              height: 50,
			  child: Text('Container',style: TextStyle(color: Colors.white),),
            )
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

這段代碼是在最上面代碼的基礎上給中間的紅色Container添加了Text子控件,此時紅色Container就不在充滿空間,再給Container添加對齊方式,代碼如下:

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Flexible(
            child: Container(
              color: Colors.red,
              height: 50,
			  alignment: Alignment.center,
			  child: Text('Container',style: TextStyle(color: Colors.white),),
            )
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

此時又填滿剩餘空間。

大家是否還記得 Container 組件的大小是如何調整的嗎?Container 默認是適配子控件大小的,但當設置對齊方式時 Container 將會填滿父組件,因此是否填滿剩餘空間取決於子組件是否需要填滿父組件。

如果把 Flexible 中子組件由 Container 改為 OutlineButton,代碼如下:

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Flexible(
          child: OutlineButton(
            child: Text('OutlineButton'),
          ),
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

OutlineButton 正常情況下是不充滿父組件的,因此最終的效果應該是不填滿剩餘空間:

下面再來介紹另一個權重組件 Expanded ,源代碼如下:

class Expanded extends Flexible {
  /// Creates a widget that expands a child of a [Row], [Column], or [Flex]
  /// so that the child fills the available space along the flex widget's
  /// main axis.
  const Expanded({
    Key key,
    int flex = 1,
    @required Widget child,
  }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}

Expanded 繼承字 Flexible,fit 參數固定為 FlexFit.tight,也就是說 Expanded 必須(強制)填滿剩餘空間。上面的 OutlineButton 想要充滿剩餘空間可以直接使用 Expanded :

Row(
      children: <Widget>[
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
        Expanded(
          child: OutlineButton(
            child: Text('OutlineButton'),
          ),
        ),
        Container(
          color: Colors.blue,
          height: 50,
          width: 100,
        ),
      ],
    )

Spacer 也是一個權重組件,源代碼如下:

@override
Widget build(BuildContext context) {
  return Expanded(
    flex: flex,
    child: const SizedBox.shrink(),
  );
}

Spacer 的本質也是 Expanded 的實現的,和Expanded的區別是:Expanded 可以設置子控件,而 Spacer 的子控件尺寸是0,因此Spacer適用於撐開 Row、Column、Flex 的子控件的空隙,用法如下:

Row(
  children: <Widget>[
    Container(width: 100,height: 50,color: Colors.green,),
    Spacer(flex: 2,),
    Container(width: 100,height: 50,color: Colors.blue,),
    Spacer(),
    Container(width: 100,height: 50,color: Colors.red,),
  ],
)

三個權重組建總結如下:

  • Spacer 是通過 Expanded 實現的,Expanded繼承自Flexible。
  • 填滿剩餘空間直接使用Expanded更方便。
  • Spacer 用於撐開 Row、Column、Flex 的子組件的空隙。

仿 掘金-我 效果

先看下效果:

拿到效果圖先不要慌 (取出手機拍照發個朋友圈),整個列表每一行的布局基本一樣,所以先寫出一行的效果:

class _SettingItem extends StatelessWidget {
  const _SettingItem(
      {Key key, this.iconData, this.iconColor, this.title, this.suffix})
      : super(key: key);

  final IconData iconData;
  final Color iconColor;
  final String title;
  final Widget suffix;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 45,
      child: Row(
        children: <Widget>[
          SizedBox(
            width: 30,
          ),
          Icon(iconData,color: iconColor,),
          SizedBox(
            width: 30,
          ),
          Expanded(
            child: Text('$title'),
          ),
          suffix,
          SizedBox(
            width: 15,
          ),
        ],
      ),
    );
  }
}

消息中心和其他行最後的樣式不一樣,單獨封裝,帶紅色背景的組件:

class _NotificationsText extends StatelessWidget {
  final String text;

  const _NotificationsText({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 10),
      decoration: BoxDecoration(
          shape: BoxShape.rectangle,
          borderRadius: BorderRadius.all(Radius.circular(50)),
          color: Colors.red),
      child: Text(
        '$text',
        style: TextStyle(color: Colors.white),
      ),
    );
  }
}

灰色後綴組件:

class _Suffix extends StatelessWidget {
  final String text;

  const _Suffix({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      '$text',
      style: TextStyle(color: Colors.grey.withOpacity(.5)),
    );
  }
}

將這些封裝好的組件組合起來:

class SettingDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        _SettingItem(
          iconData: Icons.notifications,
          iconColor: Colors.blue,
          title: '消息中心',
          suffix: _NotificationsText(
            text: '2',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.thumb_up,
          iconColor: Colors.green,
          title: '我贊過的',
          suffix: _Suffix(
            text: '121篇',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.grade,
          iconColor: Colors.yellow,
          title: '收藏集',
          suffix: _Suffix(
            text: '2個',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.shopping_basket,
          iconColor: Colors.yellow,
          title: '已購小冊',
          suffix: _Suffix(
            text: '100個',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.account_balance_wallet,
          iconColor: Colors.blue,
          title: '我的錢包',
          suffix: _Suffix(
            text: '10萬',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.location_on,
          iconColor: Colors.grey,
          title: '閱讀過的文章',
          suffix: _Suffix(
            text: '1034篇',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.local_offer,
          iconColor: Colors.grey,
          title: '標籤管理',
          suffix: _Suffix(
            text: '27個',
          ),
        ),
      ],
    );
  }
}

至此就結束了。

柱狀圖

先來看下效果:

關於動畫部分的內容會在後面的章節具體介紹。這個效果分為3大部分:

  1. 坐標軸,左邊和底部黑色直線。
  2. 矩形柱狀圖。
  3. 動畫控制部分。

坐標軸的實現如下:

class _Axis extends StatelessWidget {
  final Widget child;

  const _Axis({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        border: Border(
          left: BorderSide(color: Colors.black, width: 2),
          bottom: BorderSide(color: Colors.black, width: 2),
        ),
      ),
      child: child,
    );
  }
}

單個柱狀圖實現:

class _Cylinder extends StatelessWidget {
  final double height;
  final double width;
  final Color color;

  const _Cylinder({Key key, this.height, this.width, this.color})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: Duration(seconds: 1),
      height: height,
      width: width,
      color: color,
    );
  }
}

生成多個柱狀圖:

final double _width = 20.0;
List<double> _heightList = [60.0, 80.0, 100.0, 120.0, 140.0];

Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    crossAxisAlignment: CrossAxisAlignment.end,
    children: List.generate(_heightList.length, (index) {
      return _Cylinder(
        height: _heightList[index],
        width: _width,
        color: Colors.primaries[index % Colors.primaries.length],
      );
    }))

將此合併,然後更改每一個柱狀圖的高度:

class CylinderChart extends StatefulWidget {
  @override
  _CylinderChartState createState() => _CylinderChartState();
}

class _CylinderChartState extends State<CylinderChart> {
  final double _width = 20.0;
  List<double> _heightList = [60.0, 80.0, 100.0, 120.0, 140.0];

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        height: 200,
        width: 250,
        child: Stack(
          children: <Widget>[
            _Axis(),
            Positioned.fill(
              left: 5,
              right: 5,
              child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: List.generate(_heightList.length, (index) {
                    return _Cylinder(
                      height: _heightList[index],
                      width: _width,
                      color: Colors.primaries[index % Colors.primaries.length],
                    );
                  })),
            ),
            Positioned(
              top: 0,
              left: 30,
              child: OutlineButton(
                child: Text('反轉'),
                onPressed: () {
                  setState(() {
                    _heightList = _heightList.reversed.toList();
                  });
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}

搞定。

交流

老孟Flutter博客地址(330個控件用法):http://laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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