04 . Docker安全與Docker底層實現

Docker安全

Docker安全性時,主要考慮三個方面

# 1. 由內核的名字空間和控制組機制提供的容器內在安全
# 2. Docker程序(特別是服務端)本身的抗攻擊性
# 3. 內核安全性的加強機制對容器安全性的影響
內核命名空間

Docker容器和LXC容器很相似,所提供的安全特性也差不多。當用docker run啟動一個容器時,在後台Docker為容器創建了一個獨立的命名空間和控制組集合。

命名空間提供了最基礎也是最直接的隔離,在容器中運行的進程不會被運行在主機上的進程和其它容器發 現和作用。

每個容器都有自己獨有的網絡棧,意味着它們不能訪問其他容器的sockets或接口。不過,如果主機系統上做了相應的設置,容器可以像跟主機交互一樣的和其他容器交互。當指定公共端口或使用links來連接2個容器時,容器就可以相互通信了(可以根據配置來限制通信的策略).

從網絡架構的角度來看,所有的容器通過本地主機的網橋接口相互通信,就像物理機器通過物理交換機通信一樣。

那麼,內核中實現命名空間和私有網絡的代碼是否足夠成熟?

內核名字空間從2.6.15版本(2008年7月發布)之後被引入,數年間,這些機制的可靠性在諸多大型生產系統中被實踐驗證。

實際上,名字空間的想法和設計提出的時間要更早,最初是為了在內核中引入一種機制來實現 OpenVZ 的特性。而OpenVZ項目早在2005年就發布了,其設計和實現都已經十分成熟。

控制組

控制組是Linux容器機制的另外一個關鍵組件,負責實現資源的審計和限制.

它提供了很多有用的特性;以及確保各個容器可以公平地分享主機的內存、CPU、磁盤IO等資源;當然,更重要的是,控制組確保了當容器內的資源使用產生壓力時不會連累主機系統。

儘管控制組不負責隔離容器之間相互訪問、處理數據和進程,它在防止拒絕服務(DDOS)攻擊方面是必不可少的。尤其是在多用戶的平台(比如公有或私有的PaaS)上,控制組十分重要。例如,當某些應用程序表現異常的時候,可以保證一致地正常運行和性能。

控制組始於2006年,內核從2.6.24版本開始被引入.

Docker服務端的防護

運行一個容器或應用程序的核心是通過Docker服務端。Docker服務的運行目前需要root權限,因此其安全性十分關鍵。

首先,確保只有可信的用戶才可以訪問Docker服務。Docker允許用戶在主機和容器間共享文件夾,同時不需要限制容器的訪問權限,這就容易讓容器突破資源限制。例如,惡意用戶啟動容器的時候將主機的根目錄/映射到容器的 /host目錄中,那麼容器理論上就可以對主機的文件系統進行任意修改了。這聽起來很瘋狂?但是事實上幾乎所有虛擬化系統都允許類似的資源共享,而沒法禁止用戶共享主機根文件系統到虛擬機系統

這將會造成很嚴重的安全後果。因此,當提供容器創建服務時(例如通過一個web服務器),要更加註意進行參數的安全檢查,防止惡意的用戶用特定參數來創建一些破壞性的容器

為了加強對服務端的保護,Docker的REST API(客戶端用來跟服務端通信)在0.5.2之後使用本地的Unix套接字機制替代了原先綁定在 127.0.0.1 上的TCP套接字,因為後者容易遭受跨站腳本攻擊。現在用戶使用Unix權限檢查來加強套接字的訪問安全。

用戶仍可以利用HTTP提供REST API訪問。建議使用安全機制,確保只有可信的網絡或VPN,或證書保 護機制(例如受保護的stunnel和ssl認證)下的訪問可以進行。此外,還可以使用HTTPS和證書來加強 保護。

最近改進的Linux名字空間機制將可以實現使用非root用戶來運行全功能的容器。這將從根本上解決了容器和主機之間共享文件系統而引起的安全問題。

終極目標是改進 2 個重要的安全特性:

  • 將容器的root用戶映射到本地主機上的非root用戶,減輕容器和主機之間因權限提升而引起的安全問題;
  • 允許Docker服務端在非root權限下運行,利用安全可靠的子進程來代理執行需要特權權限的操作。這些子進程將只允許在限定範圍內進行操作,例如僅僅負責虛擬網絡設定或文件系統管理、配置操作等。
  • 最後,建議採用專用的服務器來運行Docker 和相關的管理服務(例如管理服務比如ssh監控和進程監控、管理工具nrpe、collectd等)。其它的業務服務都放到容器中去運行。
內核能力機制

能力機制(Capability)是Linux內核一個強大的特性,可以提供細粒度的權限訪問控制。 Linux內核自2.2版本起就支持能力機制,它將權限劃分為更加細粒度的操作能力,既可以作用在進程上,也可以作用在文件上。

例如,一個Web服務進程只需要綁定一個低於1024的端口的權限,並不需要root權限。那麼它只需要被授權 net_bind_service能力即可。此外,還有很多其他的類似能力來避免進程獲取root權限。

默認情況下,Docker啟動的容器被嚴格限制只允許使用內核的一部分能力.

使用能力機制對加強Docker容器的安全有很多好處。通常,在服務器上會運行一堆需要特權權限的進程,包括有ssh、cron、syslogd、硬件管理工具模塊(例如負載模塊)、網絡配置工具等等。容器跟這些進程是不同的,因為幾乎所有的特權進程都由容器以外的支持系統來進行管理。

# 1. ssh訪問被主機上ssh服務來管理;
# 2. cron通常應該作為用戶進程執行執行,權限交給使用他服務的應用來處理;
# 3. 日誌系統可由Docker或第三方服務管理;
# 4. 硬件管理無關緊要,容器中也就無需執行udevd以及類似服務;
# 5. 網絡管理也都在主機上設置,除非特殊需求,容器不需要對網絡進行配置.

從上面的例子可以看出,大部分情況下,容器並不需要真正的root權限,容器只需要少數的能力即可.為了加強安全,容器可以禁用一些沒必要的權限:

# 1. 完全禁止任何mount操作.
# 2. 禁止直接訪問本地主機的套接字.
# 3. 禁止訪問一些文件系統的操作,比如創建新的設備,修改文件屬性等.
# 4. 禁止模塊加載.

這樣,就算攻擊者在容器中取得了root權限,也不能獲得本地主機的較高權限,能進行的破壞也有限.

默認認情況下,Docker採用 白名單 機制,禁用必需功能之外的其它權限。 當然,用戶也可以根據自身需求來為Docker容器啟用額外的權限。

其他安全特性.

除了能力機制之外,還可以利用一些現有的安全機制來增強docker的安全性,例如TOMOYO,AppArmor,SELinux,GRSEC等.

Docker 當前默認只開啟了能力機制,用戶可以採用多種方案來加強Docker主機的安全,例如:

  1. 在內核中啟用GRSEC和PAX,這將增加很多編譯和運行時的安全檢查,通過地址隨機化避免惡意探測等,並且,啟用該特性不需要Docker進行任何配置.

  2. 使用一些有增強安全特性的容器模板,比如帶AppArmor的模板和Redhat帶Selinux策略的模板.這些模板提供了額外的安全特性.

  3. 用戶可以自定義訪問控制機制來定製安全策略.

跟其他添加Docker容器的第三方工具一樣(比如網絡拓撲和文件系統共享),有很多類似的機制,在不改變Docker內核情況下就可以加固現有的容器.

小結

總體來說,Docker容器還是十分安全的,特別是在容器不使用root權限來運行進程的話.

另外,用戶可以使用現有工具,比如Apparmor,SELinux,GRSEC來增強安全性,甚至自己在內核中實現更複雜的安全機制.

Docker底層實現

Docker底層的核心技術包括Linux上的命名空間(Namespaces)、控制組(ControlGroups),Union文件系統(Union file systems)和容器格式(Container format).

我們知道,傳統的虛擬機通過在宿主主機中運行hypervisor來模擬一套完整的硬件環境提供給虛擬機的操作系統.虛擬機系統看到的環境是可限制的,也是彼此隔離的,這種直接的做法實現了對資源完整的封裝,但很多時候往往意味着系統資源的浪費,例如,以宿主機和虛擬機系統都為linux系統為例,虛擬機中運行的應用其實是可以利用宿主機系統中的運行環境。

我們知道,在操作系統中,包括內核、文件系統、網絡、PID、UID、IPC、內存、硬盤、CPU等等,所有的資源都是應用進程直接共享的,要想實現虛擬化,除了要實現對內存、CPU、網絡IO、硬盤IO、存儲空間等的限制外,還要實現文件系統、網絡、PID、UID、IPC等等的相互隔離,前者相對容易實現一些,後者則需要宿主機系統的深入支持.

隨着Linux系統對於命名空間功能的完善實現,程序員可以實現上面的所有需要,讓某些進程在彼此隔離的命名空間中運行,大家雖然都共用一個內核和某些運行時環境(l例如一些系統命令和系統等),但是彼此卻看不到,大家都以為系統中只有自己的存在,這種機制就是容器,利用命名空間來做權限的隔離控制,利用cgroups來做資源分配.

容器的基本架構

Dcoker採用了c/s架構,包括客戶端和服務端,Docker守護進程(Daemon)作為服務端接受來自客戶端的請求,並處理這些請求(創建、運行、分發容器).

客戶端和服務端既可以運行在一個機器上,也可以通過socket或者RESTful API來進行通信.

Docker守護進程一般在宿主主機後台運行,等待來自客戶端的消息.

Docker客戶端則為用戶提供一系列可執行的命令,用戶用這些命令實現跟Docker守護進程交互.

命名空間

命名空間是Linux內核一個強大的特性,每個容器都有自己單獨的命名空間,運行在其中的應用都像是在獨立的操作系統運行一樣,命名空間保證了容器之間彼此互不影響.

pid命名空間

不同用戶的進程就是通過pid命名空間隔離開的,且不同命名空間可以有相同的pid,所有的LXC進程在Docker中的父進程為Docker進程,每個LXC進程具有不同的命名空間,同時由於嵌套,因此可以很方便的實現嵌套的Docker容器.

net命名空間

有了pid命名空間,每個命名空間的pid能夠實現相互隔離,但是網絡端口還是共享host的端口,網絡隔離是通過net命名空間實現的,每個net命名空間有單獨的網絡設備,IP地址,路由表,/proc/net目錄,這樣每個容器的網絡就能隔離開來,Docker默認採用veth的方式,將容器中的虛擬網卡host上的一個Docker網橋docker0連接在一起.

IPC命名空間

容器中進程交互採用了Linux常見的進程交互方法,包括信號量,消息隊列和共享內存等,然而同VM不同的是,容器的進程交互實際山還是host上具有相同pid命名空間的進程交互,因此需要在IPC資源中申請加入命名空間信息,每個IPC資源有一個唯一的32位id。

mnt命名空間

類似chroot,將一個進程放到一個特定的目錄執行,mnt命名空間允許不同命名空間的進程看到的文件結構不同,這樣每個命名空間中的進程所看到的文件目錄就被隔離開了,同chroot不同,每個命名空間的容器在/proc/mounts的信息只包含所在命名空間的mount point。

uts命名空間

UTS命名空間允許每個容器擁有獨立的hostname和domain name,使其在網絡上可以被視作一個獨立的節點而非主機上的一個進程.

每個容器可以有不同的用戶和組id,也就是說可以在容器內用容器內部的用戶執行程序而非主機上的用戶.

控制組

控制組(cgroups)是一個Linux內核的一個特性,主要用來對資源進行隔離、限制、審計等,只有能控制分配到容器的資源,才能避免當多個容器同時運行時對系統資源的競爭.

控制組技術最早由Google的程序員在2006年提出,Linux內核從2.6.24開始支持.

控制組可以提供對容器的內存、CPU、磁盤IO等資源的限制和審計管理.

聯合文件系統

聯合文件系統(UnionFS)是一種分層、輕量級並且高性能的文件系統,他支持對文件系統的修改作為一次提交來一層層的疊加,同時可以將不同目錄掛載到同一個虛擬文件系統.

聯合文件系統是Docker鏡像的基礎,鏡像可以通過分層來進行繼承,基於基礎鏡像(沒有父鏡像),可以製作各種具體的應用鏡像.

另外,不同Docker容器就可以共享一些基礎的文件系統層,同時加上自己獨有的改動層,大大提高了存儲的效率.

Docker中使用的AUFS就是一種聯合文件系統,AUFS支持為每一個成員目錄(類似Git的分支)設定只讀(readonly)、讀寫(readwrite)和寫出(whiteout-able)權限,同時AUFS里有一個類似分層的概念,對只讀權限的分支可以邏輯上進行增量的修改(不影響只讀部分的).

Docker目前支持的聯合文件系統包括OverlayFS,AUFS,BtrFS,VFS,ZFS和Device Mapper。

有可能的情況下,推薦使用overlay2存儲驅動,overlay2是目前Docker默認的存儲驅動,以前是aufs,可以通過配置以上提到的其他類型存儲驅動.

容器格式

最初,Docker採用了LXC中的容器格式,從0.7版本開始以後去除LXC,轉而使用自行開發的libcontainer,從1.11開始,進一步演進為runC和containerd。

網絡實現

Docker的網絡實現其實就是利用了Linux上的網絡命名空間和虛擬網絡設備(特別是vethpair).

基本原理

首先,要實現網絡通信,機器需要至少一個網絡接口(物理接口或虛擬接口)來收發數據包,此外,如果不同子網之間要進行通信,需要路由機制.

Docker中的網絡接口默認都是虛擬的接口,虛擬接口的優勢之一就是轉發效率較高,Linux通過在內核中進行數據複製來實現虛擬接口之間的數據轉發,發送接口的發送緩存中的數據包直接複製到接收接口的接受緩存中,對於本地系統和容器內系統來看就像是一個正常的以太網卡,只是他不需要真正同外部網絡設備通信,速度要快很多.

Docker容器網絡就是利用了這項技術,他在本地主機和容器內分別創建一個虛擬接口,並讓他們彼此連通(這樣的一對接口叫做veth pair).

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

Java 異常處理的十個建議

前言

Java異常處理的十個建議,希望對大家有幫助~

本文已上傳github:

https://github.com/whx123/JavaHome

公眾號:撿田螺的小男孩

一、盡量不要使用e.printStackTrace(),而是使用log打印。

反例:

try{
  // do what you want  
}catch(Exception e){
  e.printStackTrace();
}

正例:

try{
  // do what you want  
}catch(Exception e){
  log.info("你的程序有異常啦,{}",e);
}

理由:

  • printStackTrace()打印出的堆棧日誌跟業務代碼日誌是交錯混合在一起的,通常排查異常日誌不太方便。
  • e.printStackTrace()語句產生的字符串記錄的是堆棧信息,如果信息太長太多,字符串常量池所在的內存塊沒有空間了,即內存滿了,那麼,用戶的請求就卡住啦~

二、catch了異常,但是沒有打印出具體的exception,無法更好定位問題

反例:

try{
  // do what you want  
}catch(Exception e){
  log.info("你的程序有異常啦");
}

正例:

try{
  // do what you want  
}catch(Exception e){
  log.info("你的程序有異常啦,{}",e);
}

理由:

  • 反例中,並沒有把exception出來,到時候排查問題就不好查了啦,到底是SQl寫錯的異常還是IO異常,還是其他呢?所以應該把exception打印到日誌中哦~

三、不要用一個Exception捕捉所有可能的異常

反例:

public void test(){
    try{
        //…拋出 IOException 的代碼調用
        //…拋出 SQLException 的代碼調用
    }catch(Exception e){
        //用基類 Exception 捕捉的所有可能的異常,如果多個層次都這樣捕捉,會丟失原始異常的有效信息哦
        log.info(“Exception in test,exception:{}”, e);
    }
}

正例:

public void test(){
    try{
        //…拋出 IOException 的代碼調用
        //…拋出 SQLException 的代碼調用
    }catch(IOException e){
        //僅僅捕捉 IOException
        log.info(“IOException in test,exception:{}”, e);
    }catch(SQLException e){
        //僅僅捕捉 SQLException
        log.info(“SQLException in test,exception:{}”, e);
    }
}

理由:

  • 用基類 Exception 捕捉的所有可能的異常,如果多個層次都這樣捕捉,會丟失原始異常的有效信息哦

四、記得使用finally關閉流資源或者直接使用try-with-resource

反例:

FileInputStream fdIn = null;
try {
    fdIn = new FileInputStream(new File("/jay.txt"));
    //在這裏關閉流資源?有沒有問題呢?如果發生異常了呢?
    fdIn.close();
} catch (FileNotFoundException e) {
    log.error(e);
} catch (IOException e) {
    log.error(e);
}

正例1:

需要使用finally關閉流資源,如下

FileInputStream fdIn = null;
try {
    fdIn = new FileInputStream(new File("/jay.txt"));
} catch (FileNotFoundException e) {
    log.error(e);
} catch (IOException e) {
    log.error(e);
}finally {
    try {
        if (fdIn != null) {
            fdIn.close();
        }
    } catch (IOException e) {
        log.error(e);
    }
}

正例2:

當然,也可以使用JDK7的新特性try-with-resource來處理,它是Java7提供的一個新功能,它用於自動資源管理。

  • 資源是指在程序用完了之後必須要關閉的對象。
  • try-with-resources保證了每個聲明了的資源在語句結束的時候會被關閉
  • 什麼樣的對象才能當做資源使用呢?只要實現了java.lang.AutoCloseable接口或者java.io.Closeable接口的對象,都OK。
try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) {
    // use resources   
} catch (FileNotFoundException e) {
    log.error(e);
} catch (IOException e) {
    log.error(e);
}

理由:

  • 如果不使用finally或者try-with-resource,當程序發生異常,IO資源流沒關閉,那麼這個IO資源就會被他一直佔著,這樣別人就沒有辦法用了,這就造成資源浪費。

五、捕獲異常與拋出異常必須是完全匹配,或者捕獲異常是拋異常的父類

反例:

//BizException 是 Exception 的子類
public class BizException extends Exception {}
//拋出父類Exception
public static void test() throws Exception {}

try {
    test(); //編譯錯誤
} catch (BizException e) { //捕獲異常子類是沒法匹配的哦
    log.error(e);
}

正例:

//拋齣子類Exception
public static void test() throws BizException {}

try {
    test();
} catch (Exception e) {
    log.error(e);
}

六、捕獲到的異常,不能忽略它,至少打點日誌吧

反例:

public static void testIgnoreException() throws Exception {
    try {       
        // 搞事情
    } catch (Exception e) {     //一般不會有這個異常
        
    }
}

正例:

public static void testIgnoreException() {
    try {
        // 搞事情
    } catch (Exception e) {     //一般不會有這個異常
        log.error("這個異常不應該在這裏出現的,{}",e); 
    }
}

理由:

  • 雖然一個正常情況都不會發生的異常,但是如果你捕獲到它,就不要忽略呀,至少打個日誌吧~

七、注意異常對你的代碼層次結構的侵染(早發現早處理)

反例:

public UserInfo queryUserInfoByUserId(Long userid) throw SQLException {
    //根據用戶Id查詢數據庫
}

正例:

public UserInfo queryUserInfoByUserId(Long userid) {
    try{
        //根據用戶Id查詢數據庫
    }catch(SQLException e){
        log.error("查詢數據庫異常啦,{}",e);
    }finally{
        //關閉連接,清理資源
    }
}

理由:

  • 我們的項目,一般都會把代碼分 Action、Service、Dao 等不同的層次結構,如果你是DAO層處理的異常,儘早處理吧,如果往上 throw SQLException,上層代碼就還是要try catch處理啦,這就污染了你的代碼~

八、自定義封裝異常,不要丟棄原始異常的信息Throwable cause

我們常常會想要在捕獲一個異常后拋出另一個異常,並且希望把原始異常的信息保存下來,這被稱為異常鏈。公司的框架提供統一異常處理就用到異常鏈,我們自定義封裝異常,不要丟棄原始異常的信息,否則排查問題就頭疼啦

反例:

public class TestChainException {
    public void readFile() throws MyException{
        try {
            InputStream is = new FileInputStream("jay.txt");
            Scanner in = new Scanner(is);
            while (in.hasNext()) {
                System.out.println(in.next());
            }
        } catch (FileNotFoundException e) {
            //e 保存異常信息
            throw new MyException("文件在哪裡呢");
        }
    }
    public void invokeReadFile() throws MyException{
        try {
            readFile();
        } catch (MyException e) {
            //e 保存異常信息
            throw new MyException("文件找不到");
        }
    }
    public static void main(String[] args) {
        TestChainException t = new TestChainException();
        try {
            t.invokeReadFile();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}
//MyException 構造器
public MyException(String message) {
        super(message);
    }

運行結果如下,沒有了Throwable cause,不好排查是什麼異常了啦

正例:


public class TestChainException {
    public void readFile() throws MyException{
        try {
            InputStream is = new FileInputStream("jay.txt");
            Scanner in = new Scanner(is);
            while (in.hasNext()) {
                System.out.println(in.next());
            }
        } catch (FileNotFoundException e) {
            //e 保存異常信息
            throw new MyException("文件在哪裡呢", e);
        }
    }
    public void invokeReadFile() throws MyException{
        try {
            readFile();
        } catch (MyException e) {
            //e 保存異常信息
            throw new MyException("文件找不到", e);
        }
    }
    public static void main(String[] args) {
        TestChainException t = new TestChainException();
        try {
            t.invokeReadFile();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}
//MyException 構造器
public MyException(String message, Throwable cause) {
        super(message, cause);
    }

九、運行時異常RuntimeException ,不應該通過catch 的方式來處理,而是先預檢查,比如:NullPointerException處理

反例:

try {
  obj.method() 
} catch (NullPointerException e) {
...
}

正例:

if (obj != null){
   ...
}

十、注意異常匹配的順序,優先捕獲具體的異常

注意異常的匹配順序,因為只有第一個匹配到異常的catch塊才會被執行。如果你希望看到,是NumberFormatException異常,就拋出NumberFormatException,如果是IllegalArgumentException就拋出IllegalArgumentException。

反例:

try {
    doSomething("test exception");
} catch (IllegalArgumentException e) {       
    log.error(e);
} catch (NumberFormatException e) {
    log.error(e);
}

正例:

try {
    doSomething("test exception");
} catch (NumberFormatException e) {       
    log.error(e);
} catch (IllegalArgumentException e) {
    log.error(e);
}

理由:

  • 因為NumberFormatException是IllegalArgumentException 的子類,反例中,不管是哪個異常,都會匹配到IllegalArgumentException,就不會再往下執行啦,因此不知道是否是NumberFormatException。所以需要優先捕獲具體的異常,把NumberFormatException放前面~

公眾號

  • 歡迎關注我個人公眾號,交個朋友,一起學習哈~
  • 如果答案整理有錯,歡迎指出哈,感激不盡~

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

Spring源碼系列(一)–詳細介紹bean組件

簡介

spring-bean 組件是 Spring IoC 的核心,我們可以使用它的 beanFactory 來獲取所需的對象,對象的實例化、屬性裝配和初始化等都可以交給 spring 來管理。

針對 spring-bean 組件,我計劃分成兩篇博客來講解。本文會詳細介紹這個組件,包括以下內容。下一篇再具體分析它的源碼。

  1. spring-bean 組件的相關概念:實例化、屬性裝配、初始化、bean、beanDefinition、beanFactory。
  2. bean 組件的使用:註冊bean、獲取bean、屬性裝配、處理器等。

項目環境說明

正文開始前,先介紹下示例代碼使用的環境等。

工程環境

JDK:1.8.0_231

maven:3.6.1

IDE:Spring Tool Suites4 for Eclipse 4.12

Spring:5.2.6.RELEASE

依賴引入

除了引入 spring,這裏還額外引入了日誌和單元測試。

    <properties>
        <spring.version>5.2.6.RELEASE</spring.version>
    </properties>
    
    <dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
		<!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- logback -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.28</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <type>jar</type>
        </dependency>
    </dependencies>

幾個重要概念

實例化、屬性裝配和初始化

在 spring-bean 組件的設計中,這三個詞完整、有序地描述了生成一個新對象的整個流程,是非常重要的理論基礎。它們的具體含義如下:

  1. 實例化:創建出一個新對象。
  2. 屬性裝配:給對象的成員屬性賦值。
  3. 初始化:調用對象的初始化方法。

下面使用一段代碼來簡單演示下這個流程。

public class UserService implements IUserService {
    
    private UserDao userDao;
    
    
    public UserService() {
        super();
        System.err.println("UserService構造方法被調用");
        System.err.println("        ||");
        System.err.println("        \\/");
    }
    
    public void init() {
        System.err.println("UserService的init方法被調用");
        System.err.println("        ||");
        System.err.println("        \\/");
    }
    
    
    public UserDao getUserDao() {
        return userDao;
    }
    
    public void setUserDao(UserDao userDao) {
        System.err.println("UserService的屬性裝配中");
        System.err.println("        ||");
        System.err.println("        \\/");
        this.userDao = userDao;
    }
    
}

如果我們將這個 bean 交給 spring 管理,獲取 bean 時會在控制台打印以下內容:

什麼是bean

按照官方的說法, bean 是一個由 Spring IoC 容器實例化、組裝和管理的對象。我認為,這種表述是錯誤的,通過registerSingleton方式註冊的 bean,它就不是由 Spring IoC 容器實例化、組裝,所以,更準確的表述應該是這樣:

某個類的對象、FactoryBean 對象、描述對象或 FactoryBean 描述對象,被註冊到了 Spring IoC 容器,這時通過 Spring IoC 容器獲取的這個類的對象就是 bean。

舉個例子,使用了 Spring 的項目中, Controller 對象、Service 對象、DAO 對象等都屬於 bean。

至於什麼是 IoC 容器,在 spring-bean 組件中,我認為,beanFactory 就屬於 IoC 容器。

什麼是beanFactory

從客戶端來看,一個完整的 beanFactory 工廠包含以下基本功能:

  1. 註冊別名。對應下圖的AliasRegistry接口。
  2. 註冊單例對象。對應下圖的SingletonBeanRegistry接口。
  3. 註冊BeanDefinition對象。對應下圖的BeanDefinitionRegistry接口。
  4. 獲取 bean。對應下圖的BeanFactory接口。

在 spring-bean 組件中,DefaultListableBeanFactory就是一個完整的 beanFactory 工廠,也可以說是一個 IoC 容器。接下來的例子將直接使用它來作為 beanFactory。

至於其他的接口,這裏也補充說明下。HierarchicalBeanFactory用於提供父子工廠的支持,ConfigurableBeanFactory用於提供配置 beanFactory 的支持,ListableBeanFactory用於提供批量獲取 bean 的支持(不包含父工廠的 bean),AutowireCapableBeanFactory用於提供實例化、屬性裝配、初始化等一系列管理 bean 生命周期的支持。

什麼是beanDefinition

beanDefinaition 是一個描述對象,用來描述 bean 的實例化、初始化等信息。

在 spring-bean 組件中,beanDefinaition主要包含以下四種:

  1. RootBeanDefinition:beanFactory 中最終用於 createBean 的 beanDefinaition,不允許添加 parentName。在 BeanFactory 中以下三種實現類都會被包裝成RootBeanDefinition用於 createBean。
  2. ChildBeanDefinition:必須設置 parentName 的 beanDefinaition。當某個 Bean 的描述對象和另外一個的差不多時,我們可以直接定義一個ChildBeanDefinition,並設置它的 parentName 為另外一個的 beanName,這樣就不用重新設置一份。
  3. GenericBeanDefinition:通用的 beanDefinaition,可以設置 parentName,也可以不用設置。
  4. AnnotatedGenericBeanDefinition:在GenericBeanDefinition基礎上增加暴露註解數據的方法。

spring-bean 組件提供了BeanDefinitionBuilder用於創建 beanDefinaition,下面的例子會頻繁使用到。

使用例子

入門–簡單地註冊和獲取bean

下面通過一個入門例子來介紹註冊和獲取 bean 的過程。

    @Test
    public void testBase() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        // 創建BeanDefinition對象
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        
        // 註冊Bean
        beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
        
        // 獲取Bean
        IUserService userService = (IUserService)beanFactory.getBean("userService");
        System.err.println(userService.get("userId"));
    }

兩種註冊bean的方式

beanFactory 除了支持註冊 beanDefinition,還允許直接註冊 bean 實例,如下。和前者相比,後者的實例化、屬性裝配和初始化都沒有交給 spring 管理。

    @Test
    public void testRegisterWays() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        // 註冊Bean-- BeanDefinition方式
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
        
        // 註冊Bean-- Bean實例方式
        beanFactory.registerSingleton("userService2", new UserService());
        
        // 獲取Bean
        IUserService userService = (IUserService)beanFactory.getBean("userService");
        System.err.println(userService.get("userId"));
        IUserService userService2 = (IUserService)beanFactory.getBean("userService2");
        System.err.println(userService2.get("userId"));
    }

當然,這種方式僅支持單例 bean 的註冊,多例的就沒辦法了。

註冊多例bean

默認情況下,我們從 beanFactory 獲取到的 bean 都是單例的,即每次 getBean 獲取到的都是同一個對象,實際項目中,有時我們需要獲取到多例的 bean,這個時候就可以通過設置 beanDefinition 的 scope 來處理。如下:

    @Test
    public void testScope() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        // 註冊Bean-- BeanDefinition方式
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        rootBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
        
        // 獲取Bean--通過BeanType
        IUserService userService1 = beanFactory.getBean(IUserService.class);
        IUserService userService2 = beanFactory.getBean(IUserService.class);
        assertNotEquals(userService1, userService2);
    }

多種獲取bean的方式

beanFactory 提供了多種方式來獲取 bean 實例,如下。如果同時使用 beanName 和 beanType,獲取到指定 beanName 的 bean 後會進行類型檢查和類型類型,如果都不通過,將會報錯。

    @Test
    public void testGetBeanWays() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        // 創建BeanDefinition對象
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        
        // 註冊Bean
        beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
        
        // 獲取Bean--通過BeanName
        IUserService userService = (IUserService)beanFactory.getBean("userService");
        System.err.println(userService.get("userId"));
        // 獲取Bean--通過BeanType
        IUserService userService2 = beanFactory.getBean(IUserService.class);
        System.err.println(userService2.get("userId"));
        // 獲取Bean--通過BeanName+BeanType的方式
        IUserService userService3 = beanFactory.getBean("userService", IUserService.class);
        System.err.println(userService3.get("userId"));
    }

不同形式的beanName

通過 name 獲取 bean,這個 name 包含以下三種形式:

  1. beanName,即註冊 bean 時用的 beanName。這是使用最多的形式,需要注意一點,如果 beanName 對應的 bean 是FactoryBean,並不會返回FactoryBean的實例,而是會返回FactoryBean.getObject方法的返回結果。
  2. alias,即我們通過SimpleAliasRegistry.registerAlias(name, alias)方法註冊到 beanFactory 的別名。這時,需要將 name 解析為 alias 對應的 beanName 來獲取 bean。
  3. ‘&’ + factorybeanName,這時為了獲取FactoryBean的一種特殊格式。
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 註冊Bean--註冊的是一個 FactoryBean
        UserServiceFactoryBean userServiceFactoryBean = new UserServiceFactoryBean();
        beanFactory.registerSingleton("userServiceFactoryBean", userServiceFactoryBean);

        // 註冊BeanName的別名
        beanFactory.registerAlias("userServiceFactoryBean", "userServiceAlias01");

        // 通過BeanName獲取
        assertEquals(userServiceFactoryBean.getObject(), beanFactory.getBean("userServiceFactoryBean"));

        // 通過別名獲取
        assertEquals(userServiceFactoryBean.getObject(), beanFactory.getBean("userServiceAlias01"));

        // 通過&+FactoryBeanName的方式
        assertEquals(userServiceFactoryBean, beanFactory.getBean("&userServiceFactoryBean"));

bean衝突的處理

通過 beanType 的方式獲取 bean,如果存在多個同類型的 bean且無法確定最優先的那一個,就會報錯。

    @Test
    public void testPrimary() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); 
        
        // 創建BeanDefinition對象
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        
        // 註冊Bean
        beanFactory.registerBeanDefinition("UserRegisterBeanDefinition", rootBeanDefinition);
        beanFactory.registerSingleton("UserRegisterSingleton", new User("zzs002", 19));
        beanFactory.registerSingleton("UserRegisterSingleton2", new User("zzs002", 18));
        
        // 獲取Bean--通過BeanType
        User user = beanFactory.getBean(User.class);
        System.err.println(user);
    }

運行以上方法,將出現 NoUniqueBeanDefinitionException 的異常。

針對上面的這種問題,spring 的處理方法如下:

  1. 檢查是否存在唯一一個通過registerBeanDefinition且isPrimary = true的(存在多個會報錯),存在的話將它作為匹配到的唯一 beanName;
  2. 通過我們註冊的OrderComparator來確定優先值最小的作為唯一 beanName。注意,通過registerSingleton註冊的和通過registerBeanDefinition註冊的,比較的對象是不一樣的,前者比較的對象是 bean 實例,後者比較的對象是 bean 類型,另外,這種方法最好不要存在相同優先級的 bean。

所以,為了解決這種衝突,可以設置BeanDefinition對象的 isPrimary = true,或者為 beanFactory 設置OrderComparator,代碼如下:

    @Test
    public void testPrimary() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 為BeanFactory設置比較器
        beanFactory.setDependencyComparator(new OrderComparator() {

            @Override
            public Integer getPriority(Object obj) {
                return obj.hashCode();
            }
        });

        // 創建BeanDefinition對象
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        // rootBeanDefinition.setPrimary(true); // 設置BeanDefinition對象為isPrimary

        // 註冊Bean
        beanFactory.registerBeanDefinition("userRegisterBeanDefinition", rootBeanDefinition);
        beanFactory.registerSingleton("userRegisterSingleton", new User("zzs002", 19));
        beanFactory.registerSingleton("userRegisterSingleton2", new User("zzs003", 18));

        // 獲取Bean--通過BeanType
        User user = beanFactory.getBean(User.class);
        System.err.println(user);
    }

使用TypeConverter獲取自定義類型的對象

當我們使用 beanType 來獲取 bean 時,如果獲取到的 bean 不是指定的類型,這時,不會立即報錯,beanFactory 會嘗試使用我們註冊的TypeConverter來強制轉換。而這個類型轉換器我們可以自定義設置,如下。

    @Test
    public void testTypeConverter() {
        
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 註冊類型轉換器
        beanFactory.setTypeConverter(new TypeConverterSupport() {
            @SuppressWarnings("unchecked")
            @Override
            public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
                    @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
                // 將User轉換為UserVO
                if(UserVO.class.equals(requiredType) && User.class.isInstance(value)) {
                    User user = (User)value;
                    return (T)new UserVO(user);
                }
                return null;
            }
        });

        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        beanFactory.registerBeanDefinition("User", rootBeanDefinition);

        UserVO bean = beanFactory.getBean("User", UserVO.class);
        Assert.assertTrue(UserVO.class.isInstance(bean));
    }

屬性裝配

beanFactory 在進行屬性裝配時,會讀取 beanDefinition 對象中的PropertyValues中的propertyName=propertyValue,所以,我們想要對 bean 注入什麼參數,只要在定義 beanDefinition 時指定就行。

    @Test
    public void testPopulate() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 定義userService的beanDefinition
        AbstractBeanDefinition userServiceBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        // 定義userDao的beanDefinition
        AbstractBeanDefinition userDaoBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserDao.class).getBeanDefinition();
        // 給userService設置裝配屬性
        userServiceBeanDefinition.getPropertyValues().add("userDao", userDaoBeanDefinition);
        // userServiceBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        // userServiceBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME);

        // 註冊Bean
        beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition);
        beanFactory.registerBeanDefinition("userDao", userDaoBeanDefinition);

        // 獲取Bean
        IUserService userService = (IUserService)beanFactory.getBean("userService");
        userService.save(null);
    }

運行以上方法,發現 userDao 對象被成功注入到了 userService 對象中!

這裏補充一點,beanFactory 除了通過 beanDefinition 中的PropertyValues獲取 propertyName=propertyValue,還可以讀取 bean 中的屬性來自動定義 propertyName=propertyValue,只要設置 beanDefinition 的 autowireMode 就可以了。

bean 實例化、屬性裝配和初始化的處理器

前面講到,我們將 bean 的實例化、屬性裝配和初始化都交給了 spring 處理,然而,有時我們需要在這些節點對 bean 進行自定義的處理,這時就需要用到 beanPostProcessor。

這裏我簡單演示下如何添加處理器,以及處理器的執行時機,至於處理器的具體實現,我就不多擴展了。

    @Test
    public void testPostProcessor() {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 添加實例化處理器
        beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
            // 如果這裏我們返回了對象,則beanFactory會將它作為bean直接返回,不再進行bean的實例化、屬性裝配和初始化等操作
            public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
                if(UserService.class.equals(beanClass)) {
                    System.err.println("bean實例化之前的處理。。 --> ");
                }
                return null;
            }

            // 這裏通過返回的布爾值判斷是否需要繼續對bean進行屬性裝配和初始化等操作
            public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
                if(UserService.class.isInstance(bean)) {
                    System.err.println("bean實例化之後的處理。。 --> ");
                }
                return true;
            }
        });

        // 添加裝配處理器
        beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {

            // 這裏可以在屬性裝配前對參數列表進行調整
            public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
                if(UserService.class.isInstance(bean)) {
                    System.err.println("屬性裝配前對參數列表進行調整 --> ");
                }
                return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);
            }

        });

        // 添加初始化處理器
        beanFactory.addBeanPostProcessor(new BeanPostProcessor() {

            // 初始化前對bean進行改造
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                if(UserService.class.isInstance(bean)) {
                    System.err.println("初始化前,對Bean進行改造。。 --> ");
                }
                return bean;
            }

            // 初始化后對bean進行改造
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if(UserService.class.isInstance(bean)) {
                    System.err.println("初始化后,對Bean進行改造。。 --> ");
                }
                return bean;
            }
        });
        // 定義userService的beanDefinition
        AbstractBeanDefinition userServiceBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        // 定義userDao的beanDefinition
        AbstractBeanDefinition userDaoBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserDao.class).getBeanDefinition();
        // 給userService添加裝配屬性
        userServiceBeanDefinition.getPropertyValues().add("userDao", userDaoBeanDefinition);
        // 給userService設置初始化方法
        userServiceBeanDefinition.setInitMethodName("init");
        
        // 註冊bean
        beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition);
        beanFactory.registerBeanDefinition("userDao", userDaoBeanDefinition);

        IUserService userService = (IUserService)beanFactory.getBean("userService");
        System.err.println(userService.get("userId"));
    }

運行以上方法,控制台打印出了整個處理流程。實際開發中,我們可以通過設置處理器來改變改造生成的 bean 。

以上,基本介紹完 spring-bean 組件的使用,下篇博客再分析源碼,如果在分析過程中發現有其他特性,也會在這篇博客的基礎上擴展。

相關源碼請移步: spring-beans

本文為原創文章,轉載請附上原文出處鏈接:https://www.cnblogs.com/ZhangZiSheng001/p/13126053.html

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

噪音管制區修正 大眾運輸30公尺內夜間限55分貝

摘錄自2020年6月14日中央社報導

環保署預告「噪音管制區劃定作業準則」修正草案,調整已完成陸上交通運輸系統噪音管制標準,30公尺內的區域應劃定為第3類噪音管制區,日間不得超過65分貝、夜間55分貝。

本次修正最主要是新增距離大眾運輸系統、非地下化鐵路系統中心線外、高速公路及快速道路距離道路邊緣外至少30公尺以內的區域,應劃定為第3類噪音管制區。

環保署指出,交通系統本身屬於第4類管制區,而各地方政府目前對於交通運輸系統外噪音管制區的劃定範圍不一,從30公尺到60公尺都有。未來草案正式公告後,會要求一律以30公尺為準,30公尺外就屬於第2類噪音管制區,道路開發者、交通主管機關必須做好噪音防制設施,以減少干擾。

【其他文章推薦】

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

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

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

※貨梯使用安全與保養

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

帶頭捍衛亞馬遜雨林 巴西原住民領袖染疫喪命

摘錄自2020年6月18日中央社報導

環保運動人士今(17日)表示,最具代表性的亞馬遜雨林捍衛者之一、巴西原住民領袖帕亞康(Paulinho Paiakan)因感染2019冠狀病毒疾病(COVID-19,武漢肺炎),昨天病逝醫院。

年約65歲的帕亞康是卡亞波族(Kayapo)的領袖,他在1980年代帶領族人反抗巴西政府的美山水力發電大壩(Belo Monte Dam)計畫,因而聲名大噪。

環保團體「亞瑪遜星球」(Planet Amazon)創辦人布魯克(Gert-Peter Bruch)告訴法新社,帕亞康在巴西北部雷登桑市(Redencao)一家醫院過世。

巴西疫情迅速惡化之際,亞馬遜原住民族受到衝擊尤其明顯。根據巴西原住民協會的說法,巴西有將近5500名原住民染疫,其中287人喪生。

【其他文章推薦】

※新北市探針選用參考標準?

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

※如何正確使用飲水機?

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

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

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

※高溫殺菌機最多可達多少溫度?

俄羅斯西伯利亞飆高溫 北極圈小鎮破天荒測攝氏38°C

摘錄自2020年6月22日聯合報報導

根據氣象數據網站的資料顯示,過去曾出現攝氏零下68°C極端低溫的俄羅斯西伯利亞小鎮維爾霍揚斯克(Verkhoyansk),竟在昨天測得攝氏38°C高溫。

美聯社報導,彙整俄羅斯氣象數據網站Pogoda iKlimat的資料指出,維爾霍揚斯克鎮20日高溫達到攝氏38°C(華氏100.4°F)。

西伯利亞大部分地區今年出現異常高溫,導致大規模野火重創當地森林。俄羅斯薩哈共和國(Sakha Republic)的維爾霍揚斯克鎮在北極圈內,位於首都莫斯科(Moscow)東北方大約4660公里處。

【其他文章推薦】

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

※如何正確使用飲水機?

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

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

※精密CNC 自動車床設備介紹

※空壓機這裡買最划算!

加拉巴哥象龜體型龐大,體重最重可達400多公斤

加拉巴哥象龜體型龐大,體重最重可達400多公斤,是地表最大型的龜種,壽命可長達200年,特徵是脖子特別長,島上仙人掌為了躲避象龜啃食,拚命往高處生長。而像龜為了吃牠,演化出長長​​的脖子,據傳達爾文造訪加拉巴哥群島時,從象龜身上發現,相似物種為了適應不同的生活地,身體特徵會產生微妙的變化,進而為生物學上著名的演化論奠定基礎。
【其他文章推薦】

※客製專屬滑鼠墊、可愛造型L夾、L型資料夾、透明證件套、手提袋,專業印刷設計廠商!  

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

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

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

※貨梯使用安全與保養

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

※專業模具開發-五金製品代工製作廠

大撈特撈 加拉巴哥水域遭中國漁船「包圍」

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

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

西伯利亞森林大火 科學家憂氣候變遷成惡性循環

摘錄自2020年9月24日自由時報報導

俄羅斯西伯利亞今年林火問題嚴重,外媒表示,嚴重的林火帶來的是惡性循環,一方面釋放更多的溫室氣體,接著又因氣候暖化使森林更容易發生火災。

《BBC中文網》報導,西伯利亞的森林大火釋放出破紀錄容量的碳。更大的危機在於火焰燃燒產生二氧化碳,且因為凍土被溶解而釋放更多溫室氣體,導致西伯利亞氣溫上升,森林因此更加乾燥,更容易燃燒,成為氣候變化的惡性循環。

報導指出,科學家認為森林大火正在產生極大量的溫室氣體,改變全球氣候。

氣候變遷
環境新聞
國際新聞
俄羅斯
森林大火
溫室氣體

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

對抗氣候變遷和空污 加州2035年起禁售新汽油車

摘錄自2020年9月24日中央社報導

加州州長紐松今天(24日)宣布,加州計劃自2035年起禁止販售新出廠的汽油驅動客車和卡車,逐步淘汰傳統汽車並轉向電動車,積極減輕對化石燃料依賴,以對抗氣候變遷和嚴重空污。

州長辦公室說,加州碳污染有一半以上來自交通運輸,州內有部分地區的空氣為全國最糟。加州的宏遠目標是以1990年為基準,在2050年前減少80%溫室氣體排放量,但近幾年來,交通運輸的碳排放量仍持續增加。

這項行政命令要求加州在2035年之前,全面銷售新出廠的零碳排客車和卡車;在2045年之前,所有新販售的中重型卡車也必須為零碳排車輛。

州長辦公室說,原本駕駛汽油車的加州居民不會受到影響,二手汽油車也可繼續買賣。加州占美國所有汽車銷量大約11%。

氣候變遷
污染治理
能源轉型
環境新聞
國際新聞
美國
加州
電動車
空污

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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