作者: admin

  • 小米 10i 5G 印度發表:搭載高通 750G 5G 處理器、1.08 億像素主相機、 120Hz更新率螢幕與 4820mAh 大電量_台中搬家

    小米 10i 5G 印度發表:搭載高通 750G 5G 處理器、1.08 億像素主相機、 120Hz更新率螢幕與 4820mAh 大電量_台中搬家

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

    台中搬家公司推薦超過30年經驗,首選台中大展搬家

    小米在中國、印度以及其他海外市場,經常會有些手機以不同名稱在當地推出,像是昨(5)日才剛於印度發表的「小米 10i 5G」就是很好的例子。昨天小米在印度發表小米 10i 5G 這款新機,眼尖的人或許會發現他有些眼熟,因為它其實就是去年小米在中國推出的 Redmi Note 9 Pro 更名上市新機。

    小米 10i 5G 印度發表:搭載高通 750G 5G 處理器、1.08 億像素主相機、 120Hz更新率螢幕與 4820mAh 大電量

    去年印度率先發表 Redmi Note 9 Pro ,隨後在中國也推出了 Redmi Note 9 Pro 但在外觀、規格皆與印度版本不同。近日小米於印度發表「小米 10i 5G」新機,也就是中國版的 Redmi Note 9 Pro 。
    硬體規格方面,小米 10i 5G 搭載 Qualcomm Snapdragon 750G 5G 處理器、配備最高 8GB LPDDR4X RAM、256GB UFS2.2 ROM :

    相機方面,小米 10i 5G 配備 1.08 億像素四鏡頭主相機,鏡頭依序為 1.08 億像素標準鏡頭(Samsung ISOCELL HM2 感光元件)、800 萬像素 120° 超廣角鏡頭、200 萬像素微距鏡頭以及 200 萬像素景深鏡頭,前置鏡頭則配被 1600 萬像素自拍相機。

    小米 10i 5G 採用 6.67 吋 FHD+ 解析度螢幕,螢幕具備 120Hz 更新率、240Hz 觸控採樣率,顯示支持 HDR10 格式。此外,螢幕採用康寧第五代大猩猩玻璃保護,小米 10i 5G 也具備 IP53 防潑水等級。

    電量方面,小米 10i 5G 內建 4820mAh 大電量電池,支持 33W 功率的有線快充能在 58 分鐘為小米 10i 充電至 100% :

    其他方面,小米 10i 5G 亦具備 NFC、保留 3.5mm 耳機孔以及採用側邊指紋辨識器進行指紋辨識解鎖。顏色方面,小米 10i 5G 在印度推出 Midnight Black(午夜黑)、Atlantic Blue(大西洋藍)以及帶有漸層色的 Pacific Sunrise(太平洋日出)等三種配色選擇。

    小米 10i 5G 在印度共推出三個版本,價格依序為 6GB+64GB 版本售價 23,999  印度盧比(約合新台幣 9,153 元)、8GB+128GB 版本售價 24,999 印度盧比(約合新台幣 9,534 元)、8GB+256GB 版本售價 27,999 印度盧比(約合新台幣 10,678 元),在當地使用特定銀行卡可享 2,000 印度盧比的折扣。

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

    擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

    圖片/消息來源:Mi India

    延伸閱讀:
    Redmi K40 Pro 最新渲染圖曝光:可能是最便宜的 S888 旗艦 5G 手機之一

    疑似 OPPO Find X3 安兔兔跑分曝光,搭載高通 S888 旗艦處理器測出 77.1 萬分刷新最高分紀錄

    您也許會喜歡:

    【推爆】終身$0月租 打電話只要1元/分

    立達合法徵信社-讓您安心的選擇

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

    台中搬家公司推薦超過30年經驗,首選台中大展搬家

  • HTC Desire 21 Pro 5G 實機諜照流出!配備 4800 萬像素四鏡頭主相機、支持雙卡雙待_台中搬家公司

    HTC Desire 21 Pro 5G 實機諜照流出!配備 4800 萬像素四鏡頭主相機、支持雙卡雙待_台中搬家公司

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

    還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

    繼去年 HTC U20 5G 之後,也在下半年在台灣推出 HTC Desire 20+ 。在 2021 年初,又傳聞 HTC 即將推出新手機?最近在網路上流傳一組疑似為 HTC Desire 21 Pro 5G 的實機諜照,雖然目前還無法確認其詳細規格,不過從中仍可得知這款未發表的新機配備 4800 萬像素四鏡頭主相機並支持雙卡雙待。

    HTC Desire 21 Pro 5G 實機諜照流出!配備 4800 萬像素四鏡頭主相機、支持雙卡雙待

    去年底,在中國美篇有人上傳了一系列聲稱是 HTC Desire 21 Pro 5G 的實機諜照,雖然目前原文已經修改隱私權限無法查看,不過稍早也有人將這組照片上傳到 Slashleaks 爆料,也讓 Desire 21 Pro 5G 的實機外觀被更多人看到。
    雖然這組 Desire 21 Pro 5G 的外觀照片已經包上一層碳纖紋紋路的包膜,不過在機身背面仍可看到 HTC 的 Logo 以及相機外觀。圖中的 HTC Desire 21 Pro 5G  配備 4800 萬像素四鏡頭主相機,相機旁也有 LED 閃光燈:

    從另一張機身正面見到 Desire 21 Pro 5G 採用居中的挖孔全螢幕設計,其型號為 2QAG100 。從關於手機的其他資訊,也能得知這款新機具備雙 SIM 卡插槽支持雙卡雙待、運行 Android 10 作業系統。另外,傳聞 Desire 21 Pro 5G 在機身側邊左側的電源鍵整合側邊指紋感應器,在機身頂部未見到 3.5mm 耳機孔。

    回顧 HTC 去年推出第一款手機 U20 5G 搭載高通 Snapdragon 765G 處理器,隨後也針對中階入門手機市場推出 Desire 21+ 4G 手機。單從目前 HTC Desire 系列的產品定位來看,能預期這款 Desire 21 Pro 5G 也機上定位為中階機種。
    即便目前仍無法確定它會搭載高通還是聯發科的 5G 處理器,但能大致推測它的價格不會太高,對於有想入手平價 5G 手機的消費者在之後也將多一款選擇。

    圖片來源:Slashleaks

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

    台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

    延伸閱讀:
    小米 10i 5G 印度發表:搭載高通 750G 5G 處理器、1.08 億像素主相機、 120Hz更新率螢幕與 4820mAh 大電量

    疑似 OPPO Find X3 安兔兔跑分曝光,搭載高通 S888 旗艦處理器測出 77.1 萬分刷新最高分紀錄

    您也許會喜歡:

    【推爆】終身$0月租 打電話只要1元/分

    立達合法徵信社-讓您安心的選擇

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

    還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

  • Nintendo Switch Pro 4K 版本傳聞再現!國外分析師表示今年有望見到_網頁設計公司

    Nintendo Switch Pro 4K 版本傳聞再現!國外分析師表示今年有望見到_網頁設計公司

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

    網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

    Nintendo Switch Pro 傳聞又來了!這台幾乎每年都再傳,不過今年看起來確實蠻有機會的,畢竟 Sony PlayStation 5、Xbox Series X 都已經推出,Nintendo 想在重返市場最熱門的遊戲機話題,無疑就是要靠新款 Switch。近日就有國外分析師表示,他預測今年 Nintendo 會推出玩家期待許久的 Switch 升級版,而且將會是 4K 解析度版本。

    Nintendo Switch Pro 4K 版本傳聞再現!

    根據外媒 Nintendo Life 的報導,國外許多業界專家、分析師都紛紛表示,今年會是 Nintendo 非常重要的一年,他們預測 2021 年賣最好、最暢銷的遊戲主機,不會是 PlayStation 5,也不是 Xbox Series X,而是 Nintendo 的 Switch 系列,現行的 Switch Lite、Switch 與今年有機會見到 Switch Pro 總額加起來,將超越這兩間競爭對手。

    Kantan Games 的 Serkan Toto 博士在與 Gamesindustry.biz 訪談中提到:「我預測加強版的任天堂 4K 設備,會在這個年度亮相」。Ampere Analysis 的 Piers Harding-Rolls 也表示贊同,不過他不確定規格會是怎樣,但確實很有機會見到新款 Switch」。

    至於是基於什麼原因,他們就沒特別強調。

    不過去年確實陸續有相關消息出現,像是夏季時,任天堂曾通知開發人員,建議他們準備好 4K 解析度的遊戲。也有報導指出,任天堂正與 Innolux 公司合作,預計下一代遊戲設備會配備 mini LED 螢幕:

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

    台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

    至於規格,統整目前市場上的傳言,Switch Pro 有望具備類似 DLSS 的技術,為玩家提供更流暢的 4K 遊戲體驗。CPU 可能是基於 NVIDIA Tegra Xavier Soc 客製化版本,效能接近 Xbox One。

    下方是國外製作的 Switch Pro 概念影片:

    資料來源:Nintendo Life

    國外開發者成功在 M1 Mac 上運行 Nintendo Switch 遊戲

    您也許會喜歡:

    【推爆】終身$0月租 打電話只要1元/分

    立達合法徵信社-讓您安心的選擇

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

    以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

  • Dell 的 CES 新品包括一台 40 吋 5K 超寬曲面螢幕與內建 Windows Hello 視訊鏡頭的螢幕(你懂的)_如何寫文案

    Dell 的 CES 新品包括一台 40 吋 5K 超寬曲面螢幕與內建 Windows Hello 視訊鏡頭的螢幕(你懂的)_如何寫文案

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

    什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

    如果你最近有考慮添購或更新家中螢幕的話,也許可以參考這波 Dell 在 CES 展前所推出的一系列最新 UltraSharp 螢幕。其中不僅有號稱世界初 40 吋 5K 超寬曲面螢幕,更有為了居家工作與生活而生,內建有視訊鏡頭的貼心螢幕產品系列。繼續閱讀 Dell 的 CES 新品包括一台 40 吋 5K 超寬曲面螢幕與內建 Windows Hello 視訊鏡頭的螢幕(你懂的)報導內文。

    ▲圖片來源:Dell

    Dell 的 CES 新品包括一台 40 吋 5K 超寬曲面螢幕與內建 Windows Hello 視訊鏡頭的螢幕

    來到 CES 展前期間,Dell 直接以全球首款 40 吋 5K Ultrawide 曲面螢幕 UltraSharp 40 Curved Monitor(U4021QW) 譜出序曲。預計在 1 月底就正式開賣(起價 US$2,100 約新台幣 5.9 萬)的這款螢幕解析度為 5,120 x 2,160(144 PPI)。雖說 2500R 的曲度相對其他對手並不算太激進的配置,但包括能涵蓋 98% DCI-P3 色域並內建雙 9W 音箱。

    連接埠超豐富有包括 DisplayPort 1.4、2 個 HDMI 2.0、具備 90W 充電的 Thunderbolt 3 連接埠、3.5mm 與 RJ45 有線網路孔;USB 方面則是有充電用的 USB-C、4 + 1(Superspeed)個 USB-A 與 USB-B。

    這次也一起發表,在尺寸、色域與解析度等規格都稍稍弱化的 38 吋款 Dell UltraSharp 38 吋 2350R 曲面 USB-C 螢幕,則是也承襲了類似的豐富外接能力 — 重點是價格親民不少,但也要 US$1,500 約新台幣 4.2 萬就是了。

    另一系列相當有意思的產品,是直接內建視訊鏡頭的 Dell 新品系列,此系列共有三種螢幕尺寸,最大的版本也有曲面的規格。型號分別為 Dell 24 Video Conferencing Monitor(C2422HE)、Dell 27 Video Conferencing Monitor(C2722DE)與 Dell 34 Curved Video Conferencing Monitor(C3422WE)。他們皆支援一鍵啟動微軟 Teams 的功能,與支援 Windows Hello 的彈出式 5MP IR 視訊鏡頭。

    價格方面則是親民許多,起價為 US$520 也就是約 NT$15,000 即可為居家上班上學的視訊會議需求幫上忙。

    這波 Dell 還推出了不少新品,包括與高階螢幕同樣擁有很豐富連接機能的 USB-C Hub 螢幕 UltraSharp 27(U2722DE)與 24 USB-C Hub(U2422HE),也具備有不錯的色彩規格。此外更親民價格的選擇中,也有 24 吋與 27 吋的選擇。相信都是考慮購入新螢幕朋友可以參考的新品。

    本篇圖片 / 引用來源

    延伸閱讀:

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

    擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

    MagSafe 也能這麼實用!MOFT 磁吸支架開箱體驗

    Google 語音助理新增「耍廢模式(Do Nothing Mode)」讓你不得不 Chill

    您也許會喜歡:

    【推爆】終身$0月租 打電話只要1元/分

    立達合法徵信社-讓您安心的選擇

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

    銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

  • AirPods Pro 推超「牛」限量版,主動式降噪讓你隨時找到專屬自己的空間_網頁設計公司

    AirPods Pro 推超「牛」限量版,主動式降噪讓你隨時找到專屬自己的空間_網頁設計公司

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

    RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

    才剛跨進 2021,緊跟著的農曆新年,Apple 也想到可以帶來一些新年禮物的好選擇 — 或該說,好「牛」的選擇。Apple Store 線上商店今天開賣了一款喜氣洋洋的 AirPods Pro 牛年限量版。繼續閱讀 AirPods Pro 推超「牛」限量版,主動式降噪讓你隨時找到專屬自己的空間報導內文。

    AirPods Pro 牛年限量版官網(點我)
    享最高 12 個月 0% 利率分期

    ▲圖片來源:Apple

    AirPods Pro 推超「牛」限量版,主動式降噪讓你隨時找到專屬自己的空間

    ▲圖片來源:Apple

    新年新希望,紅包很好有禮物也棒。雖說蘋果才剛剛推出更高階耳罩式降噪耳機 AirPods Max。不過對許多人而言,更好攜帶聆聽也相對低價的 AirPods Pro 應該仍是最佳體驗的降噪耳機選擇之一。重點是它也同樣能透過 H1 晶片、各式感測器及耳塞設計,提供主動式降噪搭配優質的通透模式的體驗,也支援「空間音訊(Spatial Audio)」的神奇功能,都是現階段最好的 AirPods 使用體驗。

    ▲圖片來源:Apple

    這次蘋果為了迎接農曆新年,帶來了「很 Emoji」的牛年生肖 Logo 的 AirPods Pro 牛年限量版。在定價依然不變維持在 NT$7,990 的前提下,提供更符合年節主題的鐫刻與紅色 Logo 外包裝,希望有機會為消費者提供新年送禮的好選擇。是說,雖然你也可以自行在非限量版的 AirPods Pro 訂製鐫刻 — 還可以刻不只一個,而且也有 12 生肖可選。

    AirPods Pro 牛年限量版官網(點我)
    享最高 12 個月 0% 利率分期

    不過這牛上加牛的 Happy 牛 year 意涵的特殊 Logo 搭配包裝,顯然還是比較有新年氛圍一些些(吧?)。總之,也趁這個機會先跟大家預祝新年快樂囉!

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

    透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

    延伸閱讀:

    Google 語音助理新增「耍廢模式(Do Nothing Mode)」讓你不得不 Chill

    MagSafe 也能這麼實用!MOFT 磁吸支架開箱體驗

    您也許會喜歡:

    【推爆】終身$0月租 打電話只要1元/分

    立達合法徵信社-讓您安心的選擇

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

    當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

  • Netty源碼學習系列之3-ServerBootstrap的初始化_網頁設計公司

    Netty源碼學習系列之3-ServerBootstrap的初始化_網頁設計公司

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

    以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

    前言

        根據前文我們知道,NioEventLoopGroup和NioEventLoop是netty對Reactor線程模型的實現,而本文要說的ServerBootstrap是對上面二者的整合與調用,是一個統籌者和協調者。具體netty使用的是Reactor單線程模型還是多線程模型、抑或者主從多線程模型,都是ServerBootstrap的不同配置決定的。

        下面照例粘貼一下示例demo(以Reactor多線程模式構建),開始正文。

     1 public class NettyDemo1 {
     2     // netty服務端的一般性寫法
     3     public static void main(String[] args) {
     4         EventLoopGroup boss = new NioEventLoopGroup(1);
     5         EventLoopGroup worker = new NioEventLoopGroup();
     6         try {
     7             ServerBootstrap bootstrap = new ServerBootstrap();
     8             bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
     9                     .option(ChannelOption.SO_BACKLOG, 100)
    10                     .childHandler(new ChannelInitializer<SocketChannel>() {
    11                         @Override
    12                         protected void initChannel(SocketChannel socketChannel) throws Exception {
    13                             ChannelPipeline pipeline = socketChannel.pipeline();
    14                             pipeline.addLast(new StringDecoder());
    15                             pipeline.addLast(new StringEncoder());
    16                             pipeline.addLast(new NettyServerHandler());
    17                         }
    18                     });
    19             ChannelFuture channelFuture = bootstrap.bind(90);
    20             channelFuture.channel().closeFuture().sync();
    21         } catch (Exception e) {
    22             e.printStackTrace();
    23         } finally {
    24             boss.shutdownGracefully();
    25             worker.shutdownGracefully();
    26         }
    27     }
    28 }

     

    一、ServerBootstrap的初始化

        ServerBootstrap的無參構造器啥都沒做,它使用的build模式給屬性賦值,即上面示例中看到的,每執行一個賦值方法都會返回當前對象的引用使得可以繼續鏈式調用。下面挨個方法追蹤。

    1 public ServerBootstrap() { }

     

    1、ServerBootstrap.group方法

        ServerBootstrap有兩個可用重載group方法(如下的兩個),其中接收一個group入參的方法會調用有兩個入參的group方法,只是兩個參數傳同一個group。這兩個group方法決定了netty使用的Reactor線程模型的類型,一個group入參的方法對應Reactor單線程模型,兩個入參且不是同一個group的方法對應Reactor多線程模型或主從多線程模型(具體是哪一種取決於實例化parentGroup時的線程數)。此處只是提一下,先有個印象,後面會對線程模型進行詳細研究。

    1 public ServerBootstrap group(EventLoopGroup group) {
    2         return group(group, group);
    3     }
    1 public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
    2         super.group(parentGroup);
    3         ObjectUtil.checkNotNull(childGroup, "childGroup");
    4         if (this.childGroup != null) {
    5             throw new IllegalStateException("childGroup set already");
    6         }
    7         this.childGroup = childGroup;
    8         return this;
    9     }

        可以看到上述group方法對兩個入參進行了不同位置的賦值,將第一個參數parentGroup傳給了父類AbstractBootstrap的group方法,如下,即最終賦值給了AbstractBootstrap中的group屬性。第二個參數直接賦值給了ServerBootstrap的childGroup屬性。

    1 public B group(EventLoopGroup group) {
    2         ObjectUtil.checkNotNull(group, "group");
    3         if (this.group != null) {
    4             throw new IllegalStateException("group set already");
    5         }
    6         this.group = group;
    7         return self();
    8     }

     

    2、ServerBootstrap.option/childOption方法和ServerBootstrap.attr/childAttr方法

        這四個方法只是做了屬性的賦值,分別賦值給了AbstractBootstrap的options屬性和attrs屬性以及ServerBootstrap的childOptions屬性和childAttrs屬性。

     1 public <T> B option(ChannelOption<T> option, T value) {
     2         ObjectUtil.checkNotNull(option, "option");
     3         if (value == null) {
     4             synchronized (options) {
     5                 options.remove(option);
     6             }
     7         } else {
     8             synchronized (options) {
     9                 options.put(option, value);
    10             }
    11         }
    12         return self();
    13     }
     1 public <T> B attr(AttributeKey<T> key, T value) {
     2         ObjectUtil.checkNotNull(key, "key");
     3         if (value == null) {
     4             synchronized (attrs) {
     5                 attrs.remove(key);
     6             }
     7         } else {
     8             synchronized (attrs) {
     9                 attrs.put(key, value);
    10             }
    11         }
    12         return self();
    13     }

     

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

    台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

    3、ServerBootstrap.channel方法

        調用的是父類AbstractBootstrap的channel方法:

    1 public B channel(Class<? extends C> channelClass) {
    2         return channelFactory(new ReflectiveChannelFactory<C>(
    3                 ObjectUtil.checkNotNull(channelClass, "channelClass")
    4         ));
    5     }

        可以看到先封裝成了一個ReflectiveChannelFactory對象,然後調用channelFactory方法,下面挨個看。ReflectiveChannelFactory的構造器如下,可見就是將傳入class對象的構造器取出來賦值,此時constructor存放的就是NioServerSocketChannel的構造器。

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
            ObjectUtil.checkNotNull(clazz, "clazz");
            try {
                this.constructor = clazz.getConstructor();
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                        " does not have a public non-arg constructor", e);
            }
        }

        channelFactory方法的工作是將上面創建的ReflectiveChannelFactory對象賦值給AbstractBootstrap的channelFactory屬性:

    1 public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    2         ObjectUtil.checkNotNull(channelFactory, "channelFactory");
    3         if (this.channelFactory != null) {
    4             throw new IllegalStateException("channelFactory set already");
    5         }
    6 
    7         this.channelFactory = channelFactory;
    8         return self();
    9     }

     

    4、ServerBootstrap.handler方法和ServerBootstrap.childHandler方法

        handler方法的入參賦值給了AbstractBootstrap的handler屬性,childHandler方法的入參賦值給了ServerBootstrap的childHandler屬性。看到這裏想必園友們也能看出ServerBootstrap的賦值規律了,凡是child開頭的都放在ServerBootstrap中,而不帶child的大多放在其父類ABstractBootstrap中。

    1 public B handler(ChannelHandler handler) {
    2         this.handler = ObjectUtil.checkNotNull(handler, "handler");
    3         return self();
    4     }
    1 public ServerBootstrap childHandler(ChannelHandler childHandler) {
    2         this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler");
    3         return this;
    4     }

     

    5、完成賦值后ServerBootstrap的快照圖

     

     

    小結

        ServerBootstrap的初始化過程看起來賦了很多值,但都只是做了準備工作,看起來輕鬆又簡單,但請注意,這是暴風雨前寧靜。前面的各種賦值到底有什麼用處?很多屬性分為有child前綴和沒有child前綴,這樣設置又有什麼意圖?下一期將進入ServerBootstrap的bind方法,這是netty的深水區,很多謎底也將在這裏得到揭曉,敬請期待!

     

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

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

    網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

  • Cypress系列(6)- Cypress 的重試機制_如何寫文案

    Cypress系列(6)- Cypress 的重試機制_如何寫文案

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

    銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

    如果想從頭學起Cypress,可以看下面的系列文章哦

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

     

    前言

    重試(Retry-ability)是 Cypress 的核心概念之一,有助於我們寫出更加健壯的測試

     

    命令和斷言

    Cypress 測試中經常被調用的兩種類型,仍以前面說到的 testLogin.js 為栗子

    最後的斷言解析

    檢查標籤為 h1 的元素是否包含 jane.lane

     

    斷言的一般步驟

    1. 用 cy.get() 查詢應用程序的DOM,找到元素
    2. 針對元素或元素列表進行斷言嘗試 ,我們示例中為 .should(“contain”, “jane.lane”) 

     

    關於實際工作中的靈魂拷問

    現在的 web 應用基本都是異步的,如果出現以下情況又應該怎麼處理呢?

    1. 如果斷言發生時,應用程序尚未更新DOM怎麼辦?
    2. 如果斷言發生時,應用程序正在等待其後端響應,而導致頁面暫無結果怎麼辦?
    3. 如果斷言發生時,應用程序正在進行密集計算,而導致頁面未及時更新怎麼辦?

    上述情況再測試中經常會發生,一般處理方法是在斷言前價格固定等待時間(或像 selenium 一樣顯式、隱式等待),但仍有可能會發生測試失敗

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

    擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

     

    Cypress 如何優美的解決上述問題

    1.  cy.get() 命令之後的斷言通過,則該命令成功執行完成
    2.  cy.get() 命令之後的斷言失敗,則 cy.get() 命令會自動重新查詢 web 應用程序的 DOM 樹,然後 Cypress 將再次嘗試對 cy.get() 返回的元素進行斷言
    3. 如果斷言仍然失敗, cy.get() 仍然會重新查詢 DOM 樹….以此類推
    4. 直到斷言成功 或 cy.get() 命令超時

    總結

    • 其實很像selenium 的顯式等待,只不過 Cypress 是全局的,不用針對元素去單獨識別
    • Cypress 這種自動重試機制避免了在測試代碼中編寫硬編碼等待(強制等待),使測試代碼更加健壯

     

    多重斷言

    • 在日常測試中,有時候需要多重斷言,即獲取元素後跟多個斷言
    • 在多重斷言中,Cypress 將按順序進行斷言,即當第一個斷言通過後,會進行第二個斷言,通過後進行第三個斷言…以此類推

     

    列表的栗子

    需求

    • 假設一個下拉列表,存在兩個選項,第一個選項是“iTesting”,第二個選項是“testerTalk”
    • 我們需要驗證兩個選項的存在,並且順序正確,代碼片段如下

    代碼解析

    1. 總共有三個斷言:一個 should() ,兩個 expect() 
    2. and() 斷言實際上是 should() 斷言的別名,它是 should() 的自定義回調斷言,其中包含兩個 expect() 斷言
    3. 在測試執行過程中,如果第二個斷言失敗了,那第三個斷言永遠不會執行
    4. 如果導致第二個斷言失敗的原因被找到且修復了,且此時整個命令還沒有超時,則在進行第三個斷言時,還會再次重試第一、第二個斷言

     

    重試(Retry-ability)的條件

    前言

    • Cypress 並不會重試所有命令,當命令可能改變被測應用程序的狀態時,該命令將不會重試(如: click() ,畢竟要點擊)
    • Cypress 僅會重試那些查詢 DOM 的命令: cy.get() 、 find() 、 contains() 等
    • 可以通過官方文檔 Assertions 部分來檢查是否重試了特定命令:https://docs.cypress.io/zh-cn/guides/references/assertions.html#Chai

     

    常用的可重試命令

     

    重點啦!

    重試的超時時間默認是 4秒,對應的配置項是: defaultCommondTimeout ,如果想改重試的超時時間,在 cypress.json 文件改對應的字段值即可

     

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

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

    什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

  • ASP.NET Core MVC+Layui使用EF Core連接MySQL執行簡單的CRUD操作_網頁設計公司

    ASP.NET Core MVC+Layui使用EF Core連接MySQL執行簡單的CRUD操作_網頁設計公司

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

    RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

    前言:

      本章主要通過一個完整的示例講解ASP.NET Core MVC+EF Core對MySQL數據庫進行簡單的CRUD操作,希望能夠為剛入門.NET Core的小夥伴們提供一個完整的參考實例。關於ASP.NET Core MVC+EF操作MsSQL Server詳情請參考官方文檔(https://docs.microsoft.com/zh-cn/aspnet/core/data/ef-mvc/?view=aspnetcore-3.1)。

    示例實現功能預覽:

     博客實例源碼下載地址:

    https://github.com/YSGStudyHards/ASP.NET-Core-MVC-Layui-EF-Core-CRUD_Sample

    一、創建ASP.NET Core Web應用程序:

    注意,本章節主要以APS.NET Core 3.1版本作為博客的樣式實例!

     

    二、添加EF Core NuGet包:

      若要在項目中使用EF Core操作MySQL數據庫,需要安裝相應的數據庫驅動包。 本章教程主要使用 MySQL數據庫,所以我們需要安裝相關驅動包MySql.Data.EntityFrameworkCore。

    安裝方式:

    點擊工具=>NuGet包管理器=>程序包管理器控制台輸入以下命令:

    Install-Package MySql.Data.EntityFrameworkCore -Version 8.0.20

    點擊工具=>NuGet包管理器=>管理解決方案的NuGet程序包:

    搜索:MySql.Data.EntityFrameworkCore  點擊安裝。

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

    當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

    三、創建對應數據庫表的實體模型:

       注意該篇博客使用的是手動模型優先的方式進行數據庫表字段與模型屬性映射,當然如果大家覺得這樣子比較麻煩的話可以真正意義上的模型優先,直接創建模型在program.cs中配置創建對應模型的數據庫邏輯代碼即可無需手動創建數據庫,可參考官網文檔教程(https://docs.microsoft.com/zh-cn/aspnet/core/data/ef-rp/intro?view=aspnetcore-3.1&tabs=visual-studio#create-the-database)。

    創建用戶模型(UserInfo):

    注意:屬性大小寫和數據庫中的表字段保持一致,Id 屬性成為此類對應的數據庫表的主鍵列。 默認情況下,EF Core 將名為 Id 或 xxxID 的屬性視為主鍵。 有關詳細信息,請參閱 F Core – 密鑰。

        /// <summary>
        /// 學生信息模型
        /// </summary>
        public class UserInfo
        {
            /// <summary>
            /// 學生編號
            /// </summary>
            [Description("學生編號")]
            public int? Id { get; set; }
    
            /// <summary>
            /// 學生姓名
            /// </summary>
            [Description("學生姓名")]
            public string UserName { get; set; }
    
            /// <summary>
            /// 學生性別
            /// </summary>
            [Description("學生性別")]
            public string Sex { get; set; }
    
            /// <summary>
            /// 學生聯繫電話
            /// </summary>
            [Description("學生聯繫電話")]
            public string Phone { get; set; }
    
            /// <summary>
            /// 學生描述
            /// </summary>
            [Description("學生描述")]
            public string Description { get; set; }
    
            /// <summary>
            /// 學生愛好
            /// </summary>
            [Description("學生愛好")]
            public string Hobby { get; set; }
        }

    四、將數據庫連接字符串添加到 appsettings.json:

    {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AllowedHosts": "*",
      "ConnectionStrings": {
            "MySqlConnection":"Data Source=127.0.0.1;User ID=root;Password=root;DataBase=SchoolUserInfo_db"
      }
    }

    五、創建數據庫上下文:

    概述:

     數據庫上下文類是為給定數據模型協調 EF Core 功能的主類。 上下文派生自 Microsoft.EntityFrameworkCore.DbContext。 上下文指定數據模型中包含哪些實體。 在此項目中將數據庫上下文類命名為 SchoolUserInfoContext。

    創建:

    using Microsoft.EntityFrameworkCore;
    using Model;
    
    namespace Dal
    {
        public class SchoolUserInfoContext : DbContext
        {
            public SchoolUserInfoContext(DbContextOptions<SchoolUserInfoContext> options)
                : base(options)
            {
            }
    
            /// <summary>
            /// DbSet實體集屬性對應數據庫中的表(注意實體集名必須與表明一致)
            /// </summary>
            public DbSet<UserInfo> UserInfos { get; set; }
    
            /// <summary>
            /// TODO:當數據庫創建完成后, EF 創建一系列數據表,表名默認和 DbSet 屬性名相同。 集合屬性的名稱一般使用複數形式,但不同的開發人員的命名習慣可能不一樣,
    /// 開發人員根據自己的情況確定是否使用複數形式。 在定義 DbSet 屬性的代碼之後,添加下面代碼,對DbContext指定單數的表名來覆蓋默認的表名。
    /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<UserInfo>().ToTable("UserInfo"); } } }

    六、將上下文添加到 Startup.cs 中的依賴項注入:

            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                //注入EF Core數據庫上下文服務
                services.AddDbContext<SchoolUserInfoContext>(options =>
                    options.UseMySQL(Configuration.GetConnectionString("MySqlConnection")));
    
                services.AddControllersWithViews();
            }

    七、引入Layui樣式和js:

    前往官網下載Layui相關樣式和js包,下載地址:https://www.layui.com/

    Layui彈出層插件layer.js(有很多地方需要用到彈窗),下載地址:https://layer.layui.com/

    將相關文件存放到wwwroot文件下:

     

    將相關文件引入默認布局頁面中:

    八、 ASP.NET Core MVC 和 EF Core實現MySQL  CRUD功能:

    注意在這裏主要展示的EF Core與數據庫操作的部分代碼,詳細代碼可下載實例源碼查看。

    Create:

            /// <summary>
            /// 學生信息添加
            /// </summary>
            /// <param name="addUserInfo"></param>
            /// <returns></returns>
            public async Task<bool> Create(AddUserInfoViewModel addUserInfo)
            {
                try
                {
                    var userInfo=new UserInfo()
                    {
                        UserName = addUserInfo.UserName,
                        Sex = addUserInfo.Sex,
                        Hobby = addUserInfo.Hobby,
                        Phone = addUserInfo.Phone,
                        Description = addUserInfo.Description
                    };
    
                    _shoSchoolUserInfoContext.UserInfos.Add(userInfo);
    
                    await _shoSchoolUserInfoContext.SaveChangesAsync();
    
                    return true;
                }
                catch
                {
                    return false;
                }
            }

    Retrieve:

            /// <summary>
            /// 獲取用戶信息
            /// </summary>
            /// <param name="page">當前頁碼</param>
            /// <param name="limit">显示條數</param>
            /// <param name="userName">用戶姓名</param>
            /// <returns></returns>
            public async Task<PageSearchModel> GetPageListData(int page = 1, int limit = 15, string userName = "")
            {
                try
                {
                    List<UserInfo> listData;
                    var totalCount = 0;
                    if (!string.IsNullOrWhiteSpace(userName))
                    {
                        listData = await _shoSchoolUserInfoContext.UserInfos.Where(x => x.UserName.Contains(userName)).OrderByDescending(x => x.Id).Skip((page - 1) * limit).Take(limit).ToListAsync();
    
                        totalCount = _shoSchoolUserInfoContext.UserInfos
                            .Count(x => x.UserName.Contains(userName));
                    }
                    else
                    {
                        listData = await _shoSchoolUserInfoContext.UserInfos.OrderByDescending(x => x.Id).Skip((page - 1) * limit).Take(limit).ToListAsync();
    
                        totalCount = _shoSchoolUserInfoContext.UserInfos.Count();
                    }
    
                    return new PageSearchModel()
                    {
                        ResultMsg = "success",
                        Code = 200,
                        TotalCount = totalCount,
                        DataList = listData
                    };
                }
                catch (Exception e)
                {
                    return new PageSearchModel() { Code = 400, ResultMsg = e.Message };
                }
            }

    Update:

            /// <summary>
            /// 學生信息修改
            /// </summary>
            /// <param name="userInfo"></param>
            /// <returns></returns>
            public async Task<bool> Update(UserInfo userInfo)
            {
    
                try
                {
                    _shoSchoolUserInfoContext.UserInfos.Update(userInfo);
    
                    await _shoSchoolUserInfoContext.SaveChangesAsync();
    
                    return true;
                }
                catch
                {
                    return false;
                }
            }

    Delete:

            /// <summary>
            /// 學生信息刪除
            /// </summary>
            /// <param name="id"></param>
            /// <returns></returns>
            public async Task<bool> Delete(int? id)
            {
                try
                {
                    var searchUserInfo = await _shoSchoolUserInfoContext.UserInfos.FindAsync(id);
    
                    if (searchUserInfo == null)
                    {
                        return false;
                    }
    
                    _shoSchoolUserInfoContext.UserInfos.Remove(searchUserInfo);
                    await _shoSchoolUserInfoContext.SaveChangesAsync();
    
                    return true;
                }
                catch
                {
                    return false;
                }
            }

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

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

    透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

  • SpringAOP使用及源碼分析(SpringBoot下)_網頁設計

    SpringAOP使用及源碼分析(SpringBoot下)_網頁設計

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

    擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

    一、SpringAOP應用

    1. 先搭建一個SpringBoot項目
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.1.7.RELEASE</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    	<groupId>com.mmc</groupId>
    	<artifactId>springboot-study</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>springboot-study</name>
    	<description>Demo project for Spring Boot</description>
    
    	<properties>
    		<java.version>1.8</java.version>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		
    	</dependencies>
    	
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    </project>
    
    1. 定義一個業務邏輯類,作為切面
    public interface CalculationService {
    
        /**
         * 加法運算
         * @param x
         * @param y
         * @return
         */
        public Integer add(Integer x,Integer y);
    }
    
    /**
     * @description:
     * @author: mmc
     * @create: 2020-06-01 14:22
     **/
    @Service
    public class CalculationServiceImpl implements CalculationService {
    
        @Override
        public Integer add(Integer x, Integer y) {
            if(x==null||y==null){
                throw  new NullPointerException("參數不能為空");
            }
            return x+y;
        }
    }
    
    1. 定義一個切面類,添加通知方法
    • 前置通知(@Before):logStart:在目標方法(div)運行之前運行
    • 後置通知(@After):logEnd:在目標方法(add)運行結束之後運行(無論方法正常結束還是異常結束)
    • 返回通知(@AfterReturning):logReturn:在目標方法(add)正常返回之後運行
    • 異常通知(@AfterThrowing):logException:在目標方法(add)出現異常以後運行
    • 環繞通知(@Around):動態代理,手動推進目標方法運行(joinPoint.procced())
    
    /**
     * @description:  切面類
     * @author: mmc
     * @create: 2020-06-01 14:24
     **/
    @Aspect
    @Component
    public class LogAspects {
    
        //抽取公共的切入點表達式
        //1、本類引用
        //2、其他的切面引用
        @Pointcut("execution(public Integer com.mmc.springbootstudy.service.CalculationService.*(..))")
        public void pointCut(){};
    
        @Before("pointCut()")
        public void logStart(JoinPoint joinPoint){
            Object[] args = joinPoint.getArgs();
            System.out.println(""+joinPoint.getSignature().getName()+"運行。。。@Before:參數列表是:{"+Arrays.asList(args)+"}");
        }
    
        @After("pointCut()")
        public void logEnd(JoinPoint joinPoint){
            System.out.println(""+joinPoint.getSignature().getName()+"結束。。。@After");
        }
    
    
        //JoinPoint一定要出現在參數表的第一位
        @AfterReturning(value="pointCut()",returning="result")
        public void logReturn(JoinPoint joinPoint,Object result){
            System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:運行結果:{"+result+"}");
        }
    
        @AfterThrowing(value="pointCut()",throwing="exception")
        public void logException(JoinPoint joinPoint,Exception exception){
            System.out.println(""+joinPoint.getSignature().getName()+"異常。。。異常信息:{"+exception+"}");
        }
    }
    
    1. 寫一個controller測試
    @RequestMapping("/testaop")
       @ResponseBody
        public Integer testaop(Integer x,Integer y){
           Integer result = calculationService.add(x, y);
           return result;
       }
    
    1. 測試

    add運行。。。@Before:參數列表是:{[2, 3]}
    add結束。。。@After
    add正常返回。。。@AfterReturning:運行結果:{5}

    二、源碼分析

    主線流程圖:

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

    窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

    1. spring.factories文件里引入了AopAutoConfiguration類
    @Configuration
    @ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class })
    @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
    public class AopAutoConfiguration {
    
    	@Configuration
    	@EnableAspectJAutoProxy(proxyTargetClass = false)
    	//看配置文件,如果配置的spring.aop.proxy-target-class為false則引入JdkDynamicAutoProxyConfiguration
    	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
    			matchIfMissing = false)
    	public static class JdkDynamicAutoProxyConfiguration {
    
    	}
    
    	@Configuration
    	//開啟AspectJAutoProxy
    	@EnableAspectJAutoProxy(proxyTargetClass = true)
    	//看配置文件,如果配置的spring.aop.proxy-target-class為true則引入CglibAutoProxyConfiguration 
    	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
    			matchIfMissing = true)
    	public static class CglibAutoProxyConfiguration {
    
    	}
    
    }
    

    在包目錄下找到配置文件,並且發現他的值為true

    在上面的方法上有EnableAspectJAutoProxy註解,並傳入了proxyTargetClass=true

    1. 進入@EnableAspectJAutoProxy註解
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    //引入了AspectJAutoProxyRegistrar
    @Import({AspectJAutoProxyRegistrar.class})
    public @interface EnableAspectJAutoProxy {
        boolean proxyTargetClass() default false;
    
        boolean exposeProxy() default false;
    }
    
    1. 進入AspectJAutoProxyRegistrar類
    class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
        AspectJAutoProxyRegistrar() {
        }
    
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            //註冊了自動自動代理類
            AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
            AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
            if (enableAspectJAutoProxy != null) {
                if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                }
    
                if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                    AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
                }
            }
    
        }
    }
    
    1. 進入registerAspectJAnnotationAutoProxyCreatorIfNecessary方法裏面
     public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
            return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
        }
    

    可以看到返回了一個BeanDefinition,裏面的BeanClass類型是AnnotationAwareAspectJAutoProxyCreator,這個類看名字是一個AOP的動態代理創建類,裏面沒有啥可疑的方法。在IDEA里按Ctrl+H看他的繼承結構。有一個父類AbstractAutoProxyCreator,這個類實現了BeanPostProcessor接口。這個接口是Bean的擴展接口,在bean初始化完成後會調用到他的postProcessAfterInitialization(Object bean, String beanName)方法。

    1. 方法內容如下
     public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
            if (bean != null) {
                Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
                if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                    //如果有必要,進行包裝  
                    return this.wrapIfNecessary(bean, beanName, cacheKey);
                }
            }
    
            return bean;
        }
        
        protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
            if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
                return bean;
            } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
                return bean;
            } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
            //獲取切面的方法,第9點那裡展開討論
                Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
                if (specificInterceptors != DO_NOT_PROXY) {
                    this.advisedBeans.put(cacheKey, Boolean.TRUE);
                    //創建動態代理
                    Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                    this.proxyTypes.put(cacheKey, proxy.getClass());
                    return proxy;
                } else {
                    this.advisedBeans.put(cacheKey, Boolean.FALSE);
                    return bean;
                }
            } else {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return bean;
            }
        }
    
    1. 可以看出這裏已經在開始創建動態代理了
      protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
            if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
                AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);
            }
            //動態代理工廠
            ProxyFactory proxyFactory = new ProxyFactory();
            proxyFactory.copyFrom(this);
            if (!proxyFactory.isProxyTargetClass()) {
                if (this.shouldProxyTargetClass(beanClass, beanName)) {
                    proxyFactory.setProxyTargetClass(true);
                } else {
                    this.evaluateProxyInterfaces(beanClass, proxyFactory);
                }
            }
    
            Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);
            //切面那裡的方法
            proxyFactory.addAdvisors(advisors);
            proxyFactory.setTargetSource(targetSource);
            this.customizeProxyFactory(proxyFactory);
            proxyFactory.setFrozen(this.freezeProxy);
            if (this.advisorsPreFiltered()) {
                proxyFactory.setPreFiltered(true);
            }
            //獲取動態代理類
            return proxyFactory.getProxy(this.getProxyClassLoader());
        }
    
    1. 學過AOP的人都知道動態代理的方式有兩種,一種JDK代理,一種CGLIB動態代理。那麼Spring裏面是怎麼選擇的呢?答案就在這裏:
     public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
       // 1.config.isOptimize()是否使用優化的代理策略,目前使用與CGLIB
            // config.isProxyTargetClass() 是否目標類本身被代理而不是目標類的接口
            // hasNoUserSuppliedProxyInterfaces()是否存在代理接口
    
            if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
                return new JdkDynamicAopProxy(config);
            } else {
                Class<?> targetClass = config.getTargetClass();
                if (targetClass == null) {
                    throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
                } else {
                    //目標類不是接口或不是代理類就使用cglib代理
                    return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
                }
            }
        }
    
    1. Cglib的代理類是CglibAopProxy、ObjenesisCglibAopProxy,JDK的代理類是JdkDynamicAopProxy。在這些類裏面對目標類進行了代理,在執行方法的時候就是執行的代理類的方法,而實現了切面編程的效果。
    2. 主線流程就是這些了,還有一個沒說的就是我們如何獲取的切面方法,@Before(“pointCut()”)這些註解又是如何生效的?再回到AbstractAutoProxyCreator的wrapIfNecessary()方法
      裏面有這句代碼:
     Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
     
     
      @Nullable
        protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
            List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);
            return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
        }
        
        protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
            //查找候選的要切面附加的方法,這裏加進去的
            List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
            List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
            this.extendAdvisors(eligibleAdvisors);
            if (!eligibleAdvisors.isEmpty()) {
                eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);
            }
    
            return eligibleAdvisors;
        }
        
        
        
    
    1. 他會找到Aspect類,然後遍歷裏面的方法,並獲取Pointcut,然後構造出Advisor,加入到集合List advisors里,供動態代理時使用

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

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

    網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

  • Hystrix微服務容錯處理及回退方法源碼分析_貨運

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

    網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

    前言

    在 SpringCloud 微服務項目中,我們有了 Eureka 做服務的註冊中心,進行服務的註冊與發現和服務治理。使得我們可以摒棄硬編碼式的 ip:端口 + 映射路徑 來發送請求。我們有了 Feign 作為聲明式服務調用組件,可以像調用本地服務一樣來調用遠程服務。基於 Ribbon 我們又實現了客戶端負載均衡,輕鬆的在集群環境下選取合適的服務提供者。這樣看來我們的微服務貌似很完善了。是這樣的嗎?

    並非如此,想想我們在編碼過程中進行的健壯性檢查。類比一下服務與服務調用是否也應該更加健壯一些呢?我們目前的微服務在正常運行的時候是沒有問題的,但若是某個偏下游的服務提供者不可用,造成服務積壓,接連引起上游的服務消費者宕機,引法雪崩效應。是不是就顯得我們的微服務不堪一擊呢?因此我們需要一個組件來解決這樣的問題,前輩們參考生活中保險絲的原理做出了微服務中的保險絲-Hystrix熔斷器。下面讓我們來一起使用一下

    聲明:本文首發於博客園,作者:后青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 轉載請註明,謝謝!

    Hystrix簡介

    Hystrix主要實現了下面的功能:

    • 包裹請求:使用 HystrixCommand(或 HystrixObservableCommand) 包裹對依賴的調用邏輯。每個命令在獨立的線程中執行,使用了設計模式中的‘命令模式’
    • 跳閘機制:當某微服務的錯誤率超過一定閾值時,可以自動跳閘,停止請求該服務一段時間
    • 資源隔離:Hystrix 為每個微服務都維護了一個小型的線程池(或信號量)如果該線程池已滿,發往該依賴的請求就會被立即拒絕
    • 監控:Hystrix 可以近乎實時的監控運行指標和配置的變化,例如成功、失敗、超時和被拒絕的請求等
    • 回退機制:當請求成功、失敗、超時和被拒絕或者斷路器打開時,執行回退邏輯。回退邏輯可由開發人員自行提供
    • 自我修復:斷路器打開一段時間后,會進入‘半開’狀態,允許一個請求訪問服務提供方,如果成功。則關閉斷路器

    使用 Hystrix

    引入依賴

            <!-- 熔斷器 hystrix -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>
    

    在啟動類上添加 @EnableHystrix

    兩種情況下的回退方法

    非 Feign 調用下的回退方法

    編寫回退方法
    /**
     * getUserByAge 方法 Hystrix 回退方法
     * @param age
     * @return
     */
    public User getUserByAgeFallBack(Integer age){
        User user = new User();
        user.setName("默認用戶");
        user.setAge(age);
        return user;
    }
    
    在客戶端的方法上聲明
    @HystrixCommand(fallbackMethod = "getUserByAgeFallBack")
    

    測試:將服務提供方的代碼打斷點。調用服務消費方,會發現返回了默認用戶

    需要注意:

    1. 回退方法的返回值類型需要和原來方法返回值類型相同(否則會報 FallbackDefinitionException: Incompatible return types)
    2. 回退方法的參數列表也要和原來方法相同(否則會報 FallbackDefinitionException: fallback method wasn’t found: getUserByAgeFallBack([class java.lang.Integer]))
    3. 當我寫下第二句時,發現書中下一節介紹說可以通過在回退方法中添加第二個參數:ThrowEable 來捕獲異常,分析調用失敗的原因,我就知道我錯了。為了避免繼續得到錯誤的結論,我決定讀一讀 Hystrix 處理回退方法的源碼
    加點料:Hystrix 對回退方法的封裝的源碼如下:
    com.netflix.hystrix.contrib.javanica.utils.MethodProvider
    public FallbackMethod find(Class<?> enclosingType, Method commandMethod, boolean extended) {
    	// 首先判斷該方法的 HystrixCommand 註解上有沒有 defaultFallback / fallbackMethod 配置回退方法名稱
        if (this.canHandle(enclosingType, commandMethod)) {
        	// 調用 doFind 方法
            return this.doFind(enclosingType, commandMethod, extended);
        } else {
        	// 沒有配置的化就接着下一個判斷
            return this.next != null ? this.next.find(enclosingType, commandMethod, extended) : FallbackMethod.ABSENT;
        }
    }
    

    find 方法在用戶所請求的方法的 HystrixCommand 註解上有用 defaultFallback / fallbackMethod 配置回退方法名稱的時候,會調用 doFind 方法來尋找回退方法。該方法的參數有兩個,enclosingType 是用戶所請求的方法的類字節碼文件,commandMethod 是用戶所請求的方法

    首先通過 this.getFallbackName 獲取回退方法名稱,接着通過獲取 commandMethod 的參數類型們

    接着分兩種情況:

    1. 回調方法繼承於 commandMethod 且最後一個參數類型是 Throwable,則去掉回退方法參數列表中的 Throwable 類型進行匹配
    2. 回調方法不繼承於 commandMethod ,則存在兩個可能的參數類型列表: fallbackParameterTypes 和 extendedFallbackParameterTypes 前者是 commandMethod 是參數列表,後者是前者 + Throwable。然後兩個都進行匹配。接着使用 Java8 Optional API,按順序選取前者匹配到的方法 / 後者 / 空返回
    private FallbackMethod doFind(Class<?> enclosingType, Method commandMethod, boolean extended) {
        String name = this.getFallbackName(enclosingType, commandMethod);
        Class<?>[] fallbackParameterTypes = null;
        if (this.isDefault()) {
            fallbackParameterTypes = new Class[0];
        } else {
            fallbackParameterTypes = commandMethod.getParameterTypes();
        }
    
        if (extended && fallbackParameterTypes[fallbackParameterTypes.length - 1] == Throwable.class) {
            fallbackParameterTypes = (Class[])ArrayUtils.remove(fallbackParameterTypes, fallbackParameterTypes.length - 1);
        }
    
        Class<?>[] extendedFallbackParameterTypes = (Class[])Arrays.copyOf(fallbackParameterTypes, fallbackParameterTypes.length + 1);
        extendedFallbackParameterTypes[fallbackParameterTypes.length] = Throwable.class;
        Optional<Method> exFallbackMethod = MethodProvider.getMethod(enclosingType, name, extendedFallbackParameterTypes);
        Optional<Method> fMethod = MethodProvider.getMethod(enclosingType, name, fallbackParameterTypes);
        Method method = (Method)exFallbackMethod.or(fMethod).orNull();
        if (method == null) {
            throw new FallbackDefinitionException("fallback method wasn't found: " + name + "(" + Arrays.toString(fallbackParameterTypes) + ")");
        } else {
            return new FallbackMethod(method, exFallbackMethod.isPresent(), this.isDefault());
        }
    }
    

    由源碼可以得到結論:回退方法要麼參數列表和原始方法相同,要麼加且僅加一個類型為 Throwable 的參數。其他的都不行

    Feign 客戶端下的回退方法

    1. 設置:feign.hystrix.enabled: true

      ※回頭車貨運收費標準

      宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

    2. Feign 客戶端接口上的 @FeignClient 添加 fallback 屬性,指向回退類

    3. 回退類實現客戶端接口

    # feign的配置
    feign:
      hystrix:
        enabled: true # 打開 feign 的 hystrix 支持
    

    注意回退類加上 @Component 接口,避免因為 Spring 容器找不到該類而啟動報錯

    // Feign 客戶端接口上的 @FeignClient 添加 fallback 屬性,指向回退類
    @FeignClient(name = "SERVICE-PROVIDER", fallback = UserServiceFeignClientFallBack.class)
    public interface UserServiceFeignClient {
    
        @GetMapping("/api/v1/user/{age}")
        User getUser(@PathVariable("age") Integer age);
    
        /**
         * 用戶列表
         * @return
         */
        @GetMapping("/api/v1/users")
        List<User> getUsers();
    }
    // 回退類實現客戶端接口
    @Component 
    public class UserServiceFeignClientFallBack implements UserServiceFeignClient {
        @Override
        public User getUser(Integer age) {
            return null;
        }
    
        @Override
        public List<User> getUsers() {
            return null;
        }
    }
    

    當採用 Feign 客戶端來實現回退的時候,前面的捕捉異常方法就不起作用了,那我們應該如何來處理異常呢?可以使用 @FeignClient 的 fallbackFactory 屬性

    @FeignClient(name = "SERVICE-PROVIDER", fallbackFactory = UserServiceFallbackFactory.class)
    
    @Component
    @Slf4j
    public class UserServiceFallbackFactory implements FallbackFactory<UserServiceFeignClient> {
        @Override
        public UserServiceFeignClient create(Throwable t) {
            // 日誌最好寫在各個 fallback 方法中,而不要直接卸載 create方法中
            // 否則引用啟動時就會打印該日誌
    
            return new UserServiceFeignClient() {
                @Override
                public User getUser(Integer age) {
                    log.info("調用User服務提供者失敗", t);
                    User user = new User();
                    user.setName("默認用戶");
                    user.setAge(age);
                    return user;
                }
    
                @Override
                public List<User> getUsers() {
                    return null;
                }
            };
        }
    }
    

    注意: **fallback 和 fallbackFactory 屬性同時存在時,fallback 的優先級更高。因此開發中如果需要處理異常,只需配置 fallbackFactory 屬性即可 **

    避免業務異常走進回退方法

    在某些場景下,當發生業務異常時,我們並不想觸發 fallback。例如業務中判斷年齡 age 不能小於 1,否則拋出異常

    if(age < 1){
        throw new KeatsException(ExceptionEnum.NUM_LESS_THAN_MIN);
    }
    

    這時 Hystrix 會捕捉到異常然後執行 fallback 方法,我們可以通過下面兩個方法來避免:

    1. 繼承 HystrixBadRequestException 該類繼承自 RunntimeException
    2. 在 @HystrixCommand 添加屬性 ignoreExceptions = {KeatsException.class}

    為 Feign 禁用 Hystrix

    只要打開 feign 的 hystrix 支持開關,feign 就會使用斷路器包裹 feign 客戶端的所有方法,但很多場景並不需要這樣。該如何禁用呢?

    • 為指定客戶端禁用。需要藉助 Feign 的自定義配置。首先添加一個自定義配置類,然後配置到 @FeignClient 的 configuration 屬性中
    @Configuration
    public class FeignDisableHystrixConfiguration {
        @Bean
        @Scope("prototype")
        public Feign.Builder feignBuilder(){
            return Feign.builder();
        }
    }
    
    @FeignClient(name = "SERVICE-PROVIDER", configuration = {FeignDisableHystrixConfiguration.class})
    
    • 全局禁用: feign.hystrix.enabled: false

    本博客中所有示例代碼都已上傳至 github倉庫: https://github.com/keatsCoder/cloud-cli

    參考文獻:《Spring Cloud與Docker 微服務架構實戰》 — 周立

    碼字不易,如果你覺得讀完以後有收穫,不妨點個推薦讓更多的人看到吧!

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

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

    搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式