1. 源代碼究竟是什麼
源代碼就是人類可以看懂的機器文字,程序員在寫完代碼以後,會將源代碼編譯為機器代碼,可能是一個可執行程序exe啥的,我們平時用的就是這個可執行程序,要想增加功能,或者改善程序,就需要有源代碼了,因為人不可能通過0和1編出高級程序。
舉個例子:你用Word寫好一個文檔,生成一個PDF文件,一般來說PDF是不可修改的,你可以把這個PDF發給別人閱讀,但是別人要想修改這個PDF就不好操作。而你有這個PDF的原稿(Word文檔)你可以很方便的修改然後再生成PDF文檔。這里的Word文檔就相當於源代碼,PDF文檔就相當於軟體。
簡介
代碼,沒什麼可說的,廣義的,只要算是程序語言寫的都是,c寫出來的是代碼,編譯後,成匯編語言程序,也可以說是代碼;再匯編成,機器語言程序,也可以說是代碼;不過,算得上代碼的,也算是程序,一般都是廣義的說法。
說「源」的問題、你用c寫出來的,讓我看,那是源代碼;你把編譯成.exe文件的代碼,讓我直接運行,那就不是源代碼、你做的java游戲,如果你把自己寫的java代碼給我,那是源代碼;把處理過的可執行文件給我,那不是源代碼。
2. Android源碼解析Window系列第(一)篇---Window的基本認識和Activity的載入流程
您可能聽說過View ,ViewManager,Window,PhoneWindow,WindowManager,WindowManagerService,可是你知道這幾個類是什麼關系,幹嘛用的。概括的來說,View是放在Window中的,Window是一個抽象類,它的具體實現是PhoneWindow,PhoneWindow還有個內部類DecorView,WindowManager是一個interface,繼承自ViewManager,它是外界訪問Window的入口,,提供了add/remove/updata的方法操作View,WindowManager與WindowManagerSerice是個跨進程的過程,WindowManagerService的職責是對系統中的所有窗口進行管理。如果您不太清楚,建議往下看,否則就不要看了。
Android系統的Window有很多種,大體上來說,Framework定義了三種窗口類型;
這就是Framework定義了三種窗口類型,這三種類型定義在WindowManager的內部類LayoutParams中,WindowManager講這三種類型 進行了細化,把每一種類型都用一個int常量來表示,這些常量代表窗口所在的層,WindowManagerService在進行窗口疊加的時候,會按照常量的大小分配不同的層,常量值越大,代表位置越靠上面, 所以我們可以猜想一下,應用程序Window的層值常量要小於子Window的層值常量,子Window的層值常量要小於系統Window的層值常量。 Window的層級關系如下所示。
上面說了Window分為三種,用Window的type區分,在搞清楚Window的創建之前,我們需要知道怎麼去描述一個Window,我們就把Window當做一個實體類,給我的感覺,它必須要下面幾個欄位。
實際上WindowManager.LayoutParams對Window有很詳細的定義。
提取幾個重要的參數
Window是一個是一個抽象的概念,千萬不要認為我們所看到的就是Window,我們平時所看到的是視圖,每一個Window都對應著一個View,View和Window通過ViewRootImpl來建立聯系。有了View,Window的存在意義在哪裡呢,因為View不能單獨存在,它必須依附著Window,所以有視圖的地方就有Window,比如Activity,一個Dialog,一個PopWindow,一個菜單,一個Toast等等。
通過上面我們知道視圖和Window的關系,那麼有一個問題,是先有視圖,還是先有Window。這個答案只有在源碼中找了。應用程序的入口類是ActivityThread,在ActivityThread中有performLaunchActivity來啟動Activity,這個performLaunchActivity方法內部會創建一個Activity。
如果activity不為null,就會調用attach,在attach方法中通過PolicyManager創建了Window對象,並且給Window設置了回調介面。
PolicyManager的實現類是Policy
這樣Window就創建出來了, 所以先有Window,後有視圖,視圖依賴Window存在 ,再說一說視圖(Activity)為Window設置的回調介面。
Activity實現了這個回調介面,當Window的狀態發生變化的時候,就會回調Activity中實現的這些介面,有些回調介面我們還是熟悉的,dispatchTouchEvent,onAttachedToWindow,onDetachedFromWindow等。
下面分析view是如何附屬到window上的,通過上面可以看到,在attach之後就要執行callActivityOnCreate,在onCreate中我們會調用setContentView方法。
getWindow獲取了Window對象,Window的具體實現類是PhoneWindow,所以要看PhoneWindow的setContentView方法。
這里涉及到一個mContentParent變數,他是一個DecorView的一部分,DecorView是PhoneWindow的一個內部類,我先介紹一下關於DecorView的知識。
DecorView是Activity的頂級VIew,DecorView繼承自FrameLayout,在DecorView中有上下兩個部分,上面是標題欄,下面是內容欄,我們通過PhoneWindow的setContentView所設置的布局文件是加到內容欄(mContentParent)裡面的,View層的事件都是先經過DecorView在傳遞給我們的View的。
OK在回到setContentView的源碼分析,我們可以得到Activity的Window創建需要三步。
- 1、 如果沒有DecorView,在installDecor中創建DecorView。
- 2、將View添加到decorview中的mContentParent中。
- 3、回調Activity的onContentChanged介面。
先看看第一步,installDecor的源碼
installDecor中調用了generateDecor,繼續看
直接給new一個DecorView,有了DecorView之後,就可以載入具體的布局文件到DecorView中了,具體的布局文件和系統和主題有關系。
在看第二步,將View添加到decorview中的mContentParent中。
直接將Activity視圖加到DecorView的mContentParent中,最後一步,回調Activity的onContentChanged介面。在Activity中尋找onContentChanged方法,它是個空實現,我們可以在子Activity中處理。
到此DecorView被創建完畢,我們一開始從Thread中的handleLaunchActivity方法開始分析,首先載入Activity的位元組碼文件,利用反射的方式創建一個Activity對象,調用Activity對象的attach方法,在attach方法中,創建系統需要的Window並為設置回調,這個回調定義在Window之中,由Activity實現,當Window的狀態發生變化的時候,就會回調Activity實現的這些回調方法。調用attach方法之後,Window被創建完成,這時候需要關聯我們的視圖,在handleLaunchActivity中的attach執行之後就要執行handleLaunchActivity中的callActivityOnCreate,在onCreate中我們會調用setContentView方法。通過setContentView,創建了Activity的頂級View---DecorView,DecorView的內容欄(mContentParent)用來顯示我們的布局。 這個是我們上面分析得到了一個大致流程,走到這里,這只是添加的過程,還要有一個顯示的過程,顯示的過程就要調用handleLaunchActivity中的handleResumeActivity方法了。最後會調用makeVisible方法。
這裡面首先拿到WindowManager對象,用tWindowManager 的父介面ViewManager接收,ViewManager可以
最後調用 mDecor.setVisibility(View.VISIBLE)設置mDecor可見。到此,我們終於明白一個Activity是怎麼顯示在我們的面前了。
參考鏈接:
http://blog.csdn.net/feiclear_up/article/details/49201357
3. 源碼的作用及介紹有哪些
源碼是指編寫的最原始程序的代碼。運行的軟體是要經過編寫的,程序員編寫程序的過程中需要他們的語言。以下是由我整理關於什麼是源碼的內容,希望大家喜歡!
1、計算機裡面運行的所有東西都是用程序編出來的(包括操作系統,如Windows,還有Word等,網路游戲也一樣),而編寫程序要用到計算機語言,用計算機語言直接編出來的程序就叫源碼,比如用
VisualBasic編寫的源碼文件一般為、bas文件,而用C++編寫的一般為、cpp文件,源代碼不能直接運行,必須編譯後才能運行。源碼經過編譯處理後就可以直接在操作系統下運行了。
2、很多的站長都喜歡使用建網站的程序源碼,因為可以很方便的修改,對於任何一個seo人員來說,都是非常好的一個切入點。
3、從字面意義上來講,源文件是指一個文件,指源代碼的集合、源代碼則是一組具有特定意義的可以實現特定功能的字元(程序開發代碼)。
4、“源代碼”在大多數時候等於“源文件”。
比如在這個網頁上右鍵滑鼠,選擇查看源文件、出來一個記事本,裡面的內容就是此網頁的源代碼、"這句話就體現了他們的關系,此處的源文件是指網頁的源文件,而源代碼就是源文件的內容,所以又可以稱做網頁的源代碼、、
源代碼是指原始代碼,可以是任何語言代碼。
匯編碼是指源代碼編譯後的代碼,通常為二進制文件,比如共享庫、可執行文件、、NET中間代碼、JAVA中間代碼等。
高級語言通常指C/C++、BASIC、C#、JAVA、PASCAL、易語言等等。匯編語言就是ASM,只有這個,比這個更低級的就是機器語言了。
1、生成目標代碼,即計算機可以識別的代碼。
2、對軟體進行說明,即對軟體的編寫進行說明。為數不少的初學者,甚至少數有經驗的程序員都忽視軟體說明的編寫,因為這部分雖然不會在生成的程序中直接顯示,也不參與編譯。但是說明對軟體的學習、分享、維護和軟體復用都有巨大的好處。因此,書寫軟體說明在業界被認為是能創造優秀程序的良好習慣,一些公司也硬性規定必須書寫。
需要指出的是,源代碼的修改不能改變已經生成的目標代碼。如果需要目標代碼做出相應的修改,必須重新編譯。
雖然我們可以通過不同的語言來實現計算機的同一功能,但在執行效率上則存在不同。普遍規律是:越高級的語言,其執行效率越低。這也是為什麼匯編語言生成的文件比用BASIC語言生成文件普遍要小的原因。
源代碼就是用匯編語言和高級語言寫出來的代碼。主要對象是面向開發者;
我們平常使用的應用程序都是經過源碼編譯打包以後發布的,呈現的最後結果是面向使用者,最終客戶的。
網站程序一般就是可以用記事本打開的好多行英文的,用編程語言寫好的軟體
源程序經過編譯成目標程序,才能運行。一般目標程序不能再修改了。
我們電腦上安裝的軟體都是目標程序。源程序不可能直接運行的。
提倡軟體開源的人士認為應該提供源程序給用戶,讓用戶自己修改,有利於軟體行業的發展。反對的人覺得這樣不利於保護版權。
4. 伺服器程序源代碼分析之二:php-fpm
php作為排名top2 互聯網開發工具,非常流行,可以參考:中國最大的25個網站採用技術選型方案
php這個名稱實際上有兩層含義
直接定義:
php-fpm從php5.3.3開始已經進入到php源代碼包,之前是作為patch存在的
很少人會去讀php本身源代碼,我6年前解決php內存泄露問題的時候做了些研究,最近再查看了一番,發現php的開發者很有誠意,這是一款非常出色的伺服器軟體,支持如下
在linux伺服器上,如果不設置 events.mechanism ,那麼默認就是採用epoll,所以
php-fpm的IO模型&並發處理能力和nginx是完全一致
nginx以性能卓越聞名,大部分程序員都認為php效率低下,看了源代碼,才知道這是傳奇啊
在高性能部署的時候,大家往往會針對性的優化nginx 。我自己之前部署php程序也犯了錯誤,8G內存的server,php-fpm的max children都會設置128+,現在看來太多了,參考nginx的部署:
php-fpm配置為 3倍 cpu core number就可以了
php-fpm穩定性比nginx稍差 這是因為php-fpm內置了一個php解析器,php-fpm進程就和php程序捆綁了,如果php腳本寫得不好,有死循環或者阻塞在某個遠端資源上,會拖累載入它的php-fpm進程
而nginx和後端應用伺服器之間通過網路連接,可以設置timeout,不容易堵死的
php-fpm的fastcgi是短連接 我原以為是長連接的,看了代碼才知道也是短連接,處理一個request就關閉掉
php-fpm介面採用fastcgi 非常遺憾,php-fpm和fastcgi完全綁定了,無法獨立使用 。只能部署在支持http-fcgi協議轉換程序背後(nginx)。其實可以考慮在php-fpm代碼包裡面引入http協議支持,這樣php-fpm可以獨立運行,讓nodejs無話可說
php-fpm等同於OpenResty OpenResty是一個國人開發的nginx模塊,就是在nginx引入lua解釋器. 實際上,它和php-fpm的唯一差別就是一個採用php語法,一個用lua,所以OpenResty要作為nginx增強包使用還可以,要選擇它作為一個主要編程工具,沒有任何必要
從架構上來說,php-fpm已經做到最好,超過大多數 python部署工具,我再也不黑它了
5. IPFS(四) 源碼解讀之-p2p
package p2p
import (
"context"
"errors"
"time"
net "gx/ipfs//go-libp2p-net"
manet "gx/ipfs//go-multiaddr-net"
ma "gx/ipfs//go-multiaddr"
pro "gx/ipfs//go-libp2p-protocol"
pstore "gx/ipfs//go-libp2p-peerstore"
p2phost "gx/ipfs//go-libp2p-host"
peer "gx/ipfs//go-libp2p-peer"
)
//P2P結構保存當前正在運行的流/監聽器的信息
// P2P structure holds information on currently running streams/listeners
type P2P struct {
//監聽器
Listeners ListenerRegistry
//數據流
Streams StreamRegistry
//節點ID
identity peer.ID
//節點地址
peerHost p2phost.Host
//一個線程安全的對等節點存儲
peerstore pstore.Peerstore
}
//創建一個新的p2p結構
// NewP2P creates new P2P struct
//這個新的p2p結構不包含p2p結構中的監聽器和數據流
func NewP2P(identity peer.ID, peerHost p2phost.Host, peerstore pstore.Peerstore) *P2P {
return &P2P{
identity: identity,
peerHost: peerHost,
peerstore: peerstore,
}
}
//新建一個數據流 工具方法 構建一個有節點id,內容和協議的流
func (p2p P2P) newStreamTo(ctx2 context.Context, p peer.ID, protocol string) (net.Stream, error) {
//30s 後會自動timeout
ctx, cancel := context.WithTimeout(ctx2, time.Second 30) //TODO: configurable?
defer cancel()
err := p2p.peerHost.Connect(ctx, pstore.PeerInfo{ID: p})
if err != nil {
return nil, err
}
return p2p.peerHost.NewStream(ctx2, p, pro.ID(protocol))
}
//對話為遠程監聽器創建新的P2P流
//創建一個新的p2p流實現對對話的監聽
// Dial creates new P2P stream to a remote listener
//Multiaddr是一種跨協議、跨平台的表示格式的互聯網地址。它強調明確性和自我描述。
//對內接收
func (p2p P2P) Dial(ctx context.Context, addr ma.Multiaddr, peer peer.ID, proto string, bindAddr ma.Multiaddr) ( ListenerInfo, error) {
//獲取一些節點信息 network, host, nil
lnet, _, err := manet.DialArgs(bindAddr)
if err != nil {
return nil, err
}
//監聽信息
listenerInfo := ListenerInfo{
//節點身份
Identity: p2p.identity,
////應用程序協議標識符。
Protocol: proto,
}
//調用newStreamTo 通過ctx(內容) peer(節點id) proto(協議標識符) 參數獲取一個新的數據流
remote, err := p2p.newStreamTo(ctx, peer, proto)
if err != nil {
return nil, err
}
//network協議標識
switch lnet {
//network為"tcp", "tcp4", "tcp6"
case "tcp", "tcp4", "tcp6":
//從監聽器獲取新的信息 nla.Listener, nil
listener, err := manet.Listen(bindAddr)
if err != nil {
if err2 := remote.Reset(); err2 != nil {
return nil, err2
}
return nil, err
}
//將獲取的新信息保存到listenerInfo
listenerInfo.Address = listener.Multiaddr()
listenerInfo.Closer = listener
listenerInfo.Running = true
//開啟接受
go p2p.doAccept(&listenerInfo, remote, listener)
default:
return nil, errors.New("unsupported protocol: " + lnet)
}
return &listenerInfo, nil
}
//
func (p2p *P2P) doAccept(listenerInfo *ListenerInfo, remote net.Stream, listener manet.Listener) {
//關閉偵聽器並刪除流處理程序
defer listener.Close()
//Returns a Multiaddr friendly Conn
//一個有好的 Multiaddr 連接
local, err := listener.Accept()
if err != nil {
return
}
stream := StreamInfo{
//連接協議
Protocol: listenerInfo.Protocol,
//定位節點
LocalPeer: listenerInfo.Identity,
//定位節點地址
LocalAddr: listenerInfo.Address,
//遠程節點
RemotePeer: remote.Conn().RemotePeer(),
//遠程節點地址
RemoteAddr: remote.Conn().RemoteMultiaddr(),
//定位
Local: local,
//遠程
Remote: remote,
//注冊碼
Registry: &p2p.Streams,
}
//注冊連接信息
p2p.Streams.Register(&stream)
//開啟節點廣播
stream.startStreaming()
}
//偵聽器將流處理程序包裝到偵聽器中
// Listener wraps stream handler into a listener
type Listener interface {
Accept() (net.Stream, error)
Close() error
}
//P2PListener保存關於偵聽器的信息
// P2PListener holds information on a listener
type P2PListener struct {
peerHost p2phost.Host
conCh chan net.Stream
proto pro.ID
ctx context.Context
cancel func()
}
//等待偵聽器的連接
// Accept waits for a connection from the listener
func (il *P2PListener) Accept() (net.Stream, error) {
select {
case c := <-il.conCh:
return c, nil
case <-il.ctx.Done():
return nil, il.ctx.Err()
}
}
//關閉偵聽器並刪除流處理程序
// Close closes the listener and removes stream handler
func (il *P2PListener) Close() error {
il.cancel()
il.peerHost.RemoveStreamHandler(il.proto)
return nil
}
// Listen創建新的P2PListener
// Listen creates new P2PListener
func (p2p P2P) registerStreamHandler(ctx2 context.Context, protocol string) ( P2PListener, error) {
ctx, cancel := context.WithCancel(ctx2)
list := &P2PListener{
peerHost: p2p.peerHost,
proto: pro.ID(protocol),
conCh: make(chan net.Stream),
ctx: ctx,
cancel: cancel,
}
p2p.peerHost.SetStreamHandler(list.proto, func(s net.Stream) {
select {
case list.conCh <- s:
case <-ctx.Done():
s.Reset()
}
})
return list, nil
}
// NewListener創建新的p2p偵聽器
// NewListener creates new p2p listener
//對外廣播
func (p2p P2P) NewListener(ctx context.Context, proto string, addr ma.Multiaddr) ( ListenerInfo, error) {
//調用registerStreamHandler 構造一個新的listener
listener, err := p2p.registerStreamHandler(ctx, proto)
if err != nil {
return nil, err
}
//構造新的listenerInfo
listenerInfo := ListenerInfo{
Identity: p2p.identity,
Protocol: proto,
Address: addr,
Closer: listener,
Running: true,
Registry: &p2p.Listeners,
}
go p2p.acceptStreams(&listenerInfo, listener)
//注冊連接信息
p2p.Listeners.Register(&listenerInfo)
return &listenerInfo, nil
}
//接受流
func (p2p *P2P) acceptStreams(listenerInfo *ListenerInfo, listener Listener) {
for listenerInfo.Running {
//一個有好的 遠程 連接
remote, err := listener.Accept()
if err != nil {
listener.Close()
break
}
}
//取消注冊表中的p2p偵聽器
p2p.Listeners.Deregister(listenerInfo.Protocol)
}
// CheckProtoExists檢查是否注冊了協議處理程序
// mux處理程序
// CheckProtoExists checks whether a protocol handler is registered to
// mux handler
func (p2p *P2P) CheckProtoExists(proto string) bool {
protos := p2p.peerHost.Mux().Protocols()
for _, p := range protos {
if p != proto {
continue
}
return true
}
return false
}
6. [Spring boot源碼解析] 2 啟動流程分析
在了解 Spring Boot 的啟動流程的時候,我們先看一下一個Spring Boot 應用是如何啟動的,如下是一個簡單的 SpringBoot 程序,非常的簡潔,他是如何做到的呢,我們接下來就將一步步分解。
我們追蹤 SpringApplication.run() 方法,其實最終它主要的邏輯是新建一個 SpringApplication ,然後調用他的 run 方法,如下:
我們先來看一下創建 SpringApplication 的方法:
在將Main class 設置 primarySources 後,調用了 WebApplicationType.deceFromClasspath() 方法,該方法是為了檢查當前的應用類型,並設置給 webApplicationType 。 我們進入 deceFromClasspath 方法 :
這里主要是通過類載入器判斷是否存在 REACTIVE 相關的類信息,假如有就代表是一個 REACTIVE 的應用,假如不是就檢查是否存在 Servelt 和 ,假如都沒有,就代表應用為非 WEB 類應用,返回 NONE ,默認返回 SERVLET 類型,我們這期以我們目前最常使用的 SERVLET 類型進行講解,所以我們在應用中引入了 spring-boot-starter-web 作為依賴:
他會包含 Spring-mvc 的依賴,所以就包含了內嵌 tomcat 中的 Servlet 和 Spring-web 中的 ,因此返回了 SERVLET 類型。
回到剛才創建 SpringApplication 的構建方法中,我們設置完成應用類型後,就尋找所有的 Initializer 實現類,並設置到 SpringApplication 的 Initializers 中,這里先說一下 getSpringFactoriesInstances 方法,我們知道在我們使用 SpringBoot 程序中,會經常在 META-INF/spring.factories 目錄下看到一些 EnableAutoConfiguration ,來出發 config 類注入到容器中,我們知道一般一個 config 類要想被 SpringBoot 掃描到需要使用 @CompnentScan 來掃描具體的路徑,對於 jar 包來說這無疑是非常不方便的,所以 SpringBoot 提供了另外一種方式來實現,就是使用 spring.factories ,比如下面這個,我們從 Springboot-test 中找到的例子,這里先定義了一個ExampleAutoConfiguration,並加上了 Configuration 註解:
然後在 spring.factories 中定義如下:
那這種方式是怎麼實現的你,這就要回到我們剛才的方法 getSpringFactoriesInstances :
我們先來看一下傳入參數,這里需要注意的是 args,這個是初始化對應 type 的時候傳入的構造參數,我們先看一下 SpringFactoriesLoader#loadFactoryNames 方法:
首先是會先檢查緩存,假如緩存中存在就直接返回,假如沒有就調用 classLoader#getResources 方法,傳入 META-INF/spring.factories ,即獲取所有 jar 包下的對應文件,並封裝成 UrlResource ,然後使用 PropertiesLoaderUtils 將這些信息讀取成一個對一對的 properties,我們觀察一下 spring.factories 都是按 properties 格式排版的,假如有多個就用逗號隔開,所以這里還需要將逗號的多個類分隔開來,並加到 result 中,由於 result 是一個 LinkedMultiValueMap 類型,支持多個值插入,最後放回緩存中。最終完成載入 META-INF/spring.factories 中的配置,如下:
我們可以看一下我們找到的 initializer 有多少個:
在獲取到所有的 Initializer 後接下來是調用 方法進行初始化。
這里的 names 就是我們上面通過類載入器載入到的類名,到這里會先通過反射生成 class 對象,然後判斷該類是否繼承與 ApplicationContextInitializer ,最後通過發射的方式獲取這個類的構造方法,並調用該構造方法,傳入已經定義好的構造參數,對於 ApplicationContextInitializer 是無參的構造方法,然後初始化實例並返回,回到原來的方法,這里會先對所有的 ApplicationContextInitializer 進行排序,調用 #sort(instances) 方法,這里就是根據 @Order 中的順序進行排序。
接下來是設置 ApplicationListener ,我們跟進去就會發現這里和上面獲取 ApplicationContextInitializer 的方法如出一轍,最終會載入到如圖的 15 個 listener (這里除了 外,其他都是 SpringBoot 內部的 Listener):
在完成 SpringApplication 對象的初始化後,我們進入了他的 run 方法,這個方法幾乎涵蓋了 SpringBoot 生命周期的所有內容,主要分為九個步驟,每一個步驟這里都使用註解進行標識:
主要步驟如下:
第一步:獲取 SpringApplicationRunListener, 然後調用他的 staring 方法啟動監聽器。
第二步:根據 SpringApplicationRunListeners以及參數來准備環境。
第三步:創建 Spring 容器。
第四步:Spring 容器的前置處理。
第五步:刷新 Spring 容器。
第六步: Spring 容器的後置處理器。
第七步:通知所有 listener 結束啟動。
第八步:調用所有 runner 的 run 方法。
第九步:通知所有 listener running 事件。
我們接下來一一講解這些內容。
我們首先看一下第一步,獲取 SpringApplicationRunListener :
這里和上面獲取 initializer 和 listener 的方式基本一致,都是通過 getSpringFactoriesInstances , 最終只找到一個類就是: org.springframework.boot.context.event.EventPublishingRunListener ,然後調用其構造方法並傳入產生 args , 和 SpringApplication 本身:
我們先看一下構造函數,首先將我們獲取到的 ApplicationListener 集合添加到initialMulticaster 中, 最後都是通過操作 來進行廣播,我,他繼承於 ,我們先看一下他的 addApplicationListener 方法:
我們可以看出,最後是放到了 applicationListenters 這個容器中。他是 defaultRetriever 的成員屬性, defaultRetriever 則是 的私有類,我們簡單看一下這個類:
我們只需要看一下這里的 getApplicationListeners 方法,它主要是到 beanFactory 中檢查是否存在多的 ApplicationListener 和舊的 applicationListeners 組合並返回,接著執行 listener 的 start 方法,最後也是調用了 的 multicastEvent 查找支持對應的 ApplicationEvent 類型的通知的 ApplicationListener 的 onApplicationEvent 方法 ,這里除了會:
篩選的方法如下,都是調用了對應類型的 supportsEventType 方法 :
如圖,我們可以看到對 org.springframework.boot.context.event.ApplicationStartingEvent 感興趣的有5個 Listener
環境准備的具體方法如下:
首先是調用 getOrCreateEnvironment 方法來創建 environment ,我們跟進去可以發現這里是根據我們上面設置的環境的類型來進行選擇的,當前環境會創建 StandardServletEnvironment
我們先來看一下 StandardServletEnvironment 的類繼承關系圖,我們可以看出他是繼承了 AbstractEnvironment :
他會調用子類的 customizePropertySources 方法實現,首先是 StandardServletEnvironment 的實現如下,他會添加 servletConfigInitParams , servletContextInitParams , jndiProperties 三種 properties,當前調試環境沒有配置 jndi properties,所以這里不會添加。接著調用父類的 customizePropertySources 方法,即調用到了 StandardEnvironment 。
我們看一下 StandardEnvironment#customizePropertySources 方法,與上面的三個 properties 創建不同,這兩個是會進行賦值的,包括系統環境變數放入 systemEnvironment 中,jvm 先關參數放到 systemProperties 中:
這里會添加 systemEnvironment 和 systemProperties 這兩個 properties,最終拿到的 properties 數量如下 4個:
在創建完成 Environment 後,接下來就到了調用 configureEnvironment 方法:
我們先看一下 configurePropertySources 方法,這里主要分兩部分,首先是查詢當前是否存在 defaultProperties ,假如不為空就會添加到 environment 的 propertySources 中,接著是處理命令行參數,將命令行參數作為一個 CompositePropertySource 或則 添加到 environment 的 propertySources 裡面,
接著調用 ConfigurationPropertySources#attach 方法,他會先去 environment 中查找 configurationProperties , 假如尋找到了,先檢查 configurationProperties 和當前 environment 是否匹配,假如不相等,就先去除,最後添加 configurationProperties 並將其 sources 屬性設置進去。
回到我們的 prepareEnvironment 邏輯,下一步是通知觀察者,發送 事件,調用的是 SpringApplicationRunListeners#environmentPrepared 方法,最終回到了 #multicastEvent 方法,我們通過 debug 找到最後對這個時間感興趣的 Listener 如下:
其主要邏輯如下:
這個方法最後載入了 PropertySourceLoader , 這里主要是兩種,一個是用於 Properties 的,一個是用於 YAML 的如下:
其中 apply 方法主要是載入 defaultProperties ,假如已經存在,就進行替換,而替換的目標 PropertySource 就是 load 這里最後的一個 consumer 函數載入出來的,這里列一下主要做的事情:
1、載入系統中設置的所有的 Profile 。
2、遍歷所有的 Profile ,假如是默認的 Profile , 就將這個 Profile 加到 environment 中。
3、調用load 方法,載入配置,我們深入看一下這個方法:
他會先調用 getSearchLocations 方法,載入所有的需要載入的路徑,最終有如下路徑:
其核心方法是遍歷所有的 propertySourceLoader ,也就是上面載入到兩種 propertySourceLoader ,最紅 loadForFileExtension 方法,載入配置文件,這里就不展開分析了,說一下主要的作用,因為每個 propertySourceLoader 都有自己可以載入的擴展名,默認擴展名有如下四個 properties, xml, yml, yaml,所以最終拿到文件名字,然後通過 - 拼接所有的真實的名字,然後加上路徑一起載入。
接下來,我們分析 BackgroundPreinitializer ,這個方法在接收 ApplicationPrepareEnvironment 事件的時候真正調用了這份方法:
1、 ConversionServiceInitializer 主要負責將包括 日期,貨幣等一些默認的轉換器注冊到 formatterRegistry 中。
2、 ValidationInitializer 創建 validation 的匹配器。
3、 MessageConverterInitializer 主要是添加了一些 http 的 Message Converter。
4、 JacksonInitializer 主要用於生成 xml 轉換器的。
接著回到我們將的主體方法, prepareEnvironment 在調用完成 listeners.environmentPrepared(environment) 方法後,調用 bindToSpringApplication(environment) 方法,將 environment 綁定到 SpirngApplication 中。
接著將 enviroment 轉化為 StandardEnvironment 對象。
最後將 configurationProperties 加入到 enviroment 中, configurationProperties 其實是將 environment 中其他的 PropertySource 重新包裝了一遍,並放到 environment 中,這里主要的作用是方便 進行解析。
它主要是檢查是否存在 spring.beaninfo.ignore 配置,這個配置的主要作用是設置 javaBean 的內省模式,所謂內省就是應用程序在 Runtime 的時候能檢查對象類型的能力,通常也可以稱作運行時類型檢查,區別於反射主要用於修改類屬性,內省主要用戶獲取類屬性。那麼我們什麼時候會使用到內省呢,java主要是通過內省工具 Introspector 來完成內省的工作,內省的結果通過一個 Beaninfo 對象返回,主要包括類的一些相關信息,而在 Spring中,主要是 BeanUtils#Properties 會使用到,Spring 對內省機制還進行了改進,有三種內省模式,如下圖中紅色框框的內容,默認情況下是使用 USE_ALL_BEANINFO。假如設置為true,就是改成第三中 IGNORE_ALL_BEANINFO
首先是檢查 Application的類型,然後獲取對應的 ApplicationContext 類,我們這里是獲取到了 org.springframework.boot.web.servlet.context. 接著調用 BeanUtils.instantiateClass(contextClass); 方法進行對象的初始化。
最終其實是調用了 的默認構造方法。我們看一下這個方法做了什麼事情。這里只是簡單的設置了一個 reader 和一個 scanner,作用於 bean 的掃描工作。
我們再來看一下這個類的繼承關系
這里獲取 ExceptionReporter 的方式主要還是和之前 Listener 的方式一致,通過 getSpringFactoriesInstances 來獲取所有的 SpringBootExceptionReporter 。
其主要方法執行如下:
7. 源碼是什麼意思啊
源碼指編寫的最原始程序的代碼。
用戶平時使用軟體時就是程序把「源碼」翻譯成我們可直觀的形式表現出來供用戶使用的。任何一個網站頁面,換成源碼就是一堆按一定格式書寫的文字和符號。
源碼主要功用
1、生成目標代碼,即計算機可以識別的代碼。
2、對軟體進行說明,即對軟體的編寫進行說明。為數不少的初學者,甚至少數有經驗的程序員都忽視軟體說明的編寫,因為這部分雖然不會在生成的程序中直接顯示,也不參與編譯。
但是說明對軟體的學習、分享、維護和軟體復用都有巨大的好處。因此,書寫軟體說明在業界被認為是能創造優秀程序的良好習慣,一些公司也硬性規定必須書寫。
(7)程序源碼解析擴展閱讀:
計算機裡面運行的所有東西都是用程序編出來的,而編寫程序要用到計算機語言,用計算機語言直接編出來的程序就叫源碼,比如用VisualBasic編寫的源碼文件一般為.bas文件,而用C++編寫的一般為.cpp文件,源代碼不能直接運行,必須編譯後才能運行。源碼經過編譯處理後就可以直接在操作系統下運行了。
從字面意義上來講,源文件是指一個文件,指源代碼的集合.源代碼則是一組具有特定意義的可以實現特定功能的字元(程序開發代碼)。「源代碼」在大多數時候等於「源文件」。