標籤: USB CONNECTOR

  • 電動巴士開進太魯閣,花縣繼續推動綠能公車政策

    電動巴士開進太魯閣,花縣繼續推動綠能公車政策

    花蓮縣太魯閣客運公司今年繼續辦理增資,以採購新的電動巴士。自9月起,往來於花蓮市區、太魯閣、東華大學等地的公車路線,將有電動巴士陸續開始運行。

    花蓮縣政府的「綠能公車」政策於2014年核定太魯閣客運公司的301線、302線兩條公車路線上路,且須採用電動巴士運行。一開始因電動巴士品質不夠穩定,一度改租用柴油巴士,但受到反彈而停駛;之後在地方民代的協調下,301、302線規劃復駛,並透過增資採購7輛低底盤K9電動巴士車輛,重新展開電動巴士服務。

    太魯閣客運公司經理表示,本次採購的7輛K9電動巴士為台灣凱勝綠能科技所生產,車體採用全鋁合金打造,並配有緊急自動滅火系統、ABS防鎖剎車等。車輛搭載的電池為台灣長利科技的磷酸鋰鐵電池,每次充飽電的續航力可達250公里以上。

    K9巴士的性能佳,目前在苗栗客運、捷順交通、雲林客運、南台灣客運等公司都有同款車輛正在提供服務。太魯閣客運採購K9巴士時,為挑戰巴士的續航力,還安排一輛K9巴士直接從苗栗車體廠繞過北台灣、蘇花公路一路行駛到花蓮東華大學,總里程320.7公里,全程開冷氣;駛達目的地後,電池的電量還有43.9%之多。

    目前,301線已配合東華大學開學而正式復駛。太魯閣客運希望新城到天祥的302線也能在年底前復駛,預計將有6輛電動巴士投入服役。

    (照片來源:東華大學/太魯閣客運)

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

    【其他文章推薦】

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

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

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

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

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

    聚甘新

  • 電動車夯,立凱-KY 8月營收創歷史新高

    電池正極材料廠立凱-KY公布8月合併營收為1.47億元,年成長62.6%,月增23.1%,創下單月歷史新高;累計其前8月營收8.77億元、年增24.03%;公司指出,8月營收攀高,主因是受惠產品價格持續走揚、以及車電事業部新增加技術服務收入。

    立凱-KY並表示,公司在中國大陸與五龍電動車集團的合作亦有重要斬獲。9月4日在中國杭州開幕的二十國集團(G20)首腦峰會,中國作為接待地主國為體現國家綠色和可持續發展的理念,首次使用純電動汽車作為G20峰會首長貴賓接待用車,即採用210輛五龍旗下長江汽車自主研發的電動中巴車、電動商務車作為會議VIP接待用車和會議核心區域工作用車;而長江電動汽車不僅造型新穎,其科技配置、智慧駕馭模式更是為各國來賓所讚譽。

    立凱-KY已於上月底前完成與五龍的資本合作案,雙方預計將在資金及技術方面合作,以加速拓展大陸電動車市場版圖。

    (本文內容由授權提供)

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

    聚甘新

  • 軟件架構的核心思想

    軟件架構的核心思想

    目錄

    • 前言
    • 一、軟件架構的定義
    • 二、軟件架構與設計
    • 三、軟件架構的幾個方面
      • 系統
      • 結構
      • 環境
      • 利益相關者
    • 四、軟件架構的結構特徵
      • 運行時結構
      • 模塊結構
    • 五、軟件架構的質量屬性
      • 可修改性
      • 可測試性
      • 可擴展性
      • 性能
      • 可用性
      • 安全性
      • 可部署性
    • 六、其他常見概念
      • 內聚
      • 耦合
      • 企業架構
      • 系統架構

    前言

    welcome to chenqionghe’s blog,架構能力其實更像是一種內功,需要我們不斷地去學習,讓我們用一張正能量的圖片開啟美好的學習生活,let’s do it~

    一、軟件架構的定義

    架構是一個系統的基本組織,涵蓋所包含的組件,組件之間的關係、組件與環境的關係,以及指導架構設計和演進的原則等內容

    二、軟件架構與設計

    軟件架構和軟件設計中都有“設計”的意思,但與後者相比,前者具有更高的抽象性和更廣的範圍

    • 軟件架構關注如何對系統中的結構和交互進行較高級別的描述,它關注的是那些與系統骨架相關的決策問題,例如:功能、組織 、技術、業務和質量標準等。
    • 軟件設計關注構成系統的部件或組件,以及子系統是如何組織的,關注的問題通常更接近代碼或模塊本身,例如:
      • 將代碼分成哪些模塊?如何組織?
      • 給不同的功能分配哪些類(或模塊)?
      • 對於類“C”應該使用哪種設計模式?
      • 在運行時對象之間是如何交互的?傳遞什麼消息?如何實施交互?

    三、軟件架構的幾個方面

    系統

    系統是以特定方式組織的組件集合,以實現特定的功能。
    軟件系統是其軟件組件的集合。一個系統通常可以劃分為若干個子系統。

    結構

    結構是根據某個指導規則或原則來組成或組織在一起的一組元素的集合,可以是軟件或硬件系統。
    軟件架構可以根據觀察者的上下文展示各個層次的結構

    環境

    軟件系統所在的上下文或環境對其軟件架構有直接的影響。
    這樣的上下因素可以是技術、商業、專業、操作等

    利益相關者

    任何對某個系統及其成功與否感興趣或關心的個體或團體,都是利益相關者。
    例如:架構師、開發團隊、客戶、項目經理和營銷團隊等

    四、軟件架構的結構特徵

    運行時結構

    在運行時創建的對象及其之間的交互方式經常決定部署架構。
    部署架構與可擴展性、性能、安全性和交互操作性等質量屬性密切相關

    模塊結構

    為了分解任務,如何拆分代碼並把代碼組織到模塊和包中,這與系統的可維護性和可修改性密切相關,因為

    • 在代碼組織過程中考慮到可擴展性的話,通常會將父類放在單獨定義好的具有恰當文檔和配置的包中,這樣就可以輕易地通過增加外部模塊進行擴展,而不需要處理太多的依賴關係
    • 對於那些依賴於外部或第三方開發者(庫、框架等)的代碼,通過會根據提供的安裝或部署步驟,從外部源手動或自動地獲取並補丁全部各種依賴。此類代碼還提供多種文檔(例如README、INSTALL)等,它們清楚地記錄了這些步驟

    五、軟件架構的質量屬性

    質量屬性是系統的可度量和可測試的特性,可用於評估系統在其指定環境中的非功能性需求方面的達成情況

    可修改性

    定義為對系統進行修改的容易程度,以及系統對理髮進行調整的靈活性。
    這是討論的修改不光是代碼的修改、部署的修改,而是任何層次上的修改。
    一般架構師對可修改性的興趣點如下:

    • 難點:對系統進行修改的難易程度
    • 成本:進行修改需要的時間和資源
    • 風險:任何與系統修改相關的風險

    代碼可讀性越強,其可修改性就越強,代碼的可修改性與可讀性成正比

    可測試性

    指一個軟件系統支持通過測試來檢測故障的程度。
    可測試性也可以認為是一個軟件系統向最終用戶和集成測試隱藏了多少bug
    一個系統的可測試性越好,它能隱藏的bug就越少。

    可擴展性

    指系統能夠適應不斷增長的負載需求,但同時要保證可接受的處理性能,一般分為兩大類:

    • 橫向(水平)擴展性。意味着通過向其中添加更多多的計算節點。
    • 縱向(垂直)擴展性。涉及系統單個節點中資源的添加或移除

    性能

    指系統在給定的計算資源內完成的工作量,完成的工作量和計算資源的比例(work/unit)越高,性能越高。

    計算資源的單位有以下幾種

    • 響應時間。一個函數或執行單元運行所需要的時間。
    • 延遲。某個系統被激活並提供響應所需的時間。
    • 吞吐量。系統處理信息的某種比率。

    可用性

    指系統處於完全可操作狀態的程度,以便在任何時候獲得調用請求時可以執行的能力

    • 可靠性
      系統的可用性和可靠性密切相關,系統越可靠,可用性就越高。
    • 故障恢復能力
      影響可用性的另一個因素是從故障中恢復的能力,包括了故障檢測、故障恢復、故障預防
    • 數據一致性
      CAP定理指出,系統的可用性與其數據一致性有密切聯繫。一致性和可用性一般膛會同時成立,因為可能通信失敗,系統可以在一致性或可用性之間進行選擇

    安全性

    避免被未經過身份驗證的訪問損害數據和邏輯,同時繼續向通過誰的其他系統和角色提供服務的一種能力。

    可部署性

    指軟件從開發環境到產品運行環境移交的難易程度。
    有以下相關因素:

    • 模塊結構。
      將系統劃分為易於部署的一個個子單元,則部署會容易
    • 產品運行環境與開發環境。與
      開發環境結構非常相似的產品運行環境會使部署
    • 開發生態系統支持。
      為系統提供成熟的工具鏈支持,允許各種依賴關係自動建立和驗證等配置項內容,從而提高可部署性。
    • 標準配置。
      一個好的方式是開發者保持開發環境的配置結構和產品運行環境一致。
    • 標準化基礎設施。
      將部署保持在一個標準化的基礎設施上,提高可部署性
    • 容器使用
      隨着Docker容器技術的普及,可以規範軟件,減少啟動/停止的開銷,從而使部署更容易。

    六、其他常見概念

    內聚

    一個模塊內相關聯程度的度量,描述的是模塊內的功能聯繫
    若一個模塊之間各元素聯繫緊密,則內聚性就高(高內聚)
    如果能做到將模塊做成一個功能類聚、獨立性強、內部緊密結合才是一個理想的類聚模塊,這對初學都來說,非常不容易,不光是個人技術能力的挑戰,更是對某個領域業務水平的挑戰。

    耦合

    各模塊間相互聯繫緊密程度的一種度量。
    模塊之間聯繫少,耦合性就越低,模塊之間的相對獨立性就越強。

    企業架構

    企業架構是一個定義企業結構和行為的概念藍圖。它確定了企業結構、流程、人員和信息流動如何與其核心目標相一致,以便有效地實現當前和未來的目標

    系統架構

    系統架構是系統的基本組織形式,由其結構和行為視圖表示。該結構由兩部分確定:構成系統的組件和組件的行為。組件的行為是指組件之間的交互,以及組件與外部系統之間的交互

    以上內容由chenqionghe整理,參考《軟件架構-Python語言實現》,light weight baby~

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

    【其他文章推薦】

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

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

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

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

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

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

    聚甘新

  • spring源碼分析——BeanPostProcessor接口

    spring源碼分析——BeanPostProcessor接口

     

      BeanPostProcessor是處理bean的後置接口,beanDefinitionMaps中的BeanDefinition實例化完成后,完成populateBean,屬性設置,完成

    初始化后,這個接口支持對bean做自定義的操作。

    一:BeanPostProcessor的使用

    定義一個測試用的model對象,name屬性默認為hello

    public class BeanDemo {
    
    	private String name = "hello";
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	@Override
    	public String toString() {
    		final StringBuffer sb = new StringBuffer("BeanDemo{");
    		sb.append("name='").append(name).append('\'');
    		sb.append('}');
    		return sb.toString();
    	}
    }
    

      

    自定義一個MyBeanPostProcessor類,實現BeanPostProcessor接口

    @Service
    public class MyBeanPostProcessor implements BeanPostProcessor {
    
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		return null;
    	}
    
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		if(beanName.equals("beanDemo")){
    			BeanDemo beanDemo = (BeanDemo)bean;
    			beanDemo.setName("kitty");
    			return beanDemo;
    		}
    		return bean;
    	}
    }
    

      

     

     

    從運行結果看,spring中維護的beanName為beanDemo的對象,name屬性為ketty

     

     

    二:看看源碼怎麼實現的

    1:實例化並且註冊到beanPostProcessors集合中

     

     

     

     

    主要的實例化邏輯在這個接口,這個接口的作用就是把所有實現BeanPostProcessor接口的類實例化,然後註冊到 beanPostProcessors這個緩存中

     

     

    	public static void registerBeanPostProcessors(
    			ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
    
    		// 獲取所有實現接口BeanPostProcessor的beanName
    		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
    
    		// Register BeanPostProcessorChecker that logs an info message when
    		// a bean is created during BeanPostProcessor instantiation, i.e. when
    		// a bean is not eligible for getting processed by all BeanPostProcessors.
    		int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
    		beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
    
    		// Separate between BeanPostProcessors that implement PriorityOrdered,
    		// Ordered, and the rest.
    		/**
    		 * 把實現PriorityOrdered 和 Ordered 和 其他的處理器分開
    		 */
    		List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    		List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
    		List<String> orderedPostProcessorNames = new ArrayList<>();
    		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    		/**
    		 * 1:遍歷集合postProcessorNames
    		 * 2:判斷類型,如果是PriorityOrdered,則實例化對象放入priorityOrderedPostProcessors集合,
    		 * Ordered 則放入orderedPostProcessorNames集合,其他的放入nonOrderedPostProcessorNames集合
     		 */
    		for (String ppName : postProcessorNames) {
    			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    				BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    				priorityOrderedPostProcessors.add(pp);
    				if (pp instanceof MergedBeanDefinitionPostProcessor) {
    					internalPostProcessors.add(pp);
    				}
    			}
    			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
    				orderedPostProcessorNames.add(ppName);
    			}
    			else {
    				nonOrderedPostProcessorNames.add(ppName);
    			}
    		}
    
    		// First, register the BeanPostProcessors that implement PriorityOrdered.
    		// 首先對priorityOrderedPostProcessors集合中實例對象排序,然後註冊,放入beanFactory中緩存下來
    		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    		registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
    
    		// Next, register the BeanPostProcessors that implement Ordered.
    		// 然後再實例化實現Ordered接口的對象,完成註冊
    		List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
    		for (String ppName : orderedPostProcessorNames) {
    			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    			orderedPostProcessors.add(pp);
    			if (pp instanceof MergedBeanDefinitionPostProcessor) {
    				internalPostProcessors.add(pp);
    			}
    		}
    		sortPostProcessors(orderedPostProcessors, beanFactory);
    		registerBeanPostProcessors(beanFactory, orderedPostProcessors);
    
    		// Now, register all regular BeanPostProcessors.
    		// 最後實例化什麼都沒有實現的,完成實例化並註冊
    		List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
    		for (String ppName : nonOrderedPostProcessorNames) {
    			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    			nonOrderedPostProcessors.add(pp);
    			if (pp instanceof MergedBeanDefinitionPostProcessor) {
    				internalPostProcessors.add(pp);
    			}
    		}
    		registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
    
    		// Finally, re-register all internal BeanPostProcessors.
    		// 最後再次註冊內部postProcessor
    		sortPostProcessors(internalPostProcessors, beanFactory);
    		registerBeanPostProcessors(beanFactory, internalPostProcessors);
    
    		// Re-register post-processor for detecting inner beans as ApplicationListeners,
    		// moving it to the end of the processor chain (for picking up proxies etc).
    		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
    	}
    

      

     

     

    定義四類容器,高優先級有序、有序、無序、內部

     

     分類放入四種容器:

     

     

    註冊BeanPostProcessor,將實現BeanPostProcessor接口的對象放入beanPostProcessors緩存中

     

     

     

     

     

     

    註冊完PriorityOrdered的實現類后,再處理Ordered的實現類

     

     

    註冊什麼都沒有實現的BeanPostProcessor接口實現類,

     

     

    最後註冊內部的BeanPostProcessor對象

     

     到這裏BeanPostProcessor的實例化以及註冊工作完成,在beanFactory的beanPostProcessors集合中已經緩存了所有的beanPostProcessor的對象

     

    2:BeanPostProcessor的使用

    因為這個接口是bean的後置接口,所以需要bean創建並初始化完成,才可以發揮作用,上一步的緩存只是埋好點,以備使用,因為bean的實例化流程我們

    還沒有分析,這裏直接看一下怎麼使用的

     

     

    我們看一下init方法后的攔截,因為這個時候已經init完成,可以在後置接口中對bean做一下修改的操作

     

     

    調用到我們自定義的MyBeanPostProcessor實現類:

     

     

    把這個beanDemo對象屬性修改一下,修改完,再返回,將這個對象緩存到spring的一級緩存中。

     

     

    總結:

      BeanPostProcessor接口主要是對bean對象做一些自定義的操作,修改bean對象的信息,aop代理也是通過這種方式實現的,

    在refresh的registryBeanPostProcessor方法中實例化BeanPostProcessor對象,並且註冊到beanFactory容器的beanPostProcessors的緩存中,

    然後在後續的操作中攔截使用。

     

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

    【其他文章推薦】

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

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

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

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

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

    聚甘新

  • [經驗棧]C#監測IPv4v6網速及流量

    [經驗棧]C#監測IPv4v6網速及流量

    1、前言

      最近做項目需要用到監測網速及流量,我經過百度和牆內谷歌都沒能快速發現監測IPV6流量和網速的用例;也經過自己的一番查詢和調試,浪費了不少時間,現在作為經驗分享出來希望大家指正。

    2、C#代碼

    using System.Net.NetworkInformation;
    using System.Timers;
    
    namespace Monitor
    {
        public class MonitorNetwork
        {      
            public string UpSpeed { get; set; }   
            public string DownSpeed { get; set; }
            public string AllTraffic { get; set; }            
            private string NetCardDescription { get; set; }    
            //建立連接時上傳的數據量
            private long BaseTraffic { get; set; }    
            private long OldUp { get; set; }    
            private long OldDown { get; set; }
            private NetworkInterface networkInterface { get; set; }
            private Timer timer = new Timer() { Interval = 1000 };
        
            public void Close()
            {
                timer.Stop();   
            }
        
            public MonitorNetwork(string netCardDescription)
            {   
                timer.Elapsed += Timer_Elapsed;    
                NetCardDescription = netCardDescription;    
                timer.Interval = 1000;     
            }
    
            public bool Start()
            {
                networkInterface = null;    
                NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();    
                foreach (var var in nics)
                {
                    if (var.Description.Contains(NetCardDescription))
                    {
                        networkInterface = var;
                        break;
                    }
                }    
                if (networkInterface == null)
                {
                    return false;
                }
                else
                {    
                    BaseTraffic = (networkInterface.GetIPStatistics().BytesSent +
                                   networkInterface.GetIPStatistics().BytesReceived);    
                    OldUp = networkInterface.GetIPStatistics().BytesSent;    
                    OldDown = networkInterface.GetIPStatistics().BytesReceived;   
                    timer.Start();    
                    return true;
                }
        
            }
    
            private string[] units = new string[] {"KB/s","MB/s","GB/s" };
    
            private void CalcUpSpeed()
            {
                long nowValue = networkInterface.GetIPStatistics().BytesSent;    
                int num = 0;
                double value = (nowValue - OldUp) / 1024.0;
                while (value > 1023)
                {
                    value = (value / 1024.0);
                    num++;
                }   
                UpSpeed = value.ToString("0.0") + units[num];    
                OldUp = nowValue;    
            }
        
            private void CalcDownSpeed()
            {
                long nowValue = networkInterface.GetIPStatistics().BytesReceived;   
                int num = 0;
                double value = (nowValue - OldDown) / 1024.0;     
                while (value > 1023)
                {
                    value = (value / 1024.0);
                    num++;
                }    
                DownSpeed = value.ToString("0.0") + units[num];    
                OldDown = nowValue;    
            }
        
            private string[] unitAlls = new string[] { "KB", "MB", "GB" ,"TB"};
        
            private void CalcAllTraffic()
            {
                long nowValue = OldDown+OldUp;    
                int num = 0;
                double value = (nowValue- BaseTraffic) / 1024.0;
                while (value > 1023)
                {
                    value = (value / 1024.0);
                    num++;
                }   
                AllTraffic = value.ToString("0.0") + unitAlls[num];
            }
    
            private void Timer_Elapsed(object sender, ElapsedEventArgs e)
            {
                CalcUpSpeed();
                CalcDownSpeed();
                CalcAllTraffic();
            }
        }
    }
    

    3、胡說八道

      雖然沒能直接快速地百度到方法,但是實現這個需求的時候,心裏是有個譜,Windows系統能監測到這個網速和流量,沒理由實現不了,只需要一個方法將這個信息讀取出來就好。最後實現這個需求是利用了System.Net.NetworkInformation這個程序集,但是這個程序集沒有隻接提供網速監測的方法,而是提供了接收和發送數據量的屬性,需要自己計算出即使網速,所以這個網速不是特別的準確。

      這個程序集其實一開始就看到了,前輩方法中使用的是IPv4InterfaceStatistics類中的BytesReceived屬性和BytesSent屬性實現的,但是在這個程序集里沒有對應的IPv6類,恍恍惚惚。

      然後呢,我就下意識以為這個程序集比較老舊,不支持IPv6統計信息讀取,然後也是各種搜索無果,之後呢不死心想再來研究研究,東點點西瞅瞅,然後在NetworkInterface 類中發現了一個GetIPStatistics()方法,它的描述是“獲取此 NetworkInterface 實例的 IP 統計信息。”。

      然後就順理成章的事了,根據GetIPStatistics()返回的IPInterfaceStatistics實例中的BytesReceived屬性和BytesSent屬性就能獲取到收發的數據總量,然後根據這個信息就能計算出大約的網速。

      經測試,利用IPInterfaceStatistics實例是能讀取到IPv4和IPv6的總數據量的,因為這次的需求就是監測總量,如果需要單獨監測IPv6的可以用總量減去IPv4部分。

    4、後記

    ​  老師以前喊我認真念書,我心想有百度還不夠嗎,再念能有百度聰明,有百度懂得多,後來漸漸明白,百度懂得多都是前輩的搬磚添瓦來的,共勉。

    參考資料

      System.Net.NetworkInformation 命名空間

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

    聚甘新

  • 【Spring】循環依賴 Java Vs Spring

    菜瓜:水稻,這次我特意去看了java的循環依賴

    水稻:喲,有什麼收穫

    菜瓜:兩種情況,構造器循環依賴,屬性循環依賴

    • 構造器循環依賴在邏輯層面無法通過。對象通過構造函數創建時如果需要創建另一個對象,就會存在遞歸調用。棧內存直接溢出
    • 屬性循環依賴可以解決。在對象創建完成之後通過屬性賦值操作。
    • package club.interview.base;
      
      /**
       * 構造器循環依賴 - Exception in thread "main" java.lang.StackOverflowError
       * toString()循環打印也會異常 - Exception in thread "main" java.lang.StackOverflowError
       * @author QuCheng on 2020/6/18.
       */
      public class Circular {
      
          class A {
              B b;
      
      //        public A() {
      //            b = new B();
      //        }
      
      //        @Override
      //        public String toString() {
      //            return "A{" +
      //                    "b=" + b +
      //                    '}';
      //        }
          }
      
          class B {
              A a;
      
      //        public B() {
      //            a = new A();
      //        }
      
      //        @Override
      //        public String toString() {
      //            return "B{" +
      //                    "a=" + a +
      //                    '}';
      //        }
          }
      
          private void test() {
              B b = new B();
              A a = new A();
              a.b = b;
              b.a = a;
              System.out.println(a);
              System.out.println(b);
          }
      
          public static void main(String[] args) {
              new Circular().test();
          }
      }

    水稻:厲害啊,Spring也不支持構造函數的依賴注入,而且也不支持多例的循環依賴。同樣的,它支持屬性的依賴注入。

    • 看效果 – 如果toString()打印同樣會出現棧內存溢出。
    • package com.vip.qc.circular;
      
      import org.springframework.stereotype.Component;
      
      import javax.annotation.Resource;
      
      /**
       * @author QuCheng on 2020/6/18.
       */
      @Component("a")
      public class CircularA {
      
          @Resource
          private CircularB circularB;
      
      //    @Override
      //    public String toString() {
      //        return "CircularA{" +
      //                "circularB=" + circularB +
      //                '}';
      //    }
      }
      
      
      package com.vip.qc.circular;
      
      import org.springframework.stereotype.Component;
      
      import javax.annotation.Resource;
      
      /**
       * @author QuCheng on 2020/6/18.
       */
      @Component("b")
      public class CircularB {
      
          @Resource
          private CircularA circularA;
      
      //    @Override
      //    public String toString() {
      //        return "CircularB{" +
      //                "circularA=" + circularA +
      //                '}';
      //    }
      }
      
      
          @Test
          public void testCircular() {
              String basePackages = "com.vip.qc.circular";
              new AnnotationConfigApplicationContext(basePackages);
          }

    菜瓜:看來spring的實現應該也是通過屬性注入的吧

    水稻:你說的對。先給思路和demo,之後帶你掃一遍源碼,follow me !

    • spring的思路是給已經初始化的bean標記狀態,假設A依賴B,B依賴A,先創建A
      • 先從緩存容器(總共三層,一級拿不到就拿二級,二級拿不到就從三級緩存中拿正在創建的)中獲取A,未獲取到就執行創建邏輯
      • 對象A在創建完成還未將屬性渲染完之前標記為正在創建中,放入三級緩存容器。渲染屬性populateBean()會獲取依賴的對象B。
      • 此時B會走一次getBean邏輯,B同樣會先放入三級緩存,然後渲染屬性,再次走getBean邏輯注入A,此時能從三級緩存中拿到A,並將A放入二級容器。B渲染完成放入一級容器
      • 回到A渲染B的方法populateBean(),拿到B之後能順利執行完自己的創建過程。放入一級緩存
    •  為了證實結果,我把源碼給改了一下,看結果

      • package com.vip.qc.circular;
        
        import org.springframework.stereotype.Component;
        
        import javax.annotation.Resource;
        
        /**
         * @author QuCheng on 2020/6/18.
         */
        @Component("a")
        public class CircularA {
        
            @Resource
            private CircularB circularB;
        
            @Override
            public String toString() {
                return "CircularA{" +
                        "circularB=" + circularB +
                        '}';
            }
        }
        
        
        package com.vip.qc.circular;
        
        import org.springframework.stereotype.Component;
        
        import javax.annotation.Resource;
        
        /**
         * @author QuCheng on 2020/6/18.
         */
        @Component("b")
        public class CircularB {
        
            @Resource
            private CircularA circularA;
        
            @Override
            public String toString() {
                return "CircularB{" +
                        "circularA=" + circularA +
                        '}';
            }
        }
        
        
        測試代碼
        @Test
            public void testCircular() {
                String basePackages = "com.vip.qc.circular";
                new AnnotationConfigApplicationContext(basePackages);
        }
        
        測試結果(我改過源碼了)
        ---- 
        將a放入三級緩存
        將b放入三級緩存
        將a放入二級緩存
        將b放入一級緩存
        從二級緩存中拿到了a
        將a放入一級緩存

          

    • 再看源碼
      • 關鍵類處理getSingleton邏輯 – 緩存容器
        • public class DefaultSingletonBeanRegistry 
          
            /** Cache of singleton objects: bean name to bean instance. */
            // 一級緩存
              private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
          
              /** Cache of singleton factories: bean name to ObjectFactory. */
            // 三級緩存
              private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
          
              /** Cache of early singleton objects: bean name to bean instance. */
            // 二級緩存
              private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
      • 主流程 AbstractApplicationContext#refresh() -> DefaultListableBeanFactory#preInstantiateSingletons() -> AbstractBeanFactory#getBean() & #doGetBean()
        • protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
             /**
              * 處理FactoryBean接口名稱轉換 {@link BeanFactory#FACTORY_BEAN_PREFIX }
              */
             final String beanName = transformedBeanName(name);
                  ...
             // ①從緩存中拿對象(如果對象正在創建中且被依賴注入,會放入二級緩存)
             Object sharedInstance = getSingleton(beanName);
             if (sharedInstance != null && args == null) {
                ...
             }else {      
                      ...
                   if (mbd.isSingleton()) {
                     // ② 將創建的對象放入一級緩存
                      sharedInstance = getSingleton(beanName, () -> {
                         try {
                               // ③ 具體創建的過程,每個bean創建完成之後都會放入三級緩存,然後渲染屬性
                            return createBean(beanName, mbd, args);
                         }catch (BeansException ex) {
                           ...
             ...
             return (T) bean;
          } 
      • ①getSingleton(beanName) – 二級緩存操作
        • protected Object getSingleton(String beanName, boolean allowEarlyReference) {
             // 實例化已經完成了的放在singletonObjects
             Object singletonObject = this.singletonObjects.get(beanName);
             // 解決循環依賴
             if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
                synchronized (this.singletonObjects) {
                   singletonObject = this.earlySingletonObjects.get(beanName);
                   if (singletonObject == null && allowEarlyReference) {
                      ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                      if (singletonFactory != null) {
                         singletonObject = singletonFactory.getObject();
                         this.earlySingletonObjects.put(beanName, singletonObject);
                         if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
                            System.out.println("將"+beanName+"放入二級緩存");;
                         this.singletonFactories.remove(beanName);
                      }
                   }else if(singletonObject != null){
                      System.out.println("從二級緩存中拿到了"+beanName);
                   }
                }
             }
             return singletonObject;
          }
      • ② getSingleton(beanName,lamdba) – 一級緩存操作
        • public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
                Assert.notNull(beanName, "Bean name must not be null");
                synchronized (this.singletonObjects) {
                   Object singletonObject = this.singletonObjects.get(beanName);
                   if (singletonObject == null) {
                      if (this.singletonsCurrentlyInDestruction) {
                      ...
                      // 正在創建的bean加入singletonsCurrentlyInCreation - 保證只有一個對象創建,阻斷循環依賴
                      beforeSingletonCreation(beanName);
                                ...
                      try {
                         singletonObject = singletonFactory.getObject();
                      ...
                      finally {
                      ...
                         // 從singletonsCurrentlyInCreation中移除
                         afterSingletonCreation(beanName);
                      }
                      if (newSingleton) {
                         // 對象創建完畢 - 放入一級緩存(從其他緩存移除)
                         addSingleton(beanName, singletonObject);
                      }
                   }
                   return singletonObject;
                }
             }
                  
           //  -----  內部調用一級緩存操作
              protected void addSingleton(String beanName, Object singletonObject) {
                  synchronized (this.singletonObjects) {
                      if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
                          System.out.println("將"+beanName+"放入一級緩存");;
                      this.singletonObjects.put(beanName, singletonObject);
                      this.singletonFactories.remove(beanName);
                      this.earlySingletonObjects.remove(beanName);
                      this.registeredSingletons.add(beanName);
                  }
              }        
             
      • ③createBean(beanName, mbd, args) – 三級緩存操作
        • protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {
               ...
             if (instanceWrapper == null) {
                // 5* 實例化對象本身
                instanceWrapper = createBeanInstance(beanName, mbd, args);
             }
             ...
             boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                   isSingletonCurrentlyInCreation(beanName));
             if (earlySingletonExposure) {
                ...
                // 將創建好還未渲染屬性的bean 放入三級緩存
                addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
             }
          
             Object exposedObject = bean;
             try {
                // 渲染bean自身和屬性
                populateBean(beanName, mbd, instanceWrapper);
                // 實例化之後的後置處理 - init
                exposedObject = initializeBean(beanName, exposedObject, mbd);
             }
             catch (Throwable ex) {
             ...
             return exposedObject;
          }
            
            
             // ------------- 內部調用三級緩存操作 
             protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
                  Assert.notNull(singletonFactory, "Singleton factory must not be null");
                  synchronized (this.singletonObjects) {
                      if (!this.singletonObjects.containsKey(beanName)) {
                          if(beanName.equals("a")||beanName.equals("b")||beanName.equals("c"))
                              System.out.println("將"+beanName+"放入三級緩存");;
                          this.singletonFactories.put(beanName, singletonFactory);
                          this.earlySingletonObjects.remove(beanName);
                          this.registeredSingletons.add(beanName);
                      }
                  }
              }       

    菜瓜:demo比較簡單,流程大致明白,源碼我還需要斟酌一下,整體有了概念。這個流程好像是摻雜在bean的創建過程中,結合bean的生命周期整體理解可能會更深入一點

    水稻:是的。每個知識點都不是單一的,拿着bean的生命周期再理解一遍可能會更有收穫。

     

    討論

    • 為什麼是三級緩存,兩級不行嗎?
      • 猜測:理論上兩級也可以實現。多一個二級緩存可能是為了加快獲取的速度。假如A依賴B,A依賴C,B依賴A,C依賴A,那麼C在獲取A的時候只需要從二級緩存中就能拿到A了

    總結

    • Spring的處理方式和java處理的思想一致,構造器依賴本身是破壞語義和規範的
    • 屬性賦值–> 依賴注入 。 先創建對象,再賦值屬性,賦值的時候發現需要創建便生成依賴對象,被依賴對象需要前一個對象就從緩存容器中拿取即可

     

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

    【其他文章推薦】

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

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

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

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

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

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

    聚甘新

  • 菜渣開源一個基於 EMIT 的 AOP 庫(.NET Core)

    目錄

    • 1,快速入門
      • 1.1 繼承 ActionAttribute 特性
      • 1.2 標記代理類型
      • 2,如何創建代理類型
        • 2.1 通過API直接創建
    • 2,創建代理類型
      • 通過API
      • 通過 Microsoft.Extensions.DependencyInjection
        • 通過 Autofac
    • 3,深入使用
      • 代理類型
      • 方法、屬性代理
      • 上下文
        • 攔截方法或屬性的參數
      • 非侵入式代理

    Nuget 庫地址:https://www.nuget.org/packages/CZGL.AOP/

    Github 庫地址:https://github.com/whuanle/CZGL.AOP

    CZGL.AOP 是 基於 EMIT 編寫的 一個簡單輕量的AOP框架,支持非侵入式代理,支持.NET Core/ASP.NET Core,以及支持多種依賴注入框架。

    1,快速入門

    CZGL.AOP 使用比較簡單,你只需要使用 [Interceptor] 特性標記需要代理的類型,然後使用繼承 ActionAttribute 的特性標記要被代理的方法或屬性。

    1.1 繼承 ActionAttribute 特性

    ActionAttribute 是用於代理方法或屬性的特性標記,不能直接使用,需要繼承后重寫方法。

    示例如下:

        public class LogAttribute : ActionAttribute
        {
            public override void Before(AspectContext context)
            {
                Console.WriteLine("執行前");
            }
    
            public override object After(AspectContext context)
            {
                Console.WriteLine("執行后");
                if (context.IsMethod)
                    return context.MethodResult;
                else if (context.IsProperty)
                    return context.PropertyValue;
                return null;
            }
        }
    

    Before 會在被代理的方法執行前或被代理的屬性調用時生效,你可以通過 AspectContext 上下文,獲取、修改傳遞的參數。

    After 在方法執行后或屬性調用時生效,你可以通過上下文獲取、修改返回值。

    1.2 標記代理類型

    在被代理的類型中,使用 [Interceptor] 特性來標記,在需要代理的方法中,使用 繼承了 ActionAttribute 的特性來標記。

    此方法是侵入式的,需要在編譯前完成。

    [Interceptor]
    public class Test : ITest
    {
        [Log] public virtual string A { get; set; }
        [Log]
        public virtual void MyMethod()
        {
            Console.WriteLine("運行中");
        }
    }
    

    注意的是,一個方法或屬性只能設置一個攔截器。

    2,如何創建代理類型

    CZGL.AOP 有多種生成代理類型的方式,下面介紹簡單的方式。

    請預先創建如下代碼:

        public class LogAttribute : ActionAttribute
        {
            public override void Before(AspectContext context)
            {
                Console.WriteLine("執行前");
            }
    
            public override object After(AspectContext context)
            {
                Console.WriteLine("執行后");
                if (context.IsMethod)
                    return context.MethodResult;
                else if (context.IsProperty)
                    return context.PropertyValue;
                return null;
            }
        }
    
        public interface ITest
        {
            void MyMethod();
        }
    
        [Interceptor]
        public class Test : ITest
        {
            [Log] public virtual string A { get; set; }
            public Test()
            {
                Console.WriteLine("構造函數沒問題");
            }
            [Log]
            public virtual void MyMethod()
            {
                Console.WriteLine("運行中");
            }
        }
    

    2.1 通過API直接創建

    通過 CZGL.AOP 中的 AopInterceptor 類,你可以生成代理類型。

    示例如下:

                ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>();
                Test test2 = AopInterceptor.CreateProxyOfClass<Test>();
                test1.MyMethod();
                test2.MyMethod();
    

    CreateProxyOfInterface 通過接口創建代理類型;CreateProxyOfClass 通過類創建代理類型;

    默認調用的是無參構造函數。

    2,創建代理類型

    通過API

    你可以參考源碼解決方案

    中的 ExampleConsole 項目。

    如果要直接使用 AopInterceptor.CreateProxyOfInterfaceAopInterceptor.CreateProxyOfClass 方法,通過接口或類來創建代理類型。

            ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>();
            Test test2 = AopInterceptor.CreateProxyOfClass<Test>();
    

    如果要指定實例化的構造函數,可以這樣:

                // 指定構造函數
                test2 = AopInterceptor.CreateProxyOfClass<Test>("aaa", "bbb");
                test2.MyMethod();
    

    通過 Microsoft.Extensions.DependencyInjection

    Microsoft.Extensions.DependencyInjection 是 .NET Core/ASP.NET Core 默認的依賴注入容器。

    如果需要支持 ASP.NET Core 中使用 AOP,你可以在 Nuget 包中安裝 CZGL.AOP.MEDI

    如果你在控制台下使用 Microsoft.Extensions.DependencyInjection,你可以使用名為 BuildAopProxyIServiceCollection 拓展方法來為容器中的類型,生成代理類型。

    示例如下:

                IServiceCollection _services = new ServiceCollection();
                _services.AddTransient<ITest, Test>();
                var serviceProvider = _services.BuildAopProxy().BuildServiceProvider();
                serviceProvider.GetService<ITest>();
                return serviceProvider;
    

    你可以參考源碼解決方案中的 ExampleMEDI 項目。

    如果你要在 ASP.NET Core 中使用,你可以在 Startup 中,ConfigureServices 方法的最後一行代碼使用 services.BuildAopProxy();

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllers();
                services.BuildAopProxy();
            }
    

    還可以在 ProgramIHostBuilder 中使用 .UseServiceProviderFactory(new AOPServiceProxviderFactory()) 來配置使用 CZGL.AOP。

    示例:

            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                .UseServiceProviderFactory(new AOPServiceProxviderFactory())
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                    });
    

    可以參考解決方案中的 ExampleConsoleExampleWebMEDI 兩個項目。

    你不必擔心引入 CZGL.AOP 后,使用依賴注入會使程序變慢或者破壞容器中的原有屬性。CZGL.AOP 只會在創建容器時處理需要被代理的類型,不會影響容器中的服務,也不會幹擾到依賴注入的執行。

    通過 Autofac

    如果需要在 Autofac 中使用 AOP,則需要引用 CZGL.AOP.Autofac 包。

    如果你在控制台程序中使用 Autofac,則可以在 Build() 後面使用 BuildAopProxy()

                ContainerBuilder builder = new ContainerBuilder();
                builder.RegisterType<Test>().As<ITest>();
                var container = builder.Build().BuildAopProxy();
    
                using (ILifetimeScope scope = container.BeginLifetimeScope())
                {
                    // 獲取實例
                    ITest myService = scope.Resolve<ITest>();
                    myService.MyMethod();
                }
    
                Console.ReadKey();
            }
    

    要注意的是,在已經完成的組件註冊創建一個新的容器后,才能調用 BuildAopProxy() 方法,

    這樣針對一個新的容器你可以考慮是否需要對容器中的組件進行代理。

    如果在 ASP.NET Core 中使用 Autofac,你需要在 Program 類的 IHostBuilder 中使用:

    .UseServiceProviderFactory(new AutofacServiceProviderFactory())
    

    如果需要代理已經註冊的組件,則將其替換為:

     .UseServiceProviderFactory(new CZGL.AOP.Autofac.AOPServiceProxviderFactory())
    

    請參考 源碼解決方案中的 ExampleAutofacExampleWebAutofac 兩個項目。

    3,深入使用

    代理類型

    要被代理的類型,需要使用 [Interceptor]來標記,例如:

        [Interceptor]
        public class Test : ITest
        {
        }
    

    支持泛型類型。

    被代理的類型必須是可被繼承的。

    類型的構造函數沒有限制,你可以隨意編寫。

    在使用 API 創建代理類型並且實例化時,你可以指定使用哪個構造函數。

    例如:

    			string a="",b="",c="";
    			ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>(a,b,c);
    

    API 會根據參數的多少以及參數的類型自動尋找合適的構造函數。

    方法、屬性代理

    為了代理方法或屬性,你需要繼承 ActionAttribute 特性,然後為方法或屬性標記此特性,並且將方法或屬性設置為 virtual

    一個類型中的不同方法,可以使用不同的攔截器。

            [Log1]
            public virtual void MyMethod1(){}
            
            [Log2]
            public virtual void MyMethod2(){}
    

    對於屬性,可以在屬性上直接使用特性,或者只在 get 或 set 構造器使用。

            [Log] public virtual string A { get; set; }
            
            // 或
            public virtual string A { [Log] get; set; }
            
            // 或
            public virtual string A { get; [Log] set; }
    

    如果在屬性上使用特性,相當於 [Log] get; [Log] set;

    上下文

    一個簡單的方法或屬性攔截標記是這樣的:

        public class LogAttribute : ActionAttribute
        {
            public override void Before(AspectContext context)
            {
                Console.WriteLine("執行前");
            }
    
            public override object After(AspectContext context)
            {
                Console.WriteLine("執行后");
                if (context.IsMethod)
                    return context.MethodResult;
                else if (context.IsProperty)
                    return context.PropertyValue;
                return null;
            }
        }
    
    

    AspectContext 的屬性說明如下:

    字段 說明
    Type 當前被代理類型生成的代理類型
    ConstructorParamters 類型被實例化時使用的構造函數的參數,如果構造函數沒有參數,則 MethodValues.Length = 0,而不是 MethodValues 為 null。
    IsProperty 當前攔截的是屬性
    PropertyInfo 當前被執行的屬性的信息,可為 null。
    PropertyValue 但調用的是屬性時,返回 get 的結果或 set 的 value 值。
    IsMethod 當前攔截的是方法
    MethodInfo 當前方法的信息
    MethodValues 方法被調用時傳遞的參數,如果此方法沒有參數,則 MethodValues.Length = 0,而不是 MethodValues 為 null
    MethodResult 方法執行返回的結果(如果有)

    攔截方法或屬性的參數

    通過上下文,你可以修改方法或屬性的參數以及攔截返回結果:

        public class LogAttribute : ActionAttribute
        {
            public override void Before(AspectContext context)
            {
                // 攔截並修改方法的參數
                for (int i = 0; i < context.MethodValues.Length; i++)
                {
                    context.MethodValues[i] = (int)context.MethodValues[i] + 1;
                }
                Console.WriteLine("執行前");
            }
    
            public override object After(AspectContext context)
            {
                Console.WriteLine("執行后");
    
                // 攔截方法的執行結果
                context.MethodResult = (int)context.MethodResult + 664;
    
                if (context.IsMethod)
                    return context.MethodResult;
                else if (context.IsProperty)
                    return context.PropertyValue;
                return null;
            }
        }
    
        [Interceptor]
        public class Test
        {
            [Log]
            public virtual int Sum(int a, int b)
            {
                Console.WriteLine("運行中");
                return a + b;
            }
        }
    
                Test test = AopInterceptor.CreateProxyOfClass<Test>();
    
                Console.WriteLine(test.Sum(1, 1));
    

    方法的參數支持 inrefout;支持泛型方法泛型屬性;支持異步方法;

    非侵入式代理

    此種方式不需要改動被代理的類型,你也可以代理程序集中的類型。

        public class LogAttribute : ActionAttribute
        {
            public override void Before(AspectContext context)
            {
                Console.WriteLine("執行前");
            }
    
            public override object After(AspectContext context)
            {
                Console.WriteLine("執行后");
                if (context.IsMethod)
                    return context.MethodResult;
                else if (context.IsProperty)
                    return context.PropertyValue;
                return null;
            }
        }
    
        public class TestNo
        {
            public virtual string A { get; set; }
            public virtual void MyMethod()
            {
                Console.WriteLine("運行中");
            }
        }
    
                TestNo test3 = AopInterceptor.CreateProxyOfType<TestNo>(new ProxyTypeBuilder()
                    .AddProxyMethod(typeof(LogAttribute), typeof(TestNo).GetMethod(nameof(TestNo.MyMethod)))
                    .AddProxyMethod(typeof(LogAttribute), typeof(TestNo).GetProperty(nameof(TestNo.A)).GetSetMethod()));
    

    通過 ProxyTypeBuilder 來構建代理類型。

    代理方法或屬性都是使用 AddProxyMethod,第一個參數是要使用的攔截器,第二個參數是要攔截的方法。

    如果要攔截屬性,請分開設置屬性的 getset 構造。

    如果多個方法或屬性使用同一個攔截器,則可以這樣:

                TestNo test3 = AopInterceptor.CreateProxyOfType<TestNo>(
                    new ProxyTypeBuilder(new Type[] { typeof(LogAttribute) })
                    .AddProxyMethod("LogAttribute", typeof(TestNo).GetMethod(nameof(TestNo.MyMethod)))
                    .AddProxyMethod("LogAttribute", typeof(TestNo).GetProperty(nameof(TestNo.A)).GetSetMethod()));
    
                TestNo test3 = AopInterceptor.CreateProxyOfType<TestNo>(
                    new ProxyTypeBuilder(new Type[] { typeof(LogAttribute) })
                    .AddProxyMethod("LogAttribute", typeof(TestNo).GetMethod(nameof(TestNo.MyMethod)))
                    .AddProxyMethod(typeof(LogAttribute2), typeof(TestNo).GetProperty(nameof(TestNo.A)).GetSetMethod()));
    

    在構造函數中傳遞過去所需要的攔截器,然後在攔截時使用。

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

    【其他文章推薦】

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

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

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

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

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

    聚甘新

  • Java併發編程(05):悲觀鎖和樂觀鎖機制

    Java併發編程(05):悲觀鎖和樂觀鎖機制

    本文源碼:GitHub·點這裏 || GitEE·點這裏

    一、資源和加鎖

    1、場景描述

    多線程併發訪問同一個資源問題,假如線程A獲取變量之後修改變量值,線程C在此時也獲取變量值並且修改,兩個線程同時併發處理一個變量,就會導致併發問題。

    這種并行處理數據庫的情況在實際的業務開發中很常見,兩個線程先後修改數據庫的值,導致數據有問題,該問題復現的概率不大,處理的時候需要對整個模塊體系有概念,才能容易定位問題。

    2、演示案例

    public class LockThread01 {
        public static void main(String[] args) {
            CountAdd countAdd = new CountAdd() ;
            AddThread01 addThread01 = new AddThread01(countAdd) ;
            addThread01.start();
            AddThread02 varThread02 = new AddThread02(countAdd) ;
            varThread02.start();
        }
    }
    class AddThread01 extends Thread {
        private CountAdd countAdd  ;
        public AddThread01 (CountAdd countAdd){
            this.countAdd = countAdd ;
        }
        @Override
        public void run() {
            countAdd.countAdd(30);
        }
    }
    class AddThread02 extends Thread {
        private CountAdd countAdd  ;
        public AddThread02 (CountAdd countAdd){
            this.countAdd = countAdd ;
        }
        @Override
        public void run() {
            countAdd.countAdd(10);
        }
    }
    class CountAdd {
        private Integer count = 0 ;
        public void countAdd (Integer num){
            try {
                if (num == 30){
                    count = count + 50 ;
                    Thread.sleep(3000);
                } else {
                    count = count + num ;
                }
                System.out.println("num="+num+";count="+count);
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    

    這裏案例演示多線程併發修改count值,導致和預期不一致的結果,這是多線程併發下最常見的問題,尤其是在併發更新數據時。

    出現併發的情況時,就需要通過一定的方式或策略來控制在併發情況下數據讀寫的準確性,這被稱為併發控制,實現併發控制手段也很多,最常見的方式是資源加鎖,還有一種簡單的實現策略:修改數據前讀取數據,修改的時候加入限制條件,保證修改的內容在此期間沒有被修改。

    二、鎖的概念簡介

    1、鎖機制簡介

    併發編程中一個最關鍵的問題,多線程併發處理同一個資源,防止資源使用的衝突一個關鍵解決方法,就是在資源上加鎖:多線程序列化訪問。鎖是用來控制多個線程訪問共享資源的方式,鎖機制能夠讓共享資源在任意給定時刻只有一個線程任務訪問,實現線程任務的同步互斥,這是最理想但性能最差的方式,共享讀鎖的機制允許多任務併發訪問資源。

    2、悲觀鎖

    悲觀鎖,總是假設每次每次被讀取的數據會被修改,所以要給讀取的數據加鎖,具有強烈的資源獨佔和排他特性,在整個數據處理過程中,將數據處於鎖定狀態,例如synchronized關鍵字的實現就是悲觀機制。

    悲觀鎖的實現,往往依靠數據庫提供的鎖機制,只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據,悲觀鎖主要分為共享讀鎖和排他寫鎖。

    排他鎖基本機制:又稱寫鎖,允許獲取排他鎖的事務更新數據,阻止其他事務取得相同的資源的共享讀鎖和排他鎖。若事務T對數據對象A加上寫鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的寫鎖。

    3、樂觀鎖

    樂觀鎖相對悲觀鎖而言,採用更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務的開銷非常的占資源,樂觀鎖機制在一定程度上解決了這個問題。

    樂觀鎖大多是基於數據版本記錄機制實現,為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個version字段來實現。讀取出數據時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號等於數據庫表當前版本號,則予以更新,否則認為是過期數據。樂觀鎖機制在高併發場景下,可能會導致大量更新失敗的操作。

    樂觀鎖的實現是策略層面的實現:CAS(Compare-And-Swap)。當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能成功更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。

    4、機制對比

    悲觀鎖本身的實現機制就以損失性能為代價,多線程爭搶,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,加鎖的機制會產生額外的開銷,還有增加產生死鎖的概率,引發性能問題。

    樂觀鎖雖然會基於對比檢測的手段判斷更新的數據是否有變化,但是不確定數據是否變化完成,例如線程1讀取的數據是A1,但是線程2操作A1的值變化為A2,然後再次變化為A1,這樣線程1的任務是沒有感知的。

    悲觀鎖每一次數據修改都要上鎖,效率低,寫數據失敗的概率比較低,比較適合用在寫多讀少場景。

    樂觀鎖並未真正加鎖,效率高,寫數據失敗的概率比較高,容易發生業務形異常,比較適合用在讀多寫少場景。

    是選擇犧牲性能,還是追求效率,要根據業務場景判斷,這種選擇需要依賴經驗判斷,不過隨着技術迭代,數據庫的效率提升,集群模式的出現,性能和效率還是可以兩全的。

    三、Lock基礎案例

    1、Lock方法說明

    lock:執行一次獲取鎖,獲取后立即返回;

    lockInterruptibly:在獲取鎖的過程中可以中斷;

    tryLock:嘗試非阻塞獲取鎖,可以設置超時時間,如果獲取成功返回true,有利於線程的狀態監控;

    unlock:釋放鎖,清理線程狀態;

    newCondition:獲取等待通知組件,和當前鎖綁定;

    2、應用案例

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    public class LockThread02 {
        public static void main(String[] args) {
            LockNum lockNum = new LockNum() ;
            LockThread lockThread1 = new LockThread(lockNum,"TH1");
            LockThread lockThread2 = new LockThread(lockNum,"TH2");
            LockThread lockThread3 = new LockThread(lockNum,"TH3");
            lockThread1.start();
            lockThread2.start();
            lockThread3.start();
        }
    }
    class LockNum {
        private Lock lock = new ReentrantLock() ;
        public void getNum (){
            lock.lock();
            try {
                for (int i = 0 ; i < 3 ; i++){
                    System.out.println("ThreadName:"+Thread.currentThread().getName()+";i="+i);
                }
            } finally {
                lock.unlock();
            }
        }
    }
    class LockThread extends Thread {
        private LockNum lockNum ;
        public LockThread (LockNum lockNum,String name){
            this.lockNum = lockNum ;
            super.setName(name);
        }
        @Override
        public void run() {
            lockNum.getNum();
        }
    }
    

    這裏多線程基於Lock鎖機制,分別依次執行任務,這是Lock的基礎用法,各種API的詳解,下次再說。

    3、與synchronized對比

    基於synchronized實現的鎖機制,安全性很高,但是一旦線程失敗,直接拋出異常,沒有清理線程狀態的機會。顯式的使用Lock語法,可以在finally語句中最終釋放鎖,維護相對正常的線程狀態,在獲取鎖的過程中,可以嘗試獲取,或者嘗試獲取鎖一段時間。

    四、源代碼地址

    GitHub·地址
    https://github.com/cicadasmile/java-base-parent
    GitEE·地址
    https://gitee.com/cicadasmile/java-base-parent
    

    推薦閱讀:Java基礎系列

    序號 文章標題
    A01 Java基礎:基本數據類型,核心點整理
    A02 Java基礎:特殊的String類,和相關擴展API
    B01 Java併發:線程的創建方式,狀態周期管理
    B02 Java併發:線程核心機制,基礎概念擴展
    B03 Java併發:多線程併發訪問,同步控制
    B04 Java併發:線程間通信,等待/通知機制

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

    聚甘新

  • 從一個計算器開始說起——C#中的工廠方法模式

    從一個計算器開始說起——C#中的工廠方法模式

    工廠模式作為很常見的設計模式,在日常工作中出鏡率非常高,程序員們一定要掌握它的用法喲,今天跟着老胡一起來看看吧。

    舉個例子

    現在先讓我們來看一個例子吧,比如,要開發一個簡單的計算器,完成加減功能,通過命令行讀入形如1+1的公式,輸出2這個結果,讓我們看看怎麼實現吧。

     

    第一個版本

    這個版本裏面,我們不考慮使用模式,就按照最簡單的結構,怎麼方便怎麼來。

    思路非常簡單,僅需要實現以下幾個方法

    • 取運算數
    • 取運算符
    • 輸出結果
         class Program
        {
            static int GetOperatorIndex(string input)
            {
                int operatorIndex = 0;
                for (; operatorIndex < input.Length; operatorIndex++)
                {
                    if (!char.IsDigit(input[operatorIndex]))
                        break;
                }
                return operatorIndex;
            }
    
            static int GetOp(string input, int startIndex, int size = -1)
            {
                string subStr;
                if (size == -1)
                {
                    subStr = input.Substring(startIndex);
                }
                else
                {
                    subStr = input.Substring(startIndex, size);
                }
                return int.Parse(subStr);
            }
    
            static int CalculateExpression(string input)
            {
                var operatorIndex = GetOperatorIndex(input); //得到運算符索引
                var op1 = GetOp(input, 0, operatorIndex); //得到運算數1
                var op2 = GetOp(input, operatorIndex + 1); //得到運算數2
                switch (input[operatorIndex])
                {
                    case '+':
                        return op1 + op2;
                    case '-':
                        return op1 - op2;
                    default:
                        throw new Exception("not support");
                }
            }
    
            static void Main(string[] args)
            {
                string input = Console.ReadLine();
                while(!string.IsNullOrEmpty(input))
                {
                    var result = CalculateExpression(input);
                    Console.WriteLine("={0}", result);
                    input = Console.ReadLine();
                }            
            }
    }
    

    代碼非常簡單,毋庸置疑,這個運算器是可以正常工作的。這也可能是我們大部分人剛剛踏上工作崗位的時候可能會寫出的代碼。但它有着以下這些缺點:

    • 缺乏起碼的抽象,至少加和減應該能抽象出操作類。
    • 缺乏抽象造成了巨型客戶端,所有的邏輯都嵌套在了客戶端裏面。
    • 使用switch case缺乏擴展性,同時switch case也暗指了這部分代碼是屬於變化可能性比較高的地方,我們應該把它們封裝起來。而且不能把他們放在和客戶端代碼一起

    接下來,我們引入我們的主題,工廠方法模式。
     

    工廠方法模式版本

    工廠方法模式使用一個虛擬的工廠來完成產品構建(在這裡是運算符的構建,因為運算符是我們這個程序中最具有變化的部分),通過把可變化的部分封裝在工廠類中以達到隔離變化的目的。我們看看UML圖:

    依葫蘆畫瓢,我們設計思路如下:

    • 設計一個IOperator接口,對應抽象的Product
    • 設計AddOperator和SubtractOperator,對應具體Product
    • 設計IOperatorFactory接口生產Operator
    • 設計OperatorFactory實現抽象IFactory

    關鍵代碼如下,其他讀取操作數之類的代碼就不在贅述。

    • IOperator接口
           interface IOperator
           {
               int Calculate(int op1, int p2);
           }
    
    • 具體Operator
    	class AddOperator : IOperator
            {
                public int Calculate(int op1, int op2)
                {
                    return op1 + op2;
                }
            }
    
            class SubtractOperator : IOperator
            {
                public int Calculate(int op1, int op2)
                {
                    return op1 - op2;
                }
            }
    
    • Factory接口
    	interface IOperatorFactory
            {
                IOperator CreateOperator(char c);
            }
    
    • 具體Factory
    	class OperatorFactory : IOperatorFactory
            {
                public IOperator CreateOperator(char c)
                {
                    switch(c)
                    {
                        case '+':
                            return new AddOperator();
                        case '-':
                            return new SubtractOperator();
                        default:
                            throw new Exception("Not support");
                    }
                }
            }
    
    • 在CalculateExpression裏面使用他們
       static IOperator GetOperator(string input, int operatorIndex)
           {
               IOperatorFactory f = new OperatorFactory();
               return f.CreateOperator(input[operatorIndex]);
           }
    
           static int CalculateExpression(string input)
           {
               var operatorIndex = GetOperatorIndex(input);
               var op1 = GetOp(input, 0, operatorIndex);
               var op2 = GetOp(input, operatorIndex + 1);
               IOperator op = GetOperator(input, operatorIndex);
               return op.Calculate(op1, op2);                    
           }
    

    這樣,我們就用工廠方法重新寫了一次計算器,現在看看,好處有

    • 容易變化的創建部分被工廠封裝了起來,工廠和客戶端以接口的形式依賴,工廠內部邏輯可以隨時變化而不用擔心影響客戶端代碼
    • 工厂部分可以放在另外一個程序集,項目規劃會更加合理
    • 客戶端僅僅需要知道工廠和抽象的產品類,不需要再知道每一個具體的產品(不需要知道如何構建每一個具體運算符),符合迪米特法則
    • 擴展性增強,如果之後需要添加乘法multiple,那麼僅需要添加一個Operator類代表Multiple並且修改Facotry裏面的生成Operator邏輯就可以了,不會影響到客戶端
       

    自此,我們已經在代碼裏面實現了工廠方法模式,但可能有朋友就會想,雖然現在擴展性增強了,但是新添加運算符還是需要修改已有的工廠,這不是違反了開閉原則么。。有沒有更好的辦法呢?當然是有的。
     

    反射版本

    想想工廠方法那個版本,我們為什麼增加新的運算符就會不可避免的修改現有工廠?原因就是我們通過switch case來硬編碼“教導”工廠如何根據用戶輸入產生正確的運算符,那麼如果有一種方法可以讓工廠自動學會發現新的運算符,那麼我們的目的不就達到了?

    嗯,我想聰明的朋友們已經知道了,用屬性嘛,在C#中,這種方法完成類的自描述,是最好不過了的。

    我們的設計思路如下:

    • 定義一個描述屬性以識別運算符
    • 在運算符中添加該描述屬性
    • 在工廠啟動的時候,掃描程序集以註冊所有運算符

    代碼如下:

    • 描述屬性
        class OperatorDescriptionAttribute : Attribute
        {
            public char Symbol { get; }
            public OperatorDescriptionAttribute(char c)
            {
                Symbol = c;
            }
        }
    
    • 添加描述屬性到運算符
        [OperatorDescription('+')]
        class AddOperator : IOperator
        {
            public int Calculate(int op1, int op2)
            {
                return op1 + op2;
            }
        }
    
        [OperatorDescription('-')]
        class SubtractOperator : IOperator
        {
            public int Calculate(int op1, int op2)
            {
                return op1 - op2;
            }
        }
    
    • 讓工廠使用描述屬性
        class OperatorFactory : IOperatorFactory
        {
            private Dictionary<char, IOperator> dict = new Dictionary<char, IOperator>();
            public OperatorFactory()
            {
                Assembly assembly = Assembly.GetExecutingAssembly();
                foreach (var type in assembly.GetTypes())
                {
                    if (typeof(IOperator).IsAssignableFrom (type) 
                        && !type.IsInterface)
                    {
                        var attribute = type.GetCustomAttribute<OperatorDescriptionAttribute>();
                        if(attribute != null)
                        {
                            dict[attribute.Symbol] = Activator.CreateInstance(type) as IOperator;
                        }
                    }
                }
            }
            public IOperator CreateOperator(char c)
            {
                if(!dict.ContainsKey(c))
                {
                    throw new Exception("Not support");
                }
                return dict[c];
            }
        }
    

    經過這種改造,現在程序對擴展性支持已經很友好了,需要添加Multiple,只需要添加一個Multiple類就可以,其他代碼都不用修改,這樣就完美符合開閉原則了。

      [OperatorDescription('*')]
      class MultipleOperator : IOperator
      {
          public int Calculate(int op1, int op2)
          {
              return op1 * op2;
          }
      }
    

    這就是我們怎麼一步步從最原始的代碼走過來,一點點重構讓代碼實現工廠方法模式,最終再完美支持開閉原則的過程,希望能幫助到大家。

    其實關於最後那個通過標記屬性實現擴展,微軟有個MEF框架支持的很好,原理跟這個有點相似,有機會我們再聊聊MEF。

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

    【其他文章推薦】

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

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

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

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

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

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

    聚甘新

  • 【Flutter實戰】六大布局組件及半圓菜單案例

    【Flutter實戰】六大布局組件及半圓菜單案例

    老孟導讀:Flutter中布局組件有水平 / 垂直布局組件( RowColumn )、疊加布局組件( StackIndexedStack )、流式布局組件( Wrap )和 自定義布局組件(Flow)。

    水平、垂直布局組件

    Row 是將子組件以水平方式布局的組件, Column 是將子組件以垂直方式布局的組件。項目中 90% 的頁面布局都可以通過 Row 和 Column 來實現。

    將3個組件水平排列:

    Row(
      children: <Widget>[
        Container(
          height: 50,
          width: 100,
          color: Colors.red,
        ),
        Container(
          height: 50,
          width: 100,
          color: Colors.green,
        ),
        Container(
          height: 50,
          width: 100,
          color: Colors.blue,
        ),
      ],
    )
    

    將3個組件垂直排列:

    Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Container(
          height: 50,
          width: 100,
          color: Colors.red,
        ),
        Container(
          height: 50,
          width: 100,
          color: Colors.green,
        ),
        Container(
          height: 50,
          width: 100,
          color: Colors.blue,
        ),
      ],
    )
    

    在 Row 和 Column 中有一個非常重要的概念:主軸( MainAxis )交叉軸( CrossAxis ),主軸就是與組件布局方向一致的軸,交叉軸就是與主軸方向垂直的軸。

    具體到 Row 組件,主軸 是水平方向,交叉軸 是垂直方向。而 Column 與 Row 正好相反,主軸 是垂直方向,交叉軸 是水平方向。

    明白了 主軸 和 交叉軸 概念,我們來看下 mainAxisAlignment 屬性,此屬性表示主軸方向的對齊方式,默認值為 start,表示從組件的開始處布局,此處的開始位置和 textDirection 屬性有關,textDirection 表示文本的布局方向,其值包括 ltr(從左到右) 和 rtl(從右到左),當 textDirection = ltr 時,start 表示左側,當 textDirection = rtl 時,start 表示右側,

    Container(
      decoration: BoxDecoration(border: Border.all(color: Colors.black)),
      child: Row(
        children: <Widget>[
          Container(
            height: 50,
            width: 100,
            color: Colors.red,
          ),
          Container(
            height: 50,
            width: 100,
            color: Colors.green,
          ),
          Container(
            height: 50,
            width: 100,
            color: Colors.blue,
          ),
        ],
      ),
    )
    

    黑色邊框是Row控件的範圍,默認情況下Row鋪滿父組件。

    主軸對齊方式有6種,效果如下圖:

    spaceAround 和 spaceEvenly 區別是:

    • spaceAround :第一個子控件距開始位置和最後一個子控件距結尾位置是其他子控件間距的一半。
    • spaceEvenly : 所有間距一樣。

    和主軸對齊方式相對應的就是交叉軸對齊方式 crossAxisAlignment ,交叉軸對齊方式默認是居中。Row控件的高度是依賴子控件高度,因此子控件高都一樣時,Row的高和子控件高相同,此時是無法體現交叉軸對齊方式,修改3個顏色塊高分別為50,100,150,這樣Row的高是150,代碼如下:

    Container(
          decoration: BoxDecoration(border: Border.all(color: Colors.black)),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Container(
                height: 50,
                width: 100,
                color: Colors.red,
              ),
              Container(
                height: 100,
                width: 100,
                color: Colors.green,
              ),
              Container(
                height: 150,
                width: 100,
                color: Colors.blue,
              ),
            ],
          ),
        )
    

    主軸對齊方式效果如下圖:

    mainAxisSize 表示主軸尺寸,有 min 和 max 兩種方式,默認是 maxmin 表示盡可能小,max 表示盡可能大。

    Container(
    	decoration: BoxDecoration(border: Border.all(color: Colors.black)),
    	child: Row(
    		mainAxisSize: MainAxisSize.min,
    		...
    	)
    )
    

    看黑色邊框,正好包裹子組件,而 max 效果如下:

    textDirection 表示子組件主軸布局方向,值包括 ltr(從左到右) 和 rtl(從右到左)

    Container(
      decoration: BoxDecoration(border: Border.all(color: Colors.black)),
      child: Row(
        textDirection: TextDirection.rtl,
        children: <Widget>[
          ...
        ],
      ),
    )
    

    verticalDirection 表示子組件交叉軸布局方向:

    • up :從底部開始,並垂直堆疊到頂部,對齊方式的 start 在底部,end 在頂部。
    • down: 與 up 相反。
    Container(
      decoration: BoxDecoration(border: Border.all(color: Colors.black)),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        verticalDirection: VerticalDirection.up,
        children: <Widget>[
          Container(
            height: 50,
            width: 100,
            color: Colors.red,
          ),
          Container(
            height: 100,
            width: 100,
            color: Colors.green,
          ),
          Container(
            height: 150,
            width: 100,
            color: Colors.blue,
          ),
        ],
      ),
    )
    

    想一想這種效果完全可以通過對齊方式實現,那麼為什麼還要有 textDirectionverticalDirection 這兩個屬性,官方API文檔已經解釋了這個問題:

    This is also used to disambiguate start and end values (e.g. [MainAxisAlignment.start] or [CrossAxisAlignment.end]).

    用於消除 MainAxisAlignment.start 和 CrossAxisAlignment.end 值的歧義的。

    疊加布局組件

    疊加布局組件包含 StackIndexedStack,Stack 組件將子組件疊加显示,根據子組件的順利依次向上疊加,用法如下:

    Stack(
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Container(
          height: 170,
          width: 170,
          color: Colors.blue,
        ),
        Container(
          height: 140,
          width: 140,
          color: Colors.yellow,
        )
      ],
    )
    

    Stack 對未定位(不被 Positioned 包裹)子組件的大小由 fit 參數決定,默認值是 StackFit.loose ,表示子組件自己決定,StackFit.expand 表示盡可能的大,用法如下:

    Stack(
      fit: StackFit.expand,
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Container(
          height: 170,
          width: 170,
          color: Colors.blue,
        ),
        Container(
          height: 140,
          width: 140,
          color: Colors.yellow,
        )
      ],
    )
    

    效果只有黃色(最後一個組件的顏色),並不是其他組件沒有繪製,而是另外兩個組件被黃色組件覆蓋。

    Stack 對未定位(不被 Positioned 包裹)子組件的對齊方式由 alignment 控制,默認左上角對齊,用法如下:

    Stack(
      alignment: AlignmentDirectional.center,
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Container(
          height: 170,
          width: 170,
          color: Colors.blue,
        ),
        Container(
          height: 140,
          width: 140,
          color: Colors.yellow,
        )
      ],
    )
    

    通過 Positioned 定位的子組件:

    Stack(
      alignment: AlignmentDirectional.center,
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Container(
          height: 170,
          width: 170,
          color: Colors.blue,
        ),
        Positioned(
          left: 30,
          right: 40,
          bottom: 50,
          top: 60,
          child: Container(
            color: Colors.yellow,
          ),
        )
      ],
    )
    

    topbottomleftright 四種定位屬性,分別表示距離上下左右的距離。

    如果子組件超過 Stack 邊界由 overflow 控制,默認是裁剪,下面設置總是显示的用法:

    Stack(
      overflow: Overflow.visible,
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Positioned(
          left: 100,
          top: 100,
          height: 150,
          width: 150,
          child: Container(
            color: Colors.green,
          ),
        )
      ],
    )
    

    IndexedStack 是 Stack 的子類,Stack 是將所有的子組件疊加显示,而 IndexedStack 通過 index 只显示指定索引的子組件,用法如下:

    class IndexedStackDemo extends StatefulWidget {
      @override
      _IndexedStackDemoState createState() => _IndexedStackDemoState();
    }
    
    class _IndexedStackDemoState extends State<IndexedStackDemo> {
      int _index = 0;
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            SizedBox(height: 50,),
            _buildIndexedStack(),
            SizedBox(height: 30,),
            _buildRow(),
          ],
        );
      }
    
      _buildIndexedStack() {
        return IndexedStack(
          index: _index,
          children: <Widget>[
            Center(
              child: Container(
                height: 300,
                width: 300,
                color: Colors.red,
                alignment: Alignment.center,
                child: Icon(
                  Icons.fastfood,
                  size: 60,
                  color: Colors.blue,
                ),
              ),
            ),
            Center(
              child: Container(
                height: 300,
                width: 300,
                color: Colors.green,
                alignment: Alignment.center,
                child: Icon(
                  Icons.cake,
                  size: 60,
                  color: Colors.blue,
                ),
              ),
            ),
            Center(
              child: Container(
                height: 300,
                width: 300,
                color: Colors.yellow,
                alignment: Alignment.center,
                child: Icon(
                  Icons.local_cafe,
                  size: 60,
                  color: Colors.blue,
                ),
              ),
            ),
          ],
        );
      }
    
      _buildRow() {
        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            IconButton(
              icon: Icon(Icons.fastfood),
              onPressed: () {
                setState(() {
                  _index = 0;
                });
              },
            ),
            IconButton(
              icon: Icon(Icons.cake),
              onPressed: () {
                setState(() {
                  _index = 1;
                });
              },
            ),
            IconButton(
              icon: Icon(Icons.local_cafe),
              onPressed: () {
                setState(() {
                  _index = 2;
                });
              },
            ),
          ],
        );
      }
    }
    

    流式布局組件

    Wrap 為子組件進行水平或者垂直方向布局,且當空間用完時,Wrap 會自動換行,也就是流式布局。

    創建多個子控件做為 Wrap 的子控件,代碼如下:

    Wrap(
      children: List.generate(10, (i) {
        double w = 50.0 + 10 * i;
        return Container(
          color: Colors.primaries[i],
          height: 50,
          width: w,
          child: Text('$i'),
        );
      }),
    )
    

    direction 屬性控制布局方向,默認為水平方向,設置方向為垂直代碼如下:

    Wrap(
      direction: Axis.vertical,
      children: List.generate(4, (i) {
        double w = 50.0 + 10 * i;
        return Container(
          color: Colors.primaries[i],
          height: 50,
          width: w,
          child: Text('$i'),
        );
      }),
    )
    

    alignment 屬性控制主軸對齊方式,crossAxisAlignment 屬性控制交叉軸對齊方式,對齊方式只對有剩餘空間的行或者列起作用,例如水平方向上正好填充完整,則不管設置主軸對齊方式為什麼,看上去的效果都是鋪滿。

    說明 :主軸就是與當前組件方向一致的軸,而交叉軸就是與當前組件方向垂直的軸,如果Wrap的布局方向為水平方向 Axis.horizontal,那麼主軸就是水平方向,反之布局方向為垂直方向 Axis.vertical ,主軸就是垂直方向。

    Wrap(
    	alignment: WrapAlignment.spaceBetween,
    	...
    )
    

    主軸對齊方式有6種,效果如下圖:

    spaceAroundspaceEvenly 區別是:

    • spaceAround:第一個子控件距開始位置和最後一個子控件距結尾位置是其他子控件間距的一半。
    • spaceEvenly:所有間距一樣。

    設置交叉軸對齊代碼如下:

    Wrap(
    	crossAxisAlignment: WrapCrossAlignment.center,
    	...
    )
    

    如果 Wrap 的主軸方向為水平方向,交叉軸方向則為垂直方向,如果想要看到交叉軸對齊方式的效果需要設置子控件的高不一樣,代碼如下:

    Wrap(
      spacing: 5,
      runSpacing: 3,
      crossAxisAlignment: WrapCrossAlignment.center,
      children: List.generate(10, (i) {
        double w = 50.0 + 10 * i;
        double h = 50.0 + 5 * i;
        return Container(
          color: Colors.primaries[i],
          height: h,
          alignment: Alignment.center,
          width: w,
          child: Text('$i'),
        );
      }),
    )
    

    runAlignment 屬性控制 Wrap 的交叉抽方向上每一行的對齊方式,下面直接看 runAlignment 6中方式對應的效果圖,

    runAlignmentalignment 的區別:

    • alignment :是主軸方向上對齊方式,作用於每一行。
    • runAlignment :是交叉軸方向上將每一行看作一個整體的對齊方式。

    spacingrunSpacing 屬性控制Wrap主軸方向和交叉軸方向子控件之間的間隙,代碼如下:

    Wrap(
    	spacing: 5,
        runSpacing: 2,
    	...
    )
    

    textDirection 屬性表示 Wrap 主軸方向上子組件的方向,取值範圍是 ltr(從左到右) 和 rtl(從右到左),下面是從右到左的代碼:

    Wrap(
    	textDirection: TextDirection.rtl,
    	...
    )
    

    verticalDirection 屬性表示 Wrap 交叉軸方向上子組件的方向,取值範圍是 up(向上) 和 down(向下),設置代碼如下:

    Wrap(
    	verticalDirection: VerticalDirection.up,
    	...
    )
    

    注意:文字為0的組件是在下面的。

    自定義布局組件

    大部分情況下,不會使用到 Flow ,但 Flow 可以調整子組件的位置和大小,結合Matrix4繪製出各種酷炫的效果。

    Flow 組件對使用轉換矩陣操作子組件經過系統優化,性能非常高效。

    基本用法如下:

    Flow(
      delegate: SimpleFlowDelegate(),
      children: List.generate(5, (index) {
        return Container(
          height: 100,
          color: Colors.primaries[index % Colors.primaries.length],
        );
      }),
    )
    

    delegate 控制子組件的位置和大小,定義如下 :

    class SimpleFlowDelegate extends FlowDelegate {
      @override
      void paintChildren(FlowPaintingContext context) {
        for (int i = 0; i < context.childCount; ++i) {
          context.paintChild(i);
        }
      }
    
      @override
      bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
        return false;
      }
    }
    

    delegate 要繼承 FlowDelegate,重寫 paintChildrenshouldRepaint 函數,上面直接繪製子組件,效果如下:

    只看到一種顏色並不是只繪製了這一個,而是疊加覆蓋了,和 Stack 類似,下面讓每一個組件有一定的偏移,SimpleFlowDelegate 修改如下:

    class SimpleFlowDelegate extends FlowDelegate {
      @override
      void paintChildren(FlowPaintingContext context) {
        for (int i = 0; i < context.childCount; ++i) {
          context.paintChild(i,transform: Matrix4.translationValues(0,i*30.0,0));
        }
      }
    
      @override
      bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
        return false;
      }
    }
    

    每一個子組件比上一個組件向下偏移30。

    仿 掘金-我的效果

    效果如下:

    到拿到一個頁面時,先要將其拆分,上面的效果拆分如下:

    總體分為3個部分,水平布局,紅色區域圓形頭像代碼如下:

    _buildCircleImg() {
      return Container(
        height: 60,
        width: 60,
        decoration: BoxDecoration(
            shape: BoxShape.circle,
            image: DecorationImage(image: AssetImage('assets/images/logo.png'))),
      );
    }
    

    藍色區域代碼如下:

    _buildCenter() {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text('老孟Flutter', style: TextStyle(fontSize: 20),),
          Text('Flutter、Android', style: TextStyle(color: Colors.grey),)
        ],
      );
    }
    

    綠色區域是一個圖標,代碼如下:

    Icon(Icons.arrow_forward_ios,color: Colors.grey,size: 14,),
    

    將這3部分組合在一起:

    Container(
      color: Colors.grey.withOpacity(.5),
      alignment: Alignment.center,
      child: Container(
        height: 100,
        color: Colors.white,
        child: Row(
          children: <Widget>[
            SizedBox(
              width: 15,
            ),
            _buildCircleImg(),
            SizedBox(
              width: 25,
            ),
            Expanded(
              child: _buildCenter(),
            ),
            Icon(Icons.arrow_forward_ios,color: Colors.grey,size: 14,),
            SizedBox(
              width: 15,
            ),
          ],
        ),
      ),
    )
    

    最終的效果就是開始我們看到的效果圖。

    水平展開/收起菜單

    使用Flow實現水平展開/收起菜單的功能,代碼如下:

    class DemoFlowPopMenu extends StatefulWidget {
      @override
      _DemoFlowPopMenuState createState() => _DemoFlowPopMenuState();
    }
    
    class _DemoFlowPopMenuState extends State<DemoFlowPopMenu>
        with SingleTickerProviderStateMixin {
      //動畫必須要with這個類
      AnimationController _ctrlAnimationPopMenu; //定義動畫的變量
      IconData lastTapped = Icons.notifications;
      final List<IconData> menuItems = <IconData>[
        //菜單的icon
        Icons.home,
        Icons.new_releases,
        Icons.notifications,
        Icons.settings,
        Icons.menu,
      ];
    
      void _updateMenu(IconData icon) {
        if (icon != Icons.menu) {
          setState(() => lastTapped = icon);
        } else {
          _ctrlAnimationPopMenu.status == AnimationStatus.completed
              ? _ctrlAnimationPopMenu.reverse() //展開和收攏的效果
              : _ctrlAnimationPopMenu.forward();
        }
      }
    
      @override
      void initState() {
        super.initState();
        _ctrlAnimationPopMenu = AnimationController(
          //必須初始化動畫變量
          duration: const Duration(milliseconds: 250), //動畫時長250毫秒
          vsync: this, //SingleTickerProviderStateMixin的作用
        );
      }
    
    //生成Popmenu數據
      Widget flowMenuItem(IconData icon) {
        final double buttonDiameter =
            MediaQuery.of(context).size.width * 2 / (menuItems.length * 3);
        return Padding(
          padding: const EdgeInsets.symmetric(vertical: 8.0),
          child: RawMaterialButton(
            fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
            splashColor: Colors.amber[100],
            shape: CircleBorder(),
            constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)),
            onPressed: () {
              _updateMenu(icon);
            },
            child: Icon(icon, color: Colors.white, size: 30.0),
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Flow(
            delegate: FlowMenuDelegate(animation: _ctrlAnimationPopMenu),
            children: menuItems
                .map<Widget>((IconData icon) => flowMenuItem(icon))
                .toList(),
          ),
        );
      }
    }
    

    FlowMenuDelegate 定義如下:

    class FlowMenuDelegate extends FlowDelegate {
      FlowMenuDelegate({this.animation}) : super(repaint: animation);
      final Animation<double> animation;
    
      @override
      void paintChildren(FlowPaintingContext context) {
        double x = 50.0; //起始位置
        double y = 50.0; //橫向展開,y不變
        for (int i = 0; i < context.childCount; ++i) {
          x = context.getChildSize(i).width * i * animation.value;
          context.paintChild(
            i,
            transform: Matrix4.translationValues(x, y, 0),
          );
        }
      }
    
      @override
      bool shouldRepaint(FlowMenuDelegate oldDelegate) =>
          animation != oldDelegate.animation;
    }
    

    半圓菜單展開/收起

    代碼如下:

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    
    class DemoFlowMenu extends StatefulWidget {
      @override
      _DemoFlowMenuState createState() => _DemoFlowMenuState();
    }
    
    class _DemoFlowMenuState extends State<DemoFlowMenu>
        with TickerProviderStateMixin {
      //動畫需要這個類來混合
      //動畫變量,以及初始化和銷毀
      AnimationController _ctrlAnimationCircle;
    
      @override
      void initState() {
        super.initState();
        _ctrlAnimationCircle = AnimationController(
            //初始化動畫變量
            lowerBound: 0,
            upperBound: 80,
            duration: Duration(milliseconds: 300),
            vsync: this);
        _ctrlAnimationCircle.addListener(() => setState(() {}));
      }
    
      @override
      void dispose() {
        _ctrlAnimationCircle.dispose(); //銷毀變量,釋放資源
        super.dispose();
      }
    
      //生成Flow的數據
      List<Widget> _buildFlowChildren() {
        return List.generate(
            5,
            (index) => Container(
                  child: Icon(
                    index.isEven ? Icons.timer : Icons.ac_unit,
                    color: Colors.primaries[index % Colors.primaries.length],
                  ),
                ));
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            Positioned.fill(
              child: Flow(
                delegate: FlowAnimatedCircle(_ctrlAnimationCircle.value),
                children: _buildFlowChildren(),
              ),
            ),
            Positioned.fill(
              child: IconButton(
                icon: Icon(Icons.menu),
                onPressed: () {
                  setState(() {
                    //點擊后讓動畫可前行或回退
                    _ctrlAnimationCircle.status == AnimationStatus.completed
                        ? _ctrlAnimationCircle.reverse()
                        : _ctrlAnimationCircle.forward();
                  });
                },
              ),
            ),
          ],
        );
      }
    }
    

    FlowAnimatedCircle 代碼如下:

    class FlowAnimatedCircle extends FlowDelegate {
      final double radius; //綁定半徑,讓圓動起來
      FlowAnimatedCircle(this.radius);
    
      @override
      void paintChildren(FlowPaintingContext context) {
        if (radius == 0) {
          return;
        }
        double x = 0; //開始(0,0)在父組件的中心
        double y = 0;
        for (int i = 0; i < context.childCount; i++) {
          x = radius * cos(i * pi / (context.childCount - 1)); //根據數學得出坐標
          y = radius * sin(i * pi / (context.childCount - 1)); //根據數學得出坐標
          context.paintChild(i, transform: Matrix4.translationValues(x, -y, 0));
        } //使用Matrix定位每個子組件
      }
    
      @override
      bool shouldRepaint(FlowDelegate oldDelegate) => true;
    }
    

    交流

    老孟Flutter博客地址(330個控件用法):http://laomengit.com

    歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

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

    【其他文章推薦】

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

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

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

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

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

    聚甘新