⑴ 素材解析網站介面如何獲取
1、進行簡單設置,將ViewSource添加到Safari擴展菜單,在Safari打開任意一個網頁後,點擊底部的分享按鈕,第二行的擴展菜單滑動至最右,選擇更多,在活動頁面,找到ViewSource並將開關打開,這樣ViewSource就添加到Safari擴展菜單了。
2、找尋一些可用的在線解析。
3、查看網頁源代碼,獲取解析介面。
⑵ Android socket源碼解析(三)socket的connect源碼解析
上一篇文章著重的聊了socket服務端的bind,listen,accpet的邏輯。本文來著重聊聊connect都做了什麼?
如果遇到什麼問題,可以來本文 https://www.jianshu.com/p/da6089fdcfe1 下討論
當服務端一切都准備好了。客戶端就會嘗試的通過 connect 系統調用,嘗試的和服務端建立遠程連接。
首先校驗當前socket中是否有正確的目標地址。然後獲取IP地址和埠調用 connectToAddress 。
在這個方法中,能看到有一個 NetHooks 跟蹤socket的調用,也能看到 BlockGuard 跟蹤了socket的connect調用。因此可以hook這兩個地方跟蹤socket,不過很少用就是了。
核心方法是 socketConnect 方法,這個方法就是調用 IoBridge.connect 方法。同理也會調用到jni中。
能看到也是調用了 connect 系統調用。
文件:/ net / ipv4 / af_inet.c
在這個方法中做的事情如下:
注意 sk_prot 所指向的方法是, tcp_prot 中 connect 所指向的方法,也就是指 tcp_v4_connect .
文件:/ net / ipv4 / tcp_ipv4.c
本質上核心任務有三件:
想要能夠理解下文內容,先要明白什麼是路由表。
路由表分為兩大類:
每個路由器都有一個路由表(RIB)和轉發表 (fib表),路由表用於決策路由,轉發表決策轉發分組。下文會接觸到這兩種表。
這兩個表有什麼區別呢?
網上雖然給了如下的定義:
但實際上在Linux 3.8.1中並沒有明確的區分。整個路由相關的邏輯都是使用了fib轉發表承擔的。
先來看看幾個和FIB轉發表相關的核心結構體:
熟悉Linux命令朋友一定就能認出這裡面大部分的欄位都可以通過route命令查找到。
命令執行結果如下:
在這route命令結果的欄位實際上都對應上了結構體中的欄位含義:
知道路由表的的內容後。再來FIB轉發表的內容。實際上從下面的源碼其實可以得知,路由表的獲取,實際上是先從fib轉發表的路由字典樹獲取到後在同感加工獲得路由表對象。
轉發表的內容就更加簡單
還記得在之前總結的ip地址的結構嗎?
需要進行一次tcp的通信,意味著需要把ip報文准備好。因此需要決定源ip地址和目標IP地址。目標ip地址在之前通過netd查詢到了,此時需要得到本地發送的源ip地址。
然而在實際情況下,往往是面對如下這么情況:公網一個對外的ip地址,而內網會被映射成多個不同內網的ip地址。而這個過程就是通過DDNS動態的在內存中進行更新。
因此 ip_route_connect 實際上就是選擇一個緩存好的,通過DDNS設置好的內網ip地址並找到作為結果返回,將會在之後發送包的時候填入這些存在結果信息。而查詢內網ip地址的過程,可以成為RTNetLink。
在Linux中有一個常用的命令 ifconfig 也可以實現類似增加一個內網ip地址的功能:
比如說為網卡eth0增加一個IPV6的地址。而這個過程實際上就是調用了devinet內核模塊設定好的添加新ip地址方式,並在回調中把該ip地址刷新到內存中。
注意 devinet 和 RTNetLink 嚴格來說不是一個存在同一個模塊。雖然都是使用 rtnl_register 注冊方法到rtnl模塊中:
文件:/ net / ipv4 / devinet.c
文件:/ net / ipv4 / route.c
實際上整個route模塊,是跟著ipv4 內核模塊一起初始化好的。能看到其中就根據不同的rtnl操作符號注冊了對應不同的方法。
整個DDNS的工作流程大體如下:
當然,在tcp三次握手執行之前,需要得到當前的源地址,那麼就需要通過rtnl進行查詢內存中分配的ip。
文件:/ include / net / route.h
這個方法核心就是 __ip_route_output_key .當目的地址或者源地址有其一為空,則會調用 __ip_route_output_key 填充ip地址。目的地址為空說明可能是在回環鏈路中通信,如果源地址為空,那個說明可能往目的地址通信需要填充本地被DDNS分配好的內網地址。
在這個方法中核心還是調用了 flowi4_init_output 進行flowi4結構體的初始化。
文件:/ include / net / flow.h
能看到這個過程把數據中的源地址,目的地址,源地址埠和目的地址埠,協議類型等數據給記錄下來,之後內網ip地址的查詢與更新就會頻繁的和這個結構體進行交互。
能看到實際上 flowi4 是一個用於承載數據的臨時結構體,包含了本次路由操作需要的數據。
執行的事務如下:
想要弄清楚ip路由表的核心邏輯,必須明白路由表的幾個核心的數據結構。當然網上搜索到的和本文很可能大為不同。本文是基於LInux 內核3.1.8.之後的設計幾乎都沿用這一套。
而內核將路由表進行大規模的重新設計,很大一部分的原因是網路環境日益龐大且復雜。需要全新的方式進行優化管理系統中的路由表。
下面是fib_table 路由表所涉及的數據結構:
依次從最外層的結構體介紹:
能看到路由表的存儲實際上通過字典樹的數據結構壓縮實現的。但是和常見的字典樹有點區別,這種特殊的字典樹稱為LC-trie 快速路由查找演算法。
這一篇文章對於快速路由查找演算法的理解寫的很不錯: https://blog.csdn.net/dog250/article/details/6596046
首先理解字典樹:字典樹簡單的來說,就是把一串數據化為二進制格式,根據左0,右1的方式構成的。
如圖下所示:
這個過程用圖來展示,就是沿著字典樹路徑不斷向下讀,比如依次讀取abd節點就能得到00這個數字。依次讀取abeh就能得到010這個數字。
說到底這種方式只是存儲數據的一種方式。而使用數的好處就能很輕易的找到公共前綴,在字典樹中找到公共最大子樹,也就找到了公共前綴。
而LC-trie 則是在這之上做了壓縮優化處理,想要理解這個演算法,必須要明白在 tnode 中存在兩個十分核心的數據:
這負責什麼事情呢?下面就簡單說說整個lc-trie的演算法就能明白了。
當然先來看看方法 __ip_dev_find 是如何查找
文件:/ net / ipv4 / fib_trie.c
整個方法就是通過 tkey_extract_bits 生成tnode中對應的葉子節點所在index,從而通過 tnode_get_child_rcu 拿到tnode節點中index所對應的數組中獲取葉下一級別的tnode或者葉子結點。
其中查找index最為核心方法如上,這個過程,先通過key左移動pos個位,再向右邊移動(32 - bits)演算法找到對應index。
在這里能對路由壓縮演算法有一定的理解即可,本文重點不在這里。當從路由樹中找到了結果就返回 fib_result 結構體。
查詢的結果最為核心的就是 fib_table 路由表,存儲了真正的路由轉發信息
文件:/ net / ipv4 / route.c
這個方法做的事情很簡單,本質上就是想要找到這個路由的下一跳是哪裡?
在這裡面有一個核心的結構體名為 fib_nh_exception 。這個是指fib表中去往目的地址情況下最理想的下一跳的地址。
而這個結構體在上一個方法通過 find_exception 獲得.遍歷從 fib_result 獲取到 fib_nh 結構體中的 nh_exceptions 鏈表。從這鏈表中找到一模一樣的目的地址並返回得到的。
文件:/ net / ipv4 / tcp_output.c
⑶ OkHttp源碼解析 (三)——代理和路由
初看OkHttp源碼,由於對Address、Route、Proxy、ProxySelector、RouteSelector等理解不夠,讀源碼非常吃力,看了幾遍依然對於尋找復用連接、創建連接、連接伺服器、連接代理伺服器、創建隧道連接等邏輯似懂非懂,本篇決定梳理一遍相關的概念及基本原理。
● HTTP/1.1(HTTPS)
● HTTP/2
● SPDY
一個http請求的流程(直連):
1、輸入url及參數;
2、如果是url是域名則解析ip地址,可能對應多個ip,如果沒有指定埠,則用默認埠,http請求用80;
3、創建socket,根據ip和埠連接伺服器(socket內部會完成3次TCP握手);
4、socket成功連接後,發送http報文數據。
一個https請求的流程(直連):
1、輸入url及參數;
2、如果是url是域名則解析ip地址,可能對應多個ip,如果沒有指定埠,則用默認埠,https請求用443;
3、創建socket,根據ip和埠連接伺服器(socket內部會完成3次TCP握手);
4、socket成功連接後進行TLS握手,可通過java標准款提供的SSLSocket完成;
5、握手成功後,發送https報文數據。
1、分類
● HTTP代理:普通代理、隧道代理
● SOCKS代理:SOCKS4、SOCKS5
2、HTTP代理分類及說明
普通代理
HTTP/1.1 協議的第一部分。其代理過程為:
● client 請求 proxy
● proxy 解析請求獲取 origin server 地址
● proxy 向 origin server 轉發請求
● proxy 接收 origin server 的響應
● proxy 向 client 轉發響應
其中proxy獲取目的伺服器地址的標准方法是解析 request line 里的 request-URL。因為proxy需要解析報文,因此普通代理無法適用於https,因為報文都是加密的。
隧道代理
通過 Web 代理伺服器用隧道方式傳輸基於 TCP 的協議。
請求包括兩個階段,一是連接(隧道)建立階段,二是數據通信(請求響應)階段,數據通信是基於 TCP packet ,代理伺服器不會對請求及響應的報文作任何的處理,都是原封不動的轉發,因此可以代理 HTTPS請求和響應。
代理過程為:
● client 向 proxy 發送 CONNET 請求(包含了 origin server 的地址)
● proxy 與 origin server 建立 TCP 連接
● proxy 向 client 發送響應
● client 向 proxy 發送請求,proxy 原封不動向 origin server 轉發請求,請求數據不做任何封裝,為原生 TCP packet.
3、SOCKS代理分類及說明
● SOCKS4:只支持TCP協議(即傳輸控制協議)
● SOCKS5: 既支持TCP協議又支持UDP協議(即用戶數據包協議),還支持各種身份驗證機制、伺服器端域名解析等。
SOCK4能做到的SOCKS5都可得到,但反過來卻不行,比如我們常用的聊天工具QQ在使用代理時就要求用SOCKS5代理,因為它需要使用UDP協議來傳輸數據。
有了上面的基礎知識,下面分析結合源碼分析OkHttp路由相關的邏輯。OkHttp用Address來描述與目標伺服器建立連接的配置信息,但請求輸入的可能是域名,一個域名可能對於多個ip,真正建立連接是其中一個ip,另外,如果設置了代理,客戶端是與代理伺服器建立直接連接,而不是目標伺服器,代理又可能是域名,可能對應多個ip。因此,這里用Route來描述最終選擇的路由,即客戶端與哪個ip建立連接,是代理還是直連。下面對比下Address及Route的屬性,及路由選擇器RouteSelector。
描述與目標伺服器建立連接所需要的配置信息,包括目標主機名、埠、dns,SocketFactory,如果是https請求,包括TLS相關的SSLSocketFactory 、HostnameVerifier 、CertificatePinner,代理伺服器信息Proxy 、ProxySelector 。
Route提供了真正連接伺服器所需要的動態信息,明確需要連接的伺服器IP地址及代理伺服器,一個Address可能會有很多個路由Route供選擇(一個DNS對應對個IP)。
Address和Route都是數據對象,沒有提供操作方法,OkHttp另外定義了RouteSelector來完成選擇的路由的操作。
1、讀取代理配置信息:resetNextProxy()
讀取代理配置:
● 如果有指定代理(不讀取系統配置,在OkHttpClient實例中指定),則只用1個該指定代理;
● 如果沒有指定,則讀取系統配置的,可能有多個。
2、獲取需要嘗試的socket地址(目標伺服器或者代理伺服器):resetNextInetSocketAddress()
結合Address的host和代理,解析要嘗試的套接字地址(ip+埠)列表:
● 直連或者SOCK代理, 則用目標伺服器的主機名和埠,如果是HTTP代理,則用代理伺服器的主機名和埠;
● 如果是SOCK代理,根據目標伺服器主機名和埠號創建未解析的套接字地址,列表只有1個地址;
● 如果是直連或HTTP代理,先DNS解析,得到InetAddress列表(沒有埠),再創建InetSocketAddress列表(帶上埠),InetSocketAddress與InetAddress的區別是前者帶埠信息。
3、獲取路由列表:next()
選擇路由的流程解析:
● 遍歷每個代理對象,可能多個,直連的代理對象為Proxy.DIRECT(實際是沒有中間代理的);
● 對每個代理獲取套接字地址列表;
● 遍歷地址列表,創建Route,判斷Route如果在路由黑名單中,則添加到失敗路由列表,不在黑名單中則添加到待返回的Route列表;
● 如果最後待返回的Route列表為空,即可能所有路由都在黑名單中,實在沒有新路由了,則將失敗的路由集合返回;
● 傳入Route列表創建Selection對象,對象比較簡單,就是一個目標路由集合,及讀取方法。
為了避免不必要的嘗試,OkHttp會把連接失敗的路由加入到黑名單中,由RouteDatabase管理,該類比較簡單,就是一個失敗路由集合。
1、創建Address
Address的創建在RetryAndFollowUpInteceptor里,每次請求會聲明一個新的Address及StreamAllocation對象,而StreamAllocation使用Address創建RouteSelector對象,在連接時RouteSelector確定請求的路由。
每個Requst都會構造一個Address對象,構造好了Address對象只是有了與伺服器連接的配置信息,但沒有確定最終伺服器的ip,也沒有確定連接的路由。
2、創建RouteSelector
在StreamAllocation聲明的同時會聲明路由選擇器RouteSelector,為一次請求尋找路由。
3、選擇可用的路由Route
下面在測試過程跟蹤實例對象來理解,分別測試直連和HTTP代理HTTP2請求路由的選擇過程:
● 直連請求流程
● HTTP代理HTTPS流程
請求url: https://www.jianshu.com/p/63ba15d8877a
1、構造address對象
2、讀取代理配置:resetNextProxy
3、解析目標伺服器套接字地址:resetNextInetSocketAddress
4、選擇Route創建RealConnection
5、確定協議
測試方法:
● 在PC端打開Charles,設置埠,如何設置代理,網上有教程,比較簡單;
● 手機打開WIFI,選擇連接的WIFI修改網路,在高級選項中設置中指定了代理伺服器,ip為PC的ip,埠是Charles剛設置的埠;
● OkHttpClient不指定代理,發起請求。
1、構造address對象
2、讀取代理配置:resetNextProxy
3、解析目標伺服器套接字地址:resetNextInetSocketAddress
4、選擇Route創建RealConnection
5、創建隧道
由於是代理https請求,需要用到隧道代理。
從圖可以看出,建立隧道其實是發送CONNECT請求,header包括欄位Proxy-Connection,目標主機名,請求內容類似:
6、確定協議,SSL握手
1、代理可分為HTTP代理和SOCK代理;
2、HTTP代理又分為普通代理和隧道代理;普通代理適合明文傳輸,即http請求;隧道代理僅轉發TCP包,適合加密傳輸,即https/http2;
3、SOCK代理又分為SOCK4和SOCK5,區別是後者支持UDP傳輸,適合代理聊天工具如QQ;
4、沒有設置代理(OkHttpClient沒有指定同時系統也沒有設置),客戶端直接與目標伺服器建立TCP連接;
5、設置了代理,代理http請求時,客戶端與代理伺服器建立TCP連接,如果代理伺服器是域名,則解釋代理伺服器域名,而目標伺服器的域名由代理伺服器解析;
6、設置了代理,代理https/http2請求時,客戶端與代理伺服器建立TCP連接,發送CONNECT請求與代理伺服器建立隧道,並進行SSL握手,代理伺服器不解析數據,僅轉發TCP數據包。
如何正確使用 HTTP proxy
OkHttp3中的代理與路由
HTTP 代理原理及實現(一)
⑷ 哪位高手知道網頁源代碼解析技術
恩恩,源代碼其實一點用都不頂的,如果是下載個FLASH文件或者音樂視頻什麼的調用還好使,要是連接資料庫就不行了,基本全都隱藏了
⑸ Spring Tx源碼解析(二)
上一篇 我們介紹了 spring-tx 中的底層抽象,本篇我們一起來看看圍繞這些抽象概念 spring-tx 是如何打造出聲明式事務的吧。籠統的說, spring-tx-5.2.6.RELEASE 的實現主要分為兩個部分:
這兩部分彼此獨立又相互成就,並且每個部分都有著大量的源碼支撐,本篇我們先來分析 spring-tx 中的AOP部分吧。
EnableTransactionManagement 註解想必大家都很熟悉了,它是啟用 Spring 中注釋驅動的事務管理功能的關鍵。
EnableTransactionManagement 註解的主要作用是向容器中導入 ,至於註解中定義的幾個屬性在 Spring AOP源碼解析 中有過詳細分析,這里就不再贅述了。
由於我們並沒有使用 AspectJ ,因此導入容器的自然是 這個配置類。
這個配置類的核心是向容器中導入一個類型為 的Bean。這是一個 PointcutAdvisor ,它的 Pointcut 是 , Advice 是 TransactionInterceptor 。
利用 TransactionAttributeSource 解析 @Transactional 註解的能力來選取標注了 @Transactional 註解的方法,而 TransactionInterceptor 則根據應用提出的需求(來自對 @Transactional 註解的解析)將方法增強為事務方法,因此 可以識別出那些標注了 @Transactional 註解的方法,為它們應用上事務相關功能。
TransactionInterceptor 能對方法進行增強,但是它卻不知道該如何增強,比如是為方法新開一個獨立事務還是沿用已有的事務?什麼情況下需要回滾,什麼情況下不需要?必須有一個『人』告訴它該如何增強,這個『人』便是 TransactionAttributeSource 。
@Transactional 註解定義了事務的基礎信息,它表達了應用程序期望的事務形態。 TransactionAttributeSource 的主要作用就是解析 @Transactional 註解,提取其屬性,包裝成 TransactionAttribute ,這樣 TransactionInterceptor 的增強便有了依據。
前面我們已經見過, spring-tx 使用 來做具體的解析工作,其父類 定義了解析 TransactionAttribute 的優先順序,核心方法是 computeTransactionAttribute(...) 。
默認只解析 public 修飾的方法,這也是導致 @Transactional 註解失效的一個原因,除此之外它還實現了父類中定義的兩個模板方法:
同時為了支持 EJB 中定義的 javax.ejb.TransactionAttribute 和 JTA 中定義的 javax.transaction.Transactional 註解, 選擇將實際的提取工作代理給 TransactionAnnotationParser 。Spring 提供的 @Transactional 註解由 進行解析。
的源碼還是很簡單的,它使用 AnnotatedElementUtils 工具類定義的 find 語義來獲取 @Transactional 註解信息。 RuleBasedTransactionAttribute 中 rollbackOn(...) 的實現還是挺有意思的,其它的都平平無奇。
RollbackRuleAttribute 是用來確定在發生特定類型的異常(或其子類)時是否應該回滾,而 NoRollbackRuleAttribute 繼承自 RollbackRuleAttribute ,但表達的是相反的含義。 RollbackRuleAttribute 持有某個異常的名稱,通過 getDepth(Throwable ex) 演算法來計算指定的 Throwable 和持有的異常在繼承鏈上的距離。
程序猿只有在拿到需求以後才能開工, TransactionInterceptor 也一樣,有了 TransactionAttributeSource 之後就可以有依據的增強了。觀察類圖, TransactionInterceptor 實現了 MethodInterceptor 介面,那麼自然要實現介面中的方法:
可以看到, TransactionInterceptor 本身是沒有實現任何邏輯的,它更像一個適配器。這樣分層以後, TransactionAspectSupport 理論上就可以支持任意類型的 Advice 而不只是 MethodInterceptor 。實現上 TransactionAspectSupport 確實也考慮了這一點,我們馬上就會看到。
invokeWithinTransaction(...) 的流程還是非常清晰的:
第一步前文已經分析過了,我們來看第二步。
TransactionInfo 是一個非常簡單的類,我們就不費什麼筆墨去分析它了。接著看第三步,這一步涉及到兩個不同的操作——提交或回滾。
至此, TransactionInterceptor 於我們而言已經沒有任何秘密了。
本篇我們一起分析了 spring-tx 是如何通過 spring-aop 的攔截器將普通方法增強為事務方法的,下篇就該說道說道 PlatformTransactionManager 抽象下的事務管理細節啦,我們下篇再見~~
⑹ 如何用python解析網頁並獲得網頁真實的源碼
可以去了解下python如何調用webkit的引擎,你說的那種不是用js加密,只是用js動態載入頁面內容。必須用webkit之類的瀏覽器引擎去渲染。
⑺ 求一個單頁網站源碼!要純靜態的!越簡單越好,
純靜態、簡單得不能再簡單,請Ctrl+C:
<html>
<title>單頁網站源碼</title>
<body>這是一個單頁網站源碼</body>
</html>
⑻ mina 源碼解析(1)
mina是一款網路應用框架, 它是基於java nio 的 通訊框架, 提供抽象的事件驅動非同步的api。
mina 基本架構:
大致上分為三層IoSerivce, Filter, Handler
首先我們來看一下Filter
前面說過, mina 是基於事件驅動的, 所以我們先來看一下mina定義了那些事件:
IoEventType 類中定義了如下枚舉變數:
這些事件對於用戶使用者來說無需關心。
以上IoEventType僅是事件的類型(事件的屬性), mina對於事件是通過IoEvent對象進行封裝的。
來看一下IoEvent 都有哪些方法:
我們看到IoEvent 實現了Runnable,也就是說這個對象會交給多線程去執行run方法。
在run方法中我們看到執行的是fire(), 也就是具體的任務了。
下面來看一下fire中做的事情, 首先看以下IoEvent 中 具體做了哪些事情, 再看一下其子類IoFilterEvent又做了哪些事情。 後面我會講IoFilterEvent 的作用是什麼, 在哪裡用到。
其實做的事情就是觸發(調用) session下filterchain的對應事件類型的方法。
IoFilterEvent 是IoEvent 的子類, 從名字就可以看出來, 其與IoFilter 有一定的關系, 所以IoFilterEvent是由IoFilter 產生的事件。
而IoFilter 產生的事件有什麼特性呢?
在此插入IoFilter與IoFilterChain 的介紹。
IoFilter 是 攔截處理事件的過濾器, 如同servlet的filter。 它可以用於很多目的:
日誌記錄, 性能測量, 認證等等。
而filterchain 即代表一串IoFilter的鏈子, 其有多個IoFilter 組成。 事實上,在IoFilterChain 中,封裝了一層Entry, 每個Entry中封裝了當前的IoFilter和下一個IoFilter的引用。 可以看到Entry的介面如下
NextFilter 顧名思義, 即下一個IoFilter, 為什麼需要有這么一個介面呢?
事實上, 我們可以想像, nextFilter 是用來 觸發 下一個Iofilter的,其並不是IoFilter, 在IoFilter中, 我們可以看到這樣的調用
說完了IoFilter, IoFilterChain, 那與IoFilterEvent有什麼關系呢?
因為是IoFilter產生的事件, 當觸發這個事件時, 由於鏈式的特徵, 該事件將觸發當前IoFilter 以後的Filter, 所以我們看到, IoFilter fire()的實現其實就是調用下一個filter的對應的事件方法:
也可以這樣理解, 一個事件的產生, 產生地就是頭, 事件從當前頭向鏈後傳播。
IoEvent 做的是從chain 的開頭往後傳播, IoFilterEvent是由當前IoFilter產生往後傳播。
說完了IoFilter, 來探討一下如何將IoFilter組裝成IoFilterChain:
其實裡面的結構就是鏈表。
來看看chain中add的內容:
⑼ Feign源碼解析二
本文會基於Feign源碼,看看Feign到底是怎麼實現遠程調用
上文中,我們的 user-service 服務需要調用遠程的 order-service 服務完成一定的業務邏輯,而基本實現是order-service提供一個spi的jar包給user-service依賴,並且在user-service的啟動類上添加了一個註解
這個註解就是@EnableFeignClients,接下來我們就從這個註解入手,一步一步解開Feign的神秘面紗
該註解類上的注釋大概的意思就是:
掃描那些被聲明為 Feign Clients (只要有 org.springframework.cloud.openfeign.FeignClient 註解修飾的介面都是Feign Clients介面)的介面
下面我們繼續追蹤源碼,看看到底什麼地方用到了這個註解
利用IDEA的查找調用鏈快捷鍵,可以發現在.class類型的文件中只有一個文件用到了這個註解
OK,下面主要就是看這個類做了什麼
通過UML圖我們發現該類分別實現了 ImportBeanDefinitionRegistrar , ResourceLoaderAware 以及 EnvironmentAware 介面
這三個介面均是spring-framework框架的spring-context模塊下的介面,都是和spring上下文相關,具體作用下文會分析
總結下來就是利用這兩個重要屬性,一個獲取應用配置屬性,一個可以載入classpath下的文件,那麼FeignClientsRegistrar持有這兩個東西之後要做什麼呢?
上面將bean配置類包裝成 FeignClientSpecification ,注入到容器。該對象非常重要,包含FeignClient需要的 重試策略 , 超時策略 , 日誌 等配置,如果某個FeignClient服務沒有設置獨立的配置類,則讀取默認的配置,可以將這里注冊的bean理解為整個應用中所有feign的默認配置
由於 FeignClientsRegistrar 實現了 ImportBeanDefinitionRegistrar 介面,這里簡單提下這個介面的作用
我們知道在spring框架中,我們如果想注冊一個bean的話主要由兩種方式:自動注冊/手動注冊
知道了 ImportBeanDefinitionRegistrar 介面的作用,下面就來看下 FeignClientsRegistrar 類是何時被載入實例化的
通過IDEA工具搜索引用鏈,發現該類是在註解@EnableFeignClients上被import進來的,文章開始的圖片中有
這里提下@Import註解的作用
該註解僅有一個屬性value,使用該註解表明導入一個或者多個@Configuration類,其作用和.xml文件中的<import>等效,其允許導入@Configuration類,ImportSelector介面/ImportBeanDefinitionRegistrar介面的實現,也同樣可以導入一個普通的組件類
注意,如果是XML或非@Configuration的bean定義資源需要被導入的話,需要使用@ImportResource註解代替
這里我們導入的FeignClientsRegistrar類正是一個ImportBeanDefinitionRegistrar介面的實現
FeignClientsRegistrar重寫了該介面的 registerBeanDefinitions 方法,該方法有兩個參數註解元數據 metadata 和bean定義注冊表 registry
該方法會由spring負責調用,繼而注冊所有標注為@FeignClient註解的bean定義
下面看registerBeanDefinitions方法中的第二個方法,在該方法中完成了所有@FeignClient註解介面的掃描工作,以及注冊到spring中,注意這里注冊bean的類型為 FeignClientFactoryBean ,下面細說
總結一下該方法,就是掃描@EnableFeignClients註解上指定的basePackage或clients值,獲取所有@FeignClient註解標識的介面,然後將這些介面一一調用以下 兩個重要方法 完成 注冊configuration配置bean 和注冊 FeignClient bean
斷點位置相當重要
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
這里是利用了spring的代理工廠來生成代理類,即這里將所有的 feignClient的描述信息 BeanDefinition 設定為 FeignClientFactoryBean 類型,該類繼承自FactoryBean,因此這是一個代理類,FactoryBean是一個工廠bean,用作創建代理bean,所以得出結論,feign將所有的 feignClient bean定義的類型包裝成 FeignClientFactoryBean
最終其實就是存入了BeanFactory的beanDefinitionMap中
那麼代理類什麼時候會觸發生成呢? 在spring 刷新容器時 ,會根據beanDefinition去實例化bean,如果beanDefinition的beanClass類型為代理bean,則會調用其 T getObject() throws Exception; 方法生成代理bean,而我們實際利用注入進來的FeignClient介面就是這些一個個代理類
這里有一個需要注意的點,也是開發中會遇到的一個 啟動報錯點
如果我們同時定義了兩個不同名稱的介面 (同一個包下/或依賴方指定全部掃描我們提供的 @FeignClient ),且這兩個 @FeignClient 介面註解的 value/name/serviceId 值一樣的話,依賴方拿到我們的提供的spi依賴,啟動類上 @EnableFeignClients 註解掃描能同時掃描到這兩個介面,就會 啟動報錯
原因就是Feign會為每個@FeignClient註解標識的介面都注冊一個以serviceId/name/value為key,FeignClientSpecification類型的bean定義為value去spring注冊bean定義,又默認不允許覆蓋bean定義,所以報錯
官方提示給出的解決方法要麼改個@FeignClient註解的serviceId,name,value屬性值,要麼就開啟spring允許bean定義覆寫
至此我們知道利用在springboot的啟動類上添加的@EnableFeignClients註解,該註解中import進來了一個手動注冊bean的 FeignClientsRegistrar注冊器 ,該注冊器會由spring載入其 registerBeanDefinitions方法 ,由此來掃描所有@EnableFeignClients註解定義的basePackages包路徑下的所有標注為@FeignClient註解的介面,並將其注冊到spring的bean定義Map中,並實例化bean
下一篇博文中,我會分析為什麼我們在調用(@Resource)這些由@FeignClient註解的bean的方法時會發起 遠程調用
⑽ 求個藍奏雲直連解析介面,或者是源碼。
介面和源碼我都有。
源碼我不知道有沒有用,因為沒php環境,不過以前測試過是可以用的!
1,介面地址:放個圖片你們自己看。
2,源碼地址 :就是上面這個介面的源碼
<?php
functionMloocCurl($url,$method,$ifurl,$post_data){
$UserAgent='Mozilla/5.0(WindowsNT6.1;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/63.0.3239.132Safari/537.36';#設置ua
$curl=curl_init();
curl_setopt($curl,CURLOPT_URL,$url);
curl_setopt($curl,CURLOPT_USERAGENT,$UserAgent);
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,false);
curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
if($method=="post"){
curl_setopt($curl,CURLOPT_REFERER,$ifurl);
curl_setopt($curl,CURLOPT_POST,1);
curl_setopt($curl,CURLOPT_POSTFIELDS,$post_data);
}
$response=curl_exec($curl);
curl_close($curl);
return$response;
}
if(!empty($_GET['url'])){
$url=$_GET['url'];
#第一步
$ruleMatchDetailInList="~ifr2"sname="[sS]*?"ssrc="/(.*?)"~";
preg_match($ruleMatchDetailInList,MloocCurl($url,null,null,null),$link);
$ifurl="https://www.lanzous.com/".$link[1];
#第二步
$ruleMatchDetailInList="~=s'(.*?)';[Ss]*?=s'(.*?)'[Ss]*?=s'(.*?)'[Ss]*?=s'(.*?)'~";
preg_match($ruleMatchDetailInList,MloocCurl($ifurl,null,null,null),$segment);
#第三步
#post提交的數據
$post_data=array(
"action"=>$segment[1],
"file_id"=>$segment[2],
"t"=>$segment[3],
"k"=>$segment[4]
);
$obj=json_decode(MloocCurl("https://www.lanzous.com/ajaxm.php","post",$ifurl,$post_data));#json解析
if($obj->dom==""){#判斷鏈接是否正確
echo"鏈接有誤!";
}else{
$downUrl=$obj->dom."/file/".$obj->url."/&type=down/";
if(!empty($_GET['type'])){
$type=$_GET['type'];
if($type=="down"){
header('Location:'.$downUrl);#直接下載
}else{
header('Location:'.$downUrl);#直接下載
}
}else{
header('Location:'.$downUrl);#直接下載
}
}
}else{
$result_url=str_replace("index.php","","//".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']."");
echo"藍奏雲直連下載";
echo"<br/>";
/*echo"直接下載:"."<ahref='".$result_url."&type=down'target='_blank'>".$result_url."&type=down</a>";
echo"<br/>";
echo"輸出直鏈:"."<ahref='".$result_url."'target='_blank'>".$result_url."</a>";*/
}
?>