文:宋瑞文(加州能源特約撰述)
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
美國消費者報告(Consumer Reports)的調查顯示,電動車製造商 Tesla 的 Model S 已連續 2 年榮登年度最佳汽車。 消費者報告是根據道路表現、可靠度和防撞測試結果來評比車輛,除了選出整體表現最佳的汽車外,也會按照車種來評比。Model S 今年之所以蟬聯年度最佳汽車,原因除了充滿電後能跑 426 公里外,能夠透過網路升級軟體、及 Tesla 已克服初期面臨的技術問題,也是這輛車獲得青睞的原因。 在消費者報告劃分的十大分類中,只有 6 類是由日本車贏得最佳汽車頭銜,為消費者報告 19 年前開始評比以來最少,贏得最佳汽車頭銜的美國汽車品牌則有 3 個。除了 Tesla,另外 2 個品牌是 Buick 及雪佛蘭(Chevrolet)。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
1、背景
日誌系統接入的日誌種類多、格式複雜多樣,主流的有以下幾種日誌:
以上通過各種渠道接入的日誌,存在2個主要的問題:
為了解決上面2個問題,我們基於flink和drools規則引擎做了實時的日誌處理服務。
2、系統架構
架構比較簡單,架構圖如下:
各類日誌都是通過kafka匯總,做日誌中轉。
flink消費kafka的數據,同時通過API調用拉取drools規則引擎,對日誌做解析處理后,將解析后的數據存儲到Elasticsearch中,用於日誌的搜索和分析等業務。
為了監控日誌解析的實時狀態,flink會將日誌處理的統計數據,如每分鐘處理的日誌量,每種日誌從各個機器IP來的日誌量寫到Redis中,用於監控統計。
3、模塊介紹
系統項目命名為eagle。
eagle-api:基於springboot,作為drools規則引擎的寫入和讀取API服務。
eagle-common:通用類模塊。
eagle-log:基於flink的日誌處理服務。
重點講一下eagle-log:
對接kafka、ES和Redis
對接kafka和ES都比較簡單,用的官方的connector(flink-connector-kafka-0.10和flink-connector-elasticsearch6),詳見代碼。
對接Redis,最開始用的是org.apache.bahir提供的redis connector,後來發現靈活度不夠,就使用了Jedis。
在將統計數據寫入redis的時候,最開始用的keyby分組后緩存了分組數據,在sink中做統計處理后寫入,參考代碼如下:
String name = "redis-agg-log"; DataStream<Tuple2<String, List<LogEntry>>> keyedStream = dataSource.keyBy((KeySelector<LogEntry, String>) log -> log.getIndex()) .timeWindow(Time.seconds(windowTime)).trigger(new CountTriggerWithTimeout<>(windowCount, TimeCharacteristic.ProcessingTime)) .process(new ProcessWindowFunction<LogEntry, Tuple2<String, List<LogEntry>>, String, TimeWindow>() { @Override public void process(String s, Context context, Iterable<LogEntry> iterable, Collector<Tuple2<String, List<LogEntry>>> collector) { ArrayList<LogEntry> logs = Lists.newArrayList(iterable); if (logs.size() > 0) { collector.collect(new Tuple2(s, logs)); } } }).setParallelism(redisSinkParallelism).name(name).uid(name);
後來發現這樣做對內存消耗比較大,其實不需要緩存整個分組的原始數據,只需要一個統計數據就OK了,優化后:
String name = "redis-agg-log"; DataStream<LogStatWindowResult> keyedStream = dataSource.keyBy((KeySelector<LogEntry, String>) log -> log.getIndex()) .timeWindow(Time.seconds(windowTime)) .trigger(new CountTriggerWithTimeout<>(windowCount, TimeCharacteristic.ProcessingTime)) .aggregate(new LogStatAggregateFunction(), new LogStatWindowFunction()) .setParallelism(redisSinkParallelism).name(name).uid(name);
這裏使用了flink的聚合函數和Accumulator,通過flink的agg操作做統計,減輕了內存消耗的壓力。
使用broadcast廣播drools規則引擎
1、drools規則流通過broadcast map state廣播出去。
2、kafka的數據流connect規則流處理日誌。
//廣播規則流 env.addSource(new RuleSourceFunction(ruleUrl)).name(ruleName).uid(ruleName).setParallelism(1) .broadcast(ruleStateDescriptor); //kafka數據流 FlinkKafkaConsumer010<LogEntry> source = new FlinkKafkaConsumer010<>(kafkaTopic, new LogSchema(), properties);
env.addSource(source).name(kafkaTopic).uid(kafkaTopic).setParallelism(kafkaParallelism); //數據流connect規則流處理日誌 BroadcastConnectedStream<LogEntry, RuleBase> connectedStreams = dataSource.connect(ruleSource); connectedStreams.process(new LogProcessFunction(ruleStateDescriptor, ruleBase)).setParallelism(processParallelism).name(name).uid(name);
具體細節參考開源代碼。
4、小結
本系統提供了一個基於flink的實時數據處理參考,對接了kafka、redis和elasticsearch,通過可配置的drools規則引擎,將數據處理邏輯配置化和動態化。
對於處理后的數據,也可以對接到其他sink,為其他各類業務平台提供數據的解析、清洗和標準化服務。
項目地址:
https://github.com/luxiaoxun/eagle
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
摘錄自2019年11月7日中央社報導
愛爾蘭氣候行動部長布魯敦(Richard Bruton)今(6日)表示,2021年前將對可拋式咖啡杯課徵所謂的「拿鐵稅」,試圖改變消費者習慣並削減使用一次性塑膠對環境的影響。
愛爾蘭去年連續第3年超出年度溫室氣體排放配額量後,開始對經濟採取削減環境影響的行動,盼透過徵收最高0.25歐元的擬議拿鐵稅,促進咖啡飲用者改攜帶環保杯,進一步推進都柏林在歐盟的法定承諾。
愛爾蘭最早於2002年推出塑膠袋稅,拿鐵稅則是為了鼓勵採取較永續行為而新增的賦稅之一。這類計畫還包括對超市櫃台販賣的較昂貴中量級塑膠袋增收0.25歐元稅金。
儘管某些商家已為攜帶環保杯的顧客打折,但去年當局資助的報告發現,愛爾蘭全國490萬人每年仍丟棄高達2億個一次性咖啡杯。
布魯敦說,因為零售業缺乏回收飲食包裝的基礎設備,可分解咖啡杯也會被課稅。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
目錄
存在的問題:
TCP報文的長度 - TCP頭部的長度 > MSS(最大報文長度時)將會發生拆包,MSS一般長(1460~1480)字節。解決方法:
實驗:使用自定義協議
整體的流程:
客戶端:發送端連接服務器,將要發送的數據通過編碼器編碼,發送。
服務端:啟動、監聽端口、接收連接、將連接放在協程中處理、通過解碼器解碼數據。
//###########################
//###### Server端代碼 ######
//###########################
func main() {
// 1. 監聽端口 2.accept連接 3.開goroutine處理連接
listen, err := net.Listen("tcp", "0.0.0.0:9090")
if err != nil {
fmt.Printf("error : %v", err)
return
}
for{
conn, err := listen.Accept()
if err != nil {
fmt.Printf("Fail listen.Accept : %v", err)
continue
}
go ProcessConn(conn)
}
}
// 處理網絡請求
func ProcessConn(conn net.Conn) {
defer conn.Close()
for {
bt,err:=coder.Decode(conn)
if err != nil {
fmt.Printf("Fail to decode error [%v]", err)
return
}
s := string(bt)
fmt.Printf("Read from conn:[%v]\n",s)
}
}
//###########################
//###### Clinet端代碼 ######
//###########################
func main() {
conn, err := net.Dial("tcp", ":9090")
defer conn.Close()
if err != nil {
fmt.Printf("error : %v", err)
return
}
// 將數據編碼併發送出去
coder.Encode(conn,"hi server i am here");
}
//###########################
//###### 編解碼器代碼 ######
//###########################
/**
* 解碼:
*/
func Decode(reader io.Reader) (bytes []byte, err error) {
// 先把消息頭讀出來
headerBuf := make([]byte, len(msgHeader))
if _, err = io.ReadFull(reader, headerBuf); err != nil {
fmt.Printf("Fail to read header from conn error:[%v]", err)
return nil, err
}
// 檢驗消息頭
if string(headerBuf) != msgHeader {
err = errors.New("msgHeader error")
return nil, err
}
// 讀取實際內容的長度
lengthBuf := make([]byte, 4)
if _, err = io.ReadFull(reader, lengthBuf); err != nil {
return nil, err
}
contentLength := binary.BigEndian.Uint32(lengthBuf)
contentBuf := make([]byte, contentLength)
// 讀出消息體
if _, err := io.ReadFull(reader, contentBuf); err != nil {
return nil, err
}
return contentBuf, err
}
/**
* 編碼
* 定義消息的格式: msgHeader + contentLength + content
* conn 本身實現了 io.Writer 接口
*/
func Encode(conn io.Writer, content string) (err error) {
// 寫入消息頭
if err = binary.Write(conn, binary.BigEndian, []byte(msgHeader)); err != nil {
fmt.Printf("Fail to write msgHeader to conn,err:[%v]", err)
}
// 寫入消息體長度
contentLength := int32(len([]byte(content)))
if err = binary.Write(conn, binary.BigEndian, contentLength); err != nil {
fmt.Printf("Fail to write contentLength to conn,err:[%v]", err)
}
// 寫入消息
if err = binary.Write(conn, binary.BigEndian, []byte(content)); err != nil {
fmt.Printf("Fail to write content to conn,err:[%v]", err)
}
return err
客戶端的conn一直不被Close 有什麼表現?
四次揮手各個狀態的如下:
主從關閉方 被動關閉方
established established
Fin-wait1
closeWait
Fin-wait2
Tiem-wait lastAck
Closed Closed
如果客戶端的連接手動的關閉,它和服務端的狀態會一直保持established建立連接中的狀態。
MacBook-Pro% netstat -aln | grep 9090
tcp4 0 0 127.0.0.1.9090 127.0.0.1.62348 ESTABLISHED
tcp4 0 0 127.0.0.1.62348 127.0.0.1.9090 ESTABLISHED
tcp46 0 0 *.9090 *.* LISTEN
服務端的conn一直不被關閉 有什麼表現?
客戶端的進程結束后,會發送fin數據包給服務端,向服務端請求斷開連接。
服務端的conn不關閉的話,服務端就會停留在四次揮手的close_wait階段(我們不手動Close,服務端就任務還有數據/任務沒處理完,因此它不關閉)。
客戶端停留在 fin_wait2的階段(在這個階段等着服務端告訴自己可以真正斷開連接的消息)。
MacBook-Pro% netstat -aln | grep 9090
tcp4 0 0 127.0.0.1.9090 127.0.0.1.62888 CLOSE_WAIT
tcp4 0 0 127.0.0.1.62888 127.0.0.1.9090 FIN_WAIT_2
tcp46 0 0 *.9090 *.* LISTEN
什麼是binary.BigEndian?什麼是binary.LittleEndian?
對計算機來說一切都是二進制的數據,BigEndian和LittleEndian描述的就是二進制數據的字節順序。計算機內部,小端序被廣泛應用於現代性 CPU 內部存儲數據;大端序常用於網絡傳輸和文件存儲。
比如:
一個數的二進製表示為 0x12345678
BigEndian 表示為: 0x12 0x34 0x56 0x78
LittleEndian表示為: 0x78 0x56 0x34 0x12
思路:
UDP服務器:1、監聽 2、循環讀取消息 3、回複數據。
UDP客戶端:1、連接服務器 2、發送消息 3、接收消息。
// ################################
// ######## UDPServer #########
// ################################
func main() {
// 1. 監聽端口 2.accept連接 3.開goroutine處理連接
listen, err := net.Listen("tcp", "0.0.0.0:9090")
if err != nil {
fmt.Printf("error : %v", err)
return
}
for{
conn, err := listen.Accept()
if err != nil {
fmt.Printf("Fail listen.Accept : %v", err)
continue
}
go ProcessConn(conn)
}
}
// 處理網絡請求
func ProcessConn(conn net.Conn) {
defer conn.Close()
for {
bt,err:= coder.Decode(conn)
if err != nil {
fmt.Printf("Fail to decode error [%v]", err)
return
}
s := string(bt)
fmt.Printf("Read from conn:[%v]\n",s)
}
}
// ################################
// ######## UDPClient #########
// ################################
func main() {
udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 9091,
})
if err != nil {
fmt.Printf("error : %v", err)
return
}
_, err = udpConn.Write([]byte("i am udp client"))
if err != nil {
fmt.Printf("error : %v", err)
return
}
bytes:=make([]byte,1024)
num, addr, err := udpConn.ReadFromUDP(bytes)
if err != nil {
fmt.Printf("Fail to read from udp error: [%v]", err)
return
}
fmt.Printf("Recieve from udp address:[%v], bytes:[%v], content:[%v]",addr,num,string(bytes))
}
思路整理:
HttpServer:1、創建路由器。2、為路由器綁定路由規則。3、創建服務器、監聽端口。 4啟動讀服務。
HttpClient: 1、創建連接池。2、創建客戶端,綁定連接池。3、發送請求。4、讀取響應。
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/login", doLogin)
server := &http.Server{
Addr: ":8081",
WriteTimeout: time.Second * 2,
Handler: mux,
}
log.Fatal(server.ListenAndServe())
}
func doLogin(writer http.ResponseWriter,req *http.Request){
_, err := writer.Write([]byte("do login"))
if err != nil {
fmt.Printf("error : %v", err)
return
}
}
HttpClient端
func main() {
transport := &http.Transport{
// 撥號的上下文
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, // 撥號建立連接時的超時時間
KeepAlive: 30 * time.Second, // 長連接存活的時間
}).DialContext,
// 最大空閑連接數
MaxIdleConns: 100,
// 超過最大的空閑連接數的連接,經過 IdleConnTimeout時間後會失效
IdleConnTimeout: 10 * time.Second,
// https使用了SSL安全證書,TSL是SSL的升級版
// 當我們使用https時,這行配置生效
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, // 100-continue 狀態碼超時時間
}
// 創建客戶端
client := &http.Client{
Timeout: time.Second * 10, //請求超時時間
Transport: transport,
}
// 請求數據
res, err := client.Get("http://localhost:8081/login")
if err != nil {
fmt.Printf("error : %v", err)
return
}
defer res.Body.Close()
bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Printf("error : %v", err)
return
}
fmt.Printf("Read from http server res:[%v]", string(bytes))
}
點擊查看在github中函數相關的筆記
在golang中函數是一等公民,我們可以把一個函數當作普通變量一樣使用。
比如我們有個函數HelloHandle,我們可以直接使用它。
func HelloHandle(name string, age int) {
fmt.Printf("name:[%v] age:[%v]", name, age)
}
func main() {
HelloHandle("tom",12)
}
閉包
如何理解閉包:閉包本質上是一個函數,而且這個函數會引用它外部的變量,如下例子中的f3中的匿名函數本身就是一個閉包。 通常我們使用閉包起到一個適配的作用。
例1:
// f2是一個普通函數,有兩個入參數
func f2() {
fmt.Printf("f2222")
}
// f1函數的入參是一個f2類型的函數
func f1(f2 func()) {
f2()
}
func main() {
// 由於golang中函數是一等公民,所以我們可以把f2同普通變量一般傳遞給f1
f1(f2)
}
例2: 在上例中更進一步。f2有了自己的參數, 這時就不能直接把f2傳遞給f1了。
總不能傻傻的這樣吧f1(f2(1,2)) ???
而閉包就能解決這個問題。
// f2是一個普通函數,有兩個入參數
func f2(x int, y int) {
fmt.Println("this is f2 start")
fmt.Printf("x: %d y: %d \n", x, y)
fmt.Println("this is f2 end")
}
// f1函數的入參是一個f2類型的函數
func f1(f2 func()) {
fmt.Println("this is f1 will call f2")
f2()
fmt.Println("this is f1 finished call f2")
}
// 接受一個兩個參數的函數, 返回一個包裝函數
func f3(f func(int,int) ,x,y int) func() {
fun := func() {
f(x,y)
}
return fun
}
func main() {
// 目標是實現如下的傳遞與調用
f1(f3(f2,6,6))
}
實現方法的回調:
下面的例子中實現這樣的功能:就好像是我設計了一個框架,定好了整個框架運轉的流程(或者說是提供了一個編程模版),框架具體做事的函數你根據自己的需求自己實現,我的框架只是負責幫你回調你具體的方法。
// 自定義類型,handler本質上是一個函數
type HandlerFunc func(string, int)
// 閉包
func (f HandlerFunc) Serve(name string, age int) {
f(name, age)
}
// 具體的處理函數
func HelloHandle(name string, age int) {
fmt.Printf("name:[%v] age:[%v]", name, age)
}
func main() {
// 把HelloHandle轉換進自定義的func中
handlerFunc := HandlerFunc(HelloHandle)
// 本質上會去回調HelloHandle方法
handlerFunc.Serve("tom", 12)
// 上面兩行效果 == 下面這行
// 只不過上面的代碼是我在幫你回調,下面的是你自己主動調用
HelloHandle("tom",12)
}
直觀上看註冊路由這一步,就是它要做的就是將在路由器url pattern和開發者提供的func關聯起來。 很容易想到,它裏面很可能是通過map實現的。
func main() {
// 創建路由器
// 為路由器綁定路由規則
mux := http.NewServeMux()
mux.HandleFunc("/login", doLogin)
...
}
func doLogin(writer http.ResponseWriter,req *http.Request){
_, err := writer.Write([]byte("do login"))
if err != nil {
fmt.Printf("error : %v", err)
return
}
}
姑且將ServeMux當作是路由器。我們使用http包下的 NewServerMux 函數創建一個新的路由器對象,進而使用它的HandleFunc(pattern,func)函數完成路由的註冊。
跟進NewServerMux函數,可以看到,它通過new函數返回給我們一個ServeMux結構體。
func NewServeMux() *ServeMux {
return new(ServeMux)
}
這個ServeMux結構體長下面這樣:在這個ServeMux結構體中我們就看到了這個維護pattern和func的map
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
這個muxEntry長下面這樣:
type muxEntry struct {
h Handler
pattern string
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
看到這裏問題就來了,上面我們手動註冊進路由器中的僅僅是一個有規定參數的方法,到這裏怎麼成了一個Handle了?我們也沒有說去手動的實現Handler這個接口,也沒有重寫ServeHTTP函數啊, 在golang中實現一個接口不得像下面這樣搞嗎?**
type Handle interface {
Serve(string, int, string)
}
type HandleImpl struct {
}
func (h HandleImpl)Serve(string, int, string){
}
帶着這個疑問看下面的方法:
// 由於函數是一等公民,故我們將doLogin函數同普通變量一樣當做入參傳遞進去。
mux.HandleFunc("/login", doLogin)
func doLogin(writer http.ResponseWriter,req *http.Request){
...
}
跟進去看 HandleFunc 函數的實現:
首先:HandleFunc函數的第二個參數是接收的函數的類型和doLogin函數的類型是一致的,所以doLogin能正常的傳遞進HandleFunc中。
其次:我們的關注點應該是下面的HandlerFunc(handler)
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
跟進這個HandlerFunc(handler) 看到下圖,真相就大白於天下了。golang以一種優雅的方式悄無聲息的為我們完成了一次適配。這麼看來上面的HandlerFunc(handler)並不是函數的調用,而是doLogin轉換成自定義類型。這個自定義類型去實現了Handle接口(因為它重寫了ServeHTTP函數)以閉包的形式完美的將我們的doLogin適配成了Handle類型。
在往下看Handle方法:
第一:將pattern和handler註冊進map中
第二:為了保證整個過程的併發安全,使用鎖保護整個過程。
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
概覽圖:
和java對比着看,在java一組複雜的邏輯會被封裝成一個class。在golang中對應的就是一組複雜的邏輯會被封裝成一個結構體。
對應HttpServer肯定也是這樣,http服務器在golang的實現中有自己的結構體。它就是http包下的Server。
它有一系列描述性屬性。如監聽的地址、寫超時時間、路由器。
server := &http.Server{
Addr: ":8081",
WriteTimeout: time.Second * 2,
Handler: mux,
}
log.Fatal(server.ListenAndServe())
我們看它啟動服務的函數:server.ListenAndServe()
實現的邏輯是使用net包下的Listen函數,獲取給定地址上的tcp連接。
再將這個tcp連接封裝進 tcpKeepAliveListenner 結構體中。
在將這個tcpKeepAliveListenner丟進Server的Serve函數中處理
// ListenAndServe 會監聽開發者給定網絡地址上的tcp連接,當有請求到來時,會調用Serve函數去處理這個連接。
// 它接收到所有連接都使用 TCP keep-alives相關的配置
//
// 如果構造Server時沒有指定Addr,他就會使用默認值: “:http”
//
// 當Server ShutDown或者是Close,ListenAndServe總是會返回一個非nil的error。
// 返回的這個Error是 ErrServerClosed
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
// 底層藉助於tcp實現
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
// tcpKeepAliveListener會為TCP設置一個keep-alive 超時時長。
// 它通常被 ListenAndServe 和 ListenAndServeTLS使用。
// 它保證了已經dead的TCP最終都會消失。
type tcpKeepAliveListener struct {
*net.TCPListener
}
接着去看看Serve方法,上一個函數中獲取到了一個基於tcp的Listener,從這個Listener中可以不斷的獲取出新的連接,下面的方法中使用無限for循環完成這件事。conn獲取到后將連接封裝進httpConn,為了保證不阻塞下一個連接到到來,開啟新的goroutine處理這個http連接。
func (srv *Server) Serve(l net.Listener) error {
// 如果有一個包裹了 srv 和 listener 的鈎子函數,就執行它
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener
}
// 將tcp的Listener封裝進onceCloseListener,保證連接不會被關閉多次。
l = &onceCloseListener{Listener: l}
defer l.Close()
// http2相關的配置
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)
// 如果沒有接收到請求睡眠多久
var tempDelay time.Duration // how long to sleep on accept failure
baseCtx := context.Background() // base is always background, per Issue 16220
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
// 開啟無限循環,嘗試從Listenner中獲取連接。
for {
rw, e := l.Accept()
// accpet過程中發生錯屋
if e != nil {
select {
// 如果從server的doneChan中可以獲取內容,返回Server關閉了
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
// 如果發生了 net.Error 並且是臨時的錯誤就睡5毫秒,再發生錯誤睡眠的時間*2,上線是1s
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
// 如果沒有發生錯誤,清空睡眠的時間
tempDelay = 0
// 將接收到連接封裝進httpConn
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
// 開啟一條新的協程處理這個連接
go c.serve(ctx)
}
}
在c.serve(ctx)中就會去解析http相關的報文信息~,將http報文解析進Request結構體中。
部分代碼如下:
// 將 server 包裹為 serverHandler 的實例,執行它的 ServeHTTP 方法,處理請求,返迴響應。
// serverHandler 委託給 server 的 Handler 或者 DefaultServeMux(默認路由器)
// 來處理 "OPTIONS *" 請求。
serverHandler{c.server}.ServeHTTP(w, w.req)
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// 如果沒有定義Handler就使用默認的
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
// 處理請求,返迴響應。
handler.ServeHTTP(rw, req)
}
可以看到,req中包含了我們前面說的pattern,叫做RequestUri,有了它下一步就知道該回調ServeMux中的哪一個函數。
func main() {
// 創建連接池
// 創建客戶端,綁定連接池
// 發送請求
// 讀取響應
transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, // 連接超時
KeepAlive: 30 * time.Second, // 長連接存活的時間
}).DialContext,
// 最大空閑連接數
MaxIdleConns: 100,
// 超過最大空閑連接數的連接會在IdleConnTimeout后被銷毀
IdleConnTimeout: 10 * time.Second,
TLSHandshakeTimeout: 10 * time.Second, // tls握手超時時間
ExpectContinueTimeout: 1 * time.Second, // 100-continue 狀態碼超時時間
}
// 創建客戶端
client := &http.Client{
Timeout: time.Second * 10, //請求超時時間
Transport: transport,
}
// 請求數據,獲得響應
res, err := client.Get("http://localhost:8081/login")
if err != nil {
fmt.Printf("error : %v", err)
return
}
defer res.Body.Close()
// 處理數據
bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Printf("error : %v", err)
return
}
fmt.Printf("Read from http server res:[%v]", string(bytes))
}
http.Client的代碼其實是很多的,全部很細的過一遍肯定也會難度,下面可能也是只能提及其中的一部分。
首先明白一件事,我們編寫的HttpClient是在干什麼?(雖然這個問題很傻,但是總得問一下)是在發送Http請求。
一般我們在開發的時候,更多的編寫的是HttpServer的代碼。是在處理Http請求, 而不是去發送Http請求,Http請求都是是前端通過ajax經由瀏覽器發送到後端的。
其次,Http請求實際上是建立在tcp連接之上的,所以如果我們去看http.Client肯定能找到net.Dial("tcp",adds)相關的代碼。
那也就是說,我們要看看,http.Client是如何在和服務端建立連接、發送數據、接收數據的。
http.Client中有機幾個比較重要的struct,如下
http.Client結構體中封裝了和http請求相關的屬性,諸如 cookie,timeout,redirect以及Transport。
type Client struct {
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
Timeout time.Duration
}
Tranport實現了RoundTrpper接口:
type RoundTripper interface {
// 1、RoundTrip會去執行一個簡單的 Http Trancation,併為requestt返回一個響應
// 2、RoundTrip不會嘗試去解析response
// 3、注意:只要返回了Reponse,無論response的狀態碼是多少,RoundTrip返回的結果:err == nil
// 4、RoundTrip將請求發送出去后,如果他沒有獲取到response,他會返回一個非空的err。
// 5、同樣,RoundTrip不會嘗試去解析諸如重定向、認證、cookie這種更高級的協議。
// 6、除了消費和關閉請求體之外,RoundTrip不會修改request的其他字段
// 7、RoundTrip可以在一個單獨的gorountine中讀取request的部分字段。一直到ResponseBody關閉之前,調用者都不能取消,或者重用這個request
// 8、RoundTrip始終會保證關閉Body(包含在發生err時)。根據實現的不同,在RoundTrip關閉前,關閉Body這件事可能會在一個單獨的goroutine中去做。這就意味着,如果調用者想將請求體用於後續的請求,必須等待知道發生Close
// 9、請求的URL和Header字段必須是被初始化的。
RoundTrip(*Request) (*Response, error)
}
看上面RoundTrpper接口,它裏面只有一個方法RoundTrip,方法的作用就是執行一次Http請求,發送Request然後獲取Response。
RoundTrpper被設計成了一個支持併發的結構體。
Transport結構體如下:
type Transport struct {
idleMu sync.Mutex
// user has requested to close all idle conns
wantIdle bool
// Transport的作用就是用來建立一個連接,這個idleConn就是Transport維護的空閑連接池。
idleConn map[connectMethodKey][]*persistConn // most recently used at end
idleConnCh map[connectMethodKey]chan *persistConn
}
其中的connectMethodKey也是結構體:
type connectMethodKey struct {
// proxy 代理的URL,當他不為空時,就會一直使用這個key
// scheme 協議的類型, http https
// addr 代理的url,也就是下游的url
proxy, scheme, addr string
}
persistConn是一個具體的連接實例,包含連接的上下文。
type persistConn struct {
// alt可選地指定TLS NextProto RoundTripper。
// 這用於今天的HTTP / 2和以後的將來的協議。 如果非零,則其餘字段未使用。
alt RoundTripper
t *Transport
cacheKey connectMethodKey
conn net.Conn
tlsState *tls.ConnectionState
// 用於從conn中讀取內容
br *bufio.Reader // from conn
// 用於往conn中寫內容
bw *bufio.Writer // to conn
nwrite int64 // bytes written
// 他是個chan,roundTrip會將readLoop中的內容寫入到reqch中
reqch chan requestAndChan
// 他是個chan,roundTrip會將writeLoop中的內容寫到writech中
writech chan writeRequest
closech chan struct{} // closed when conn closed
另外補充一個結構體:Request,他用來描述一次http請求的實例,它定義於http包request.go, 裏面封裝了對Http請求相關的屬性
type Request struct {
Method string
URL *url.URL
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
Header Header
Body io.ReadCloser
GetBody func() (io.ReadCloser, error)
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
Cancel <-chan struct{}
Response *Response
ctx context.Context
}
這幾個結構體共同完成如下圖所示http.Client的工作流程
我們想發送一次Http請求。首先我們需要構造一個Request,Request本質上是對Http協議的描述(因為大家使用的都是Http協議,所以將這個Request發送到HttpServer后,HttpServer能識別並解析它)。
// 從這行代碼開始往下看
res, err := client.Get("http://localhost:8081/login")
// 跟進Get
req, err := NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
// 跟進Do
func (c *Client) Do(req *Request) (*Response, error) {
return c.do(req)
}
// 跟進do,do函數中有下面的邏輯,可以看到執行完send后已經拿到返回值了。所以我們得繼續跟進send方法
if resp, didTimeout, err = c.send(req, deadline); err != nil
// 跟進send方法,可以看到send中還有一send方法,入參分別是:request,tranpost,deadline
// 到現在為止,我們沒有看到有任何和服務端建立連接的動作發生,但是構造的req和擁有連接池的tranport已經見面了~
resp, didTimeout, err = send(req, c.transport(), deadline)
// 繼續跟進這個send方法,看到了調用了rt的RoundTrip方法。
// 這個rt就是我們編寫HttpClient代碼時創建的,綁定在http.Client上的tranport實例。
// 這個RoundTrip方法的作用我們在上面已經說過了,最直接的作用就是:發送request 並獲取response。
resp, err = rt.RoundTrip(req)
但是RoundTrip他是個定義在RoundTripper接口中的抽象方法,我們看代碼肯定是要去看具體的實現嘛
這裏可以使用斷點調試法:在上面最後一行上打上斷點,會進入到他的具體實現中。從圖中可以看到具體的實現在roundtrip中。
RoundTrip中調用的函數是我們自定義的transport的roundTrip函數, 跟進去如下:
緊接着我們需要一個conn,這個conn我們通過Transport可以獲取到。conn的類型為persistConn。
// roundTrip函數中又一個無限for循環
for {
// 檢查請求的上下文是否關閉了
select {
case <-ctx.Done():
req.closeBody()
return nil, ctx.Err()
default:
}
// 對傳遞進來的req進行了有一層的封裝,封裝后的這個treq可以被roundTrip修改,所以每次重試都會新建
treq := &transportRequest{Request: req, trace: trace}
cm, err := t.connectMethodForRequest(treq)
if err != nil {
req.closeBody()
return nil, err
}
// 到這裏真的執行從tranport中獲取和對應主機的連接,這個連接可能是http、https、http代理、http代理的高速緩存, 但是無論如何我們都已經準備好了向這個連接發送treq
// 這裏獲取出來的連接就是我們在上文中提及的persistConn
pconn, err := t.getConn(treq, cm)
if err != nil {
t.setReqCanceler(req, nil)
req.closeBody()
return nil, err
}
var resp *Response
if pconn.alt != nil {
// HTTP/2 path.
t.decHostConnCount(cm.key()) // don't count cached http2 conns toward conns per host
t.setReqCanceler(req, nil) // not cancelable with CancelRequest
resp, err = pconn.alt.RoundTrip(req)
} else {
// 調用persistConn的roundTrip方法,發送treq並獲取響應。
resp, err = pconn.roundTrip(treq)
}
if err == nil {
return resp, nil
}
if !pconn.shouldRetryRequest(req, err) {
// Issue 16465: return underlying net.Conn.Read error from peek,
// as we've historically done.
if e, ok := err.(transportReadFromServerError); ok {
err = e.err
}
return nil, err
}
testHookRoundTripRetried()
// Rewind the body if we're able to. (HTTP/2 does this itself so we only
// need to do it for HTTP/1.1 connections.)
if req.GetBody != nil && pconn.alt == nil {
newReq := *req
var err error
newReq.Body, err = req.GetBody()
if err != nil {
return nil, err
}
req = &newReq
}
}
整理思路:然後看上面代碼中獲取conn和roundTrip的實現細節。
我們需要一個conn,這個conn可以通過Transport獲取到。conn的類型為persistConn。但是不管怎麼樣,都得先獲取出 persistConn,才能進一步完成發送請求再得到服務端到響應。
然後關於這個persistConn結構體其實上面已經提及過了。重新貼在下面
type persistConn struct {
// alt可選地指定TLS NextProto RoundTripper。
// 這用於今天的HTTP / 2和以後的將來的協議。 如果非零,則其餘字段未使用。
alt RoundTripper
conn net.Conn
t *Transport
br *bufio.Reader // 用於從conn中讀取內容
bw *bufio.Writer // 用於往conn中寫內容
// 他是個chan,roundTrip會將readLoop中的內容寫入到reqch中
reqch chan requestAndChan
// 他是個chan,roundTrip會將writeLoop中的內容寫到writech中
nwrite int64 // bytes written
cacheKey connectMethodKey
tlsState *tls.ConnectionState
writech chan writeRequest
closech chan struct{} // closed when conn closed
跟進 t.getConn(treq, cm)代碼如下:
// 先嘗試從空閑緩衝池中取得連接
// 所謂的空閑緩衝池就是Tranport結構體中的: idleConn map[connectMethodKey][]*persistConn
// 入參位置的cm如下:
/* type connectMethod struct {
// 代理的url,如果沒有代理的話,這個值為nil
proxyURL *url.URL
// 連接所使用的協議 http、https
targetScheme string
// 如果proxyURL指定了http代理或者是https代理,並且使用的協議是http而不是https。
// 那麼下面的targetAddr就會不包含在connect method key中。
// 因為socket可以復用不同的targetAddr值
targetAddr string
}*/
t.getIdleConn(cm);
// 空閑緩衝池有的空閑連接的話返回conn,否則進行如下的select
select {
// todo 這裏我還不確定是在干什麼,目前猜測是這樣的:每個服務器能打開的socket句柄是有限的
// 每次來獲取鏈接的時候,我們就計數+1。當整體的句柄在Host允許範圍內時我們不做任何干涉~
case <-t.incHostConnCount(cmKey):
// count below conn per host limit; proceed
// 重新嘗試從空閑連接池中獲取連接,因為可能有的連接使用完后被放回連接池了
case pc := <-t.getIdleConnCh(cm):
if trace != nil && trace.GotConn != nil {
trace.GotConn(httptrace.GotConnInfo{Conn: pc.conn, Reused: pc.isReused()})
}
return pc, nil
// 請求是否被取消了
case <-req.Cancel:
return nil, errRequestCanceledConn
// 請求的上下文是否Done掉了
case <-req.Context().Done():
return nil, req.Context().Err()
case err := <-cancelc:
if err == errRequestCanceled {
err = errRequestCanceledConn
}
return nil, err
}
// 開啟新的gorountine新建連接一個連接
go func() {
/**
* 新建連接,方法底層封裝了tcp client dial相關的邏輯
* conn, err := t.dial(ctx, "tcp", cm.addr())
* 以及根據不同的targetScheme構建不同的request的邏輯。
*/
// 獲取到persistConn
pc, err := t.dialConn(ctx, cm)
// 將persistConn寫到chan中
dialc <- dialRes{pc, err}
}()
// 再嘗試從空閑連接池中獲取
idleConnCh := t.getIdleConnCh(cm)
select {
// 如果上面的go協程撥號成功了,這裏就能取出值來
case v := <-dialc:
// Our dial finished.
if v.pc != nil {
if trace != nil && trace.GotConn != nil && v.pc.alt == nil {
trace.GotConn(httptrace.GotConnInfo{Conn: v.pc.conn})
}
return v.pc, nil
}
// Our dial failed. See why to return a nicer error
// value.
// 將Host的連接-1
t.decHostConnCount(cmKey)
select {
...
下面代碼中的cm長這樣
// dialConn是Transprot的方法
// 入參:context上下文, connectMethod
// 出參:persisnConn
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error) {
// 構建將要返回的 persistConn
pconn := &persistConn{
t: t,
cacheKey: cm.key(),
reqch: make(chan requestAndChan, 1),
writech: make(chan writeRequest, 1),
closech: make(chan struct{}),
writeErrCh: make(chan error, 1),
writeLoopDone: make(chan struct{}),
}
trace := httptrace.ContextClientTrace(ctx)
wrapErr := func(err error) error {
if cm.proxyURL != nil {
// Return a typed error, per Issue 16997
return &net.OpError{Op: "proxyconnect", Net: "tcp", Err: err}
}
return err
}
// 判斷cm中使用的協議是否是https
if cm.scheme() == "https" && t.DialTLS != nil {
var err error
pconn.conn, err = t.DialTLS("tcp", cm.addr())
if err != nil {
return nil, wrapErr(err)
}
if pconn.conn == nil {
return nil, wrapErr(errors.New("net/http: Transport.DialTLS returned (nil, nil)"))
}
if tc, ok := pconn.conn.(*tls.Conn); ok {
// Handshake here, in case DialTLS didn't. TLSNextProto below
// depends on it for knowing the connection state.
if trace != nil && trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
if err := tc.Handshake(); err != nil {
go pconn.conn.Close()
if trace != nil && trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(tls.ConnectionState{}, err)
}
return nil, err
}
cs := tc.ConnectionState()
if trace != nil && trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(cs, nil)
}
pconn.tlsState = &cs
}
} else {
// 如果不是https協議就來到這裏,使用tcp向httpserver撥號,獲取一個tcp連接。
conn, err := t.dial(ctx, "tcp", cm.addr())
if err != nil {
return nil, wrapErr(err)
}
// 將獲取到tcp連接交給我們的persistConn維護
pconn.conn = conn
// 處理https相關邏輯
if cm.scheme() == "https" {
var firstTLSHost string
if firstTLSHost, _, err = net.SplitHostPort(cm.addr()); err != nil {
return nil, wrapErr(err)
}
if err = pconn.addTLS(firstTLSHost, trace); err != nil {
return nil, wrapErr(err)
}
}
}
// Proxy setup.
switch {
// 如果代理URL為空,不做任何處理
case cm.proxyURL == nil:
// Do nothing. Not using a proxy.
//
case cm.proxyURL.Scheme == "socks5":
conn := pconn.conn
d := socksNewDialer("tcp", conn.RemoteAddr().String())
if u := cm.proxyURL.User; u != nil {
auth := &socksUsernamePassword{
Username: u.Username(),
}
auth.Password, _ = u.Password()
d.AuthMethods = []socksAuthMethod{
socksAuthMethodNotRequired,
socksAuthMethodUsernamePassword,
}
d.Authenticate = auth.Authenticate
}
if _, err := d.DialWithConn(ctx, conn, "tcp", cm.targetAddr); err != nil {
conn.Close()
return nil, err
}
case cm.targetScheme == "http":
pconn.isProxy = true
if pa := cm.proxyAuth(); pa != "" {
pconn.mutateHeaderFunc = func(h Header) {
h.Set("Proxy-Authorization", pa)
}
}
case cm.targetScheme == "https":
conn := pconn.conn
hdr := t.ProxyConnectHeader
if hdr == nil {
hdr = make(Header)
}
connectReq := &Request{
Method: "CONNECT",
URL: &url.URL{Opaque: cm.targetAddr},
Host: cm.targetAddr,
Header: hdr,
}
if pa := cm.proxyAuth(); pa != "" {
connectReq.Header.Set("Proxy-Authorization", pa)
}
connectReq.Write(conn)
// Read response.
// Okay to use and discard buffered reader here, because
// TLS server will not speak until spoken to.
br := bufio.NewReader(conn)
resp, err := ReadResponse(br, connectReq)
if err != nil {
conn.Close()
return nil, err
}
if resp.StatusCode != 200 {
f := strings.SplitN(resp.Status, " ", 2)
conn.Close()
if len(f) < 2 {
return nil, errors.New("unknown status code")
}
return nil, errors.New(f[1])
}
}
if cm.proxyURL != nil && cm.targetScheme == "https" {
if err := pconn.addTLS(cm.tlsHost(), trace); err != nil {
return nil, err
}
}
if s := pconn.tlsState; s != nil && s.NegotiatedProtocolIsMutual && s.NegotiatedProtocol != "" {
if next, ok := t.TLSNextProto[s.NegotiatedProtocol]; ok {
return &persistConn{alt: next(cm.targetAddr, pconn.conn.(*tls.Conn))}, nil
}
}
if t.MaxConnsPerHost > 0 {
pconn.conn = &connCloseListener{Conn: pconn.conn, t: t, cmKey: pconn.cacheKey}
}
// 初始化persistConn的bufferReader和bufferWriter
pconn.br = bufio.NewReader(pconn) // 可以從上面給pconn維護的tcpConn中讀數據
pconn.bw = bufio.NewWriter(persistConnWriter{pconn})// 可以往上面pconn維護的tcpConn中寫數據
// 新開啟兩條和persistConn相關的go協程。
go pconn.readLoop()
go pconn.writeLoop()
return pconn, nil
}
上面的兩條goroutine 和 br bw共同完成如下圖的流程
發送req的邏輯在http包的下的tranport包中的func (t *Transport) roundTrip(req *Request) (*Response, error) {}函數中。
如下:
// 發送treq
resp, err = pconn.roundTrip(treq)
// 跟進roundTrip
// 可以看到他將一個writeRequest結構體類型的實例寫入了writech中
// 而這個writech會被上圖中的writeLoop消費,藉助bufferWriter寫入tcp連接中,完成往服務端數據的發送。
pc.writech <- writeRequest{req, writeErrCh, continueCh}
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
@
目錄
AOP,也就是面向切面編程,它可以將公共的代碼抽離出來,動態的織入到目標類、目標方法中,大大提高我們編程的效率,也使程序變得更加優雅。如事務、操作日誌等都可以使用AOP實現。這種織入可以是在運行期動態生成代理對象實現,也可以在編譯期、類加載時期靜態織入到代碼中。而Spring正是通過第一種方法實現,且在代理類的生成上也有兩種方式:JDK Proxy和CGLIB,默認當類實現了接口時使用前者,否則使用後者;另外Spring AOP只能實現對方法的增強。
AOP的術語很多,雖然不清楚術語我們也能很熟練地使用AOP,但是要理解分析源碼,術語就需要深刻體會其含義。
在熟悉了AOP術語后,下面就來看看Spring是如何創建代理對象的,是否還記得上一篇提到的AOP的入口呢?在AbstractAutowireCapableBeanFactory類的applyBeanPostProcessorsAfterInitialization方法中循環調用了BeanPostProcessor的postProcessAfterInitialization方法,其中一個就是我們創建代理對象的入口。這裡是Bean實例化完成去創建代理對象,理所當然應該這樣,但實際上在Bean實例化之前調用了一個resolveBeforeInstantiation方法,這裏實際上我們也是有機會可以提前創建代理對象的,這裏放到最後來分析,先來看主入口,進入到AbstractAutoProxyCreator類中:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//創建當前bean的代理,如果這個bean有advice的話,重點看
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//如果有切面,則生成該bean的代理
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//把被代理對象bean實例封裝到SingletonTargetSource對象中
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
先從緩存中拿,沒有則調用wrapIfNecessary方法創建。在這個方法裏面主要看兩個地方:getAdvicesAndAdvisorsForBean和createProxy。簡單一句話概括就是先掃描后創建,問題是掃描什麼呢?你可以先結合上面的概念思考下,換你會怎麼做。進入到子類AbstractAdvisorAutoProxyCreator的getAdvicesAndAdvisorsForBean方法中:
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
//找到合格的切面
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
//找到候選的切面,其實就是一個尋找有@Aspectj註解的過程,把工程中所有有這個註解的類封裝成Advisor返回
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//判斷候選的切面是否作用在當前beanClass上面,就是一個匹配過程。現在就是一個匹配
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
//對有@Order@Priority進行排序
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
在findEligibleAdvisors方法中可以看到有兩個步驟,第一先找到所有的切面,即掃描所有帶有@Aspect註解的類,並將其中的切點(表達式)和增強封裝為切面,掃描完成后,自然是要判斷哪些切面能夠連接到當前Bean實例上。下面一步步來分析,首先是掃描過程,進入到AnnotationAwareAspectJAutoProxyCreator類中:
protected List<Advisor> findCandidateAdvisors() {
// 先通過父類AbstractAdvisorAutoProxyCreator掃描,這裏不重要
List<Advisor> advisors = super.findCandidateAdvisors();
// 主要看這裏
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
這裏委託給了BeanFactoryAspectJAdvisorsBuilderAdapter類,並調用其父類的buildAspectJAdvisors方法創建切面對象:
public List<Advisor> buildAspectJAdvisors() {
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new ArrayList<>();
aspectNames = new ArrayList<>();
//獲取spring容器中的所有bean的名稱BeanName
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
for (String beanName : beanNames) {
if (!isEligibleBean(beanName)) {
continue;
}
Class<?> beanType = this.beanFactory.getType(beanName);
if (beanType == null) {
continue;
}
//判斷類上是否有@Aspect註解
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
// 當@Aspect的value屬性為""時才會進入到這裏
// 創建獲取有@Aspect註解類的實例工廠,負責獲取有@Aspect註解類的實例
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
//創建切面advisor對象
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
else {
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}
return advisors;
}
這個方法裏面首先從IOC中拿到所有Bean的名稱,並循環判斷該類上是否帶有@Aspect註解,如果有則將BeanName和Bean的Class類型封裝到BeanFactoryAspectInstanceFactory中,並調用ReflectiveAspectJAdvisorFactory.getAdvisors創建切面對象:
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
//從工廠中獲取有@Aspect註解的類Class
Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
//從工廠中獲取有@Aspect註解的類的名稱
String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
validate(aspectClass);
// 創建工廠的裝飾類,獲取實例只會獲取一次
MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
List<Advisor> advisors = new ArrayList<>();
//這裏循環沒有@Pointcut註解的方法
for (Method method : getAdvisorMethods(aspectClass)) {
//非常重要重點看看
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
if (advisor != null) {
advisors.add(advisor);
}
}
if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
advisors.add(0, instantiationAdvisor);
}
//判斷屬性上是否有引介註解,這裏可以不看
for (Field field : aspectClass.getDeclaredFields()) {
//判斷屬性上是否有DeclareParents註解,如果有返回切面
Advisor advisor = getDeclareParentsAdvisor(field);
if (advisor != null) {
advisors.add(advisor);
}
}
return advisors;
}
private List<Method> getAdvisorMethods(Class<?> aspectClass) {
final List<Method> methods = new ArrayList<>();
ReflectionUtils.doWithMethods(aspectClass, method -> {
// Exclude pointcuts
if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
methods.add(method);
}
});
methods.sort(METHOD_COMPARATOR);
return methods;
}
根據Aspect的Class拿到所有不帶@Pointcut註解的方法對象(為什麼是不帶@Pointcut註解的方法?仔細想想不難理解),另外要注意這裏對method進行了排序,看看這個METHOD_COMPARATOR比較器:
private static final Comparator<Method> METHOD_COMPARATOR;
static {
Comparator<Method> adviceKindComparator = new ConvertingComparator<>(
new InstanceComparator<>(
Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
(Converter<Method, Annotation>) method -> {
AspectJAnnotation<?> annotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
return (annotation != null ? annotation.getAnnotation() : null);
});
Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);
METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
}
關注InstanceComparator構造函數參數,記住它們的順序,這就是AOP鏈式調用中同一個@Aspect類中Advice的執行順序。接着往下看,在getAdvisors方法中循環獲取到的methods,分別調用getAdvisor方法,也就是根據方法逐個去創建切面:
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrderInAspect, String aspectName) {
validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
//獲取pointCut對象,最重要的是從註解中獲取表達式
AspectJExpressionPointcut expressionPointcut = getPointcut(
candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
if (expressionPointcut == null) {
return null;
}
//創建Advisor切面類,這才是真正的切面類,一個切面類裏面肯定要有1、pointCut 2、advice
//這裏pointCut是expressionPointcut, advice 增強方法是 candidateAdviceMethod
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
//從候選的增強方法裏面 candidateAdviceMethod 找有有註解
//Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class
//並把註解信息封裝成AspectJAnnotation對象
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
}
//創建一個PointCut類,並且把前面從註解裏面解析的表達式設置進去
AspectJExpressionPointcut ajexp =
new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
if (this.beanFactory != null) {
ajexp.setBeanFactory(this.beanFactory);
}
return ajexp;
}
之前就說過切面的定義,是切點和增強的組合,所以這裏首先通過getPointcut獲取到註解對象,然後new了一個Pointcut對象,並將表達式設置進去。然後在getAdvisor方法中最後new了一個InstantiationModelAwarePointcutAdvisorImpl對象:
public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,
Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
this.declaredPointcut = declaredPointcut;
this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
this.methodName = aspectJAdviceMethod.getName();
this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
this.aspectJAdviceMethod = aspectJAdviceMethod;
this.aspectJAdvisorFactory = aspectJAdvisorFactory;
this.aspectInstanceFactory = aspectInstanceFactory;
this.declarationOrder = declarationOrder;
this.aspectName = aspectName;
if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
// Static part of the pointcut is a lazy type.
Pointcut preInstantiationPointcut = Pointcuts.union(
aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
// Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
// If it's not a dynamic pointcut, it may be optimized out
// by the Spring AOP infrastructure after the first evaluation.
this.pointcut = new PerTargetInstantiationModelPointcut(
this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
this.lazy = true;
}
else {
// A singleton aspect.
this.pointcut = this.declaredPointcut;
this.lazy = false;
//這個方法重點看看,創建advice對象
this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
}
}
這個就是我們的切面類,在其構造方法的最後通過instantiateAdvice創建了Advice對象。注意這裏傳進來的declarationOrder參數,它就是循環method時的序號,其作用就是賦值給這裏的declarationOrder屬性以及Advice的declarationOrder屬性,在後面排序時就會通過這個序號來比較,因此Advice的執行順序是固定的,至於為什麼要固定,後面分析完AOP鏈式調用過程自然就明白了。
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
//獲取有@Aspect註解的類
Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
validate(candidateAspectClass);
//找到candidateAdviceMethod方法上面的註解,並且包裝成AspectJAnnotation對象,這個對象中就有註解類型
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
}
AbstractAspectJAdvice springAdvice;
//根據不同的註解類型創建不同的advice類實例
switch (aspectJAnnotation.getAnnotationType()) {
case AtPointcut:
if (logger.isDebugEnabled()) {
logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
}
return null;
case AtAround:
//實現了MethodInterceptor接口
springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtBefore:
//實現了MethodBeforeAdvice接口,沒有實現MethodInterceptor接口
springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfter:
//實現了MethodInterceptor接口
springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfterReturning:
//實現了AfterReturningAdvice接口,沒有實現MethodInterceptor接口
springAdvice = new AspectJAfterReturningAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
break;
case AtAfterThrowing:
//實現了MethodInterceptor接口
springAdvice = new AspectJAfterThrowingAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
break;
default:
throw new UnsupportedOperationException(
"Unsupported advice type on method: " + candidateAdviceMethod);
}
// Now to configure the advice...
springAdvice.setAspectName(aspectName);
springAdvice.setDeclarationOrder(declarationOrder);
String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
if (argNames != null) {
springAdvice.setArgumentNamesFromStringArray(argNames);
}
//計算argNames和類型的對應關係
springAdvice.calculateArgumentBindings();
return springAdvice;
}
這裏邏輯很清晰,就是拿到方法上的註解類型,根據類型創建不同的增強Advice對象:AspectJAroundAdvice、AspectJMethodBeforeAdvice、AspectJAfterAdvice、AspectJAfterReturningAdvice、AspectJAfterThrowingAdvice。完成之後通過calculateArgumentBindings方法進行參數綁定,感興趣的可自行研究。這裏主要看看幾個Advice的繼承體系:
可以看到有兩個Advice是沒有實現MethodInterceptor接口的:AspectJMethodBeforeAdvice和AspectJAfterReturningAdvice。而MethodInterceptor有一個invoke方法,這個方法就是鏈式調用的核心方法,但那兩個沒有實現該方法的Advice怎麼處理呢?稍後會分析。
到這裏切面對象就創建完成了,接下來就是判斷當前創建的Bean實例是否和這些切面匹配以及對切面排序。匹配過程比較複雜,對理解主流程也沒什麼幫助,所以這裏就不展開分析,感興趣的自行分析(AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply())。下面看看排序的過程,回到AbstractAdvisorAutoProxyCreator.findEligibleAdvisors方法:
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
//找到候選的切面,其實就是一個尋找有@Aspectj註解的過程,把工程中所有有這個註解的類封裝成Advisor返回
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//判斷候選的切面是否作用在當前beanClass上面,就是一個匹配過程。。現在就是一個匹配
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
//對有@Order@Priority進行排序
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
sortAdvisors方法就是排序,但這個方法有兩個實現:當前類AbstractAdvisorAutoProxyCreator和子類AspectJAwareAdvisorAutoProxyCreator,應該走哪個呢?
通過類圖我們可以肯定是進入的AspectJAwareAdvisorAutoProxyCreator類,因為AnnotationAwareAspectJAutoProxyCreator的父類是它。
protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors = new ArrayList<>(advisors.size());
for (Advisor element : advisors) {
partiallyComparableAdvisors.add(
new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR));
}
List<PartiallyComparableAdvisorHolder> sorted = PartialOrder.sort(partiallyComparableAdvisors);
if (sorted != null) {
List<Advisor> result = new ArrayList<>(advisors.size());
for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
result.add(pcAdvisor.getAdvisor());
}
return result;
}
else {
return super.sortAdvisors(advisors);
}
}
這裏排序主要是委託給PartialOrder進行的,而在此之前將所有的切面都封裝成了PartiallyComparableAdvisorHolder對象,注意傳入的DEFAULT_PRECEDENCE_COMPARATOR參數,這個就是比較器對象:
private static final Comparator<Advisor> DEFAULT_PRECEDENCE_COMPARATOR = new AspectJPrecedenceComparator();
所以我們直接看這個比較器的compare方法:
public int compare(Advisor o1, Advisor o2) {
int advisorPrecedence = this.advisorComparator.compare(o1, o2);
if (advisorPrecedence == SAME_PRECEDENCE && declaredInSameAspect(o1, o2)) {
advisorPrecedence = comparePrecedenceWithinAspect(o1, o2);
}
return advisorPrecedence;
}
private final Comparator<? super Advisor> advisorComparator;
public AspectJPrecedenceComparator() {
this.advisorComparator = AnnotationAwareOrderComparator.INSTANCE;
}
第一步先通過AnnotationAwareOrderComparator去比較,點進去看可以發現是對實現了PriorityOrdered和Ordered接口以及標記了Priority和Order註解的非同一個@Aspect類中的切面進行排序。這個和之前分析BeanFacotryPostProcessor類是一樣的原理。而對同一個@Aspect類中的切面排序主要是comparePrecedenceWithinAspect方法:
private int comparePrecedenceWithinAspect(Advisor advisor1, Advisor advisor2) {
boolean oneOrOtherIsAfterAdvice =
(AspectJAopUtils.isAfterAdvice(advisor1) || AspectJAopUtils.isAfterAdvice(advisor2));
int adviceDeclarationOrderDelta = getAspectDeclarationOrder(advisor1) - getAspectDeclarationOrder(advisor2);
if (oneOrOtherIsAfterAdvice) {
// the advice declared last has higher precedence
if (adviceDeclarationOrderDelta < 0) {
// advice1 was declared before advice2
// so advice1 has lower precedence
return LOWER_PRECEDENCE;
}
else if (adviceDeclarationOrderDelta == 0) {
return SAME_PRECEDENCE;
}
else {
return HIGHER_PRECEDENCE;
}
}
else {
// the advice declared first has higher precedence
if (adviceDeclarationOrderDelta < 0) {
// advice1 was declared before advice2
// so advice1 has higher precedence
return HIGHER_PRECEDENCE;
}
else if (adviceDeclarationOrderDelta == 0) {
return SAME_PRECEDENCE;
}
else {
return LOWER_PRECEDENCE;
}
}
}
private int getAspectDeclarationOrder(Advisor anAdvisor) {
AspectJPrecedenceInformation precedenceInfo =
AspectJAopUtils.getAspectJPrecedenceInformationFor(anAdvisor);
if (precedenceInfo != null) {
return precedenceInfo.getDeclarationOrder();
}
else {
return 0;
}
}
這裏就是通過precedenceInfo.getDeclarationOrder拿到在創建InstantiationModelAwarePointcutAdvisorImpl對象時設置的declarationOrder屬性,這就驗證了之前的說法(實際上這裏排序過程非常複雜,不是簡單的按照這個屬性進行排序)。
當上面的一切都進行完成后,就該創建代理對象了,回到AbstractAutoProxyCreator.wrapIfNecessary,看關鍵部分代碼:
//如果有切面,則生成該bean的代理
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//把被代理對象bean實例封裝到SingletonTargetSource對象中
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
注意這裏將被代理對象封裝成了一個SingletonTargetSource對象,它是TargetSource的實現類。
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
//創建代理工廠
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
//proxyTargetClass 是否對類進行代理,而不是對接口進行代理,設置為true時,使用CGLib代理。
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
//把advice類型的增強包裝成advisor切面
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
////用來控制代理工廠被配置后,是否還允許修改代理的配置,默認為false
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
//獲取代理實例
return proxyFactory.getProxy(getProxyClassLoader());
}
這裏通過ProxyFactory對象去創建代理實例,這是工廠模式的體現,但在創建代理對象之前還有幾個準備動作:需要判斷是JDK代理還是CGLIB代理以及通過buildAdvisors方法將擴展的Advice封裝成Advisor切面。準備完成則通過getProxy創建代理對象:
public Object getProxy(@Nullable ClassLoader classLoader) {
//根據目標對象是否有接口來判斷採用什麼代理方式,cglib代理還是jdk動態代理
return createAopProxy().getProxy(classLoader);
}
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() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
首先通過配置拿到對應的代理類:ObjenesisCglibAopProxy和JdkDynamicAopProxy,然後再通過getProxy創建Bean的代理,這裏以JdkDynamicAopProxy為例:
public Object getProxy(@Nullable ClassLoader classLoader) {
//advised是代理工廠對象
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
這裏的代碼你應該不陌生了,就是JDK的原生API,newProxyInstance方法傳入的InvocationHandler對象是this,因此,最終AOP代理的調用就是從該類中的invoke方法開始。至此,代理對象的創建就完成了,下面來看下整個過程的時序圖:
代理對象的創建過程整體來說並不複雜,首先找到所有帶有@Aspect註解的類,並獲取其中沒有@Pointcut註解的方法,循環創建切面,而創建切面需要切點和增強兩個元素,其中切點可簡單理解為我們寫的表達式,增強則是根據@Before、@Around、@After等註解創建的對應的Advice類。切面創建好后則需要循環判斷哪些切面能對當前的Bean實例的方法進行增強並排序,最後通過ProxyFactory創建代理對象。
熟悉JDK動態代理的都知道通過代理對象調用方法時,會進入到InvocationHandler對象的invoke方法,所以我們直接從JdkDynamicAopProxy的這個方法開始:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
//從代理工廠中拿到TargetSource對象,該對象包裝了被代理實例bean
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
//被代理對象的equals方法和hashCode方法是不能被代理的,不會走切面
.......
Object retVal;
// 可以從當前線程中拿到代理對象
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
//這個target就是被代理實例
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
//從代理工廠中拿過濾器鏈 Object是一個MethodInterceptor類型的對象,其實就是一個advice對象
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
//如果該方法沒有執行鏈,則說明這個方法不需要被攔截,則直接反射調用
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
retVal = proxy;
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
這段代碼比較長,我刪掉了不關鍵的地方。首先來看this.advised.exposeProxy這個屬性,這在@EnableAspectJAutoProxy註解中可以配置,當為true時,會將該代理對象設置到當前線程的ThreadLocal對象中,這樣就可以通過AopContext.currentProxy拿到代理對象。這個有什麼用呢?我相信有經驗的Java開發都遇到過這樣一個BUG,在Service實現類中調用本類中的另一個方法時,事務不會生效,這是因為直接通過this調用就不會調用到代理對象的方法,而是原對象的,所以事務切面就沒有生效。因此這種情況下就可以從當前線程的ThreadLocal對象拿到代理對象,不過實際上直接使用@Autowired注入自己本身也可以拿到代理對象。
接下來就是通過getInterceptorsAndDynamicInterceptionAdvice拿到執行鏈,看看具體做了哪些事情:
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class<?> targetClass) {
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
//從代理工廠中獲得該被代理類的所有切面advisor,config就是代理工廠對象
Advisor[] advisors = config.getAdvisors();
List<Object> interceptorList = new ArrayList<>(advisors.length);
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
Boolean hasIntroductions = null;
for (Advisor advisor : advisors) {
//大部分走這裏
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
//如果切面的pointCut和被代理對象是匹配的,說明是切面要攔截的對象
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
boolean match;
if (mm instanceof IntroductionAwareMethodMatcher) {
if (hasIntroductions == null) {
hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
}
match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
}
else {
//接下來判斷方法是否是切面pointcut需要攔截的方法
match = mm.matches(method, actualClass);
}
//如果類和方法都匹配
if (match) {
//獲取到切面advisor中的advice,並且包裝成MethodInterceptor類型的對象
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
if (mm.isRuntime()) {
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
//如果是引介切面
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
return interceptorList;
}
這也是個長方法,看關鍵的部分,因為之前我們創建的基本上都是InstantiationModelAwarePointcutAdvisorImpl對象,該類是PointcutAdvisor的實現類,所以會進入第一個if判斷里,這裏首先進行匹配,看切點和當前對象以及該對象的哪些方法匹配,如果能匹配上,則調用getInterceptors獲取執行鏈:
private final List<AdvisorAdapter> adapters = new ArrayList<>(3);
public DefaultAdvisorAdapterRegistry() {
registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
registerAdvisorAdapter(new AfterReturningAdviceAdapter());
registerAdvisorAdapter(new ThrowsAdviceAdapter());
}
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList<>(3);
Advice advice = advisor.getAdvice();
//如果是MethodInterceptor類型的,如:AspectJAroundAdvice
//AspectJAfterAdvice
//AspectJAfterThrowingAdvice
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
//處理 AspectJMethodBeforeAdvice AspectJAfterReturningAdvice
for (AdvisorAdapter adapter : this.adapters) {
if (adapter.supportsAdvice(advice)) {
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
}
這裏我們可以看到如果是MethodInterceptor的實現類,則直接添加到鏈中,如果不是,則需要通過適配器去包裝后添加,剛好這裡有MethodBeforeAdviceAdapter和AfterReturningAdviceAdapter兩個適配器對應上文兩個沒有實現MethodInterceptor接口的類。最後將Interceptors返回。
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
返回到invoke方法后,如果執行鏈為空,說明該方法不需要被增強,所以直接反射調用原對象的方法(注意傳入的是TargetSource封裝的被代理對象);反之,則通過ReflectiveMethodInvocation類進行鏈式調用,關鍵方法就是proceed:
private int currentInterceptorIndex = -1;
public Object proceed() throws Throwable {
//如果執行鏈中的advice全部執行完,則直接調用joinPoint方法,就是被代理方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
return proceed();
}
}
else {
//調用MethodInterceptor中的invoke方法
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
這個方法的核心就在兩個地方:invokeJoinpoint和interceptorOrInterceptionAdvice.invoke(this)。當增強方法調用完后就會通過前者調用到被代理的方法,否則則是依次調用Interceptor的invoke方法。下面就分別看看每個Interceptor是怎麼實現的。
public Object invoke(MethodInvocation mi) throws Throwable {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
JoinPointMatch jpm = getJoinPointMatch(pmi);
return invokeAdviceMethod(pjp, jpm, null, null);
}
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
public Object invoke(MethodInvocation mi) throws Throwable {
Object retVal = mi.proceed();
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
if (shouldInvokeOnReturnValueOf(method, returnValue)) {
invokeAdviceMethod(getJoinPointMatch(), returnValue, null);
}
}
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable ex) {
if (shouldInvokeOnThrowing(ex)) {
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
throw ex;
}
}
這裏的調用順序是怎樣的呢?其核心就是通過proceed方法控制流程,每執行完一個Advice就會回到proceed方法中調用下一個Advice。可以思考一下,怎麼才能讓調用結果滿足如下圖的執行順序。
以上就是AOP的鏈式調用過程,但是這隻是只有一個切面類的情況,如果有多個@Aspect類呢,這個調用過程又是怎樣的?其核心思想和“棧”一樣,就是“先進后出,後進先出”。
在上文創建代理對象的時候有這樣一個方法:
protected Advisor[] buildAdvisors(@Nullable String beanName, @Nullable Object[] specificInterceptors) {
//自定義MethodInterceptor.拿到setInterceptorNames方法注入的Interceptor對象
Advisor[] commonInterceptors = resolveInterceptorNames();
List<Object> allInterceptors = new ArrayList<>();
if (specificInterceptors != null) {
allInterceptors.addAll(Arrays.asList(specificInterceptors));
if (commonInterceptors.length > 0) {
if (this.applyCommonInterceptorsFirst) {
allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
}
else {
allInterceptors.addAll(Arrays.asList(commonInterceptors));
}
}
}
Advisor[] advisors = new Advisor[allInterceptors.size()];
for (int i = 0; i < allInterceptors.size(); i++) {
//對自定義的advice要進行包裝,把advice包裝成advisor對象,切面對象
advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
}
return advisors;
}
這個方法的作用就在於我們可以擴展我們自己的Interceptor,首先通過resolveInterceptorNames方法獲取到通過setInterceptorNames方法設置的Interceptor,然後調用DefaultAdvisorAdapterRegistry.wrap方法將其包裝為DefaultPointcutAdvisor對象並返回:
public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
if (adviceObject instanceof Advisor) {
return (Advisor) adviceObject;
}
if (!(adviceObject instanceof Advice)) {
throw new UnknownAdviceTypeException(adviceObject);
}
Advice advice = (Advice) adviceObject;
if (advice instanceof MethodInterceptor) {
return new DefaultPointcutAdvisor(advice);
}
for (AdvisorAdapter adapter : this.adapters) {
if (adapter.supportsAdvice(advice)) {
return new DefaultPointcutAdvisor(advice);
}
}
throw new UnknownAdviceTypeException(advice);
}
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}
需要注意DefaultPointcutAdvisor構造器裏面傳入了一個Pointcut.TRUE,表示這種擴展的Interceptor是全局的攔截器。下面來看看如何使用:
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("自定義攔截器");
return invocation.proceed();
}
}
首先寫一個類實現MethodInterceptor 接口,在invoke方法中實現我們的攔截邏輯,然後通過下面的方式測試,只要UserService 有AOP攔截就會發現自定義的MyMethodInterceptor也生效了。
public void costomInterceptorTest() {
AnnotationAwareAspectJAutoProxyCreator bean = applicationContext.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
bean.setInterceptorNames("myMethodInterceptor ");
UserService userService = applicationContext.getBean(UserService.class);
userService.queryUser("dark");
}
但是如果換個順序,像下面這樣:
public void costomInterceptorTest() {
UserService userService = applicationContext.getBean(UserService.class);
AnnotationAwareAspectJAutoProxyCreator bean = applicationContext.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
bean.setInterceptorNames("myMethodInterceptor ");
userService.queryUser("dark");
}
這時自定義的全局攔截器就沒有作用了,這是為什麼呢?因為當執行getBean的時候,如果有切面匹配就會通過ProxyFactory去創建代理對象,注意Interceptor是存到這個Factory對象中的,而這個對象和代理對象是一一對應的,因此調用getBean時,還沒有myMethodInterceptor這個對象,自定義攔截器就沒有效果了,也就是說要想自定義攔截器生效,就必須在代理對象生成之前註冊進去。
在上一篇文章我分析了Spring是如何通過三級緩存來解決循環依賴的問題的,但你是否考慮過第三級緩存為什麼要存在?我直接將bean存到二級不就行了么,為什麼還要存一個ObjectFactory對象到第三級緩存中?這個在學習了AOP之後就很清楚了,因為我們在@Autowired對象時,想要注入的不一定是Bean本身,而是想要注入一個修改過後的對象,如代理對象。在AbstractAutowireCapableBeanFactory.getEarlyBeanReference方法中循環調用了SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference方法,AbstractAutoProxyCreator對象就實現了該方法:
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
this.earlyProxyReferences.add(cacheKey);
}
// 創建代理對象
return wrapIfNecessary(bean, beanName, cacheKey);
}
因此,當我們想要對循壞依賴的Bean做出修改時,就可以像AOP這樣做。
Spring的代理對象基本上都是在Bean實例化完成之後創建的,但在文章開始我就說過,Spring也提供了一個機會在創建Bean對象之前就創建代理對象,在AbstractAutowireCapableBeanFactory.resolveBeforeInstantiation方法中:
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
Object bean = null;
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
// Make sure bean class is actually resolved at this point.
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
Class<?> targetType = determineTargetType(beanName, mbd);
if (targetType != null) {
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null) {
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
}
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}
protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
if (result != null) {
return result;
}
}
}
return null;
}
主要是InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation方法中,這裏又會進入到AbstractAutoProxyCreator類中:
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
protected TargetSource getCustomTargetSource(Class<?> beanClass, String beanName) {
// We can't create fancy target sources for directly registered singletons.
if (this.customTargetSourceCreators != null &&
this.beanFactory != null && this.beanFactory.containsBean(beanName)) {
for (TargetSourceCreator tsc : this.customTargetSourceCreators) {
TargetSource ts = tsc.getTargetSource(beanClass, beanName);
if (ts != null) {
return ts;
}
}
}
// No custom TargetSource found.
return null;
}
看到這裏大致應該明白了,先是獲取到一個自定義的TargetSource對象,然後創建代理對象,所以我們首先需要自己實現一個TargetSource類,這裏直接繼承一個抽象類,getTarget方法則返回原始對象:
public class MyTargetSource extends AbstractBeanFactoryBasedTargetSource {
@Override
public Object getTarget() throws Exception {
return getBeanFactory().getBean(getTargetBeanName());
}
}
但這還不夠,上面首先判斷了customTargetSourceCreators!=null,而這個屬性是個數組,可以通過下面這個方法設置進來:
public void setCustomTargetSourceCreators(TargetSourceCreator... targetSourceCreators) {
this.customTargetSourceCreators = targetSourceCreators;
}
所以我們還要實現一個TargetSourceCreator類,同樣繼承一個抽象類實現,並只對userServiceImpl對象進行攔截:
public class MyTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator {
@Override
protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource(Class<?> beanClass, String beanName) {
if (getBeanFactory() instanceof ConfigurableListableBeanFactory) {
if(beanName.equalsIgnoreCase("userServiceImpl")) {
return new MyTargetSource();
}
}
return null;
}
}
createBeanFactoryBasedTargetSource方法是在AbstractBeanFactoryBasedTargetSourceCreator.getTargetSource中調用的,而getTargetSource就是在上面getCustomTargetSource中調用的。以上工作做完后,還需要將其設置到AnnotationAwareAspectJAutoProxyCreator對象中,因此需要我們注入這個對象:
@Configuration
public class TargetSourceCreatorBean {
@Autowired
private BeanFactory beanFactory;
@Bean
public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
AnnotationAwareAspectJAutoProxyCreator creator = new AnnotationAwareAspectJAutoProxyCreator();
MyTargetSourceCreator myTargetSourceCreator = new MyTargetSourceCreator();
myTargetSourceCreator.setBeanFactory(beanFactory);
creator.setCustomTargetSourceCreators(myTargetSourceCreator);
return creator;
}
}
這樣,當我們通過getBean獲取userServiceImpl的對象時,就會優先生成代理對象,然後在調用執行鏈的過程中再通過TargetSource.getTarget獲取到被代理對象。但是,為什麼我們在getTarget方法中調用getBean就能拿到被代理對象呢?
繼續探究,通過斷點我發現從getTarget進入時,在resolveBeforeInstantiation方法中返回的bean就是null了,而getBeanPostProcessors方法返回的Processors中也沒有了AnnotationAwareAspectJAutoProxyCreator對象,也就是沒有進入到AbstractAutoProxyCreator.postProcessBeforeInstantiation方法中,所以不會再次獲取到代理對象,那AnnotationAwareAspectJAutoProxyCreator對象是在什麼時候移除的呢?
帶着問題,我開始反推,發現在AbstractBeanFactoryBasedTargetSourceCreator類中有這樣一個方法buildInternalBeanFactory:
protected DefaultListableBeanFactory buildInternalBeanFactory(ConfigurableBeanFactory containingFactory) {
DefaultListableBeanFactory internalBeanFactory = new DefaultListableBeanFactory(containingFactory);
// Required so that all BeanPostProcessors, Scopes, etc become available.
internalBeanFactory.copyConfigurationFrom(containingFactory);
// Filter out BeanPostProcessors that are part of the AOP infrastructure,
// since those are only meant to apply to beans defined in the original factory.
internalBeanFactory.getBeanPostProcessors().removeIf(beanPostProcessor ->
beanPostProcessor instanceof AopInfrastructureBean);
return internalBeanFactory;
}
在這裏移除掉了所有AopInfrastructureBean的子類,而AnnotationAwareAspectJAutoProxyCreator就是其子類,那這個方法是在哪裡調用的呢?繼續反推:
protected DefaultListableBeanFactory getInternalBeanFactoryForBean(String beanName) {
synchronized (this.internalBeanFactories) {
DefaultListableBeanFactory internalBeanFactory = this.internalBeanFactories.get(beanName);
if (internalBeanFactory == null) {
internalBeanFactory = buildInternalBeanFactory(this.beanFactory);
this.internalBeanFactories.put(beanName, internalBeanFactory);
}
return internalBeanFactory;
}
}
public final TargetSource getTargetSource(Class<?> beanClass, String beanName) {
AbstractBeanFactoryBasedTargetSource targetSource =
createBeanFactoryBasedTargetSource(beanClass, beanName);
// 創建完targetSource后就移除掉AopInfrastructureBean類型的BeanPostProcessor對象,如AnnotationAwareAspectJAutoProxyCreator
DefaultListableBeanFactory internalBeanFactory = getInternalBeanFactoryForBean(beanName);
......
return targetSource;
}
至此,關於TargetSource接口擴展的原理就搞明白了。
本篇篇幅比較長,主要搞明白Spring代理對象是如何創建的以及AOP鏈式調用過程,而後面的擴展則是對AOP以及Bean創建過程中一些疑惑的補充,可根據實際情況學習掌握。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
摘錄自2019年11月12日中央通訊社綜合報導
短短一年多來,墨西哥加勒比海地區的珊瑚因為遭到一種罕為人知「石珊瑚組織損失症」(SCTLD)侵襲,已經損失30%。這種疾病會造成珊瑚鈣化和死亡。
專家警告,這種疾病可能造成大部分中美洲珊瑚礁(Mesoamerican Reef)死亡。這處龐大的弧狀珊瑚礁群範圍廣達超過1000公里,為墨西哥、貝里斯、瓜地馬拉和宏都拉斯等國家共有。
SCTLD已使加勒比海地區陷入困境,這種疾病可能摧毀環礁地區民眾賴以為生的觀光產業。中美洲珊瑚礁是僅次於澳洲大堡礁的世界第2大珊瑚礁。科學家表示,旅遊業太發達非常有可能讓問題火上加油。
「健康珊瑚礁、健康人民」在墨西哥的協調人員蘇鐸(Melina Soto)說,SCTLD只需幾週時間,就可以殺死需要花費數十年生長起來的珊瑚組織。蘇鐸表示:「如果以這種速度繼續下去,這個生態系統將會在未來的5到10年內崩潰。」
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
受4月2日北京宣佈純電動小客車不尾號限行()的利好政策推動,據4月9日北京小客車指標調控管理辦公室公佈的資料顯示,本期搖號個人新能源車指標的申請者首次突破三千,達3874人,而此前申請指標的人數徘徊在2000多人左右。根據今年的指標配置方案,本期分配給個人的新能車指標為3333個,這意味著從這一期開始,在北京購買純電動車也要搖號分配了。 在北京剛剛結束的新能源汽車工作會議上,北京相關新能源小客車採取備案制的方案已經通過,知情人士稱,未來進口純電動汽車也有望通過備案進入免購置稅目錄,這無疑將大大增加北京純電動汽車的供應類型。 而針對充電樁安裝困難問題,北京市科委新能源與新材料處處長許心超透露,現在北京正著手做三件事:一是和百度合作,標識所有充電樁地圖並上線;二是印製北京充電樁地圖,發放給消費者和計程車司機;三是辦充電體驗周活動,聯合所有在京車企,做充電實際體驗。 北京市環保局機動車排放管理處處長李昆生在4月7日透露,未來北京將嚴格控制機動車總量,每年新增小客車指標由24萬輛削減到15萬輛,力爭在2017年底前,全市機動車保有量控制在600萬輛以內,而新能源和清潔能源汽車的應用規模將達到20萬輛。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
先提個問題,完全二叉樹/滿二叉樹,區別?前者是指每一層都是緊湊靠左排列,最後一層可能未排滿,後者是一種特殊的完全二叉樹,
每層都是滿的,即節點總數和深度滿足N=(2^n) -1。堆Heap,一堆蘋果,為了賣相好,越好看的越往上放,就是大頂堆;為了蘋果堆
的穩定,質量越小越往上放,就是小頂堆;堆首先是完全二叉樹,但只確保父節點和子節點大小邏輯,不關心左右子節點的大小關係,
通常是一個可以被看做一棵樹的數組對象,是個很常見的結構,比如BST對象,都與堆有關係,今天就說下這個重要的數據結構和應用。
作者原創文章,謝絕一切轉載,違者必究!
本文只發表在”公眾號”和”博客園”,其他均屬複製粘貼!如果覺得排版不清晰,請查看公眾號文章。
準備:
Idea2019.03/Gradle6.0.1/Maven3.6.3/JDK11.0.4
難度: 新手–戰士–老兵–大師
目標:
1.堆的構建和調整算法
為理解堆的原理,先看優先級隊列,它是一種數據結構,插入或者刪除元素的時候,元素會自動排序,(優先級不是狹義的數值大小,
但為了通俗理解,這裏以字母序為例),通常使用數組存儲,我們可以按照下圖進行轉換,序號 0 不用:
優先級隊列的實現(Java版):
public class PriorityQueue<Key extends Character> { /** 存儲元素的數組 */ private Key[] keys; private int N = 0; public PriorityQueue(int capacity){ // 下標0不用,多分配一個單位 keys = (Key[]) new Character[capacity + 1]; } public Key max(){ return keys[1]; } public void insert(Key e){ N ++; keys[N] = e; swim(N); } public Key delMax(){ Key max = keys[1]; swap(1,N); keys[N] = null; N --; // 讓第一個元素下沉到合適的位置 sink(1); return max; } /** 上浮第k個元素*/ private void swim(int k){ // 比父節點小,即進行交換,直到根 while (k > 1 && less(parent(k),k)){ swap(k,parent(k)); k = parent(k); } } /** 下沉第 k 個元素*/ private void sink(int k){ while(k < N){ int small = left(k); if (right(k) < N && less(right(k),left(k))){ small = right(k); } if (less(k,small)){ swap(k,small); k = small; } } } private void swap(int i,int j){ Key temp = keys[i]; keys[i] = keys[j]; keys[j] = temp; } /** 元素i和j大小比較*/ private boolean less(int i,int j){ // 'a' - 'b' = -1 ; return keys[i].compareTo(keys[j]) > 0; } /** 元素i的父節點*/ private int parent(int i){ return i/2; } /** 元素i的左子節點*/ private int left(int i){ return i * 2; } /** 元素i的右子節點*/ private int right(int i){ return i * 2 + 1; } }
以上代碼解析:
1 swim 上浮,對於元素k,是否需要上浮,僅需與其父節點比較,大於父節點則交換,迭代直到根節點;
2 sink 下沉,對於元素k,是否需要下沉,需先比較其左右子節點,找出左右子節點中較小者,較小者若比父節點大,則交換,迭代直到末尾元素;
3 insert 插入,先將元素放到數組末尾位置,再對其進行上浮操作,直到合適位置;
4 delMax 刪除最大值,大根堆,故第一個元素最大,先將首末元素交換,再刪除末尾元素,再對首元素下沉操作,直到合適位置;
總結:以上只是Java簡化版,java.util.PriorityQueue 是JDK原版,客官可自行研究。但設計還是非常有技巧的,值得思考一番,假設 insert 插入
到首位,會導致數組大量元素移動。delMax 若直接刪除首位最大值,則需要進一步判斷左右子節點大小,並進行先子節點上浮再首元素下沉操作。
有了這個堆結構,就可以進行堆排序了,將待排數全部加入此堆結構,然後依次取出,即成有序序列了!
如要求不使用上述堆數據結構。思路(升序為例):將數組構建為一個大頂堆,首元素即為數組最大值,首尾元素交換;排除末尾元素后調整大頂堆,
則新的首元素即為次最大值,交換首尾並再排除末尾元素;如此循環,最後的數組即為升序排列。
public class HeapSort02 { public static void main(String []args){ int []arr = {2,1,8,6,4,7,3,0,9,5}; sort(arr); System.out.println(Arrays.toString(arr)); } public static void sort(int []arr){ int len = arr.length; // 創建一個大頂堆 for(int i = (int) Math.ceil(len/2 - 1); i >= 0; i--){ //從第一個非恭弘=叶 恭弘子結點從下至上,從右至左調整結構 adjustHeap(arr,i,len); } // 交換首尾元素,並重新調整大頂堆 for(int j = len-1;j > 0;j--){ swap(arr,0,j); adjustHeap(arr,0,j); } } /** 迭代寫法*/ public static void adjustHeap(int []arr,int i,int length){ int temp = arr[i]; for (int k = 2*i + 1; k < length; k=k*2 + 1) { // 注意這裏的k + 1 < length // 如果右子節點大於左子節點,則比較對象為右子節點 if (k + 1 < length && arr[k] < arr[k+1]){ k++; } if (arr[k] > temp){ // 不進行值交換 arr[i] = arr[k]; i = k; } else{ break; } } arr[i] = temp; } /** 遞歸寫法*/ private static void adjustHeap2(int[] arr, int i, int len){ int left = 2 * i + 1; int right = 2 * i + 2; int maxIndex = i; // 注意這裏的 left < len if (left < len && arr[left] > arr[maxIndex]){ maxIndex = left; } if (right < len && arr[right] > arr[maxIndex]){ maxIndex = right; } if (maxIndex != i){ swap(arr,i,maxIndex); adjustHeap2(arr,maxIndex,len); } } /** 交換元素 */ public static void swap(int []arr,int a ,int b){ int temp=arr[a]; arr[a] = arr[b]; arr[b] = temp; } }
以上代碼解析:
1完全二叉樹結構中,如果根節點順序號為 0,總節點數為 N,則最末節點的父節點即為最後一個非恭弘=叶 恭弘子節點,順序號為 ceil(N/2 -1),
2 adjustHeap2 為啥使用三個參數,不用中間的參數可以?使用三個參數,是為了進行遞歸調用,因為遞歸肯定是縮小計算規模,而這裏的形參arr和len是固定不變的;
3 adjustHeap是非遞歸寫法,不用中間的參數可以?調用一在“構建大頂堆”處,可寫為函數體內初始化 i,並形成雙重 for 循環;調用二在“重新調整大頂堆”處,
可見中間參數為 0,可直接去掉。故回答是可以!但需要調整寫法,且影響該方法復用,這裏直接寫為三個形參的函數更為優雅而已。
4非遞歸寫法理解:類似插入排序思想(依次移動並找到合適的位置再插入),先將 arr[i] 取出,然後此節點和左右子樹進行比較,如子樹更大則子節點上升一層,使
用for循環迭代到最終位置,並進行賦值;
以 i=0 為例:
5遞歸方式理解:定位目標元素的左右子樹,若子樹值更大,則進行值交換,且因為子樹發生了變化,故需要對子樹進行遞歸處理;
在N個數中找出前K個最大的數: 思路:從N個數中取出前K個數,形成一個數組[K],將該數組調整為一個小頂堆,則可知堆頂為K個數中最小值,
然後依次將剩餘 N-K 個數與堆頂比較,若大於,則替換掉並調整堆,直到所有元素加入完畢,堆中元素即為目標集合。
public class HeapSort { public static void main(String[] args) { int[] arr = new int[100]; for (int i = 0; i < 100; i++) { arr[i] = i + 1; } // 前10個最大的數 int k = 10; // 構造小頂堆 for (int i = (int) Math.ceil(k/2 - 1); i >= 0; i--) { adjustHeap(arr,i,k); } // 依次比較剩餘元素 for (int i = 10; i < arr.length; i++) { if (arr[i] > arr[0]){ swap(arr,0,i); adjustHeap(arr,0,k); } } // 輸出結果 for (int i = 0; i < 10; i++) { System.out.print(arr[i]+"-"); } } /** 非迭代寫法 ,對arr[i]進行調整 */ private static void adjustHeap(int[] arr,int i,int length){ int temp = arr[i]; for (int k = i * 2 + 1; k < length; k = k * 2 + 1) { // 因第一次循環中可能越界,故需要 k+1 < length if (k + 1 < length && arr[k] > arr[k + 1]){ k++; } if (arr[k] < temp){ arr[i] = arr[k]; i = k; } else { break; } } arr[i] = temp; } /** 遞歸寫法 */ private static void adjustHeap2(int[] arr,int i,int length){ int left = i * 2 + 1; int right = i * 2 + 2; int samller = i; if (left < length && arr[left] > arr[samller]){ samller = right; } if (right < length && arr[right] > arr[samller]){ samller = right; } if (samller != i){ swap(arr,i,samller); adjustHeap2(arr,samller,length); } } /** 交換元素 */ public static void swap(int []arr,int a ,int b){ int temp=arr[a]; arr[a] = arr[b]; arr[b] = temp; } }
以上代碼解析:按照”初始化—構建小頂堆—比較調整—輸出結果”執行。注意for循環中,因第一次循環中未使用for語句條件判斷,可能越界,故需要 k+1 < length。
輸出結果如下:
請看官思考,如果需求變為找出N個數中找出前K個最小的數,該如何實現? 建議動腦且動手的寫一遍!因為魔鬼在細節!
全文完!
我近期其他文章:
只寫原創,敬請關注
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
4月23日,武漢首次面向全國公開招標的新增1100輛純電動客運計程車項目正式簽約。其中,武漢電動汽車示範運營有限公司、比亞迪汽車工業有限公司各中標500輛純電動巡遊計程車,車型分別為東風風神A60EV、東風啟辰晨風以及比亞迪純電動車E6;而武漢三環汽車有限公司則中標100輛純電動計程車,車型為特斯拉MODEL S85。 武漢電動汽車示範運營有限公司人士介紹,為滿足旗下500台純電動計程車的運營需要,他們與廠家共同在全市佈局了500多個充電樁和3個換電站,目前已建好約130個,可一樁多充。其車型慢充需要6小時,快充45分鐘可充到80%,充一次電可行駛180公里,有備用電池換。 目前,武漢的士日均行駛里程為300多公里,每天花時間充一次電似乎不夠跑。該人士表示有意與管理部門商約,採用司機單班制,而不是常規的雙班制,司機每天完成定額就行了。此外,8月底,100輛特斯拉計程車就可以上路。初始價可能在人民幣16元至25元之間,每公里為人民幣3.5元至4元。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!