月份: 2021 年 1 月

  • 如果你也喜歡自主品牌,那麼這些事情你有必要了解!

    如果你也喜歡自主品牌,那麼這些事情你有必要了解!

    可尼瑪事與願違啊。剛過去俗稱“金九銀十”銷售旺季的10月份,我國的乘用車銷量共計234。41萬輛,而光轎車就賣出了117。05萬輛。哪怕現在SUV市場再怎麼火,當月的轎車銷量依然要比SUV多出27。45萬輛。所以事實就是,轎車依然是中國汽車市場上最好賣的車型。

    憑藉著SUV浪潮的興起,我國的自主品牌頓時間風生水起,長城、吉利、傳祺…迅速取代合資品牌,成為中國車市的第一驅動力。甚至連一些名不見經傳的品牌,譬如力帆、野馬等也都憑藉推出SUV過上了翻身農奴把歌唱的好日子。咋一看,革面全面勝利,形勢一片大好!然鵝,在一片繁榮的背後貌似隱藏着危機四伏的危險。

    這個危機四伏的危險是什麼?

    咱們的自主品牌相當缺乏能夠拿得出手的轎車產品呀!除了帝豪、帝豪GL、艾瑞澤5、逸動之外,大家還能叫得上幾款口碑、銷量都不錯的自主品牌轎車么?沒有了。如果要說不光口碑好、銷量好,而且還有歷史傳承的自主品牌轎車,那就只有累計銷量剛剛突破百萬輛的帝豪了。而合資品牌呢?朗逸、福睿斯、高爾夫、卡羅拉,個個的銷量、口碑都是彪悍級的。

    SUV市場火熱,就拚命地研發SUV、銷售SUV固然是人之常情。畢竟吃飽了,才有力氣幹活,把現錢都掙了是正常思維。但咱們自主品牌偏偏是在這樣的思維下走到了極端。最典型的,莫過於剛剛憑藉哈弗H6單月7萬+銷量刷新中國單一車型月均銷量紀錄的長城。哈弗H6在SUV風頭無兩,但旗下的轎車車型C30,近半年來的月均的銷量卻只有千餘輛的水平。略有些營養不良的感覺。

    這個危機到底有多危險?

    有的人說:大家都買SUV了,誰還會買轎車?!我也是這樣想的啊!可尼瑪事與願違啊!剛過去俗稱“金九銀十”銷售旺季的10月份,我國的乘用車銷量共計234.41萬輛,而光轎車就賣出了117.05萬輛。哪怕現在SUV市場再怎麼火,當月的轎車銷量依然要比SUV多出27.45萬輛。所以事實就是,轎車依然是中國汽車市場上最好賣的車型。相信大夥都已經明白到轎車市場的重要性,但絕大多數的自主品牌卻竟然一致地選擇無視這個市場。這不叫集中力量干大事,這叫把所有雞蛋都放一個籃子里。就差合資品牌給你絆一跤,讓你的籃子里的雞蛋都摔破。

    合資品牌在轎車領域有多強勢?

    要說合資品牌跟自主品牌一樣,把全副身家押寶在SUV車型上那還不至於讓人膽戰心驚,但問題就在於合資品牌在轎車領域的牛逼程度簡直是垄斷式經營(不是人家想垄斷,是自主品牌把這市場拱手相讓)。同樣是“金九銀十”的10月份,轎車銷量的前十位企業分別為:一汽大眾、上汽大眾、上汽通用、東風日產、吉利控股、北京現代、長安福特、東風悅達起亞、廣汽本田和神龍汽車。十個茅坑,竟然有九個被合資品牌佔了。更可怕的是,自主品牌壓根連剩下的肉都吃不上。因為這十家車企的轎車銷量,已經佔到了整個轎車市場的72.53%。估計自主品牌也就能夠吃上兩口青菜吧,清淡點。

    自主品牌你們要當個“德智體美勞全面健康發展”的好學生呀!

    說實話,看着自主品牌由民間小作坊生產、向合資企業討落後的技術,到擁有媲美國際一線車企的現代化生產車間(去看看吉利台州的基地就一目瞭然了)以及發動機、汽車互聯等核心技術方面的創新性領先,我作為一个中國人是相當的自豪,我相信大家也是。但也正因為這一份對自主品牌的熱切期待,才更使得我們去發現自主品牌在轎車市場方面的不足,才憂心好不容易才建立起來的中國汽車工業因為轎車市場的乏力而付諸一旦。所以,請自主品牌們發展的步伐再穩一些、再穩一些。

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※推薦台中搬家公司優質服務,可到府估價

  • 國產SUV稱霸!最接地氣的銷量榜解析

    國產SUV稱霸!最接地氣的銷量榜解析

    期望非常大,如果又遇到黑它的人,請大家備好磚頭,你懂的。

    哈弗H6的銷量如此火爆,下個月還能賣7萬輛嗎?期望非常大,如果又遇到黑它的人,請大家備好磚頭,你懂的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

    網頁設計最專業,超強功能平台可客製化

  • 價錢低逼格不低,小弟車型這麼屌,叫大哥怎麼混?

    價錢低逼格不低,小弟車型這麼屌,叫大哥怎麼混?

    由於全系的X1使用了UKL平台,橫置布局的發動機,再加上對車內空間的優化,最終的表現非常出色,加上X1的第二排座椅還可以前後移動和調節靠背角度,空間表現已經超越了其大哥X3。X1 xDrive25Li使用了代號為B48A20Ο0的高功率版2。

    在中國的傳統家庭里,有一個非常有趣的現象,如果你有哥哥或者姐姐,一般情況下都不能比哥哥姐姐早結婚,父母要催婚,肯定是先從哥哥姐姐下手,如果弟弟妹妹先結婚了,哥哥姐姐會過上很慘的被催婚生活。

    其實在很多領域都存在着這樣的現象,例如考量業績的銷售業,鄉鎮級分銷點的銷量比縣城級分銷點高,那就非常尷尬了。

    但是在汽車行業,卻是截然不同,消費者非常樂意看到某車型超越自己的大哥,最典型的例子就是經常被冠以“小S”稱號的奔馳E級。

    我們拿同樣是320 L的奔馳E級與S級進行對比,雖然兩者採用了同樣的設計語言,但是在車身尺寸上還是相差甚遠的,奔馳E 320 L 4MATIC的車身尺寸為:5065x1860x1482 mm,軸距:3079 mm,奔馳S 320 L 商務型的車身尺寸為:5250x1899x1494 mm,軸距:3165 mm。

    奔馳E級雖然後排座椅的橫向空間和頭部空間不及S級,但是後排的腿部縱向空間表現幾乎一樣,非常出色。奔馳E 320 L 4MATIC指導價:62.98萬,奔馳S 320 L 商務型指導價:93.80萬,兩者同樣擁有后風擋遮陽簾和後排側遮陽簾,E 320L還多出了後排側隱私玻璃。

    兩車的指導價雖然相差30.82萬,但是發動機同樣是3.0T雙渦輪增壓V6發動機,E 320 L 4MATIC更是擁有9AT和全時四驅系統,配置上比S 320 L 商務型多出了無鑰匙進入、電動/感應後備廂、方向盤/電動座椅/后視鏡記憶功能,還有自適應巡航、主動剎車等一系列高科技配置。

    雖然奔馳E級和S級在氣場上還是有不少差距,但如果讓選擇,還是會選擇E級,因為是配置控,同時E 320的机械品質足以滿足絕大多數情景的需求。

    接下來的這個對比更加有看點,寶馬X1 xDrive25Li 豪華型對比寶馬X3 sDrive20i,兩者的指導價分別是43.9萬和42.1萬,價格非常接近,重點是兩車的車身尺寸差距很小,寶馬X1的軸距也只是比X3短了30mm而已。

    由於全系的X1使用了UKL平台,橫置布局的發動機,再加上對車內空間的優化,最終的表現非常出色,加上X1的第二排座椅還可以前後移動和調節靠背角度,空間表現已經超越了其大哥X3。

    X1 xDrive25Li使用了代號為B48A20Ο0的高功率版2.0T發動機,X3 sDrive20i則是使用N20B20的低功率版2.0T發動機,X1的動力表現比X3要好出不少,而且X1 xDrive25Li是前置適時四驅,X3 sDrive20i只是前置后驅。

    在配置方面,X1 xDrive25Li多出了無鑰匙進入系統、電動/感應後備廂方向盤換擋、HUD抬頭显示、GpS導航、藍牙、LED大燈、後排出風口、自動泊車、車道偏離預警等等,第二排座椅還可以前後移動和調節靠背角度。

    與奔馳的E級和S級不同,X1 xDrive25Li和X3 sDrive20i在價格上相差無幾,X1使用了新的平台、新的動力總成、新的設計,可以說是把還沒換代的老X3打敗了,動力更強、配置更高。

    林肯的MKC和MKX這兩款SUV的情況和X1、X3的情況有點相似,我們拿MKC的2017款 2.3T 四驅總統系列與MKX的2015款 2.0T 兩驅尊享版作對比,兩者的指導價分別是43.88萬和44.98萬。

    先來進行配置對比MKC僅多出了方向盤加熱、前排座椅通風、後排座椅加熱、自適應遠近光、感應雨刷、自動泊車入位、自適應巡航、車道偏離預警和併線輔助,兩者在配置上的差異其實並不是很大,主要是因為最低配的MKX配置水平真心不低。

    MKC和MKX的外觀內飾設計都非常相似,慶幸的是兩車都還沒有使用林肯MKZ的那個最新前臉設計,它們都很好的保留了林肯家族該有的美式設計美學,霸氣的中網和貫穿式的尾燈非常漂亮。

    空間表現並不是它們的優勢,重點是MKC和MKX的內飾氛圍都非常豪華,僅看內飾的話,真的感覺不出MKC定位比MKX低,這就是消費者最喜歡的典型例子:花更少的錢得到更高級的視覺享受。

    其實同品牌的雞頭鳳尾之選,還是有不少的,例如日產的軒逸和天籟,新款天籟的外觀設計真是佩服,相信有不少人把天籟硬生生看成軒逸,還有國內即將上市的寶馬5系,無論是設計元素還是各項配置,都在向著7系靠攏。

    雖然說上面提到的在很多方面都向著大哥靠攏,但我們還是要理性對待,外觀內飾的設計,還有車輛的配置,這些都能夠做到互相媲美,但是在車輛的行駛品質,動態體驗方面,不同級別的車型還是存在着本質區別的,是否值得購買,就要看你注重車輛的是哪個方面了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計最專業,超強功能平台可客製化

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

    ※回頭車貨運收費標準

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

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

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家公司費用怎麼算?

  • 這些國產“豪車”價錢可不豪,十幾萬就能考慮入手了!

    這些國產“豪車”價錢可不豪,十幾萬就能考慮入手了!

    觀致身處的這個惡性循環:產品力不足 → 經銷商加盟量少 → 銷量沒起色 → 成本居高不下 → 新產品性價比低 → 潛在經銷商更加望而卻步,如此循環。觀致在期待着一個救世主,挽救頹勢。觀致的全新跨界轎車觀致3 GT指導價為11。

    稍有起色的自主車企都慢慢開始轉型,尋求向上發展,要涉足中高端路線,要麼推出高端化產品,要麼就再立門戶、開設新的子品牌來抗衡豐田、福特、大眾等一干合資大咖。

    而成功“解決溫飽問題、活下來”的自主品牌,都基本具備了推出中高端品牌的造車技術經驗、品牌簇擁者以及市場營銷基礎,自下而上的往上發展亦無可厚非。

    傳祺GM8

    相比建立中高端子品牌,推出高端化產品來提升品牌形象的效果並沒有前者來得直接,前車之鑒就有行政級轎車東風A9、GA8,走越野路線的哈弗H9,地方補貼的新能源產物榮威E950,市場表現皆慘淡。當然也不乏市場熱烈的車型,吉利博瑞、傳祺GS8把准了時代的脈搏,一炮走紅,銷量當然理想。

    更看好自主車企在推出高端化產品獲得成功后再去涉足中高端子品牌,循序漸進,遵循企業發展規律。期待廣汽傳祺今後能為我們繼續打造高端化產品(GA8、GS8、GM8三足鼎立),甚至乎帶來一個高端子品牌。

    東風A9

    傳祺GA8

    哈弗H9

    榮威e950

    吉利博瑞

    傳祺GS8

    觀致可以說是該領域的先行者(紅旗打一開始就沒想過要走量、盈利,姑且不算),剛面世時,被外界大肆宣傳、捧得很高,但由於撿錯了敲門磚,觀致選擇以轎車作為第一款面向消費者的產品,而冷落了受眾面更廣的SUV,加上觀致3自身產品力不足、經銷商營銷不力以及售價偏高,導致最終無人問津、摔得很痛。

    觀致5

    但我們並不能單單隻看到觀致試水失敗這個表象,認為觀致更起到了帶頭作用,吹響了自主品牌走出國門、邁向高端的號角,一石激起千層浪,寶沃、吉利、長城等車企都紛紛覬覦這塊待開發的市場,哪怕它們選取的路徑、理念不同,但他們都為中華汽車製造業爭一口氣,擺脫以往廉價、低端的形象,從事汽車行業的甚是欣慰。

    寶沃BX7

    下面來為大家介紹自主高端品牌剛推出以及即將推出的量產車,一同拭目以待。

    LYNK & CO

    LYNK & CO的品牌發布會地點在德國柏林,現場充滿前衛、時尚元素。吉利集團高級設計副總裁彼得·霍布里,過往阿斯頓馬丁、捷豹、路虎和沃爾沃等品牌不少作品都出自他手,博瑞和博越皆由他帶領的設計團隊完成, LYNK & CO 01同樣如此。彼得·霍布里:“車型應當風格鮮明、引人注目,能夠吸引包括中國、歐洲和美國在內的全球消費者。”反正是被吸引住了。

    分體式大燈組,LED光帶式日間行車燈,L形尾燈設計前瞻、個性,糅合到一起卻相當和諧,毫不違和,延伸至引擎蓋上的LED日間行車燈靈感源自北歐上空的極光,絢麗奪目,不得不感嘆設計團隊的功架。

    相信這款車將來量產後會消化不少來自沃爾沃的技術,尤其在動力總成和主動安全技術方面。(需要指出的是,吉利僅僅是收購了沃爾沃乘用車,而不是指整個沃爾沃集團,扮演着控股的角色,並未達到為所欲為的地步。)

    WEY

    WEY:長城的高端品牌WEY就這樣低調、悄無聲息出現在我們眼前,相對LYNK & CO要低調,魏建軍講到的:“民營企業沒有後路才能發展”,表達了長城一往無前、決意要做出成績的堅定決心,決意破釜沉舟。

    哈弗系列的動力和底盤總成缺乏新意,W01的底盤結構與哈弗H7十分相像,就連動力總成也是2.0T搭配7速雙離合變速箱。而W02的底盤則與哈弗H6相同。

    W 01

    W 02

    核心三大件沒升級、行駛質感沒得到提升的話,再多的噱頭也只是徒勞,希望WEY最後出來的產品不會令失望。

    觀致

    奇瑞與以色列集團各持股50%的方式成立觀致,給觀致帶來一定的合資背景,但銷量始終不如人意,在歐洲銷售期間更是無人問津。觀致身處的這個惡性循環:產品力不足 → 經銷商加盟量少 → 銷量沒起色 → 成本居高不下 → 新產品性價比低 → 潛在經銷商更加望而卻步,如此循環。觀致在期待着一個救世主,挽救頹勢。

    觀致的全新跨界轎車觀致3 GT指導價為11.09-13.99萬元。增加了一套跨界風格的車身套件,如前後的保險杠下護板、運動包圍、黑色輪眉,相信年輕人會對其青睞有加。

    觀致3 GT採用1.6T發動機,最大功率為156ps,峰值扭矩230N·m,參數要比起轎車版要高點,匹配6速手動或6速雙離合變速箱。

    希望觀致能推出更多競爭力強的產品去豐富產品線、整頓經營,力挽狂瀾。挫折並不像江河那樣不可逾越,而是前進的動力,觀致彆氣餒!

    寶沃

    寶沃BX5在2016年廣州車展正式亮相。寶沃BX5定位緊湊型SUV,長寬高分別是4483×1876×1677mm,軸距2685mm,在同級別車型中,它是屬於規格相對比較大的類型。

    新車依舊採用寶沃家族式的多邊形格柵設計,后包圍的裝飾件帶有濃濃地運動氣息,寶沃這次將目標人群瞄準在年輕人。

    寶沃BX5將提供1.4T混動和1.8T汽油發動機版本。1.8T發動機最大功率190ps,峰值扭矩280N·m,搭配6速手自一體變速箱,如無意外將會是BX7上那副AISIN愛信6AT。預計寶沃還將推出BX7 TS和BX6 TS,進一步豐富產品線,實現真正的品牌復興。

    寶沃BX7 TS

    左為寶沃BX6 TS,右為寶沃BX5

    總結:很慶幸出生在這個時代,能見證着自主汽車的起步,在市場摸爬滾打,獨當一面再到後來往上發展,開拓高端品牌、突破自我,自豪感油然而生。但不少人卻對自主汽車嗤之以鼻,用他們有限的認知、先入為主地去詆毀它們,這是所不能接受的。

    自主汽車目前所經歷的發展階段是一種歷史的必然,哪怕是鍵盤車神們跪舔的德系、日系亦同樣經歷過,給自主汽車更多耐心和鼓勵,它一定會用更多低價格、高品質、效費比更理想的產品來回報國民,證明我大中華也是汽車強國。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

    ※推薦台中搬家公司優質服務,可到府估價

  • 7.89萬起的SUV配置竟然這麼高,教授看好他的銷量喔!

    7.89萬起的SUV配置竟然這麼高,教授看好他的銷量喔!

    新車落地時,可以在選配廠家幫你加裝的行車記錄儀,集合在駕駛艙的后視鏡裏面,通過中控台的按鍵,可以將畫面轉到這個9寸彩色大屏幕上,可以手動操作拍照和錄製功能,非常方便。除了最低配之外,其他配置都是真皮包裹座椅,高配則還有座椅加熱和電動調節功能,乘坐起來給人的感覺還可以,雖然不能說包裹性很強,但是舒適性還是夠的,至於後排空間,如果一個正常體型的180CM身高小伙子坐進去,大概腿部空間還能夠有近兩拳的距離。

    自從上次發完森雅R7的產品介紹后,後台很多粉絲都在追問這車新搭載的自動擋究竟開起來怎麼樣,愛信的6AT是否能夠做到眾望所歸?諸如此類問題。沒錯,這一期將詳細地往“體驗”這方面,來講一講這台車,除了價格很實惠之外,是否真的能夠為我們帶來些什麼收穫?

    森雅R7作為一汽的產品,在手動擋上市時,就已經有這3個亮點,一個是超高顏值,一個是同級中最長的軸距,最後一個則是越級的配置,定價便宜,非常符合三四線城市人們購車的需求,所以在當時就已經備受粉絲關注,然而這次自動擋6AT的到來,更為許多“不會開手動擋”的消費者解決了另一個難題。

    貌似在中國,有着這麼一個不成文的規定,但凡是新車,首先一定要在外觀上足夠吸引人,才能稱為成功了一半,眾泰走抄襲之路贏得了群眾的眼球,陸風靠着路虎的外觀檔次一下高了不少,而作為一汽旗下的森雅R7,則靠着與眾不同的雄鷹外觀,也贏得了許多人的關注,大燈犀利,整個車頭看起來讓人有種舒服的感覺,腰線優雅,作為一款小型SUV,整體風格小巧儒雅。

    全車前大燈都採用鹵素光源,但都帶有日間行車燈,視覺效果蠻不錯,17英寸鋁合金輪轂同樣為全系標配,加上紅色的剎車卡鉗,運動感一下子上來了,在之前,後台就有很多粉絲在評論說,車子顏值是不錯,不過可惜就可惜在車標上,但覺得現在自主品牌實力不斷地雄厚,只要車子質量好,車標改不改,都是事後的問題了,不必過於糾結。

    其實買車群眾可以分為兩種人,一種是堅持買自主品牌車,一種是只考慮合資車,但你瞧我們自主品牌的內飾風格,不說那些抄襲寶馬奧迪內飾的其他牌子,就拿森雅R7的內飾作為例子,7.89萬自動擋版本,能夠有這種設計還是少有的,按鍵實在,使用起來非常簡單,屏幕也夠大,觸控反應靈敏。

    除了自動舒適型之外,其他自動車型全配有真皮方向盤,ESp車身穩定系統,上坡輔助,定速巡航,多功能方向盤,發動機啟停裝置,頂配則多了全景攝像頭,雖然這些每次都會說,但有了就非常不同,買配置也是我們中國人選車所考慮的因素之一,凡是熱門的車,肯定有着一套非常齊整的配置,才能稱之為性價比高。

    新車落地時,可以在選配廠家幫你加裝的行車記錄儀,集合在駕駛艙的后視鏡裏面,通過中控台的按鍵,可以將畫面轉到這個9寸彩色大屏幕上,可以手動操作拍照和錄製功能,非常方便。

    除了最低配之外,其他配置都是真皮包裹座椅,高配則還有座椅加熱和電動調節功能,乘坐起來給人的感覺還可以,雖然不能說包裹性很強,但是舒適性還是夠的,至於後排空間,如果一個正常體型的180CM身高小伙子坐進去,大概腿部空間還能夠有近兩拳的距離。

    先說說這台1.6L的自然吸氣發動機,技術是基於大眾EA系列發動機自主研發而成的,具有進氣側的可變氣門正時技術,最大功率116匹馬力,峰值扭矩155牛米,在城市中跟車行駛的話,搭配着油門踏板,初段給人的加速感還是有的,反應积極,匹配着日本愛信第三代6速手自一體變速器,加速感覺還是比較平順。

    當繼續深踩油的時候,中後段的動力則有點力不從心了,但畢竟這是一台1.6L自然吸氣發動機,並沒有像其他發動機一樣有渦輪增壓器的介入,但1.6L自然吸氣+愛信成熟的6AT,可以很好地控制油耗表現。

    在平時的道路上,這款自動變速箱在升擋的節奏上,還是能夠與我們駕駛員做到節奏一致的,而在上坡的時候,變速箱則退到低速擋,同時將發動機轉速升到3000左右,將這股力氣供給前輪,動力不會很突兀,高轉數難免發動機噪音會有一些,這是難免的,但在平時的駕駛中,經過隔音棉,噪音得到了挺好的控制,不會讓人感覺到這款國產車一加速就立馬掉檔次。

    底盤懸架則採用前麥弗遜后扭力梁設計,這種設計無論是在這個價位,還是這個等級,都非常之常見,在行駛過程中,來自底盤的噪音並不大,減振器在濾振方面,確實挺到位,開起來確實還是有質感的,這種調校給人一種厚實的感覺。

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

    【其他文章推薦】

    ※回頭車貨運收費標準

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

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

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

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • Action的三種實現方式,struts.xml配置的詳細解釋及其簡單執行過程(二)

    Action的三種實現方式,struts.xml配置的詳細解釋及其簡單執行過程(二)

    勿以惡小而為之,勿以善小而不為————————–劉備

    勸諸君,多行善事積福報,莫作惡

    上一章簡單介紹了Struts2的’兩個蝴蝶飛,你好’ (一),如果沒有看過,請觀看上一章

    一 Action的三種實現方式

    上一章開發的HelloAction和HelloAction2,並沒有繼承任何類或者實現任何接口,但是必須有一個execute() 方法,方法返回值是String類型。

    這樣的代碼不容易理解,更並不能使人看得出這個類是干什麼的,甚至不能區分這個控制器類與普通的Java類有什麼區別,通常開發中不這樣做。

    我們開發者在開發Struts2框架的時候,希望自己寫的這個Action類能夠具有易理解性,且已經支持某些功能,如參數接收,文件上傳等。

    一.一 第一種實現方式(普通Java類,裏面只包含execute()方法)

    package com.yjl.web.action;
    import org.apache.log4j.Logger;
    /**
    * @author 兩個蝴蝶飛
    * @version 創建時間:2018年8月23日 上午9:41:32
    * @description 第一種實現方式,普通java類,
    * 有一個execute()方法,也可以多寫幾個方法,用action中的標籤method來控制,可以正常訪問。
    */
    public class Hello1Action {
    	private static Logger logger=Logger.getLogger(Hello1Action.class);
    	public String execute() {
    		logger.info("兩個蝴蝶飛,web層你好");
    		return "success";
    	}
    }
    

    不具有開發時要求的規範性,且不支持某些struts2自身提供的功能。

    方法名稱只有一個 execute()

    一.二 第二種實現方式(實現Action接口)

    package com.yjl.web.action;
    import com.opensymphony.xwork2.Action;
    /**
    * @author 兩個蝴蝶飛
    * @version 創建時間:2018年8月23日 上午10:54:03
    * @description 第二種實現方式,實現Action接口,重寫裏面的execute()方法
    * 有一個execute()方法和五個String類型的常量
    */
    public class Hello2Action implements Action{
    	@Override
    	public String execute() throws Exception {
    		return Action.SUCCESS;
    		//return Action.ERROR;
    		//return Action.LOGIN;
    		//return Action.NONE;
    		//return Action.INPUT;
    	}
    }
    

    注意,Action接口是xwork2包下的接口。

    實現了Action接口,使開發者能夠看出來這是一個Action,具有了一定程度上的開發規範,

    但是實現了Action接口,所以必須要重寫execute()方法。

    一般自己寫Action,構思好之後上來就直接add(), edit(), delete(). select() 這些業務方法,

    每次都要重寫execute()方法,不太方便。 而且這種方式不具有struts2中某些功能,如驗證框架和國際化。

    Action中接口中有五個常用的結果字符串(好多方法都返回success,error,login,input,none,故將其封裝了一下) .

    這些字符串雖然是大寫,然而真實的值是全部小寫.

    package com.opensymphony.xwork2;
    
    public abstract interface Action
    {
      public static final String SUCCESS = "success";
      public static final String NONE = "none";
      public static final String ERROR = "error";
      public static final String INPUT = "input";
      public static final String LOGIN = "login";
      
      public abstract String execute()
        throws Exception;
    }
    

    一.三 繼承ActionSupport類(官方推薦)

    	package com.yjl.web.action;
    	import com.opensymphony.xwork2.ActionSupport;
    	/**
    	* @author 兩個蝴蝶飛
    	* @version 創建時間:2018年8月23日 上午11:04:20
    	* @description 第三種方式,繼承ActionSupport類。
    	* ActionSupport類實現了Action接口,也有Action中的五個常量.
    	*/
    	public class Hello3Action extends ActionSupport{
    		public String list() {
    			return "list";
    		}
    	}
    
    

    繼承了ActionSupport類,不需要重新寫execute()方法,直接寫業務方法即可。

    ActionSupport類,已經實現了 Action接口。 其具備Action中的五個常量,並且該類還實現了其他接口,

    源代碼:

    	public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable{
        ...
    	public String execute() throws Exception
      	{
    		//默認返回的是 success 字符串 
      		 return "success";
     	}
     	...
    }
    

    如驗證框架(Validateable,ValidationAware),國際化(LocaleProvider)。

    以後開發中,使用 繼承 ActionSupport 類的形式。

    二 配置文件 struts.xml中節點的詳細解釋

    在src下有一個struts.xml的配置文件,它配置了開發者自己編寫實現的Action,是struts2框架的核心,不能改變文件名稱。(注意,是struts.xml,並不是struts2.xml,並沒有那個2)。

    在struts.xml中,最上面是一個約束, 是一個根節點。

    二.一 修改常量節點

    在struts-core.jar核心包下,有一個包org.apache.struts2包下,有一個default.properties屬性文件,裏面記錄了很多常用的常量,

    其中常見的有:

    struts.i18n.encoding=UTF-8 
    struts.multipart.maxSize=2097152
    struts.action.extension=action,,
    struts.enable.DynamicMethodInvocation = false
    struts.devMode = false
    struts.ui.theme=xhtml
    struts.ognl.allowStaticMethodAccess=false
    

    建議修改后的值為:

    ###國際化操作,編碼格式為UTF-8
    struts.i18n.encoding=UTF-8
    ###上傳文件時最大的上傳大小,默認為2M. 根據項目情況具體填寫值,建議後面加兩個00
    struts.multipart.maxSize=209715200
    ###struts的訪問後綴名, struts1框架默認的是 .do 
    struts.action.extension=action,,
    ###struts是否可以訪問靜態方法
    struts.enable.DynamicMethodInvocation =true
    ###struts是否是開發者模式
    struts.devMode =true
    ###struts中ui標籤的主題,建議為simple
    struts.ui.theme=simple
    ###ognl中是否可以訪問靜態方法,為true
    struts.ognl.allowStaticMethodAccess=true
    

    可以在struts.xml中進行相應的修改,如

     <!--修改國際化編碼 -->
    <constant name="struts.i18n.encoding" value="UTF-8"></constant>
    <!--修改是否為開發者模式 -->
    <constant name="struts.devMode" value="true"></constant>
    

    按照name,value值的形式進行填寫。

    也可以在src下新建一個struts.properties,然後將這些值放置進去,struts也會自動struts.propeties中的常量值的。

    也可以在web.xml中,在 中,以 局部參數的形式傳遞進去。

    建議使用第一種方式,在struts.xml中用 ,畢竟這個文件常常打開,出錯了也容易發現。

    二.二 分模塊開發

    在實際的項目中,有很多的模塊,如果所有的配置都放在一個struts.xml,那麼一旦這個struts.xml被其他人誤操作導致了錯誤,那麼其他人的項目將無法運行的,當配置內容過多時,struts.xml的內容太長,不便於維護,所以最好是分模塊開發,一個模塊用一個配置文件,然後再利用 進行導入, 類似 於jsp中的 靜態包含一樣。

    所以建議每一個模塊都寫一個模塊.xml,然後在struts.xml中引入即可。如有三個模塊 User模塊和Class,Course,那麼可以將User的配置放置在user.xml中,Class配置放置在class.xml中,course模塊放置在course.xml,在struts.xml中只需要

    	<include file="user.xml"></include>
    	<include file="class.xml"></include>
    	<include file="course.xml"></include>
    

    靜態包含即可。 注意,file的文件路徑引用是否輸入正確。

    正確的位置引用,點擊ctrl+模塊.xml時,可以跳轉到相應的.xml文件中。如果沒有跳轉和反應,那說明位置引用錯誤,需要重新檢查一下。

    二.三 包節點

    在struts.xml配置文件中,最重要的節點就是package節點。 package,分包。 可以將action進行分包處理。

    這樣每一個action或者每一組action用package進行隔開,便於維護,類似於java中package的概念。

    二.三.一 <package> 節點的使用

    <package name="hello" extends="struts-default" namespace="/">
            <!--具體的Action-->
    </package>
    

    package中name節點是package的名字,是獨一無二的,不能夠重複。 最好與模塊名相同或者起一個有意義的名稱。

    extends節點表示繼承,即package之間可以相互的繼承,來避免重複化功能的編寫。 默認為struts-default。

    struts-default中struts已經定義了很多功能,開發者自己寫的包只需要extends 這個包名struts-default,

    就擁有了struts已經定義好的功能。 如攔截器功能,文件上傳功能。

    用戶也可以自己繼承自己所寫的包 。如父包名為

    那麼子包只需要 , 這樣child包不但擁有struts-default的功能,也擁有parent包中的特殊功能,這也是Java的多重繼承的體現。 所以package的name 要符合標識符的規範,具有可讀性。

    namespace節點表示命名空間,以/開頭,默認是”/” 。是為了在訪問路徑和訪問請求url方面體現package的分包作用. package中的name是在配置文件中體現分包,namespace是在url中體現分包。 建議開發中,namespace的路徑名與name保持一致。 package中的namespace的值與子節點action中name的值,共同構成了完整的訪問請求路徑。

    二.三.二 <package></package> 子節點<action></action>節點的使用

    在Hello3Action中定義兩個方法,一個是list()查詢,一個是add()添加的方法。

    package com.yjl.web.action;
    import org.apache.log4j.Logger;
    import com.opensymphony.xwork2.ActionSupport;
    /**
    * @author 兩個蝴蝶飛
    * @version 創建時間:2018年8月23日 上午11:04:20
    * @description 測試action標籤中method的方法訪問
    */
    public class Hello3Action extends ActionSupport{
    	private static final long serialVersionUID = 8737138848863458260L;
    	Logger logger=Logger.getLogger(Hello3Action.class);
    	public String list() {
    		logger.info("執行list方法");
    		return "list";
    	}
    	public String add() {
    		logger.info("執行add方法");
    		return "add";
    	}
    }
    
    

    標籤,有三個基本的屬性,

    	<action name="list" class="com.yjl.web.action.Hello3Action"
            method="list">
    
    </action>
    

    其中name為action的名字,表示區別一個package包下的不同的action。 其中這個name的值,不應該隨便取,應該是要訪問的方法名。

    在瀏覽器客戶端請求的url為 /項目名/package的namespace名稱/action的name名稱.action;

    class為要訪問的那個Action的全限定名稱,是class,用.(點)進行分隔。

    其中,class 可以省略, 省略默認為 ActionSupport 類, 全限定名稱為: com.opensymphony.xwork2.ActionSupport
    method為要訪問的那個方法名稱,類 extends ActionSupport 后,有很多很多的方法,如list(), add(), delete()等,那麼怎麼知道具體要訪問哪個方法呢? 用method這個屬性. method=”要方法的方法名” ,是方法名。

    action還有一個節點是converter,表示所用的是哪一個類型轉換器。(後面會有相應的解釋)

    很清楚, action 中的 class指定了訪問的是哪一個action, method 指定了訪問的是哪一個具體的方法, 利用了反射技術實現。

    在本實例了有兩個方法,所以要進行寫兩個Action, 一個Action類中會有多個方法,難道要一個個配置多個Action嗎?

    Struts2提供了一些簡單的方式

    二.三.三 配置Action的三種形式

    二.三.三.一 通過配置method的屬性完成

    簡單舉例如下:

    	<action name="list" class="com.yjl.web.action.Hello3Action"
    		method="list">
    			
    	</action>
      <action name="add" class="com.yjl.web.action.Hello3Action"
    		method="add">
                
        </action>
    

    缺點: 有幾個方法,就要配置有幾個action,當方法過多時,不易維護。

    二.三.三.二 通過配置 通配符完成。

    簡單舉例如下:

    	<action name="Hello3_*" class="com.yjl.web.action.Hello3Action"
    		method="{1}">
    			
    		</action>
    

    name的值為: 類簡寫名(去掉Action后)_* method中的值取第一個{1},從1開始,不是從0開始。

    這樣訪問Hello3Action中的list方法,訪問路徑就是 Hello3_list

    訪問Hello3Action中的add方法,訪問路徑就是Hello3_add

    簡化了action的相關配置。

    也有的人配置的更狠, 會配置成_, 即:

    	<action name="*_*" class="com.yjl.web.action.{1}Action"
    		method="{2}">
    			
    		</action>
    

    User類中的list就是User_list, User類中的add就是User_add,

    Class類中的list就是Class_list,Class類中的add就是Class_add

    這樣雖說簡化了開發,但卻不利用 result 節點的維護 ,不建議這樣配置。

    好多類的好多方法返回值,都寫在這一個action 下面,會亂。

    二.三.三.三 動態方法訪問

    不是用 * 通配符,而是用! 號。 即:

    想訪問UserAction中list方法() 前端寫url為 userAction!list.action
    想訪問UserAction中add方法() 前端寫url為 userAction!add.action
    想訪問ClassAction中list方法() 前端寫url為 classAction!list.action
    想訪問ClassAction中add方法() 前端寫url為 classAction!add.action

    這樣訪問也特別的方便。

    這樣的話, action中只需要配置name和class即可。 method已經由外部指定了,不需要寫method的值了。

    需要先添加變量 struts.enable.DynamicMethodInvocation, 使其變成 true,開啟。

    	<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant>
    

    如果是UserAction的話,配置應該是:

    <action name="userAction" class="com.yjl.web.action.UserAction" >
    			
    </action>
    

    ClassAction的話,配置應該是

    <action name="classAction" class="com.yjl.web.action.ClassAction" >
    			
    </action>
    

    二.三.四 action子節點result的配置

    result表示結果,是對方法的返回值進行相應的分析。有兩個屬性,name和type

    	<result name="success" type="dispatcher">/index.jsp</result>
    

    其中name的值要與方法的返回值保持一致。

    如 list方法返回值是return SUCCESS,那麼這個list方法的返回值對應的result的值就是 ,

    如果返回是”hello”, 那麼這個name的返回值就是

    如果在action中配置通配符, name=Hello3_*形式,method=”{1}”, 那麼為了簡化result的配置,可以將result配置成 name={1},

    相應的.jsp,可以變成 /{1}.jsp。

    但這樣必須保證Action中方法的名稱與返回值的名稱相同,並且與跳轉到的jsp的名稱也要相同, 這樣不太好。

    result中type五種常見的形式, dispatcher(轉發到jsp),redirect(重定向到jsp), chain(轉發到另外一個方法),redirectAction(重定向到另外一個方法),stream(上傳和下載流)

    其中dispathcer和redirect是跳轉到jsp,如果想要傳遞數據,用dispather,

    如果不想傳遞數據,用redirect (dispathcer是轉發,redirect是重定向)

    chain,redirectAction是跳轉到action的操作,一般用於這同一個類中兩個方法之間的跳轉,

    如add()添加成功之後,需要跳轉到list()方法進行显示結果,這時就可以配置成:

    	<result name="add" type="redirectAction">Hello3_list</result>
    

    地址url也會相應的改變,如果是chain的話,地址欄是不會改變的。 chain是轉發到action, redirectAction是重定向到action.

    也可以在不同包之間的action進行的跳轉 。

    如 add 方法 想到跳轉到 /class 命名空間下的 Hello2Action 的 list 方法。

    <result name="add" type="redirectAction">
    	<!-- 要跳轉到哪一個命名空間,即哪一個包 -->
    	<param name="namespace">/class</param>
    	<!-- 要跳轉到哪一個Action 不加後綴 -->
    	<param name="actionName">Hello2Action</param>
    	<!-- 跳轉到哪一個方法 -->
    	<param name="method">list</param>
    	<!-- 可能要傳遞的參數. 用ognl表達式,根據情況添加 -->
    	<param name="id">${id}</param>
    </result>
    

    通過 param 標籤來配置帶參還是不帶參。

    二.四 全局結果頁面與局部結果頁面。

    這個全局是相對於package來說的,是package中的全局,並不是所有的struts.xml中的全局,所以全局結果的節點位置應該放在package節點裏面,與action節點平行。 用 節點。

    常用的全局結果頁面有兩種:

    error錯誤頁面,頁面出錯了都显示這個頁面,

    login 登錄頁面, 如果沒有登錄,輸入任何url都會跳轉到login頁面(認證時用)

    noprivilege 沒有權限頁面,如果用戶沒有權限訪問了某一個頁面,會給出相應的提示(授權時用)

    <global-results>
    			<result name="error">/error/error.jsp</result>
    			<result name="login">/login.jsp</result>
                <result name="noprivilege">/noprivilege.jsp</result>
    </global-results>
    

    當全局結果頁面與局部結果頁面發生衝突時,以局部結果頁面為準。

    全局配置時:

    <global-results>
    			<result name="success">/successGlobal.jsp</result>
    </global-results>
    

    在該包下的某個action 的方法result 也返回了 success

    	<result name='success'>success.jsp</result>
    

    那麼,當邏輯視圖為 success時,最終將返回 success.jsp

    二.五 配置跳轉頁面

    在開發中,常常有這麼一種情況,

    請求login.jsp 時,為 /login, 那麼就跳轉到 login.jsp 頁面,

    語法為 register.jsp 時,為 /register, 那麼就跳轉到 register 頁面。

    這個時候,配置 為:

    	<action name="*">
    			<result>/WEB-INF/content/{1}.jsp</result>
    	</action>
    

    將頁面放置在 content 文件夾下面,避免用戶直接訪問 jsp頁面。

    注意,要將此 action 放置在最後, 當所有上面的action都不匹配時,才匹配這一個action.

    三 Struts2的執行流程

    當用戶在客戶端發送一個請求后,如常用的標準的http://localhost:8080/Struts_Hello/user/User_add.action時,

    會經過前端控制器(StrutsPrepareAndExecuteFilter) 過濾器,執行一連串的過濾器鏈,然後根據user 找到了對應的package的namespape,進入到具體的package包下。 利用通配符的方式進行訪問,User_add會進行匹配相應的action,根據class和method找到是哪一個類的哪一個方法,在實例化類Action之前,會先執行攔截器。通過反射實例化類,運行方法, 方法運行成功之後,有一個返回值,這個返回值會與剛才action下的 中的name進行相應的匹配,匹配到哪一個,就執行哪一個result。 如果是diapatcher或者redirect,就显示到相應的.jsp頁面(帶有數據), 如果是chain或者redirectAction,那麼就去執行那一個方法,之後進行返回具體的視圖。

    執行過程圖如下:

    謝謝您的觀看!!!

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

  • 程序員實用JDK小工具歸納,工作用得到

    程序員實用JDK小工具歸納,工作用得到

    在JDK的安用裝目錄bin下,有一些有非常實用的小工具,可用於分析JVM初始配置、內存溢出異常等問題,我們接下來將對些常用的工具進行一些說明。

    JDK小工具簡介

    在JDK的bin目錄下面有一些小工具,如javac,jar,jstack,jstat等,在日常編譯運行過程中有着不少的“額外”功能,那麼它們是怎麼工作的呢?雖然這些文件本身已經被編譯成可執行二進制文件了,但是其實它們的功能都是由tools.jar這個工具包(配合一些dll或者so本地庫)完成的,每個可執行文件都對應一個包含main函數入口的java類(有興趣可以閱讀openJDK相關的源碼,它們的對應關係如下(更多可去openJDK查閱):

    javac com.sun.tools.javac.Main
    jar sun.tools.jar.Main
    jps sun.tools.jps.Jps
    jstat sun.tools.jstat.Jstat
    jstack    sun.tools.jstack.JStack
    ...

    tools.jar的使用

    我們一般開發機器上都會安裝JDK+jre,這時候,要用這些工具,直接運行二進制可執行文件就行了,但是有時候,機器上只有jre而沒有JDK,我們就無法用了么?

    如果你知道如上的對應關係的話,我們就可以”構造”出這些工具來(當然也可以把JDK安裝一遍,本篇只是介紹另一種選擇),比如我們編寫

    //Hello.java
    public class Hello{
        public static void main(String[] args)throws Exception{
            while(true){
                test1();
                Thread.sleep(1000L);
            }
        }
        public static void test1(){
            test2();
        }
        public static void test2(){
            System.out.println("invoke test2");
        }
    }

    可以驗證如下功能轉換關係

    1.編譯源文件:

    javac Hello.java => java -cp tools.jar com.sun.tools.javac.Main Hello.java

    結果一樣,都可以生成Hello.class文件
    然後我們開始運行java -cp . Hello

    2.查看java進程:

    jps => java -cp tools.jar sun.tools.jps.Jps

    結果一樣,如下:

    4615 Jps
    11048 jar
    3003 Hello

    3.動態查看內存:

    jstat -gcutil 3003 100 3 => java -cp tools.jar sun.tools.jstat.Jstat -gcutil 3003 100 3

    發現結果是一樣的

      S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
      0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000
      0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000
      0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000

    4.查看當前運行棧信息
    正常情況,執行如下命令結果也是一樣,可以正常輸出

    jstack 3003 =》 java -cp tools.jar sun.tools.jstack.JStack 3003

    但是有的jre安裝不正常的時候,會報如下錯誤

    Exception in thread "main" java.lang.UnsatisfiedLinkError: no attach in java.library.path

    這是因為jstack的運行需要attach本地庫的支持,我們需要在系統變量裏面配置上其路徑,假如路徑為/home/JDK/jre/bin/libattach.so
    命令轉換成

    jstack 3003 =》 java -Djava.library.path=/home/JDK/jre/bin -cp tools.jar sun.tools.jstack.JStack 3003

    就可以實現了
    在linux系統中是libattach.so,而在windows系統中是attach.dll,它提供了一個與本機jvm通信的能力,利用它可以與本地的jvm進行通信,許多java小工具就可能通過它來獲取jvm運行時狀態,也可以對jvm執行一些操作

    attach使用

    1. 編寫agent.jar代理包

    • 編寫一個Agent類
    //Agent.java
    public class Agent{
        public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
            System.out.println("agent : " + args);
        }
    }
    • 編譯Agent
    java -cp tools.jar com.sun.tools.javac.Main Agent.java
    //或者
    javac Agent.java
    • 再編manifest.mf文件
    //manifest.mf
    Manifest-Version: 1.0
    Agent-Class: Agent
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true
    • 把Agent.class和manifest.mf進行打包成agent.jar
    java -cp tools.jar sun.tools.jar.Main -cmf manifest.mf agent.jar Agent.class
    //或者
    jar -cmf manifest.mf agent.jar Agent.class

    2.attach進程

    • 編寫如下attach類,編譯並執行
    //AttachMain.java
    public class AttachMain {
        public static void main(String[] args) throws Exception {
            com.sun.tools.attach.VirtualMachine vm = com.sun.tools.attach.VirtualMachine.attach(args[0]);
            vm.loadAgent("agent.jar", "inject params");
            vm.detach();
        }
    }
    • 編譯:
    java -cp tools.jar com.sun.tools.javac.Main -cp tools.jar AttachMain.java
    //或者
    javac -cp tools.jar AttachMain.java
    • 執行attach
    java -cp .:tools.jar AttachMain 3003
    • 查看Hello進程有如下輸出:
    invoke test2
    invoke test2
    invoke test2
    invoke test2
    invoke test2
    invoke test2
    invoke test2
    agent : inject params
    invoke test2

    說明attach成功了,而且在目標java進程中引入了agent.jar這個包,並且在其中一個線程中執行了manifest文件中agentmain類的agentmain方法,詳細原理可以見JVMTI的介紹,例如oracle的介紹

    3. 用attach製作小工具

    • 寫一個使進程OutOfMemory/StackOverFlow的工具
      有了attach的方便使用,我們可以在agentmain中新起動一個線程(為避免把attach線程污染掉),在裏面無限分配內存但不回收,就可以產生OOM或者stackoverflow
      代碼如下:
    //Agent.java for OOM
    public class Agent{
        public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
            new Thread() {
                @Override
                public void run() {
                    java.util.List<byte[]> list = new java.util.ArrayList<byte[]>();
                    try {
                        while(true) {
                            list.add(new byte[100*1024*1024]);
                            Thread.sleep(100L);
                        }
                    } catch (InterruptedException e) {
                    }
                }
            }.start();
        }
    }
    //Agent.java for stackoverflow
    public class Agent{
        public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
            new Thread() {
                @Override
                public void run() {
                    stackOver();
                }
                private void stackOver(){
                    stackOver();
                }
            }.start();
        }
    }

    當測試OOM的時候,hello進程的輸出為:

    invoke test2
    Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
            at Agent$1.run(Agent.java:9)
    invoke test2
    invoke test2
    invoke test2

    說明發生OOM了, 但是OOM線程退出了,其它線程還在正常運行。

    如果我們需要進程在OOM的時候產生一些動作,我們可以在進程啟動的時候增加一些OOM相關的VM參數

    • OOM的時候直接kill掉進程:-XX:OnOutOfMemoryError=”kill -9 %p”
      結果如下:
    invoke test2
    invoke test2
    #
    # java.lang.OutOfMemoryError: Java heap space
    # -XX:OnOutOfMemoryError="kill -9 %p"
    #   Executing /bin/sh -c "kill -9 26829"...
    Killed
    • OOM的時候直接退出進程:-XX:+ExitOnOutOfMemoryError
      結果如下:
    invoke test2
    invoke test2
    Terminating due to java.lang.OutOfMemoryError: Java heap space
    • OOM的時候進程crash掉:-XX:+CrashOnOutOfMemoryError
      結果如下:
    invoke test2
    invoke test2
    Aborting due to java.lang.OutOfMemoryError: Java heap space
    invoke test2#
    # A fatal error has been detected by the Java Runtime Environment:
    #
    #  Internal Error (debug.cpp:308)
    , pid=42675, tid=0x00007f3710bf4700
    #  fatal error: OutOfMemory encountered: Java heap space
    #
    # JRE version: Java(TM) SE Runtime Environment (8.0_171-b11) (build 1.8.0_171-b11)
    # Java VM: Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode linux-amd64 compressed oops)
    # Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
    #
    # An error report file with more information is saved as:
    # /root/hanlang/test/hs_err_pid42675.log
    #
    # If you would like to submit a bug report, please visit:
    #   http://bugreport.java.com/bugreport/crash.jsp
    #
    Aborted
    • OOM的時候dump內存:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof
      結果生成dump文件

    asm的應用

    1.asm使用原理

    asm是一個java字節碼工具,提供一種方便的函數/屬性級別修改已經編譯好的.class文件的方法, asm的簡單使用原理介紹如下:

    • 通過ClassReader讀取.class文件的字節碼內容,並生成語法樹;
    • ClassReader的方法accept(ClassVisitor classVisitor, int parsingOptions)功能是讓classVisitor遍歷語法樹,默認ClassVisitor是一個代理類,需要有一個具體的實現在遍歷語法樹的時候做一些處理;
    • 用ClassWriter是ClassVisitor的一個實現,它的功能是把語法樹轉換成字節碼;
    • 通常我們會定義一個自己的ClassVisitor,可以重寫裏面的一些方法來改寫類處理邏輯,然後讓ClassWriter把處理之後的語法樹轉換成字節碼;

    2.下面是具體的實現步驟:

    • 引入asm依賴包
    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm</artifactId>
        <version>7.0</version>
    </dependency>
    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm-commons</artifactId>
        <version>7.0</version>
    </dependency>
    //或者引入如下包
    asm-commons-7.0.jar
    asm-analysis-7.0.jar
    asm-tree-7.0.jar
    asm-7.0.jar
    • 定義一個ClassVisitor,功能是在所有方法調用前和調用後分別通過System.out.println打印一些信息
      輸入為字節碼,輸出也是字節碼
    //MyClassVisitor.java
    public class MyClassVisitor extends ClassVisitor {
        private static final Type SYSTEM;
        private static final Type OUT;
        private static final Method PRINTLN;
        static {
            java.lang.reflect.Method m = null;
            try {
                m = PrintStream.class.getMethod("println", new Class<?>[] {String.class});
            } catch (Exception e) {
            }
            SYSTEM = Type.getType(System.class);
            OUT = Type.getType(PrintStream.class);
            PRINTLN = Method.getMethod(m);
        }
    
        private String cName;
    
        public MyClassVisitor(byte[] bytes) {
            super(Opcodes.ASM7, new ClassWriter(ClassWriter.COMPUTE_FRAMES));
            new ClassReader(bytes).accept(this, ClassReader.EXPAND_FRAMES);
        }
        String format(String name) {
            return name.replaceAll("<", "_").replaceAll("\\$|>", "");
        }
        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            cName = format(name);
            super.visit(version, access, name, signature, superName, interfaces);
        }
    
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            if ((access & 256) != 0) {
                return super.visitMethod(access, name, desc, signature, exceptions);
            }
            return new MyMethodAdapter(super.visitMethod(access, name, desc, signature, exceptions), access, name, desc);
        }
    
        public byte[] getBytes() {
            return ((ClassWriter) cv).toByteArray();
        }
    
        class MyMethodAdapter extends AdviceAdapter {
            private String mName;
    
            public MyMethodAdapter(MethodVisitor methodVisitor, int acc, String name, String desc) {
                super(Opcodes.ASM7, methodVisitor, acc, name, desc);
                this.mName = format(name);
            }
    
            @Override
            protected void onMethodEnter() {
                getStatic(SYSTEM, "out", OUT);
                push(cName + "." + mName + " start");
                this.invokeVirtual(OUT, PRINTLN);
            }
    
            @Override
            protected void onMethodExit(int opcode) {
                getStatic(SYSTEM, "out", OUT);
                push(cName + "." + mName + " end");
                this.invokeVirtual(OUT, PRINTLN);
            }
        }
    }
    • 定義一個簡單的classLoader來加載轉換后的字節碼
    //MyLoader.java
    class MyLoader extends ClassLoader {
        private String cname;
        private byte[] bytes;
        public MyLoader(String cname, byte[] bytes) {
            this.cname = cname;
            this.bytes = bytes;
        }
    
        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            Class<?> clazz = null;
            if (clazz == null && cname.equals(name)) {
                try {
                    clazz = findClass(name);
                } catch (ClassNotFoundException e) {
                }
            }
            if (clazz == null) {
                clazz = super.loadClass(name, resolve);
            }
            return clazz;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            Class<?> clazz = this.findLoadedClass(name);
            if (clazz == null) {
                clazz = defineClass(name, bytes, 0, bytes.length);
            }
            return clazz;
        }
    }
    • 加載轉換Hello類,然後反向調用其方法

    //將如下main函數加入MyClassVisitor.java中

    public static void main(String[] args) throws Exception {
        try (InputStream in = Hello.class.getResourceAsStream("Hello.class")) {
            byte[] bytes = new byte[in.available()];
            in.read(bytes);
            String cname = Hello.class.getName();
            Class<?> clazz = new MyLoader(cname, new MyClassVisitor(bytes).getBytes()).loadClass(cname);
            clazz.getMethod("test1").invoke(null);
        }
    }
    • 編譯
    java -cp tools.jar com.sun.tools.javac.Main -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
    //或者
    javac -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
    • 運行
    java -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. MyClassVisitor
    //結果如下:
    Hello.test1 start
    Hello.test2 start
    invoke test2
    Hello.test2 end
    Hello.test1 end

    asm的使用很廣泛,最常用的是在spring aop裏面切面的功能就是通過asm來完成的

    3. 利用asm與Instrument製作調試工具

    • Instrument工具

    Instrument類有如下方法,可以增加一個類轉換器

    addTransformer(ClassFileTransformer transformer, boolean canRetransform)

    執行如下方法的時候,對應的類將會被重新定義

    retransformClasses(Class<?>... classes)
    • 與asm配合使用
      當我們修改Agent.java代碼為下面內容
    //Agent
    public class Agent {
        public static void agentmain(String args, Instrumentation inst) {
            try {
                URLClassLoader loader = (URLClassLoader)Agent.class.getClassLoader();
                Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
                method.setAccessible(true);//代碼級引入依賴包
                method.invoke(loader, new File("asm-7.0.jar").toURI().toURL());
                method.invoke(loader, new File("asm-analysis-7.0.jar").toURI().toURL());
                method.invoke(loader, new File("asm-tree-7.0.jar").toURI().toURL());
                method.invoke(loader, new File("asm-commons-7.0.jar").toURI().toURL());
                inst.addTransformer(new ClassFileTransformer() {
                    @Override
                    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                        ProtectionDomain protectionDomain, byte[] bytes) {
                        return new MyClassVisitor(bytes).getBytes();
                    }
                }, true);
                inst.retransformClasses(Class.forName("Hello"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    • 編譯並打包成agent.jar
    //編譯
    javac -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
    //打包
    jar -cmf manifest.mf agent.jar MyLoader.class MyClassVisitor.class MyClassVisitor\$MyMethodAdapter.class Agent.class Agent\$1.class
    • attach進程修改字節碼
    //執行
    java -cp .:tools.jar AttachMain 3003
    //執行前後Hello進程的輸出變化為
    invoke test2
    invoke test2
    invoke test2
    Hello.test1 start
    Hello.test2 start
    invoke test2
    Hello.test2 end
    Hello.test1 end
    Hello.test1 start
    Hello.test2 start
    invoke test2
    Hello.test2 end
    Hello.test1 end

    利用asm及instrument工具來實現熱修改字節碼現在有許多成熟的工具,如btrace(https://github.com/btraceio/btrace,jvm-sandbox https://github.com/alibaba/jvm-sandbox)

     

    點擊關注,第一時間了解華為雲新鮮技術~

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

    【其他文章推薦】

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

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

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

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

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

    網頁設計最專業,超強功能平台可客製化

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

  • 除了FastJson,你也應該了解一下Jackson(二)

    除了FastJson,你也應該了解一下Jackson(二)

    概覽

    上一篇文章介紹了Jackson中的映射器ObjectMapper,以及如何使用它來實現Json與Java對象之間的序列化和反序列化,最後介紹了Jackson中一些序列化/反序列化的高級特性。而本文將會介紹Jackson中的一些常用的(序列化/反序列化)註解,並且通過示例來演示如何使用這些註解,從而來提高我們在處理Json上的工作效率。

    序列化註解

    @JsonAnyGetter

    @JsonAnyGetter註解允許靈活地使用映射(鍵值對,如Map)字段作為標準屬性。

    我們聲明如下Java類:

    @Data
    @Accessors(chain = true)
    public static class ExtendableBean {
        public String name;
        private Map<String, String> properties;
    
        @JsonAnyGetter
        public Map<String, String> getProperties() {
            return properties;
        }
    }
    

    編寫測試代碼,測試@JsonAnyGetter:

    @Test
    public void testJsonAnyGetter() throws JsonProcessingException {
        ExtendableBean extendableBean = new ExtendableBean();
        Map<String, String> map = new HashMap<>();
        map.put("age", "13");
        extendableBean.setName("dxsn").setProperties(map);
        log.info(new ObjectMapper().writeValueAsString(extendableBean));
      	//打印:{"name":"dxsn","age":"13"}
        assertThat(new ObjectMapper().writeValueAsString(extendableBean)).contains("name");
        assertThat(new ObjectMapper().writeValueAsString(extendableBean)).contains("age");
    }
    

    如上,可以看properties屬性中的鍵值對(Map)被擴展到了ExtendableBean的Json對象中。

    @JsonGetter

    @JsonGetter註解是@JsonProperty註解的替代品,用來將一個方法標記為getter方法。

    我們創建以下Java類

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class MyBean {
        public int id;
        private String name;
    
        @JsonGetter("name")
        public String getTheName() {
            return name;
        }
    }
    

    如上,我們在類中聲明了一個getTheName()方法,並且使用@JsonGetter(“name”)修飾,此時,該方法將會被Jackson認作是name屬性的get方法。

    編寫測試代碼:

    @Test
    public void testJsonGetter() throws JsonProcessingException {
        MyBean myBean = new MyBean(1, "dxsn");
        String jsonStr = new ObjectMapper().writeValueAsString(myBean);
        log.info(jsonStr);
        assertThat(jsonStr).contains("id");
        assertThat(jsonStr).contains("name");
    }
    

    可以看到,jackson將私有屬性name,也進行了序列化。

    @JsonPropertyOrder

    我們可以使用@JsonPropertyOrder註解來指定Java對象的屬性序列化順序。

    @JsonPropertyOrder({"name", "id"})
    //order by key's name
    //@JsonPropertyOrder(alphabetic = true)
    @Data
    @Accessors(chain = true)
    public static class MyOrderBean {
      public int id;
      public String name;
    }
    

    編寫測試代碼:

    @Test
    public void testJsonPropertyOrder1() throws JsonProcessingException {
        MyOrderBean myOrderBean = new MyOrderBean().setId(1).setName("dxsn");
        String jsonStr = new ObjectMapper().writeValueAsString(myOrderBean);
        log.info(jsonStr);
        assertThat(jsonStr).isEqualTo("{\"name\":\"dxsn\",\"id\":1}");
    }
    

    如上,可以看到序列化得到的Json對象中屬性的排列順序正是我們在註解中指定的順序。

    @JsonRawValue

    @JsonRawValue註解可以指示Jackson按原樣序列化屬性。

    在下面的例子中,我們使用@JsonRawValue嵌入一些定製的JSON作為一個實體的值:

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class RawBean {
        public String name;
        @JsonRawValue
        public String json;
    }
    

    編寫測試代碼:

    @Test
    public void testJsonRawValue() throws JsonProcessingException {
        RawBean rawBean = new RawBean("dxsn", "{\"love\":\"true\"}");
        log.info(new ObjectMapper().writeValueAsString(rawBean));
      	//輸出:{"name":"dxsn","json":{"love":"true"}}
        String result = new ObjectMapper().writeValueAsString(rawBean);
        assertThat(result).contains("dxsn");
        assertThat(result).contains("{\"love\":\"true\"}");
    }
    

    @JsonValue

    @JsonValue表示Jackson將使用一個方法來序列化整個實例。

    下面我們創建一個枚舉類:

    @AllArgsConstructor
    public static enum TypeEnumWithValue {
        TYPE1(1, "Type A"), TYPE2(2, "Type 2");
        private Integer id;
        private String name;
    
        @JsonValue
        public String getName() {
            return name;
        }
    }
    

    如上,我們在getName()上使用@JsonValue進行修飾。

    編寫測試代碼:

    @Test
    public void testJsonValue() throws JsonProcessingException {
        String  jsonStr = new ObjectMapper().writeValueAsString(TypeEnumWithValue.TYPE2);
        log.info(jsonStr);
        assertThat(jsonStr).isEqualTo("Type 2");
    }
    

    可以看到,枚舉類的對象序列化后的值即getName()方法的返回值。

    @JsonRootName

    如果啟用了包裝(wrapping),則使用@JsonRootName註解可以指定要使用的根包裝器的名稱。

    下面我們創建一個使用@JsonRootName修飾的Java類:

    @JsonRootName(value = "user")
    @Data
    @AllArgsConstructor
    public static class UserWithRoot {
        public int id;
        public String name;
    }
    

    編寫測試:

    @Test
    public void testJsonRootName() throws JsonProcessingException {
        UserWithRoot userWithRoot = new UserWithRoot(1, "dxsn");
        ObjectMapper mapper = new ObjectMapper();
      	//⬇️重點!!!
        mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
      
        String result = mapper.writeValueAsString(userWithRoot);
        log.info(result);
      	//輸出:{"user":{"id":1,"name":"dxsn"}}
        assertThat(result).contains("dxsn");
        assertThat(result).contains("user");
    }
    

    上面代碼中,我們通過開啟ObjectMapper的SerializationFeature.WRAP_ROOT_VALUE。可以看到UserWithRoot對象被序列化后的Json對象被包裝在user中,而非單純的{"id":1,"name":"dxsn"}

    @JsonSerialize

    @JsonSerialize註解表示序列化實體時要使用的自定義序列化器。

    我們定義一個自定義的序列化器:

    public static class CustomDateSerializer extends StdSerializer<Date> {
        private static SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    
        public CustomDateSerializer() {
            this(null);
        }
    
        public CustomDateSerializer(Class<Date> t) {
            super(t);
        }
    
        @Override
        public void serialize(
            Date value, JsonGenerator gen, SerializerProvider arg2) throws IOException, JsonProcessingException {
            gen.writeString(formatter.format(value));
        }
    }
    

    使用自定義的序列化器,創建Java類:

    @Data
    @AllArgsConstructor
    public static class Event {
        public String name;
        @JsonSerialize(using = CustomDateSerializer.class)
        public Date eventDate;
    }
    

    編寫測試代碼:

    @Test
    public void testJsonSerialize() throws ParseException, JsonProcessingException {
        SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
        String toParse = "20-12-2014 02:30:00";
        Date date = formatter.parse(toParse);
        Event event = new Event("party", date);
        String result = new ObjectMapper().writeValueAsString(event);
        assertThat(result).contains(toParse);
    }
    

    可以看到,使用@JsonSerialize註解修飾指定屬性后,將會使用指定的序列化器來序列化該屬性。

    反序列化註解

    @JsonCreator

    我們可以使用@JsonCreator註解來優化/替換反序列化中使用的構造器/工廠。

    當我們需要反序列化一些與我們需要獲取的目標實體不完全匹配的JSON時,它非常有用。

    現在,有如下一個Json對象:

    {"id":1,"theName":"My bean"}
    

    我們聲名了一個Java類:

    @Data
    public static class BeanWithCreator {
        private int id;
        private String name;
    }
    

    此時,在我們的目標實體中沒有theName字段,只有name字段。現在,我們不想改變實體本身,此時可以通過使用@JsonCreator和@JsonProperty註解來修飾構造函數:

    @Data
    public static class BeanWithCreator {
        private int id;
        private String name;
    
        @JsonCreator
        public BeanWithCreator(@JsonProperty("id") int id, @JsonProperty("theName") String name) {
            this.id = id;
            this.name = name;
        }
    }
    

    編寫測試:

    @Test
    public void beanWithCreatorTest() throws JsonProcessingException {
        String str = "{\"id\":1,\"theName\":\"My bean\"}";
        BeanWithCreator bean = new ObjectMapper()
            .readerFor(BeanWithCreator.class)
            .readValue(str);
     	 	assertThat(bean.getId()).isEqualTo(1);
        assertThat(bean.getName()).isEqualTo("My bean");
    }
    

    可以看到,即使Json對象中的字段名和實體類中不一樣,但由於我們手動指定了映射字段的名字,從而反序列化成功。

    @JacksonInject

    @JacksonInject表示java對象中的屬性將通過注入來賦值,而不是從JSON數據中獲得其值。

    創建如下實體類,其中有字段被@JacksonInject修飾:

    public static class BeanWithInject {
        @JacksonInject
        public int id;
        public String name;
    }
    

    編寫測試:

    @Test
    public void jacksonInjectTest() throws JsonProcessingException {
        String json = "{\"name\":\"dxsn\"}";
        InjectableValues inject = new InjectableValues.Std()
            .addValue(int.class, 1);
        BeanWithInject bean = new ObjectMapper().reader(inject)
            .forType(BeanWithInject.class)
            .readValue(json);
        assertThat(bean.id).isEqualTo(1);
        assertThat(bean.name).isEqualTo("dxsn");
    }
    

    如上,我們在測試中將json字符串(僅存在name字段)進行反序列化,其中id通過注入的方式對屬性進行賦值。

    @JsonAnySetter

    @JsonAnySetter允許我們靈活地使用映射(鍵值對、Map)作為標準屬性。在反序列化時,JSON的屬性將被添加到映射中。

    創建一個帶有@JsonAnySetter的實體類:

    public static class ExtendableBean {
        public String name;
        public Map<String, String> properties;
    
        @JsonAnySetter
        public void add(String key, String value) {
            if (properties == null) {
                properties = new HashMap<>();
            }
            properties.put(key, value);
        }
    }
    

    編寫測試:

    @Test
    public void testJsonAnySetter() throws JsonProcessingException {
        String json = "{\"name\":\"dxsn\", \"attr2\":\"val2\", \"attr1\":\"val1\"}";
        ExtendableBean extendableBean = new ObjectMapper().readerFor(ExtendableBean.class).readValue(json);
        assertThat(extendableBean.name).isEqualTo("dxsn");
        assertThat(extendableBean.properties.size()).isEqualTo(2);
    }
    

    可以看到,json對象中的attr1,attr2屬性在反序列化之後進入了properties。

    @JsonSetter

    @JsonSetter是@JsonProperty的替代方法,它將方法標記為屬性的setter方法。
    當我們需要讀取一些JSON數據,但目標實體類與該數據不完全匹配時,這非常有用,因此我們需要優化使其適合該數據。

    創建如下實體類:

    @Data
    public static class MyBean {
        public int id;
        private String name;
    
        @JsonSetter("name")
        public void setTheName(String name) {
            this.name = "hello " + name;
        }
    }
    

    編寫測試:

    @Test
    public void testJsonSetter() throws JsonProcessingException {
        String json = "{\"id\":1,\"name\":\"dxsn\"}";
        MyBean bean = new ObjectMapper().readerFor(MyBean.class).readValue(json);
        assertThat(bean.getName()).isEqualTo("hello dxsn");
    }
    

    可以看到,json對象中的name屬性為“dxsn”,我們通過在MyBean類中定義了使用@JsonSetter(“name”)註解修飾的方法,這表明該類的對象在反序列話的時候,name屬性將來自此方法。最後MyBean對象中name的值變為了hello dxsn。

    @JsonDeserialize

    @JsonDeserialize註解指定了在反序列化的時候使用的反序列化器。

    如下,定義了一個自定義的反序列化器:

    public static class CustomDateDeserializer extends StdDeserializer<Date> {
        private static SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    
        public CustomDateDeserializer() {
            this(null);
        }
    
        public CustomDateDeserializer(Class<?> vc) {
            super(vc);
        }
    
        @Override
        public Date deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException {
            String date = jsonparser.getText();
            try {
                return formatter.parse(date);
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    創建一個使用@JsonDeserialize(using = CustomDateDeserializer.class)修飾的實體類:

    public static class Event {
        public String name;
        @JsonDeserialize(using = CustomDateDeserializer.class)
        public Date eventDate;
    }
    

    編寫測試:

    @Test
    public void whenDeserializingUsingJsonDeserialize_thenCorrect()
        throws IOException {
        String json = "{\"name\":\"party\",\"eventDate\":\"20-12-2014 02:30:00\"}";
        SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
        Event event = new ObjectMapper().readerFor(Event.class).readValue(json);
        assertThat(event.name).isEqualTo("party");
        assertThat(event.eventDate).isEqualTo(df.format(event.eventDate));
    
    }
    

    可以看到,在Event對象中,eventDate屬性通過自定義的反序列化器,將“20-12-2014 02:30:00”反序列化成了Date對象。

    @JsonAlias

    @JsonAlias在反序列化期間為屬性定義一個或多個替代名稱。讓我們通過一個簡單的例子來看看這個註解是如何工作的:

    @Data
    public static class AliasBean {
        @JsonAlias({"fName", "f_name"})
        private String firstName;
        private String lastName;
    }
    

    如上,我們編寫了一個使用@JsonAlias修飾的AliasBean實體類。

    編寫測試:

    @Test
    public void whenDeserializingUsingJsonAlias_thenCorrect() throws IOException {
        String json = "{\"fName\": \"John\", \"lastName\": \"Green\"}";
        AliasBean aliasBean = new ObjectMapper().readerFor(AliasBean.class).readValue(json);
        assertThat(aliasBean.getFirstName()).isEqualTo("John");
    }
    

    可以看到,即使json對象中的字段名是fName,但是由於在AliasBean中使用@JsonAlias修飾了firstName屬性,並且指定了兩個別名。所以反序列化之後fName被映射到AliasBean對象的firstName屬性上。

    更多

    除上述註解之外,Jackson還提供了很多額外的註解,這裏不一一列舉,接下來會例舉幾個常用的註解:

    • @JsonProperty:可以在類的指定屬性上添加@JsonProperty註解來表示其對應在JSON中的屬性名。
    • @JsonFormat:此註解在序列化對象中的日期/時間類型屬性時可以指定一種字符串格式輸出,如:@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = “dd-MM-yyyy hh:mm:ss”)。
    • @JsonUnwrapped:@JsonUnwrapped定義了在序列化/反序列化時應該被扁平化的值。
    • @JsonIgnore:序列化/反序列化時忽略被修飾的屬性。
    • ……

    總結

    本文主要介紹了Jackson常用的序列化/反序列化註解,最後介紹了幾個常用的通用註解。Jackson中提供的註解除了本文列舉的還有很多很多,使用註解可以讓我們的序列化/反序列化工作更加輕鬆。如果你想將某庫換成Jackson,希望這篇文章可以幫到你。

    本文涉及的代碼地址:https://gitee.com/jeker8chen/jackson-annotation-demo

    歡迎訪問筆者博客:blog.dongxishaonian.tech

    關注筆者公眾號,推送各類原創/優質技術文章 ⬇️

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

    網頁設計最專業,超強功能平台可客製化

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

  • NetAnalyzer筆記 之 十四 NetAnalyzer 6.0 的使用方法 — 3.協議分析與統計

    NetAnalyzer筆記 之 十四 NetAnalyzer 6.0 的使用方法 — 3.協議分析與統計

    數據分析

    完成了數據的抓取,那麼接下來就是NetAnalyzer的第二個重點部分了,協議分析作為整個軟件的核心之一,在最新的NetAnalyzer中已經得到了巨大的提升。NetAnalyzer中協議分析分為單數據包分析,和聯合分析兩種分析方式,對於聯合分析會根據不同的協議特性進行形成不同的分析方案,目前支持傳輸協議(TCP/UDP)協議分析, HTTP協議分析。在數據統計部分部分還增加了針對ARP協議的圖形化分析。對於協議分析,需要了解相關的網絡知識或是有相關專業背景支持。

    單數據包分析,在獲取到數據包后,軟件工作界面數據包列表框中會显示所獲取的所用數據包,並且對這次數據做了一些簡單的分析,我們可以憑藉這些數據簡單判斷所對應的的數據包類型。

     

    數據包列表

    當我們選中一行,即選中一個數據包,我們可以看到對該數據包詳細的數據分析信息,並一樹狀結構樹呈現出來,並在右側显示該數據包原始信息。當我們選中協議樹中一個字段時,右側的數據就會定位到當前字端所分析數據的位置。

     

    數據分析

    然後通過對應的協議格式進行匹配與分析,如這部分的IP協議。

     

    IPv4協議格式

    需要注意的是,NetAnalyzer目前對於選中的字段只能精確到字節層次,對於一些協議,其中一個字節可能包含了多個字段,或是跨字節的字段,則會選擇全部的字節數據,比如IPv6協議。

     

    IPv6協議格式

    其中的版本字段只佔用了4bit(1字節為8bit),通信類型佔了8bit 也就是1字節,但是因為其中前面部分使用了版本字段所在字節後面的4bit,所以改字段為一個典型的跨字節字段,同樣流標籤字段使用了20bit,佔用第二個字節的4bit加上後面自身的2個字節(16bit)。

     

    解析后的IPv6數據

    對於類型的字段因為NetAnalyzer使用十六進制显示數據,並不能清晰表達bit層次的信息所以當選定字段后默認選中改字段所在的字節,如點擊版本選中方式如下,

     

    IPv6版本信息

    選中通信類型和流標籤則呈現方式如下。

     

    通信類型和流標籤共用數據

    數據分析標籤

    雖然NetAnalyzer盡可能多分析每個數據包所包含的信息,但是依舊存在很多數據需要我們手動去解析。所以軟件增加了數據標籤。

     

    數據分析

    數據標籤頁點擊 显示 按鈕 就可以打開數據轉換窗口,當然也可以在常規轉換中點擊任意功能可以打開轉換窗口

     

    轉換窗口管理

     

    關閉按鈕為關閉轉換窗口,清空則是清空當前窗口內的數據。

    點擊清空按鈕,則清空轉換信息。

     

    常規轉換工具

    NetAnalyzer中提供了一部分簡單的轉換功能,這些功能只有在載荷數據被選中的情況的才可以啟用,

    如點擊二進制按鈕,則對所選的數據轉換為對應的二進制字符串。如下圖所示。

     

    常規數據轉換窗口

    除了一些簡單的轉換功能,還集成了MangoScript擴展方式和插件擴展方式(無可用插件的

    時候不显示)的轉換。

     

    擴展MangoScript的解析

    如下面通過MangoScript針對某即時通信軟件的數據分析。

    針對於MangoScript和插件兩種方式的轉換,將會在在《NetAnalyzer使用說明書 二 擴展與開發》中詳細說明,此處不再贅述。

     

     

    定位轉換功能需要配合常規轉換進行使用,有時候我們確定某個字節會在一個確定的位置出現,比如IP地址字段,我們選中該位置,位置字段就會出現一串代碼 (10,1) [26]-4

    (x,y)[offset] – length

     x: 十六進制編輯器水平方向的偏移量

     y :   十六進制編輯器垂直方向的偏移量

    offset : 字節偏移量,offset = y * 16 + x

    length :  當前選擇的數據長度

     

    數據轉換

    所以代碼 (10,1) [26]-4 確定了當前IP地址的位置,此時點擊 常規轉換 -> IPv4地址 則會在模式中記錄當前的轉換模式,然後點擊定位轉換,就會在當前數據包列表中針對每個數據包這個位置執行定位操作,這對於尋找所需要的數據非常重要。

     

    選擇了IPv4轉換

     

    執行定位轉換

    對於MangoScript和插件擴展依然支持定位轉換。

     

    區塊複製,主要是對一些已經選中的字節進行複製轉為代碼,字節數組,以及保存的功能,以及數據做手動分析,腳本分析以及自定義轉換等,後續將會說明,此處不再詳細介紹。

     

    數據塊操作

     

    字節定位,與定位轉換類似,但是字節定位主要是用來在數據包列表中查找相同位置出現相同字節序列的數據包。算作一個查找功能。

     

    字節定位

    分析標籤

    分析標籤下個功能依託於數據包列表,分別有載荷數據提取,數據包標記,編碼轉換,數據查找,統計等相關功能,是聯合分析的主要功能,下面將會着重對一下功能進行說明。

     

    數據分析標籤

    TCP/UDP協議分析   前面介紹的都是基於單包的數據分析,而在協議分析中,我們大部分分析的數據都是依託於TCP/UDP的長連接數據,這部分數據的特點就是有多個數據包通過tcp或udp相關協議完成數據重組后才可以使用(基於udp的連接數據可能不是很嚴格)。

    NetAnalyzer 除了提供基於單包的數據分析,更提供了基於連接數據的分析,而分析出來的數據不僅僅是在窗口上呈現一堆亂碼,更可以通過DocBar將獲取的數據提取出來進行使用。

    開始 標籤最後一部分就是基於長連接的分析。點擊TCP/UDP 按鈕

     

    基於TCP/UDP載荷數據查看

    此時NetAnalyzer便會切換到載荷數據模式(該過程可以通過配置,使用獨立窗口打開)。在該模式下會打開專有的載荷數據菜單,數據區域也會變為對於載荷數據的分析,這裏先介紹一個NetAnalyzer中的DocBar工具,如下圖

     

    DocBar

    在文本模式下,分析載荷數據會显示該工具條,該工具條會提供針對當前數據塊的各種操作,當然在不動情況下,显示的工具和數量,都有所不同,下面是對當前各個功能的說明。

    l   對當前數據塊進行摺疊

    l   選中當前的分析數據

    l   保存當前原始數據

    l   查看原始數據(bytes數據)

    l   MangScript解析數據

    l   手動測試數據

     

    對於其他情況下的工具在這裏不會一一介紹,但是碰到的時候會有說明,並且隨着後續功能點的增加,DocBar可能會有更多的功能添加進來。

     

    tcp/udp 的分析分為 文本模式原始模式 ,文本模式主要是用於分析載荷數據為文本的數據,我們可以通過下面兩種方式更改文本編碼方式,分析數據。

    文本模式下,呈現方式如下:

     

    查看載荷數據

    原始模式分析如下,可用通過TCP/UDP的下拉菜單命令 字節數據 切換為原始數據

     

     

    字節查詢方式

     

    字節方式呈現

    對於在該功能下針對TCP的所有數據都已經進行過TCP重組,所以最終分析完成的數據並不是按照數據包方式做簡單呈現就可以的,都會做數據的篩查與整理。如果需要單包分析的使用者需要注意一下。

     

    HTTP數據分析 http作為最有網絡代表意義的協議,NetAnalyzer提供了更加完善的分析,http基於tcp協議,所以數據還原等都建立在tcp數據還原的基礎之上。通過http分析,我們可以還原很多有意義的數據,如獲取到Http所傳輸的的html、js、css數據文件,還可以獲取到基於http協議分析得到的圖片,文件等信息,如下圖分別為還原后的圖片和zip壓縮包。

     

    http方式分析出的圖片

     

    http方式分析出的文件

    對於常規的字符串或圖片可以直接在NetAnalyzer呈現,但是對於其他類型的文件,如視頻、音樂、以及上面提到的zip壓縮包文件,在在NetAnalyzer會簡單显示為二進制數據,該數據如果過長,則會截斷显示,但是在後面會加入【全部數據】下鑽選項,當點擊該數據后則會打開原始數據對話框,並且會完整显示當前的數據,如下圖所示。

     

    查看原始數據

    原始數據對話框中,提供了簡單的數據另存為和數據識別相關的功能。

     

    原始數據保存

    保存 保存當前窗口中的數據為一個文件。

    保存選擇數據 是當選擇對話框中其中的一段數據保存為文件,有時候數據可能存在偏差,或者我們需要提取選定的數據保存為文件,可以通過下拉保存選定的數據進行保存。

    數據識別功能。

    轉為… 則是將當前的數據轉到編碼轉換工具中進行進一步分析。

    自動識別 為了更加快速的實現數據提取,NetAnalyzer增加了數據識別模塊,通過整理不同文件的頭部或尾部字節形成數據識別特徵,當進行自動識別的時候,可以快速定位字節。

     

    文件識別

    添加特徵 將選定的指定字節添加為文件識別頭,並且添加相關信息,形成一個特徵。

     

    添加文件識別

    識別管理 管理特徵庫,在後續將詳細介紹該功能點。

     

    載荷數據分析出的文件

    除了使用常規的識別方式,在載荷數據提取中也加入了數據識別功能。在使用的時候點擊數據識別就可以在下方显示被識別到的數據類型,有時候可能會存在多個類型和誤識別的情況,使用的時候請務必注意。

    有時候通過HTTP協議還原部分二進制數據,如下面還原ZIP文件,文檔會以二進制數據呈現,而我們可以通過0x50 0x4B(PK)推斷出該文件很有可能是zip文件 ,所以我們點擊全部數據 ,打開原始數據窗口,這部分數據正好是zip的全部數據。

       

    保存的zip文件內容

    此時點擊將當前數據保存為zip文件。減壓就可以看到對應的文件內容。

     

    在載荷數據模式下,菜單會自動切換為,載荷模式菜單

     

    載荷數據標籤

    該菜單下提供了很多常用的字符串轉換工具

     

    格式轉換工具

    如下面通過通過Cookie格式化,格式化了http頭中的cookie字段

     

    Cookie格式化

    需要注意的是使用這些字段首先需要選中被轉換的文本,然後點擊需對應的功能項。其中如果點擊轉換為…,則啟動NetAnalyzer附帶的編碼轉換工具,進行集中處理。

     

    編碼轉換工具

    針對html字符串數據,還提供了過濾標籤和HTML預覽功能,因為該部分功能都很類型,且使用簡單,用戶自行嘗試使用即可。

     

     

    時序圖 在數據分析中,除了對於數據本身的分析之外,有時候我們還要去評測一些數據質量等方面的內容。並且可以通過圖像化的方式表現出來。

     

    TCP時序圖分析

    時序圖模擬TCP/UDP在數據網絡中的數據傳輸過程,還原網絡通信場景,如該圖可以完整的反映TCP三次握手以及斷開連接四次揮手的情景。可以作為對當前分析數據從另外一個方面的反饋,更具有參考意義。

    點擊

     

    時序圖選項

    就可以看到針對於當前tcp/udp 數據交互的情況。

     

     

    數據標記

    在分析標籤下面,有標記功能,實現對當前採集會話數據連接的進行快速識別。

     

    數據標記

    NetAnalyzer提供了四中顏色對數據包鏈接進行區分。

    如TCP數據包,就會通過源IP地址+源端口地址+目標IP地址+目標端口 作為一個特徵來進行識別,此處的源和目標具有相對性。

    注*  ctrl+鼠標左鍵 可以實現對數據會話的快速標記 顏色為紅色

     

    標記完成的數據

    通過點擊清理標記,可還原數據。

     

     

    數據包查找 

     

    數據包查找

    在數據包列表模式下使用Ctrl+F即可以打開數據包查找功能。

    該功能主要是實現快速查找數據包的功能,可以通過編號,協議,地址(mac/ip),端口,關鍵字等五種方式查找數據包。還可以通過數據列表導航按鈕進行數據包列表瀏覽。

     

     

    編碼方式

    在通過TCP/UDP 或HTTP 功能還原數據的時候,有時候會出現亂碼,尤其是對非英文字符。在HTTP協議中通常都會在頭部信息中攜帶編碼方法,通過提取就可以獲取到編碼方式,但是仍然後部分服務並不提供編碼字段,這時候就需要我們通過手動切換,來嘗試還原相關信息。

    通過菜單欄或者是狀態欄都可以對編碼方案進行切換

     

    字符編碼

     

    狀態欄字符編碼

    這裏需要注意的是如果http頭部包含了編碼方式,則使用頭部提供的編碼方式。

     

     

    數據統計

    目前NetAnalyzer显示了大量的統計方式,涵蓋了數據報表、流量分析、主機通信矩,傳輸報告、ARP報告等多種統計方式。

     

    數據報表

     

    報表信息

    對當前捕獲的數據表中的數據進行統計與歸類。呈現方式如有圖所示。

     

    報表內容

    包含一些基本信息,數據量與時間直線圖,數據量佔比,關係圖等信息

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

    ※回頭車貨運收費標準

  • 為什麼阿里巴巴Java開發手冊中不允許魔法值出現在代碼中?

    為什麼阿里巴巴Java開發手冊中不允許魔法值出現在代碼中?

    在閱讀《阿里巴巴Java開發手冊》時,發現有一條關於關於常量定義的規約,具體內容如下:

    圖中的反例是將數據緩存起來,並使用魔法值加鏈路 id 組成 key,這就可能會出現其他開發人員在複製粘貼的時候,少複製 _ 的情況發生,這種錯誤很難去檢查到,因為讀取緩存不存在,可能會去數據庫讀取,很難察覺到。

    如果在生產環境中,大量的請求進來,緩存全部失效,直接請求數據庫,導致數據庫連接過多,查詢效率變低的問題發生,因此看來魔法值確實應該避免出現在代碼中。

    另外在 《Clean Code》 和 《重構》 等書中也提到了類似的問題,在代碼中出現原始形態数字通常來說是壞現象,應該用命名良好的常量類隱藏它。

    靜態常量取代魔法值

    像下面這個例子:

    if (billCount > 75) {
        //todo
    } else {
        //todo
    }
    

    如果在不了解這塊的業務的同事,在讀到這塊代碼的時候,可能會想,75 是什麼鬼,為啥和這個數比較,背後深藏着什麼秘密嗎?可能只有當時的開發人員記得了,導致代碼可讀性和可維護性極差。

    如果聲明一個常量,來替換該魔法值,可能就會使代碼的可讀性和可維護性大大增加。

    static final Integer BASIC_BILL_COUNT = 75;
    

    還有些魔法表達式,比如:

    if (value > 60 && value <= 80 && type = 1) {
        // todo
    }
    

    比如這個表達式是表示狀態為正常且項目活躍,就可以定義:

    boolean isActiveProject = value > 60 && value <= 80 && type = 1;
    

    這樣是不是可讀性就提高了,一眼就可以看出來這塊代碼的邏輯。

    枚舉類取代魔法值

    還有一種消除魔法值的方式是使用枚舉類代替,下面讓我們舉個例子:

    if (eventId == 1) {
        System.out.println("睡覺");
    } else if (eventId == 2) {
        System.out.println("吃飯");
    } else if (eventId == 3) {
        System.out.println("打豆豆");
    }
    

    如上代碼是針對事件 id 去執行相應的事件,如果事件比較少,大家還可以勉強記住每個 eventId 對應的含義,但是隨着事件 id 的增多,很可能會發生,新來的員工把事件 id 給搞混了,導致執行錯誤的事件,發生 bug。

    那麼我們可以使用枚舉類來表示相應的事件:

    public enum EventEnum {
    
        /**
         * 睡覺
         */
        SLEEP_EVENT(1, "睡覺"),
    
        /**
         * 吃飯
         */
        EAT_EVENT(2, "吃飯"),
    
        /**
         * 打豆豆
         */
        FIGHT_PEA_EVENT(3, "打豆豆");
    
        private int eventId;
        private String desc;
    
        EventEnum(int eventId, String desc) {
            this.eventId = eventId;
            this.desc = desc;
        }
    
        public int getEventId() {
            return eventId;
        }
    
        public String getDesc() {
            return desc;
        }
    }
    

    修改完之後的代碼如下:

    if (eventId == EventEnum.SLEEP_EVENT.getEventId()) {
        System.out.println("睡覺");
    } else if (eventId == EventEnum.EAT_EVENT.getEventId()) {
        System.out.println("吃飯");
    } else if (eventId == EventEnum.FIGHT_PEA_EVENT.getEventId()) {
        System.out.println("打豆豆");
    }
    

    是不是可讀性急劇提升,還不快看看自己代碼中有沒有這樣的魔法值出現,有的話趕緊改造起來。

    還有如果你需要在不同的地點引用同一數值,魔法數會讓你煩惱不已,因為一旦這些数字發生改變,就必須在程序中找到所有的魔法值,並將它們全部修改一遍,這樣就太費時費力了。

    其實不只是 Java 不應該在代碼中使用魔法值,其他語言亦是如此。

    總結

    本文主要介紹了為什麼不允許在代碼中出現魔法值以及如何將代碼中已有的魔法值去除掉。

    代碼可讀性還是比較重要的,你肯定不希望別人在接手你的代碼的時候,罵到這数字啥意思,這代碼寫得跟粑粑一樣。

    最好的關係就是互相成就,大家的在看、轉發、留言三連就是我創作的最大動力。

    參考

    《Java開發手冊》泰山版

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

    【其他文章推薦】

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

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

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

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

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

    網頁設計最專業,超強功能平台可客製化

    ※回頭車貨運收費標準