2016“鴻情•粵意”捷豹路虎媒體品鑒會在廣州舉行

得益於捷豹家族引以為傲的智能全鋁車身架構,全新捷豹XFL雖然擁有同級領先的修長車身,但整體表現依然輕盈靈活,表現出良好的動態性能。首次引入的後排開門警示系統,在監測到後方有車輛靠近時,會提醒後排乘客暫勿開啟車門,保護車內外人員安全,首次應用於捷豹車型上的nanoe™車內空氣凈化技術為乘客奉上純凈空氣。

12月18日,廣州鴻粵銳虎捷豹路虎4S中心聯手廣東鴻粵銳虎捷豹路虎4S中心舉辦了2016“鴻情•粵意”捷豹路虎媒體品鑒會。品鑒會上,來自廣州地區的30位主流媒體再度體驗了捷豹路虎車型的非凡魅力、進一步加深了對捷豹“性能美學”品牌DNA和路虎品牌始終如一的探索和發現精神的了解。

廣州鴻粵銳虎汽車銷售服務有限公司是集銷售、維修、零配件供應、信息反饋四位一體的4S標準捷豹路虎經銷店。公司在成立一年之內,先後榮獲“南區最佳售後經銷商”、“季度最佳區域售後服務獎”、“路虎中國誠信經銷商”、“年度最佳售後服務團隊”等獎項。這些殊榮,是鴻粵銳虎實力的見證,也是尊貴路虎車主們卓越服務的保證。

廣東鴻粵銳虎汽車銷售服務有限公司,是華南區最大的捷豹路虎旗艦4S店,也是廣州唯一一家擁有路虎越野體驗中心旗艦店。它致力於為廣東客戶帶來頂級豪華座駕體驗,把具有純正英倫皇室的血統和文化呈現給高端消費群體。

此次媒體品鑒會在雅韻軒精素茶館舉行,來自廣州地區網站、電台、自媒體等20家主流媒體共同體驗品鑒了包括捷豹XFL、路虎發現神行、路虎攬勝極光等捷豹路虎車型的卓越性能。

為中國市場專屬打造的全新捷豹XFL,表現出新格調運動商務座駕的獨特風範,就像一位身着套裝,舉止優雅的英倫紳士。在軸距增加140毫米后,其軸距達到3100毫米,帶來寬綽舒適的後排乘坐空間。得益於捷豹家族引以為傲的智能全鋁車身架構,全新捷豹XFL雖然擁有同級領先的修長車身,但整體表現依然輕盈靈活,表現出良好的動態性能。首次引入的後排開門警示系統,在監測到後方有車輛靠近時,會提醒後排乘客暫勿開啟車門,保護車內外人員安全,首次應用於捷豹車型上的nanoe™車內空氣凈化技術為乘客奉上純凈空氣。

路虎品牌旗下的全能全地形豪華SUV發現神行以優秀的全地形能力輕鬆征服各種複雜地形,在城市道路上,發現神行又表現出優異的駕乘舒適性和燃油經濟性。超凡多功能設計讓全家出遊非常實用。靈活的座椅布局營造超強的儲物能力,容納所有行李,42處儲物空間讓手機、平板電腦、玩具,保溫杯都有各自的安身之處。最多4個12V電源接口和4個USB接口,滿足多個电子設備同時充電的需求。攬勝極光又是另外一種風格,其動感時尚的設計超越了人們對SUV車型的傳統認知,引領時尚潮流,滿足年輕消費者對個性的追求,其內飾在簡潔精緻的基礎上,更凸顯了對豪華的追求,是追求時尚的不二之選。

通過現場品鑒體驗,廣州地區媒體記者親身領略了捷豹車型誘人設計、卓越性能、精湛工藝以及路虎旗下車型豪華理念和強大的全地形能力、增進了對捷豹路虎的品牌的了解,對於捷豹和路虎品牌在廣州地區的知名度提升起到重要作用。

同時,歷時一個多月的捷豹路虎系列區域活動也隨着這場品鑒會進入尾聲。北至呼和浩特、吉林,西至西安、甘肅,南至廣州、深圳,東至青島,捷豹和路虎已為全國20餘座城市的消費者帶去獨一無二的專屬體驗,一次次踐行了品牌深耕全國區域市場的承諾。未來,捷豹路虎也將持續在區域市場發力,不斷將更多更好的產品、更尊貴的體驗,帶給地區媒體與消費者。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

一部有內涵的車,這些功能必不可少

另外,相信有了感應式尾門的MpV,也沒有像全新GL8這麼人性化,電動尾門能夠在任意角度停止,方便了不同身高的用戶使用。除了感應式尾門,全新GL8幾乎將人性化配置做到極致,還配備了電動滑移門,其具備仿夾功能,在滑移門關閉過程中,如遇到一定阻力,滑移們會自動停止,避免傷害用戶。

不知道從什麼時候開始,很多車一上來就要用 “大氣”、“亮麗”等外在的詞去定義自身,殊不知隨着生活品質的不斷提高,用戶對車的 “內涵”也有更多的追求。而什麼樣的車才能算得上有“內涵”?這個應該是被定義,而非自定義。它需要經歷時間的積累以及用戶的考驗。

時光荏苒,從1999年上市至今,17年來別克GL8在受眾心中都享有極高口碑,前後經歷4次換代,從最初的“大轎子“大空間,到追求舒適與”陸上公務艙“,再到升級的陸尊、GL8商務版,科技配置一直在升級,品質不斷在提高,”內涵“可以說滲透到了全新GL8骨子里。全新GL8也一直身體力行的在詮釋真正的“內涵”不是簡單的堆砌,而是從“心”出發。

領先科技——安吉星4G LTE/WiFi、HUD抬頭显示、Carplay

在這個幾乎每個人都離不開網絡的時代,無論到哪裡,WiFi都顯得無比重要。全新一代GL8,自帶4G Wi-Fi熱點,速度高達100M/s,穩定可靠的4G LTE最多可支持7台移動設備同時接入,幾乎覆蓋了整車人的WiFi。讓全新GL8在點火的狀態下,成為了一個大的移動熱點,可隨時隨地暢享車內信息娛樂,大大豐富車生活,給用戶帶來了真切的需求。

第10代Onstar還可全時在線助理為用戶提供包括碰撞自動求助、緊急救援協助、安全保障、導航、車況監測、全聲控免提電話、手機應用等7大類26項服務,全新GL8簡直可以說應有盡有,讓移動辦公暢通無阻。

導航不必低頭的HUD抬頭显示配置,更是貼心的領先科技配備。HUD抬頭显示可以在很大程度上避免分散駕駛員的注意力,從而提高行車安全性。

而Carplay配置目前在很多高端車身上都使用了,如:法拉利、奧迪Q7等等,但在MpV車型裏面,全新GL8無疑是做了領頭羊,這也展現GL8不斷追求科技、提升“內涵”的表現。其可以利用智能手機的絕大部分常用App,比如:QQ音樂、導航等,這點相信大家都知道手機的App永遠比車載的要更新更方便。

再則, Carplay不僅可以給手機充電,同時能夠通過數據線將手機的基本功能,比如:撥打電話、切換音樂等,通過車輛的多功能方向盤直接控制,這科技,大大提高了行車安全性。

人性化配置——感應式尾門、電動滑移門、車輛自動上鎖

全新GL8的感應式尾門配置,給用戶帶來的將是很大的便捷服務。據說這個配置在多數MpV裏面是沒有的,如夏朗這款車型,便經常有車主抱怨雙手搬着行李箱的時候,沒有感應式尾門很不方便。另外,相信有了感應式尾門的MpV,也沒有像全新GL8這麼人性化,電動尾門能夠在任意角度停止,方便了不同身高的用戶使用。

除了感應式尾門,全新GL8幾乎將人性化配置做到極致,還配備了電動滑移門,其具備仿夾功能,在滑移門關閉過程中,如遇到一定阻力,滑移們會自動停止,避免傷害用戶;在滑移門開啟過程中,如果車窗處於打開狀態,滑移門開啟時將停止在安全位置,防止夾傷用戶手或頭部。

像GL8這樣,才是真正從每個用戶的角度考慮,如:車輛自動上鎖功能,當車輛熄火后,用戶帶着鑰匙,關車門離開車輛后,即使忘記上鎖,當用戶走出3米的距離車輛會自動上鎖,並鳴號提示。以及其音響和空調系統會在用戶撥打藍牙電話時自動降低風速和調低音量,並在掛電話后自動恢復。

周全防護——智慧安全系統

看到這裏,可能有人會說全新GL8配置在服務這方面確實是很人性化,但這麼大的車,駕駛方面有沒有保障。這方面無需擔心,GL8在安全配置方面一直以來都是“自帶光環”,全新GL8更是採用了智慧安全系統,擁有ACC自適應巡航、FCA 前方碰撞預警、CMB 碰撞自動剎車、LKA 車道保持、SBZA 側盲區預警、ApA自動泊車系統等。

當在城市中行駛時需要頻繁的停車和起步,如果你開的是GL8,ACC自適應巡航可以幫你實現“停車/起步”功能,以及“自動跟車”。

另外,FCA 前方碰撞預警則可以幫你綜合評估,前方慢速行駛或者靜止的車輛進行碰撞危險指數,並採取主動躲避動作,從而提高安全保護。

如果前方有障礙物,沒來得及採取操作,CMB 碰撞自動剎車系統不單可避免了車子不必要的損失,重要的是保護了乘員的安全。

再則,你是否曾有這樣的顧慮,自己的車是否有壓線,對方車輛是否會在毫不知情的情況下變線等等,開GL8有LKA 車道保持系統,從此,這些顧慮再也不會有。

當你在轉彎或車速比較高時,側盲區預警系統將時刻幫用戶注意着左右車輛;如果你是新手,發現停車位有點窄,那就用自動泊車。

全新GL8以上的大部分配置幾乎可以說是被很多同級別MpV忽視的,如今市面上很多車型,為了迎合大眾年輕時尚化的需求,紛紛在外觀造型等元素上堆砌、下“功夫”,但這些做法可能只會是曇花一現。唯有像全新GL8真正從用戶的角度出發考慮,讓用戶享受到最佳體驗,把每個細節、“內涵”做到極致,必不可少的功能一個都不少,才能贏得用戶持久的支持。

事實也在證明,在全新一代GL8還沒上市前,上汽通用別克GL8今年1-10月份在華累計銷量61516萬輛,比奧德賽和艾力紳銷量加起來還高出不少。相信這個不斷提升“內涵”、擁有高科技的全新GL8,會俘獲更多用戶的心。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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

無懼疫情泰國市場仍售野味 專家憂成武漢第二

摘錄自2020年3月9日星島日報報導

新冠肺炎疫情蔓延至今,仍未有緩解跡象,甚至蔓延至世界各地。在疫情暴發初期,不少專家都認為病毒源於野味,更直指是因為武漢海鮮市場暗售的野味。近日,澳洲節目揭發,泰國市場有商人無懼疫情持續,仍出售蜥蜴、猴子、野貓等野味,市場環境更是惡劣,野生動物均被困在狹窄的籠子。專家擔憂,恐會成為第二個武漢,並直指是「沉睡定時炸彈」。

據澳洲Nine電視台節目《60 Minutes》報道,美國環境與人權調查員Steven Galster與記者早前走訪泰國曼谷的洽圖洽市場(Chatuchak Market),發現有商人仍出售野生動物,包括野貓、狐狸、狨猴、蛇、蜥蜴、烏龜及穿山甲等野生動物。

雖然中國已關閉逾2萬類似的市場,但仍有其他亞洲國家經營。Galster又指為遏制病毒,應該關閉所有非法的野生動物交易市場,阻止病毒擴散與復發。

食品安全
生活環境
國際新聞
泰國
武漢肺炎
野味
傳統市場

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

【算法】leetcode算法筆記:二叉樹,動態規劃和回溯法

前言

寫的比較匆忙,測試用例是能全部跑通的,不過考慮內存和效率的話,還有許多需要改進的地方,所以請多指教

在二叉樹中增加一行

題目描述

給定一個二叉樹,根節點為第1層,深度為 1。在其第 d 層追加一行值為 v 的節點。

添加規則:給定一個深度值 d (正整數),針對深度為 d-1 層的每一非空節點 N,為 N 創建兩個值為 v 的左子樹和右子樹。

將 N 原先的左子樹,連接為新節點 v 的左子樹;

將 N 原先的右子樹,連接為新節點 v 的右子樹。

如果 d 的值為 1,深度 d – 1 不存在,則創建一個新的根節點 v,原先的整棵樹將作為 v 的左子樹。

Example

Input: 
A binary tree as following:
       4
     /   \
    2     6
   / \   / 
  3   1 5   

v = 1

d = 2

Output: 
       4
      / \
     1   1
    /     \
   2       6
  / \     / 
 3   1   5  

來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/add-one-row-to-tree

 
基本思想
二叉樹的先序遍歷   
代碼的基本結構
不是最終結構,而是大體的結構

/**
 * @param {number} cd:current depth,遞歸當前深度
 * @param {number} td:target depth, 目標深度
 */
var traversal = function (node, v, cd, td) {
    // 遞歸到目標深度,創建新節點並返回
  if (cd === td) {
    // return 新節點
  }
  // 向左子樹遞歸
  if (node.left) {
    node.left = traversal (node.left, v, cd + 1, td);
  }
  // 向右子樹遞歸
  if (node.right) {
    node.right = traversal (node.right, v, cd + 1, td);
  }
  // 返回舊節點
  return node;
};
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} v
 * @param {number} d
 * @return {TreeNode}
 */
var addOneRow = function (root, v, td) {
    // 從根節點開始遞歸
  traversal (root, v, 1, td);
  return root;
};

 

 

具體分析
我們可以分類討論,分三種情況處理  
第1種情況:目標深度<=當前遞歸路徑的最大深度 
處理方法:val節點替換該目標深度對應的節點,並且

  • 如果目標節點原來是左子樹,那麼重置后目標節點是val節點的左子樹

  • 如果目標節點原來是右子樹,那麼重置后目標節點是val節點的右子樹

 

第2種情況:目標深度>當前遞歸路徑的最大深度
閱讀題目發現,有這麼一個描述:“輸入的深度值 d 的範圍是:[1,二叉樹最大深度 + 1]”
所以呢,當目標深度恰好比當前路徑的樹的深度再深一層時,處理方式是:
在最底下那一層節點的左右分支新增val節點

 

第3種情況:目標深度為1

我們再分析題意,題目里說:“如果 d 的值為 1,深度 d – 1 不存在,則創建一個新的根節點 v,原先的整棵樹將作為 v 的左子樹。”

這說明當:目標深度為1時,我們的處理方式是這樣的 

 

全部代碼 

/**
 * @param {v} val,插入節點攜帶的值
 * @param {cd} current depth,遞歸當前深度
 * @param {td} target depth, 目標深度
 * @param {isLeft}  判斷原目標深度的節點是在左子樹還是右子樹
 */
var traversal = function (node, v, cd, td, isLeft) {
  debugger;
  if (cd === td) {
    const newNode = new TreeNode (v);
    // 如果原來是左子樹,重置后目標節點還是在左子樹上,否則相反
    if (isLeft) {
      newNode.left = node;
    } else {
      newNode.right = node;
    }
    return newNode;
  }
  // 處理上述的第1和第2種情況
  if (node.left || (node.left === null && cd + 1 === td)) {
    node.left = traversal (node.left, v, cd + 1, td, true);
  }
  if (node.right || (node.right === null && cd + 1 === td)) {
    node.right = traversal (node.right, v, cd + 1, td, false);
  }
  return node;
};
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} v
 * @param {number} d
 * @return {TreeNode}
 */
var addOneRow = function (root, v, td) {
  // 處理目標深度為1的情況,也就是上述的第3種情況
  if (td === 1) {
    const n = new TreeNode (v);
    n.left = root;
    return n;
  }
  traversal (root, v, 1, td);
  return root;
};

 

單詞拆分 

題目描述 

給定一個非空字符串 s 和一個包含非空單詞列表的字典 wordDict,判定 s 是否可以被空格拆分為一個或多個在字典中出現的單詞。

說明:

1.拆分時可以重複使用字典中的單詞。

2.你可以假設字典中沒有重複的單詞。

 

Example 

example1
輸入: s = "applepenapple", wordDict = ["apple", "pen"]
輸出: true
解釋: 返回 true 因為 "applepenapple" 可以被拆分成 "apple pen apple"。
注意: 你可以重複使用字典中的單詞。

example2
輸入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
輸出: false

來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/word-break

 

基本思想 

動態規劃

具體分析
動態規劃的關鍵點是:尋找狀態轉移方程
有了這個狀態轉移方程,我們就可以根據上一階段狀態和決策結果,去求出本階段的狀態和結果
然後,就可以從初始值,不斷遞推求出最終結果。
在這個問題里,我們使用一個一維數組來存放動態規劃過程的遞推數據
假設這個數組為dp,數組元素都為true或者false,
dp[N] 存放的是字符串s中從0到N截取的子串是否是“可拆分”的布爾值
讓我們從一個具體的中間場景出發來思考計算過程
假設我們有

wordDict = ['ab','cd','ef']
s ='abcdef'

並且假設目前我們已經得出了N=1到N=5的情況,而現在需要計算N=6的情況

或者說,我們已經求出了dp[1] 到dp[5]的布爾值,現在需要計算dp[6] = ?  
該怎麼計算呢?
現在新的字符f被加入到序列“abcde”的後面,如此以來,就新增了以下幾種6種需要計算的情況

A序列 + B序列
1.abcdef + ""
2.abcde + f
3.abcd + ef
4.abc + def
5.ab + cdef
6.a + bcdef
注意:當A可拆且B可拆時,則A+B也是可拆分的

 

從中我們不難發現兩點

  1. 當A可拆且B可拆時,則A+B也是可拆分的

  2. 這6種情況只要有一種組合序列是可拆分的,abcdef就一定是可拆的,也就得出dp[6] = true了

下面是根據根據已有的dp[1] 到dp[5]的布爾值,動態計算dp[6] 的過程

(注意只要計算到可拆,就可以break循環了)  
具體代碼

var initDp = function (len) {
  let dp = new Array (len + 1).fill (false);
  return dp;
};
/**
 * @param {string} s
 * @param {string[]} wordDict
 * @return {boolean}
 */
var wordBreak = function (s, wordDict) {
  // 處理空字符串
  if (s === '' && wordDict.indexOf ('') === -1) {
    return false;
  }
  const len = s.length;
  // 默認初始值全部為false
  const dp = initDp (len);
  const a = s.charAt (0);
  // 初始化動態規劃的初始值
  dp[0] = wordDict.indexOf (a) === -1 ? false : true;
  dp[1] = wordDict.indexOf (a) === -1 ? false : true;
  // i:end
  // j:start
  for (let i = 1; i < len; i++) {
    for (let j = 0; j <= i; j++) {
      // 序列[0,i] = 序列[0,j] + 序列[j,i]
      // preCanBreak表示序列[0,j]是否是可拆分的
      const preCanBreak = dp[j];
      // 截取序列[j,i]
      const str = s.slice (j, i + 1);
      // curCanBreak表示序列[j,i]是否是可拆分的
      const curCanBreak = wordDict.indexOf (str) !== -1;
      // 情況1: 序列[0,j]和序列[j,i]都可拆分,那麼序列[0,i]肯定也是可拆分的
      const flag1 = preCanBreak && curCanBreak;
      // 情況2: 序列[0,i]本身就存在於字典中,所以是可拆分的
      const flag2 = curCanBreak && j === 0;
      if (flag1 || flag2) {
        // 設置bool值,本輪計算結束
        dp[i + 1] = true;
        break;
      }
    }
  }
  // 返回最後結果
  return dp[len];
};

全排列 

題目描述

給定一個沒有重複数字的序列,返回其所有可能的全排列。

Example

輸入: [1,2,3]
輸出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/permutations

 

基本思想

回溯法

 

具體分析

  1. 深度優先搜索搞一波,index在遞歸中向前推進

  2. 當index等於數組長度的時候,結束遞歸,收集到results中(數組記得要深拷貝哦)

  3. 兩次数字交換的運用,計算出兩種情況

總結

想不通沒關係,套路一波就完事了 

具體代碼

var swap = function (nums, i, j) {
  const temp = nums[i];
  nums[i] = nums[j];
  nums[j] = temp;
};

var recursion = function (nums, results, index) {
  // 剪枝
  if (index >= nums.length) {
    results.push (nums.concat ());
    return;
  }
  // 初始化i為index
  for (let i = index; i < nums.length; i++) {
    // index 和 i交換??
    // 統計交換和沒交換的兩種情況
    swap (nums, index, i);
    recursion (nums, results, index + 1);
    swap (nums, index, i);
  }
};
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function (nums) {
  const results = [];
  recursion (nums, results, 0);
  return results;
};

 

 

 

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

【其他文章推薦】

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

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

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

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

豬瘟疫情持續 日本沖繩阿古豬赴離島避難

摘錄自2020年03月15日中央通訊社日本報導

日本沖繩縣沖繩本島豬瘟(Classical Swine Fever,又稱典型豬瘟,不是非洲豬瘟)疫情未見平息,為避免當地特有種「阿古豬」被注射疫苗,決定送30隻純種阿古豬到離島避難。

豬對沖繩民眾來說是不可或缺的食材,號稱「除了聲音以外全部都可以吃」,其中的「阿古豬」大約在600年前從中國來到沖繩後,被視為沖繩特有種,肉質在全日本都相當受歡迎。

為了降低感染豬瘟風險。沖繩縣政府把未接種疫苗的30頭純種阿古豬列為隔離對象,送往久米島上已有的隔離設施避難。沖繩本島全數豬隻將被注射豬瘟疫苗,久米島的隔離措施只是為了避免讓阿古豬被接種疫苗的暫時性措施,最後計畫是在久米島及其他沖繩縣離島建設阿古豬專用設施,屆時會再度移動阿古豬。

豬瘟是一種對肉豬或野豬具高度傳染力的疾病,但不會傳染給人類。根據日本家畜傳染病預防法規定,只要養豬場發現豬隻感染豬瘟,就必須撲殺養豬場內所有豬隻。

永續發展
糧食
動物福利
經濟動物
國際新聞
沖繩
阿古豬
豬瘟

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

SpringSecurity系列之自定義登錄驗證成功與失敗的結果處理

一、需要自定義登錄結果的場景

在我之前的文章中,做過登錄驗證流程的源碼解析。其中比較重要的就是

  • 當我們登錄成功的時候,是由AuthenticationSuccessHandler進行登錄結果處理,默認跳轉到defaultSuccessUrl配置的路徑對應的資源頁面(一般是首頁index.html)。
  • 當我們登錄失敗的時候,是由AuthenticationfailureHandler進行登錄結果處理,默認跳轉到failureUrl配置的路徑對應的資源頁面(一般是登錄頁login.html)。

但是在web應用開發過程中需求是千變萬化的,有時需要我們針對登錄結果做個性化處理,比如:

  • 我們希望不同的人登陸之後,看到不同的首頁
  • 我們應用是前後端分離的,驗證響應結果是JSON格式數據,而不是頁面跳轉

以上的這些情況,使用Spring Security作為安全框架的時候,都需要我們使用本節學到的知識進行自定義的登錄驗證結果處理。

二、自定義登陸成功的結果處理

為了滿足上面的需求,我們該如何去做呢?下面一小節我們來說明一下。AuthenticationSuccessHandler接口是Security提供的認證成功處理器接口,我們只需要去實現它即可。但是通常來說,我們不會直接去實現AuthenticationSuccessHandler接口,而是繼承SavedRequestAwareAuthenticationSuccessHandler 類,這個類會記住用戶上一次請求的資源路徑,比如:用戶請求books.html,沒有登陸所以被攔截到了登錄頁,當你萬成登陸之後會自動跳轉到books.html,而不是主頁面。

@Component
public class MyAuthenticationSuccessHandler 
                        extends SavedRequestAwareAuthenticationSuccessHandler {

    //在application配置文件中配置登陸的類型是JSON數據響應還是做頁面響應
    @Value("${spring.security.logintype}")
    private String loginType;

    private  static ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, 
                                        HttpServletResponse response, 
                                        Authentication authentication) 
                                        throws ServletException, IOException {

        if (loginType.equalsIgnoreCase("JSON")) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(AjaxResponse.success()));
        } else {
            // 會幫我們跳轉到上一次請求的頁面上
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
}
  • 在上面的自定義登陸成功處理中,既適應JSON前後端分離的應用登錄結果處理,也適用於模板頁面跳轉應用的登錄結果處理
  • ObjectMapper 是Spring Boot默認集成的JSON數據處理類庫Jackson中的類。
  • AjaxResponse是一個自定義的通用的JSON數據接口響應類。

三、自定義登錄失敗的結果處理

這裏我們同樣沒有直接實現AuthenticationFailureHandler接口,而是繼承SimpleUrlAuthenticationFailureHandler 類。該類中默認實現了登錄驗證失敗的跳轉邏輯,即登陸失敗之後回到登錄頁面。我們可以利用這一點簡化我們的代碼。

@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    //在application配置文件中配置登陸的類型是JSON數據響應還是做頁面響應
    @Value("${spring.security.logintype}")
    private String loginType;

    private  static ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response, 
                                        AuthenticationException exception) 
                                        throws IOException, ServletException {

        if (loginType.equalsIgnoreCase("JSON")) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(
                    objectMapper.writeValueAsString(
                            AjaxResponse.error(
                                    new CustomException(
                                        CustomExceptionType.USER_INPUT_ERROR,
                                        "用戶名或密碼存在錯誤,請檢查后再次登錄"))));
        } else {
            response.setContentType("text/html;charset=UTF-8");
            super.onAuthenticationFailure(request, response, exception);
        }

    }
}
  • 在上面的自定義登陸失敗處理中,既適應JSON前後端分離的應用登錄失敗結果處理,也適用於模板頁面跳轉應用的登錄失敗結果處理
  • 登陸失敗之後,將默認跳轉到默認的failureUrl,即登錄界面。

四、配置SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Resource
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http.csrf().disable() //禁用跨站csrf攻擊防禦,後面的章節會專門講解
           .formLogin()
           .successHandler(myAuthenticationSuccessHandler)
           .failureHandler(myAuthenticationFailureHandler)
           .defaultSuccessUrl("/index")//登錄認證成功后默認轉跳的路徑
           .failureUrl("/login.html") //登錄認證是被跳轉頁面
}
  • 將自定義的AuthenticationSuccessHandler和AuthenticationFailureHandler注入到Spring Security配置類中
  • 使用fromlogin模式,配置successHandler和failureHandler。
  • 並且配置defaultSuccessUrl和failureUrl

    期待您的關注

  • 博主最近新寫了一本書:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

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

【其他文章推薦】

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

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

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

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

神奇!澳洲高速公路驚見三眼蛇 3顆眼球皆具正常視力

摘錄自2020年3月24日自由時報報導

澳洲野生動物聞名世界,經常發現出乎人意料之外的特別生物;日前,澳洲北部高速公路上驚現一條三隻眼睛的蛇,北領地公園(The Northern Territory Parks)野生動物局將這隻特別的蛇拍照分享在社群網路上,引起廣泛討論。

根據《BBC》報導,相關單位為這條地毯莫瑞蟒(Carpet python)取了暱稱,叫做「蟒蛇蒙蒂(Monty Python)」;該品種蟒蛇為澳洲常見蛇類,於3月初被發現。專家分析後認為,蒙蒂頭部正中間多出來的一隻眼應該是自然突變。

公園野生動物局對蒙蒂展開研究,經X光掃描後,確認蒙蒂並非雙頭蛇,這特別的小生物反而更像擁有著「長有3個眼窩的頭骨,嵌著3顆可正常運作的眼球」。為何蒙蒂會有第三隻眼?佛萊推測,那可能是被牠吸收的雙胞胎手足所剩下的部分。這隻特別的蟒蛇在3月初被發現後,似乎因為畸形而導致進食困難,幾週後便過世了。

生態保育
國際新聞
澳洲
蟒蛇

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

法國因疫情全國封城 巴黎空污程度改善近三成

摘錄自2020年3月25日自由時報報導

為打擊武漢肺炎疫情,法國實施全國封城。根據大巴黎地區空氣品質監測局報告,這讓巴黎空污程度下降20%至30%,氮氧化物排放量更降低超過六成。

自3月17日生效的全國封城令讓無數汽車及貨車停駛,並大幅降低巴黎兩座機場的航班量。大巴黎地區空氣品質監測局(Airparif)報告指出,全國封城政策開始兩天後,巴黎大都會區空氣品質改善「20%至30%」,氮氧化物排放量下降超過六成。

大巴黎空品局表示,空污減少後,溫室氣體二氧化碳也隨之下降,凸顯了這兩個問題的關聯,這也顯示改善空氣品質會為氣候帶來好處。然而,空品局也表示,封城沒讓細懸浮微粒PM2.5跟懸浮微粒PM10顯著減少。

空氣污染
公害污染
國際新聞
巴黎
武漢肺炎
封城

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

中國自主研製出2.0版新能源汽車動力系統

中車株洲電機公司自主研製的新能源汽車第二代動力系統——輪邊永磁驅動系統22日成功下線。這實現了中國國產新能源汽車動力系統由第一代動力集中型向第二代動力分散型的革命性轉變,打破了對國外進口的依賴。

據中車株洲電機技術人員介紹,驅動系統改變了新能源汽車過去所採用的中央電機集中驅動方式,直接由2台安裝在電動汽車車橋兩側的永磁輪邊電機進行驅動。其中,永磁電機額定功率、轉速、扭矩等各項技術指標優異,最高效率可達96%,電機控制器輸出頻率高達600Hz,提升了電動汽車運行的平穩性與舒適性。

其扁平式的電機結構非常先進,裝車後與採用集中驅動的電動汽車相比,地板高度可以降低180-320mm,達到低地板水準,能夠取消上下車門處的臺階,車內後排坐椅也無需階梯狀佈置,可有效方便乘客特別是老人及小孩上下車,並縮短電動汽車停站時間,緩解公交壓力。

目前,中國新能源汽車第二代動力分散型驅動系統多依賴進口,且停留在三相非同步電機的水準。而中車株洲電機自主研製的第二代輪邊驅動系統成功將高鐵永磁技術運用於新能源汽車,通過以永磁高鐵核心動力技術為支撐,有效克服了永磁體失磁難題,保證了電機在各種極限條件下的磁體穩定。

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

【其他文章推薦】

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

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

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

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

netty源碼解析(4.0)-29 Future模式的實現

  Future模式是一個重要的異步併發模式,在JDK有實現。但JDK實現的Future模式功能比較簡單,使用起來比較複雜。Netty在JDK Future基礎上,加強了Future的能力,具體體現在:

  1. 更加簡單的結果返回方式。在JDK中,需要用戶自己實現Future對象的執行及返回結果。而在Netty中可以使用Promise簡單地調用方法返回結果。
  2. 更加靈活的結果處理方式。JDK中只提供了主動得到結果的get方法,要麼阻塞,要麼輪詢。Netty除了支持主動get方法外,還可以使用Listener被動監聽結果。
  3. 實現了進度監控。Netty提供了ProgressiveFuture、ProgressivePromise和GenericProgressiveFutureListener接口及其實現,支持對執行進程的監控。

  吹了那麼多牛,有一個關鍵問題還沒弄清楚:Future到底是幹嘛的?io.netty.util.concurrent.Future代碼的第一行註釋簡潔第回答了這個問題:Future就是異步操作的結果。這裏面有三個關鍵字:異步,操作,結果。首先,Future首先是一個“結果”;其次這個結果產生於一個“操作”,操作具體是什麼可以隨便定義;最後這個操作是”異步”執行的,這就意味着“操作”可能在另一個線程中併發執行,也可能隨後在同一個線程中執行,什麼時候產生結果是一件不確定的事。

  異步調用過程的一般過程是:調用方喚起一個異步操作,在接下來的某個恰當的時間點得到的異步操作操作的結果。要正確地完成上述步驟,需要解決以下幾個問題:

  • 怎樣維護這個調用狀態?
  • 如何獲取異步操作的結果?
  • 何時處理結果?

  io.netty.util.concurrent.DefaultPromise是Future的默認實現,以上三個問題的答案都能在這個類的代碼中找到。

 

DefaultPromise的派生體系

  下面是DefaultPromis及其父類,接口的聲明:

  public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> 

  public abstract class AbstractFuture<V> implements Future<V>

  public interface Promise<V> extends Future<V> 

  public interface Future<V> extends java.util.concurrent.Future<V> 

   可以看出,DefaultPromise派生自AbstractFuture類,並實現了Promise接口。抽象類型AbstractFuture派生自Future, 接口Promise派生自Future。Future派生自JDK的Future接口。

  和JDK的Future相比,Netty的Future接口增加一些自己的方法:

   /**
     當操作成功時返回true*/
    boolean isSuccess();

    /**
   只有當操作可以被取消時返回true
*/ boolean isCancellable(); /** 返回操作的異常*/ Throwable cause(); /** 添加一個監聽器到future。當操作完成(成功或失敗都算完成,此事isDone()返回true)時, 會通知這個監聽器。如果添加時操作已經完成,
   這個監聽器會立即被通知。
*/ Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener); /** 和上個方法一樣,可以同時添加多個監聽器*/ Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners); /** 刪除指定的監聽器, 如果這個監聽器還沒被通知的話。*/ Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener); /** 功能和上個方法一樣,可以同時刪除多個監聽器。*/ Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners); /** 同步等待直到操作完成。會被打斷。 */ Future<V> sync() throws InterruptedException; /**    同步等着知道操作完成。不會被打斷。 */ Future<V> syncUninterruptibly(); /** 同sync*/ Future<V> await() throws InterruptedException; /** 同synUniterruptibliy*/ Future<V> awaitUninterruptibly(); /** 等待,直到操作完成或超過指定的時間。會被打斷。*/ boolean await(long timeout, TimeUnit unit) throws InterruptedException; /** 同上*/ boolean await(long timeoutMillis) throws InterruptedException; /** 同上,不會被打斷。*/ boolean awaitUninterruptibly(long timeout, TimeUnit unit); /** 同上。*/ boolean awaitUninterruptibly(long timeoutMillis); /** 立即得到結果,不會阻塞。如果操作沒有完成或沒有成功,返回null*/ V getNow();

  Netty的Future最大的特點是增加了Listener被動接收任務完成通知,下面是兩個Listener接口的定義:

public interface GenericFutureListener<F extends Future<?>> extends EventListener {
    void operationComplete(F future) throws Exception;
}

public interface GenericProgressiveFutureListener<F extends ProgressiveFuture<?>> extends GenericFutureListener<F> {
    void operationProgressed(F future, long progress, long total) throws Exception;
}

  把一個listener添加到future之後。當異步操作完成之後,listener會被通知一次,同時會回調operationComplete方法。參數future是當前通知的future,這意味這,一個listener可以被添加到多個future中。

  當異步操作進度發送變化時,listener會被通知,同時會回調operationProgressed方法。progress是當前進度,total是總進度。progress==total表示操作完成。如果不知道何時完成操作progress=-1。

  Promise定義的方法:

    /**
    設置結果。把這個future設置為success,通知所有的listener,
  如果這個future已經是success或failed(操作已經完成),會拋出IllegalStateException
*/ Promise<V> setSuccess(V result); /**
同上。只有在操作沒有完成的時候才會生效,且會返回true */ boolean trySuccess(V result); /** 設置異常。把這個future設置為failed狀態,通知所有的listener.
如果這個future已經完成,會拋出IllegalStateException
*/ Promise<V> setFailure(Throwable cause); /** 同上。只有在操作沒有完成時才會生效,且返回ture */ boolean tryFailure(Throwable cause); /** 設置當前前future的操作不能被取消。這個future沒有完成且可以設置成功或這個future已經完成,返回true。否則返回false */ boolean setUncancellable();

 

DefaultPromise的設計

關鍵屬性

  volatile Object result;

  異步操作的結果。可以通過它的值知道當前future的狀態。

  final EventExecutor executor;

  通知listener的線程。

  Object listeners;

  維護添加到當前future的listener對象。

  short waiters;

  記錄當前真正等待結果的線程數量。

  boolean notifyingListeners;

  是否正在通知listener,防止多線程併發執行通知操作。

 

狀態管理

  future有4種狀態: 未完成, 未完成-不能取消,完成-成功,完成-失敗。使用isDone()判斷是否完成,它代碼如下:

1     @Override
2     public boolean isDone() {
3         return isDone0(result);
4     }
5 
6     private static boolean isDone0(Object result) {
7         return result != null && result != UNCANCELLABLE;
8     }

  第7行是判斷當前完成狀態的。result != null 且 result != UNCANCELLABLE,表示處於完成狀態。

  result默認是null, 此時future處於未完成狀態。可以使用setUncancellable方法把它設置成為完成-不能取消狀態。

1     @Override
2     public boolean setUncancellable() {
3         if (RESULT_UPDATER.compareAndSet(this, null, UNCANCELLABLE)) {
4             return true;
5         }
6         Object result = this.result;
7         return !isDone0(result) || !isCancelled0(result);
8     }

  第3行,使用原子操作設置result的值,只有result==null時才能把result設置成UNCANCELLABLE。當result==UNCANCELLABLE時,不允許取消異步操作。

  使用isSuccess方法判斷future是否處於完成-成功狀態。

1     @Override
2     public boolean isSuccess() {
3         Object result = this.result;
4         return result != null && result != UNCANCELLABLE && !(result instanceof CauseHolder);
5     }

  第4行是完成-成功狀態result的取值:除null, UNCANCELLABLE和CauseHolder對象的任何值。

  只有滿足isDone() && !isSuccess()時,future處於完成失敗狀態,可以使用cause方法獲取異常。

  調用setSuccess和trySuccess方法,能夠把狀態轉換成完成-成功。

 1     @Override
 2     public Promise<V> setSuccess(V result) {
 3         if (setSuccess0(result)) {
 4             notifyListeners();
 5             return this;
 6         }
 7         throw new IllegalStateException("complete already: " + this);
 8     }
 9     
10     private boolean setSuccess0(V result) {
11         return setValue0(result == null ? SUCCESS : result);
12     }

  第3行嘗試把狀態設置成完成-成功狀態。如果可以,在第4行通知所有的listener。否則第7行拋出錯誤。第11行給出了成功的默認值SUCCESS。trySuccess少了第7行,不會拋出異常。

  調用setFailure和tryFailure方法,能夠包狀態轉換成完成-失敗狀態。

 1     @Override
 2     public Promise<V> setFailure(Throwable cause) {
 3         if (setFailure0(cause)) {
 4             notifyListeners();
 5             return this;
 6         }
 7         throw new IllegalStateException("complete already: " + this, cause);
 8     }
 9 
10     private boolean setFailure0(Throwable cause) {
11         return setValue0(new CauseHolder(checkNotNull(cause, "cause")));
12     }

  第3行嘗試把專題設置成完成-失敗狀態。如果可以,在第4行通知所有listener。否則在第7行拋出異常。第11行把異常包裝成CauseHolder對象。tryFailure少了第7行,不會拋出異常。

 

獲取異步操作的結果

  當異步操作完成時,調用Promise提供的setSuccess和trySuccess設置成功的結果,調用setFailure和tryFailure設置異常結果。不論什麼結果,都會使用setValue0方法保存到result屬性上。

1     private boolean setValue0(Object objResult) {
2         if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
3             RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
4             checkNotifyWaiters();
5             return true;
6         }
7         return false;
8     }

  第2,3行,使用原子操作設置result的值,只有result==null或result==UNCANCELLABLE時,才能設置成功。如果設置成功,在第4行喚醒所有等待中的線程。可以使用get方法得到result值。如果isSucess()==true, result的值是SUCCESS或異步操作的結果。否則result的值是CauseHolder對象,此時可以調用cause方法得到異常對象。

  使用get或cause,只有在異步操作完成后才能順利得到結果。可以使用listener,被動等待操作完成通知。

 

使用listener異步通知處理結果

  Future的listener是必須實現GenericFutureListener接口,調用方法可以在operationComplete方法中處理異步操作的結果。

  listeners屬性用來保存使用addListener,addListeners方法添加到future的listener。listeners可能使用一個GenericFutureListener對象,也可能是一個GenericFutureListener數組。所有添加listener方法都會調用addListener0方法添加listener。

1     private void addListener0(GenericFutureListener<? extends Future<? super V>> listener) {
2         if (listeners == null) {
3             listeners = listener;
4         } else if (listeners instanceof DefaultFutureListeners) {
5             ((DefaultFutureListeners) listeners).add(listener);
6         } else {
7             listeners = new DefaultFutureListeners((GenericFutureListener<? extends Future<V>>) listeners, listener);
8         }
9     }

  這段代碼中使用了一個DefaultFutureListeners類,它內部維護了一個GenericFutureListener數組。

  當一次操作完成時,會調用notifyListeners方法通知listeners中所有的listener,並調用listener的operationComplete方法。只有當isDone()==true時才會調用notifyListeners方法。觸發點在下面的一些方法中:

  addListener, addListeners。

  setSuccess, trySuccess。

  setFailure, tryFailure。

  notifyListeners的代碼如下:

 1     private void notifyListeners() {
 2         EventExecutor executor = executor();
 3         if (executor.inEventLoop()) {
 4             final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
 5             final int stackDepth = threadLocals.futureListenerStackDepth();
 6             if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
 7                 threadLocals.setFutureListenerStackDepth(stackDepth + 1);
 8                 try {
 9                     notifyListenersNow();
10                 } finally {
11                     threadLocals.setFutureListenerStackDepth(stackDepth);
12                 }
13                 return;
14             }
15         }
16 
17         safeExecute(executor, new Runnable() {
18             @Override
19             public void run() {
20                 notifyListenersNow();
21             }
22         });
23     }

  這段代碼的作用是調用notifyListenersNow。如果當前線程就是executor的線程,在第9行直接調用notifyListenerNow,否則在第20行,把notifyListnerNow放在executor中執行。第4-7行和11行的作用是防止遞歸調用導致線程棧溢出,MAX_LISTENER_STACK_DEPTH就是listener遞歸調用的最大深度。

  notifyListenerNow的作用是,確保沒有併發執行notifyListener0或notifyListners0方法,且所有的listener只能被通知一次。

 1     private void notifyListenersNow() {
 2         Object listeners;
 3         synchronized (this) {
 4             // Only proceed if there are listeners to notify and we are not already notifying listeners.
 5             if (notifyingListeners || this.listeners == null) {
 6                 return;
 7             }
 8             notifyingListeners = true;
 9             listeners = this.listeners;
10             this.listeners = null;
11         }
12         for (;;) {
13             if (listeners instanceof DefaultFutureListeners) {
14                 notifyListeners0((DefaultFutureListeners) listeners);
15             } else {
16                 notifyListener0(this, (GenericFutureListener<? extends Future<V>>) listeners);
17             }
18             synchronized (this) {
19                 if (this.listeners == null) {
20                     // Nothing can throw from within this method, so setting notifyingListeners back to false does not
21                     // need to be in a finally block.
22                     notifyingListeners = false;
23                     return;
24                 }
25                 listeners = this.listeners;
26                 this.listeners = null;
27             }
28         }
29     }

  第3-11行的作用是防止多個線程併發執行11行之後的代碼。

  結合第5,9,10行可知, listeners中的所有listener只能被通知一次。

  13-17行,通知所有listeners。notifyListener0通知一個listener,notifyListeners0通知所有的listener。

  最後,18-27行,檢查在通知listeners的過程中,是否有新的listener被添加進來。如果有,25,26行得到所有新添加的listener並清空listeners屬性,13-17行繼續通知新添加的listener。否則,運行22,23行結束通知過程。

 1     private void notifyListeners0(DefaultFutureListeners listeners) {
 2         GenericFutureListener<?>[] a = listeners.listeners();
 3         int size = listeners.size();
 4         for (int i = 0; i < size; i ++) {
 5             notifyListener0(this, a[i]);
 6         }
 7     }
 8 
 9     @SuppressWarnings({ "unchecked", "rawtypes" })
10     private static void notifyListener0(Future future, GenericFutureListener l) {
11         try {
12             l.operationComplete(future);
13         } catch (Throwable t) {
14             logger.warn("An exception was thrown by " + l.getClass().getName() + ".operationComplete()", t);
15         }
16     }

  1-7行,notifyListeners0對每個listener調用一次notifyListener0,參數是當前的future。

  10-16,調用listener的operationComplete方法,捕獲了所有的異常,確保接下來可以繼續通知下一個listener。

 

使用await機制同步等待結果

  可以使用一系列的await,awaitXXX方法同步等待結果。這些方法可以分為: 能被打斷的,不能被打斷的。一直等待的,有超時時間的。await0方法是最複雜的等待實現,所有帶超時時間的await方法都會調用它。

 1     private boolean await0(long timeoutNanos, boolean interruptable) throws InterruptedException {
 2         if (isDone()) {
 3             return true;
 4         }
 5 
 6         if (timeoutNanos <= 0) {
 7             return isDone();
 8         }
 9 
10         if (interruptable && Thread.interrupted()) {
11             throw new InterruptedException(toString());
12         }
13 
14         checkDeadLock();
15 
16         long startTime = System.nanoTime();
17         long waitTime = timeoutNanos;
18         boolean interrupted = false;
19         try {
20             for (;;) {
21                 synchronized (this) {
22                     if (isDone()) {
23                         return true;
24                     }
25                     incWaiters();
26                     try {
27                         wait(waitTime / 1000000, (int) (waitTime % 1000000));
28                     } catch (InterruptedException e) {
29                         if (interruptable) {
30                             throw e;
31                         } else {
32                             interrupted = true;
33                         }
34                     } finally {
35                         decWaiters();
36                     }
37                 }
38                 if (isDone()) {
39                     return true;
40                 } else {
41                     waitTime = timeoutNanos - (System.nanoTime() - startTime);
42                     if (waitTime <= 0) {
43                         return isDone();
44                     }
45                 }
46             }
47         } finally {
48             if (interrupted) {
49                 Thread.currentThread().interrupt();
50             }
51         }
52     }

  這個方法返回的條件有: (1)isDone()==true;(2)允許被打斷(interrupted==true)的情況下被打斷;(3)已經超時。2-12行分別檢查了這3種情況。

  25,35行管理waiters屬性,這個屬性用來記錄當前正在等待的線程數。inWaiters方法正常情況下會把waiters加1,當檢查到waiters==Short.MAX_VALUE時會拋出異常,防止過多的線程等待。

  27行,調用wait等待,經歷waitTime后超時返回。在等待過程中,會被setValue0方法調用notifyAll喚醒。

  29-33行,處理被打斷的異常,如果運行被打斷,在30行拋出這個異常返回。

  38-45行,不論什麼原因線程被喚醒,檢查是否滿足返回條件,如果不滿足,繼續循環等待。

  沒有超時的wait方法實現要簡單一些,只需判讀返回條件(1)(2)。

 

跟蹤異步操作的執行進度

  如果想要跟蹤異步操作的執行進度,future需要換成DefaultProgressivePromise對象,listener需要換成GenericProgressiveFutureListener類型。DefaultProgressivePromise派生自DefaultPromise同時實現了ProgressivePromise接口。GenericProgressiveFutureListener接口派生自GenericFutureListener接口。

  ProgressivePromise定義了setProgress和tryProgress方法用來更新進度,是不是很眼熟,和Promise接口定義返回結果的方法很類似。

ProgressivePromise<V> setProgress(long progress, long total);
boolean tryProgress(long progress, long total);

  GenericProgressiveFutureListener定義了operationProgressed方法用來處理進度更新通知。

     void operationProgressed(F future, long progress, long total) throws Exception;

  

  DefaultProgressivePromise自己只實現了setProgress和tryProgress方法,其它都是復用了DefaultPromise的實現。

 1     @Override
 2     public ProgressivePromise<V> setProgress(long progress, long total) {
 3         if (total < 0) {
 4             // total unknown
 5             total = -1; // normalize
 6             if (progress < 0) {
 7                 throw new IllegalArgumentException("progress: " + progress + " (expected: >= 0)");
 8             }
 9         } else if (progress < 0 || progress > total) {
10             throw new IllegalArgumentException(
11                     "progress: " + progress + " (expected: 0 <= progress <= total (" + total + "))");
12         }
13 
14         if (isDone()) {
15             throw new IllegalStateException("complete already");
16         }
17 
18         notifyProgressiveListeners(progress, total);
19         return this;
20     }

  3-12行,檢查progress和total的合法性。

  14行,如isDone()==true,拋出異常。只有在操作還沒完成的是否更新進度才有意義。

  18行,調用notifyProgressiveListeners觸發進度更新通知,這個方法在DefaultPromise中實現。

  notifyProgressiveListeners實現了觸發進度更新通知的主要流程:

 1     void notifyProgressiveListeners(final long progress, final long total) {
 2         final Object listeners = progressiveListeners();
 3         if (listeners == null) {
 4             return;
 5         }
 6 
 7         final ProgressiveFuture<V> self = (ProgressiveFuture<V>) this;
 8 
 9         EventExecutor executor = executor();
10         if (executor.inEventLoop()) {
11             if (listeners instanceof GenericProgressiveFutureListener[]) {
12                 notifyProgressiveListeners0(
13                         self, (GenericProgressiveFutureListener<?>[]) listeners, progress, total);
14             } else {
15                 notifyProgressiveListener0(
16                         self, (GenericProgressiveFutureListener<ProgressiveFuture<V>>) listeners, progress, total);
17             }
18         } else {
19             if (listeners instanceof GenericProgressiveFutureListener[]) {
20                 final GenericProgressiveFutureListener<?>[] array =
21                         (GenericProgressiveFutureListener<?>[]) listeners;
22                 safeExecute(executor, new Runnable() {
23                     @Override
24                     public void run() {
25                         notifyProgressiveListeners0(self, array, progress, total);
26                     }
27                 });
28             } else {
29                 final GenericProgressiveFutureListener<ProgressiveFuture<V>> l =
30                         (GenericProgressiveFutureListener<ProgressiveFuture<V>>) listeners;
31                 safeExecute(executor, new Runnable() {
32                     @Override
33                     public void run() {
34                         notifyProgressiveListener0(self, l, progress, total);
35                     }
36                 });
37             }
38         }
39     }

  第3行,從listeners中選出GenericProgressiveFutureListener類型的listener。

  10-38行。調用notifyProgressiveListeners0, notifyProgressiveListener0通知進度跟新。11-17行,在當前線程中調用。

  19-37行,在executor中調用。notifyProgressiveListener0隻是簡單地調用listener的operationProgressed方法。notifyProgressiveListeners0是對每個listener調用一次notifyProgressiveListener0。

  和完成通知相比,進度更新通知要更加簡單。進度更新通知沒有處理併發問題,沒有處理棧溢出問題。

  

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

【其他文章推薦】

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

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

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

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!