情報局:大排量發動機都狗帶了,霧霾還會繼續嗎?

未來,奧迪Q8的量產版還可能搭載3。0T V6汽油發動機或4。0T V8柴油發動機,並且會配備奧迪最新的48V電氣系統。(奧迪的設計師不是都跑光了。)中興手機也造車日前,中興通訊通過收購珠海廣通客車,獲得了進入汽車市場的資質以及傳統造車技術。

奔馳E180

日前,有媒體拍攝到了北京奔馳E 180 L的無偽裝實車圖,外觀與在售E級加長保持一致,內飾最大的不同就是採用了傳統机械式指針儀錶盤。

動力方面將搭載一台1.6T發動機,最大功率為115kW,峰值扭矩250牛米,與發動機匹配的是7速手自一體變速箱。預計將於2017年春季上市,將會成為國產全新E級的入門車型,售價也將進一步下探。(或將掀起一股新的扣標潮)

紅旗N501

近日,有媒體曝光一組紅旗全新大型車的路試諜照。據悉,新車將基於紅旗L5的平台進行打造,內部代號為N501,外觀方面。。。與勞斯萊斯古斯特有些相似。

動力將會搭載全新的4.0T V8雙渦輪增壓發動機,還說沒有搭載6.0L V12自然吸氣發動機的計劃。

全新寶馬x3

據海外媒體報道,全新寶馬X3將於2017年8月正式發布,並在同年9月開幕的2017法蘭克福車展上面向全球消費者亮相,它將比現款的X3(F25)在車身尺寸上有全面的放大。

在中國還將實現國產加長。新車將搭載四缸和六缸發動機,此外還將推出性能車型X3 M40i、X3 M40d,以及純電動版本車型(也就是更大更粗更強)

奧迪Q8e-tron概念車

而奧迪則正式公布了Q8e-tron概念車的官圖,並且將在2017年1月的北美車展上展出發布。直接與寶馬X6以及奔馳GLE Coupe進行pK。

從名字來看,這款奧迪Q8概念車將會搭載一套純電動或者插電式混動系統。

未來,奧迪Q8的量產版還可能搭載3.0T V6汽油發動機或4.0T V8柴油發動機,並且會配備奧迪最新的48V電氣系統。(奧迪的設計師不是都跑光了?)

中興手機也造車

日前,中興通訊通過收購珠海廣通客車,獲得了進入汽車市場的資質以及傳統造車技術。

此前中興智能汽車副總經理曾表示爭取在五年時間內,商用車市場做到業內前五”。

另有中興通訊的工作人員表示,中興收購廣通客車主要是為了取得資質,目前新能源客車的訂單量對於整個公司的業績還不構成影響,收購廣通就是為了牌照,所以產能有多少,能夠獲得多少,在於國家政策補貼。。。(哦哦,這個我們知道了)

Top gear賽道被賣

最近,前英國BBC媒體報道稱電視汽車節目《TopGear》專用賽道即將轉變成為生活住宅區,準備開發1800至2600棟全新房屋。

在經過多年的鬥爭,最終經過英國Waverly理事會公布10比8的投票結果,決定將《TopGear》使用的測試賽道重新開發規劃成生活、商業住宅區。(是不是李嘉誠做的?)

沃德十佳發動機大排全軍覆沒

不久前,沃德十佳發動機2017年的評選公布了名單,除了純自吸動力全軍覆沒之外,那就是今年沒有一台V8發動機,最大排量只有3.6L,要知道沃德是美國人的機構,V8發動機在他們心中的地位就像中國的乒乓球一樣。

其實這個評選是對美國本土銷售的車型進行了接近日常駕駛狀況的測試后,根據每台發動機的馬力、扭矩、技術含量,以及實際使用中表現出來的工作特性進行全面評估,最終選出十台綜合表現最好的發動機。

最早始於1994年,在每年年末發布。整個評選過程有着非常明確的規則,首先獲得提名的發動機必須是批量生產,而且所裝備的汽車開始銷售的時間也有明確的規定。同時參評的發動機所裝備的汽車售價須低於6萬美元,(有些沒獲獎的發動機可能是車賣得太貴了),此次的獲獎發動機要麼是混合動力,要麼是渦輪增壓,雖然曾有人研究表明不能盲目相信沃德十佳發動機

有諸多“潛規則”,比如豪車賣得便宜(這TM誰都喜歡啊),奇葩發動機容易獲獎:像沃爾沃的机械渦輪雙增壓,眼看着汽車的混合動力越來越多,排量是越來越小,排放越來越好,限號越來越多,要是以後路上全是4缸混動車等霧霾出來的時候,誰會成為下一個污染源呢?

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

不得不服!這台全能SUV居然能甩對手一條街

同時,奧迪Q7後排座椅支持前後移動,以身高178cm的體驗者,調節至最後面的後排實測腿部空間可超過2拳,即便調節最前面的腿部空間仍達1拳有餘。奧迪Q7的後備箱空間也不失所望,高達890L容積滿足一切對空間的想象。兼顧科技實用 配置領先同級在配置上,為了更好地體現兩者的差異性,我們不妨拿奧迪Q7和寶馬X5各自的頂配車型對比,從兩車的表格可以明顯看出,奧迪Q7擁有寶馬X5所不具備的感應後備箱,12。

在整個汽車大環境不太景氣的背景下,唯獨SUV銷量始終保持了逆市上揚的態勢,SUV市場份額從2012年的15%,一路攀升到今年的39%,由此可見,國人是有多喜歡SUV的高視野、高底盤以及大空間。然而,隨着消費需求的不斷升級,SUV同質化情況的加劇,用戶已經不滿足於SUV基礎單一的功能性,更需要兼具豪華感、科技感、駕駛性能等高端產品元素。

這也是為何豪華中大型SUV市場能受到極高的用戶關注度,而在這個級別中,最引人矚目想必是寶馬X5,奧迪Q7這兩款標杆之作。眾所周知,寶馬X5號稱是最強公路SUV,那麼奧迪Q7呢?事實上,奧迪Q7不僅是一款全路況SUV,同時還是一款能越野的SUV。因此,比較這兩台車就多了一番趣味性和參考價值。話不多說,還是趕緊看看寶馬X5和奧迪Q7,誰會更符合消費者的審美眼光呢?

外觀各具特色 奧迪Q7更顯豪華

寶馬X5的外觀風格強調力量感,雙腎前臉和天使眼是我們所熟知的寶馬家族化設計標誌,採用大量犀利有力量感的線條來突顯整車的運動感。而奧迪Q7作為奧迪旗下的旗艦車型,外觀方面自然也不敢馬虎,六邊形大嘴,搭配炫酷的LED 矩陣式大燈,兼具時尚和科技感,造型設計趨向於年輕動感。總的來說,同為德系的兩款豪華SUV都有很高的顏值,但寶馬X5更強調其運動身份,設計風格偏向於張揚高調。而奧迪Q7看上去更顯尊貴豪華,氣宇軒昂。

尺寸方面,長寬高達到5086/1968/1716mm,軸距3001mm,但相比寶馬X5,奧迪Q7在長度和軸距上更具優勢。此外,奧迪Q7還是同級別長度和軸距的領先者,舒適、寬敞不在話下。

內飾風格迥異 奧迪Q7科技完勝

兩車內飾的豪華感和品質感都營造得很出色,做工用料均無可挑剔。寶馬X5這套內飾設計相信大家已經非常熟悉,層次感分明,懸浮式中控大屏配合大量的實體按鍵,延續了寶馬以往的設計理念,但看久之後難免會有些審美疲勞。反觀之下,奧迪Q7的內飾則帶來一種新鮮感,煥然一新的內飾設計,瞬間燃爆用戶最敏銳的視覺神經,最大的驚喜莫過於這塊12.3英寸的全液晶儀錶盤,科技感爆棚,無論操作還是反饋的人性化程度都非常高,着實是令人愛不惜手。

正如上面所提到的,奧迪Q7的長度和軸距都要比寶馬X5優秀,實際表現也確實如此。同時,奧迪Q7後排座椅支持前後移動,以身高178cm的體驗者,調節至最後面的後排實測腿部空間可超過2拳,即便調節最前面的腿部空間仍達1拳有餘。奧迪Q7的後備箱空間也不失所望,高達890L容積滿足一切對空間的想象。

兼顧科技實用 配置領先同級

在配置上,為了更好地體現兩者的差異性,我們不妨拿奧迪Q7和寶馬X5各自的頂配車型對比,從兩車的表格可以明顯看出,奧迪Q7擁有寶馬X5所不具備的感應後備箱,12.3英寸全液晶儀錶盤,自動泊車,LED矩陣式大燈等科技配置。 尤其是在操控輔助方面,奧迪Q7旗艦車型所配備的quattro全時四驅系統、後輪主動轉向系統(選裝),同時全車減重300KG,足以提供奧迪Q7良好的操控性,賦予了奧迪Q7全路況駕駛性能。

更強勁的動力 更經濟的油耗

動力系統方面,兩車的銷量主力均為2.0T車型,寶馬X5和奧迪Q7都採用了2.0T發動機和8速手自一體變速箱的動力總成,寶馬X5的最大功率為180Kw,最大扭矩為350Nm,而奧迪Q7的最大功率為185Kw,最大扭矩為370Nm。從兩車的技術參數可見,奧迪Q7相對寶馬X5略勝一籌。而且,在燃油經濟性上,奧迪Q7 8.5L的官方百公里油耗數據,也確實比寶馬X5更有話語權。

如此全能的奧迪Q7會是你明年的目標?

說完了一大段后,誰更適合你,答案不正明擺着嗎?相比寶馬X5,奧迪Q7在許多方面都擁有過硬的實力,隨便一個產品點就能讓你為之傾心。氣場十足的高顏值外觀、由內到外的科技感和品質感,哦!別忘了,還有奧迪Q7最引以為傲的動力性和操控性。當然,或許你還在為操控還是舒適,實用還是逼格而煩惱,那麼綜合來看,除了全能均衡的奧迪Q7,還能有誰?嗯嗯,看到這之後,已經猜到你為明年購車計劃定好目標了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

就算你年薪二十萬,教授也只推薦買這些十多萬的車

對不起,真正為玩車的人是玩手動擋的不喜歡日系車。那高爾夫R-Line也是能讓你爽翻的車重只有1。3噸的R-Line卻有着150ps的馬力搭配換擋奇快的7速雙離合秒殺BBA不是夢當然,也有人買車不是為了玩對於顧家好男人來說空間太小,就算車子會飛也沒有用那這個時候,擁有寬裕後排的B級車就是你的首選什麼。

老K是的一位朋友

他是某保險公司的高級銷售

相當的敬業

所謂一分耕耘一分收穫

兢兢業業的老K現在已經是年薪二十萬的有錢淫

收入比叫獸高好幾個檔次呢

有錢了,逼格肯定要跟上

老K立馬去4S給提了一輛BBA

然而

讓老K意想不到的是

養一輛豪車比他想象的吃力

油耗和保險已經夠厲害了

維修保養就更別說

進去一次4S店

坑你個三五千都是洒洒碎

所以老K很後悔當初沒有聽叫獸的話

買一輛十來萬的車

然而這已經太遲了

為了避免大家重走老K的路

叫獸有必要說說

哪怕你年薪二十萬,買一輛十來萬的車就夠了

十來萬的車子其實也可以很有逼格

雖然太愛面子不是好事

但是一個人開什麼車子的確體現了他的品味

就像老K的BBA,起碼在外人看來不是垃圾

然而你真要高逼格的話,十來萬的車確實也有

記得第一次看見博瑞實車的時候

還以為是哪個品牌的新款進口車

走進一看才發現這是大名鼎鼎的博瑞

連都會產生這種錯覺

那對於不太懂車的人來說

博瑞的逼格就不言而喻了

那假如想要強勁的動力呢?

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

現在的編程和二十年前有什麼不同?

 

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

@Autowired 註解詳解

前言

我們平時使用 Spring 時,想要 依賴注入 時使用最多的是 @Autowired 註解了,本文主要講解 Spring 是如何處理該註解並實現 依賴注入 的功能的。

正文

首先我們看一個測試用例:

User 實體類:

public class User {

    private Long id;
    private String name;

	// 省略 get 和 set 方法
}

測試類:

public class AnnotationDependencyInjectTest {

    /**
     * @Autowired 字段注入
     */
    @Autowired
    private User user;

    private City city;

    /**
     * @Autowired 方法注入
     */
    @Autowired
    public void initCity(City city) {
        this.city = city;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AnnotationDependencyInjectTest.class);
        context.refresh();
        AnnotationDependencyInjectTest bean = context.getBean(AnnotationDependencyInjectTest.class);
        // @Autowired 字段注入
        System.out.println(bean.user);
        // @Autowired 方法注入
        System.out.println(bean.city);
        UserHolder userHolder = context.getBean(UserHolder.class);
        // @Autowired 構造器注入
        System.out.println(userHolder.getUser());
        context.close();
    }

    @Bean
    public User user() {
        User user = new User();
        user.setId(1L);
        user.setName("leisurexi");
        return user;
    }

    @Bean
    public City city() {
        City city = new City();
        city.setId(1L);
        city.setName("北京");
        return city;
    }
    
    /**
     * @Autowired 構造函數注入
     */
    static class UserHolder {

        private User user;

        @Autowired
        public UserHolder(User user) {
            this.user = user;
        }

        public User getUser() {
            return user;
        }

        public void setUser(User user) {
            this.user = user;
        }
    }

}

上面分別展示了 @Autowired 註解的字段注入和方法注入,下面我們開始分析 Spring 是如何實現的。

首先使 @Autowired 註解生效的一個關鍵類是 AutowiredAnnotationBeanPostProcessor,該類實現了 InstantiationAwareBeanPostProcessorAdapter 抽象類;該抽象類就是一個適配器的作用提供了接口方法的默認實現,InstantiationAwareBeanPostProcessorAdapter 又實現了 SmartInstantiationAwareBeanPostProcessor 接口,同時實現該接口的 determineCandidateConstructors() 方法可以指定 bean 的候選構造函數;然後 SmartInstantiationAwareBeanPostProcessor 接口又繼承了 InstantiationAwareBeanPostProcessor 接口,該接口提供了 bean 實例化前後的生命周期回調以及屬性賦值前的後置處理方法,@Autowired 註解的屬性注入就是通過重寫該接口的 postProcessProperties() 實現的。這兩個接口都在 在 Spring IoC createBean 方法詳解 一文中有介紹過。下面我們看一下 AutowiredAnnotationBeanProcessor 的繼承關係圖:

關於 AutowiredAnnotationBeanPostProcessor 這個後置處理器是怎麼加入到 beanFactory 中的,我們在 Spring IoC component-scan 節點詳解 一文中介紹過主要是通過 AnnotationConfigUtils#registerAnnotationConfigProcessors() 實現的。

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
    BeanDefinitionRegistry registry, @Nullable Object source) {
    
    // 忽略其它代碼
    
    // 註冊用於處理@Autowired、@Value、@Inject註解的後置處理器
    if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new
            RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def,
                                           AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
    // 忽略其它代碼
    
}

屬性和方法注入

AutowiredAnnotationBeanPostProcessor 中跟屬性注入有關的方法有兩個:postProcessMergedBeanDefinitionpostProcessPropertyValues

前者是 MergedBeanDefinitionPostProcessor 接口中的方法,定義如下:

public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {

	/**
	 * 對指定bean的BeanDefinition合併后的處理方法回調
	 */
	void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);

	/**
	 * @since 5.1
	 * 通知已重新設置指定beanName的BeanDefinition,如果實現該方法應該清除受影響的bean的所有元數據
	 */
	default void resetBeanDefinition(String beanName) {
	}

}

後者是 InstantiationAwareBeanPostProcessor 接口中的方法,定義如下:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

	/**
	 * Bean 實例化前調用,返回非 {@code null} IoC 容器不會對 Bean 進行實例化 並且後續的生命周期回調方	  *	法不會調用,返回 {@code null} 則進行 IoC 容器對 Bean 的實例化
	 */
	@Nullable
	default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		return null;
	}

	/**
	 * Bean 實例化之後,屬性填充之前調用,返回 {@code true} 則進行默認的屬性填充步驟,返回 {@code 		 * false} 會跳過屬性填充階段。
	 */
	default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		return true;
	}

	/**
	 * Bean 實例化后屬性賦值前調用,PropertyValues 是已經封裝好的設置的屬性值,返回 {@code null} 繼續
	 * 使用現有屬性,否則會替換 PropertyValues。
	 * @since 5.1版本新加的和底下的方法一樣
	 */
	@Nullable
	default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
			throws BeansException {
		return null;
	}

	/**
	 * 跟上面方法一樣的功能,只不過是5.1以前版本所使用的
	 * 返回 {@code null} 會跳過屬性填充階段
	 */
	@Deprecated
	@Nullable
	default PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

		return pvs;
	}

}

關於這兩個方法的調用時機,可以查看 Spring IoC createBean 方法詳解。

AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

首先執行的是 postProcessMergedBeanDefinition()

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 尋找需要注入的字段或方法,並封裝成 InjectionMetadata,見下文詳解
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    // 檢查元數據中的註解信息
    metadata.checkConfigMembers(beanDefinition);
}

InjectionMetadata 就是注入的元信息描述,主要字段如下:

public class InjectionMetadata {
    
    // 需要依賴注入的目標類
    private final Class<?> targetClass;
    // 注入元素的集合
    private final Collection<InjectedElement> injectedElements;
    
    // 忽略其它代碼
    
}

InjectedElement 就是注入的元素,主要字段如下:

public abstract static class InjectedElement {
    
    // 注入的屬性或方法
    protected final Member member;
    // 需要注入的是否是字段
    protected final boolean isField;
    
}

AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    // Fall back to class name as cache key, for backwards compatibility with custom callers.
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // Quick check on the concurrent map first, with minimal locking.
    // 首先從緩存中獲取
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    // 判斷是否需要刷新,即metadata為null或者metadata中存儲的targetClass和當前clazz不等
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        // 這裏相當於是一個double check,防止多線程出現的併發問題
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                // 構建注入元信息,見下文詳解
                metadata = buildAutowiringMetadata(clazz);
                // 放入緩存中
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    // 返回注入元信息
    return metadata;
}

AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    }
    // 判斷當前類或其字段或其方法是否標註了autowiredAnnotationTypes中的註解,沒有的話直接返回空的
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;

    do {
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
        // 遍歷targetClass中的字段
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            // 獲取field上的@Autowired註解信息
            MergedAnnotation<?> ann = findAutowiredAnnotation(field);
            if (ann != null) {
                // 如果字段是靜態類型是不會進行注入的
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                // 獲取@Autowired註解中的required屬性
                boolean required = determineRequiredStatus(ann);
                // 將裝成AutowiredFieldElement添加進currElements
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });
        // 遍歷targetClass中的方法
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // 找到橋接方法
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            // 判斷方法的可見性,如果不可見則直接返回
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            // 獲取method上的@Autowired註解信息
            MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                // 如果是靜態方法是不會進行注入的
                if (Modifier.isStatic(method.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                // 方法注入沒有參數就違背了初衷,就是在脫褲子放屁
                if (method.getParameterCount() == 0) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation should only be used on methods with parameters: " + method);
                    }
                }
                // 獲取@Autowired註解中的required屬性
                boolean required = determineRequiredStatus(ann);
                // 將方法和目標類型封裝成屬性描述符
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                // 封裝成AutowiredMethodElement添加進currElements
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });
        // 將currElements整個添加進elements
        elements.addAll(0, currElements);
        // 獲取targetClass的父類,進行下一次循環
        targetClass = targetClass.getSuperclass();
    }
    // 當targetClass為空或者targetClass等於Object.class時會退出循環
    while (targetClass != null && targetClass != Object.class);
    // 將elements和clazz封裝成InjectionMetadata返回
    return InjectionMetadata.forElements(elements, clazz);
}

上面代碼中的 findAutowiredAnnotation() 就是在遍歷 autowiredAnnotationTypes 屬性,看字段或者方法上的註解是否存在於 autowiredAnnotationTypes 中,或者其派生註解,找到第一個就返回,不會再繼續遍歷了。

public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
		implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
    
    private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
    
    public AutowiredAnnotationBeanPostProcessor() {
        this.autowiredAnnotationTypes.add(Autowired.class);
        this.autowiredAnnotationTypes.add(Value.class);
        try {
            this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
                                              ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
            logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
        }
        catch (ClassNotFoundException ex) {
            // JSR-330 API not available - simply skip.
        }
	}
    
}

AutowiredAnnotationBeanPostProcessor 類的構造函數中,我們可以發現 autowiredAnnotationTypes 默認添加了 @Autowired@Value 以及 @Inject (在 JSR-330 的jar包存在於當前環境時)。

至此,使用 @Autowired 修飾的字段和方法已經封裝成 InjectionMetadata 並放在 injectionMetadataCache 緩存中,便於後續使用。

AutowireAnnotationBeanPostProcessor#postProcessProperties

postProcessMergedBeanDefinition() 調用后 bean 就會進行實例化接着調用 postProcessProperties()

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 獲取緩存中的 InjectionMetadata
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        // 進行屬性的注入
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    // 返回注入的屬性
    return pvs;
}

// InjectMetadata.java
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 獲取檢查后的元素
    Collection<InjectedElement> checkedElements = this.checkedElements;
    // 如果checkedElements不為空就使用checkedElements,否則使用injectedElements
    Collection<InjectedElement> elementsToIterate =
        (checkedElements != null ? checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
        // 遍歷elementsToIterate
        for (InjectedElement element : elementsToIterate) {
            if (logger.isTraceEnabled()) {
                logger.trace("Processing injected element of bean '" + beanName + "': " + element);
            }
            // AutowiredFieldElement、AutowiredMethodElement這兩個類繼承InjectionMetadata.InjectedElement,各自重寫了inject方法
            element.inject(target, beanName, pvs);
        }
    }
}

AutowiredFieldElement#inject

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 強轉成Field類型
    Field field = (Field) this.member;
    Object value;
    if (this.cached) {
        // 如果緩存過,直接使用緩存的值,一般第一次注入都是false
        value = resolvedCachedArgument(beanName, this.cachedFieldValue);
    }
    else {
        // 構建依賴描述符
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        desc.setContainingClass(bean.getClass());
        Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
        Assert.state(beanFactory != null, "No BeanFactory available");
        // 獲取類型轉換器
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        try {
            // 進行依賴解決,獲取符合條件的bean
            value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        }
        catch (BeansException ex) {
            throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
        }
        // 加鎖
        synchronized (this) {
            // 如果沒有被緩存
            if (!this.cached) {
                // 找到了需要的bean || 該字段是必要的
                if (value != null || this.required) {
                    // 將依賴描述符賦值給cachedFieldValue
                    this.cachedFieldValue = desc;
                    // 註冊bean的依賴關係,用於檢測是否循環依賴
                    registerDependentBeans(beanName, autowiredBeanNames);
                    // 如果符合條件的bean只有一個
                    if (autowiredBeanNames.size() == 1) {
                        String autowiredBeanName = autowiredBeanNames.iterator().next();
                        // beanFactory含有名為autowiredBeanName的bean && 類型是匹配的
                        if (beanFactory.containsBean(autowiredBeanName) &&
                            beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                            // 將該屬性解析到的bean的信息封裝成ShortcutDependencyDescriptor
                            // 之後可以通過調用resolveShortcut()來間接調beanFactory.getBean()快速獲取bean
                            this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                desc, autowiredBeanName, field.getType());
                        }
                    }
                }
                else {
                    this.cachedFieldValue = null;
                }
                // 緩存標識設置為true
                this.cached = true;
            }
        }
    }
    // 如果找到了符合的bean,設置字段可訪問,利用反射設置值
    if (value != null) {
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
    }
}

上面代碼中的 beanFactory.resolveDependency() 在 Spring IoC createBean 方法詳解 一文中有介紹過,這裏不再贅述;同樣 registerDependentBeans() 最終會調用 DefaultSingletonBeanRegistry.registerDependentBean() ,該方法在 Spring IoC getBean 方法詳解 一文中有介紹過,這裏也不再贅述。

AutowiredMethodElement#inject

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 檢查是否需要跳過
    if (checkPropertySkipping(pvs)) {
        return;
    }
    // 強轉成Method類型
    Method method = (Method) this.member;
    Object[] arguments;
    if (this.cached) {
        // Shortcut for avoiding synchronization...
        // 如果緩存過,直接調用beanFactory.resolveDependency()返回符合的bean
        arguments = resolveCachedArguments(beanName);
    }
    else {
        // 獲取參數數量
        int argumentCount = method.getParameterCount();
        arguments = new Object[argumentCount];
        // 創建依賴描述符數組
        DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
        // 記錄用於自動注入bean的名稱集合
        Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
        Assert.state(beanFactory != null, "No BeanFactory available");
        // 獲取類型轉換器
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        // 遍歷參數
        for (int i = 0; i < arguments.length; i++) {
            // 將方法和參數的下標構建成MethodParameter,這裏面主要記錄了參數的下標和類型
            MethodParameter methodParam = new MethodParameter(method, i);
            // 將MethodParameter構建成DependencyDescriptor
            DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
            currDesc.setContainingClass(bean.getClass());
            descriptors[i] = currDesc;
            try {
                // 進行依賴解決,找到符合條件的bean
                Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
                if (arg == null && !this.required) {
                    arguments = null;
                    break;
                }
                arguments[i] = arg;
            }
            catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
            }
        }
        // 這裏跟字段注入差不多,就是註冊bean的依賴關係,並且緩存每個參數的ShortcutDependencyDescriptor
        synchronized (this) {
            if (!this.cached) {
                if (arguments != null) {
                    DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
                    registerDependentBeans(beanName, autowiredBeans);
                    if (autowiredBeans.size() == argumentCount) {
                        Iterator<String> it = autowiredBeans.iterator();
                        Class<?>[] paramTypes = method.getParameterTypes();
                        for (int i = 0; i < paramTypes.length; i++) {
                            String autowiredBeanName = it.next();
                            if (beanFactory.containsBean(autowiredBeanName) &&
                                beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
                                cachedMethodArguments[i] = new ShortcutDependencyDescriptor(descriptors[i], autowiredBeanName, paramTypes[i]);
                            }
                        }
                    }
                    this.cachedMethodArguments = cachedMethodArguments;
                }
                else {
                    this.cachedMethodArguments = null;
                }
                this.cached = true;
            }
        }
    }
    // 找到了符合條件的bean
    if (arguments != null) {
        try {
            // 設置方法可訪問,利用反射進行方法調用,傳入參數
            ReflectionUtils.makeAccessible(method);
            method.invoke(bean, arguments);
        }
        catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

構造器注入

構造器注入就是通過調用 determineCandidateConstructors() 來返回合適的構造器。

public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeanCreationException {

    // Quick check on the concurrent map first, with minimal locking.
    // 首先從緩存中獲取
    Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
    // 緩存為空
    if (candidateConstructors == null) {
        // Fully synchronized resolution now...
        // 這裏相當於double check
        synchronized (this.candidateConstructorsCache) {
            candidateConstructors = this.candidateConstructorsCache.get(beanClass);
            if (candidateConstructors == null) {
                Constructor<?>[] rawCandidates;
                try {
                    // 獲取beanClass的所有構造函數
                    rawCandidates = beanClass.getDeclaredConstructors();
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() +"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
                }
                // 存放標註了@Autowired註解的構造器
                List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
                // 存放標註了@Autowired註解,並且required為true的構造器
                Constructor<?> requiredConstructor = null;
                Constructor<?> defaultConstructor = null;
                for (Constructor<?> candidate : rawCandidates) {
                    // 獲取構造器上的@Autowired註解信息
                    MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
                    if (ann == null) {
                        // 如果沒有從候選者找到註解,則嘗試解析beanClass的原始類(針對CGLIB代理)
                        Class<?> userClass = ClassUtils.getUserClass(beanClass);
                        if (userClass != beanClass) {
                            try {
                                Constructor<?> superCtor =
userClass.getDeclaredConstructor(candidate.getParameterTypes());
                                ann = findAutowiredAnnotation(superCtor);
                            }
                            catch (NoSuchMethodException ex) {
                                // Simply proceed, no equivalent superclass constructor found...
                            }
                        }
                    }
                    if (ann != null) {
                        // 如果requiredConstructor不為空,代表有多個標註了@Autowired且required為true的構造器,此時Spring不知道選擇哪個拋出異常
                        if (requiredConstructor != null) {
                            throw new BeanCreationException(beanName, "Invalid autowire-marked constructor: " + candidate +". Found constructor with 'required' Autowired annotation already: " + requiredConstructor);
                        }
                        // 獲取@Autowired註解的reuired屬性的值
                        boolean required = determineRequiredStatus(ann);
                        if (required) {
                            // 如果當前候選者是@Autowired(required = true),則之前不能存在其他使用@Autowire註解的構造函數,否則拋異常
                            if (!candidates.isEmpty()) {
                                throw new BeanCreationException(beanName,"Invalid autowire-marked constructors: " + candidates +". Found constructor with 'required' Autowired annotation: " + candidate);
                            }
                            // required為true將當前構造器賦值給requiredConstructor
                            requiredConstructor = candidate;
                        }
                        // 將當前構造器加入進候選構造器中
                        candidates.add(candidate);
                    }
                    // 沒有標註了@Autowired註解且參數長度為0,賦值為默認構造器
                    else if (candidate.getParameterCount() == 0) {
                        defaultConstructor = candidate;
                    }
                }
                // 有標註了@Autowired註解的構造器
                if (!candidates.isEmpty()) {
                    // Add default constructor to list of optional constructors, as fallback.
                    // 沒有標註了@Autowired且required為true的構造器
                    if (requiredConstructor == null) {
                        // 默認構造器不為空
                        if (defaultConstructor != null) {
                            // 將默認構造器加入進候選構造器中
                            candidates.add(defaultConstructor);
                        }
                    }
                    // 將候選者賦值給candidateConstructors
                    candidateConstructors = candidates.toArray(new Constructor<?>[0]);
                }
                // 只有1個構造器 && 參數長度大於0(非默認構造器),只能用它當做候選者了
                else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
                    candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
                }
                // 只有1個構造器 && 參數長度大於0,只能用它當做候選者了
                else if (nonSyntheticConstructors == 2 && primaryConstructor != null &&
                         defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {
                    candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor};
                }
                else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {
                    candidateConstructors = new Constructor<?>[] {primaryConstructor};
                }
                // 返回一個空的Constructor
                else {
                    candidateConstructors = new Constructor<?>[0];
                }
                // 緩存候選的構造器
                this.candidateConstructorsCache.put(beanClass, candidateConstructors);
            }
        }
    }
    // 如果候選構造器長度大於0,直接返回,否則返回null
    return (candidateConstructors.length > 0 ? candidateConstructors : null);
}

關於 SmartInstantiationAwareBeanPostProcessor 接口的調用時機,在 Spring IoC createBean 方法詳解 一文中有介紹過,這裏就不再贅述了。

總結

本文主要介紹了 Spring 對 @Autowired 註解的主要處理過程,結合前面的 Spring IoC getBean 方法詳解 和 Spring IoC createBean 方法詳解 文章一起看才能更好的理解。

最後,我模仿 Spring 寫了一個精簡版,代碼會持續更新。地址:https://github.com/leisurexi/tiny-spring。

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

【其他文章推薦】

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

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

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

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

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

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

痞子衡嵌入式:鏈接函數到8字節對齊地址或可進一步提升i.MXRT1xxx內核執行性能

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是i.MXRT上進一步提升代碼執行性能的經驗

  今天跟大家聊的這個話題還是跟痞子衡最近這段時間參与的一個基於i.MXRT1170的大項目有關,痞子衡在做其中的開機動畫功能,之前寫過一篇文章 《降低刷新率是定位LCD花屏显示問題的第一大法》 介紹了開機動畫功能的實現以及LCD显示注意事項,在此功能上,痞子衡想進一步測試從芯片上電到LCD屏显示第一幅完整圖像的時間,這個時間我們暫且稱為1st UI時間,該時間的長短對項目有重要意義。

  痞子衡分別測試了代碼在XIP執行下和在TCM里執行下的1st UI時間,得到的結果竟然是XIP執行比TCM執行還要快50ms,這是怎麼回事?這完全顛覆了我們的理解,i.MXRT上TCM是與內核同頻的,Flash速度遠低於TCM。如果是XIP執行,即使有I-Cache加速,也最多與TCM執行一樣快,怎麼可能做到比TCM執行快這麼多。於是痞子衡便開始深挖這個奇怪的現象,然後發現了進一步提升代碼執行性能的秘密。

一、引出計時差異問題

  痞子衡的開機動畫程序是基於 \SDK_2.x.x_MIMXRT1170-EVK\boards\evkmimxrt1170\jpeg_examples\sd_jpeg 例程的,只是去了SD卡和libjpeg庫相關代碼。工程有兩個build,一個是TCM里執行(即debug),另一個是XIP執行(即flexspi_nor_debug)。

  項目板上的Flash型號是MX25UW51345G,痞子衡將其配成Octal mode, DDR, 166MHz用於啟動。項目板上還有兩個LED燈,痞子衡在LED燈上飛了兩根線,連同POR引腳一起連上示波器,用於精確測量1st UI各部分時間組成。

  示波器通道1連接POR引腳,表明1st UI時間起點;通道2連接LED1 GPIO,表明ROM啟動時間(進入用戶APP的時間點);通道3連接LED2 GPIO,做兩次電平變化,分別是1st圖像幀開始和結束的時間點。翻轉LED GPIO代碼位置如下:

void light_led(uint32_t ledIdx, uint8_t ledVal);

void SystemInit (void) {
    // 將LED1置1,標示ROM啟動時間
    light_led(1, 1);

    SCB->CPACR |= ((3UL << 10*2) | (3UL << 11*2));

    // ...
}

void APP_InitDisplay(void)
{
    // ...

    g_dc.ops->enableLayer(&g_dc, 0);

    // 將LED2置1,標示1st圖像幀開始時間點
    light_led(2, 1);
}

int main(void)
{
    BOARD_ConfigMPU();
    BOARD_InitBootPins();
    BOARD_BootClockRUN();
    BOARD_ResetDisplayMix();

    APP_InitDisplay();

    while (1)
	{
	    // ...
	}
}

static void APP_BufferSwitchOffCallback(void *param, void *switchOffBuffer)
{
    s_newFrameShown = true;

    // 將LED2置0,標示1st圖像幀結束時間點
    light_led(2, 0);
}

  上圖是痞子衡抓到的波形(30Hz,XIP),痞子衡一共做了四次測試,分別是30Hz LCD刷新率下的XIP/TCM以及60Hz LCD刷新率下的XIP/TCM,結果如下錶所示。表中的Init Time一欄表示的是開機動畫程序代碼執行時間(從SystemInit()函數開始執行到APP_InitDisplay()函數結束的時間),可以看到TCM執行比XIP執行慢近50ms,這便是奇怪問題所在。

代碼位置 LCD刷新率 POR Time Boot Time Init Time Launch Time
XIP 30Hz 3.414ms 10.082ms 34.167ms + 153ms 32.358ms
TCM 30Hz 3.414ms 10.854ms 33.852ms + 203ms 32.384ms
XIP 60Hz 3.414ms 9.972ms 18.142ms + 153ms 16.166ms
TCM 60Hz 3.414ms 10.92ms 17.92ms + 203ms 16.104ms

二、定位計時差異問題

  對於開機動畫代碼,XIP執行比TCM執行快這個結果,痞子衡是不相信的,於是痞子衡便用二分法逐步查找,發現時間差異是BOARD_InitLcdPanel()函數里的DelayMs()調用引起的,這些人為插入的延時是LCD屏控制器手冊里的要求,總延時時間應該是153ms,但是這個函數的執行在XIP下(153ms)和TCM里(203ms)時間不同。

static void BOARD_InitLcdPanel(void)
{
    // ...

#if (DEMO_PANEL ==  DEMO_PANEL_TM103XDKP13)
    // ...

    /* Power LCD on */    
    GPIO_PinWrite(LCD_RESET_GPIO, LCD_RESET_GPIO_PIN, 1);
    DelayMs(2);
    GPIO_PinWrite(LCD_RESET_GPIO, LCD_RESET_GPIO_PIN, 0);
    DelayMs(5);
    GPIO_PinWrite(LCD_RESET_GPIO, LCD_RESET_GPIO_PIN, 1);
    DelayMs(6);
    GPIO_PinWrite(LCD_STBYB_GPIO, LCD_STBYB_GPIO_PIN, 1);
    DelayMs(140);
#endif
    // ...
}

  所以現在的問題就是為何在TCM里執行DelayMs(153)需要203ms,而XIP執行下是精確的。讓我們進一步查看DelayMs()函數的原型,這個函數其實調用的是SDK_DelayAtLeastUs()函數,SDK_DelayAtLeastUs()函數從命名上看就很有意思,AtLeast即保證軟延時一定能滿足用戶設置的時間,但也可能超過這個時間。為何是AtLeast設計,其實這裏就涉及到Cortex-M7內核一個很重要的特性 – 指令雙發射,軟件延時的本質是靠CPU執行指令來消耗時間,但是CPU拿指令到底是單發射還是雙發射有一定的不確定性,因此無法做到精確,如果以全雙發射來計算,就能得出最小延時時間。

#define DelayMs                  VIDEO_DelayMs

#if defined(__ICCARM__)
static void DelayLoop(uint32_t count)
{
    __ASM volatile("    MOV    R0, %0" : : "r"(count));
    __ASM volatile(
        "loop:                          \n"
        "    SUBS   R0, R0, #1          \n"
        "    CMP    R0, #0              \n"
        "    BNE    loop                \n");
}
#endif

void SDK_DelayAtLeastUs(uint32_t delay_us, uint32_t coreClock_Hz)
{
    assert(0U != delay_us);
    uint64_t count = USEC_TO_COUNT(delay_us, coreClock_Hz);
    assert(count <= UINT32_MAX);

#if (__CORTEX_M == 7)
    count = count / 3U * 2U;
#else
    count = count / 4;
#endif
    DelayLoop(count);
}

void VIDEO_DelayMs(uint32_t ms)
{
    SDK_DelayAtLeastUs(ms * 1000U, SystemCoreClock);
}

  分析到現在,問題已經轉化成為何XIP下執行指令雙發射概率比TCM里執行指令雙發射概率更大,關於這個現象並沒有在ARM官方文檔里查找到相關信息,DelayLoop()循環里只是3條指令,XIP下執行肯定是在Cache line里,這跟在TCM里執行並沒有什麼區別。讓我們再去看看兩個工程的map文件,找到DelayLoop()函數鏈接地址,這個函數在兩個測試工程下鏈接地址對齊不一樣,這意味着測試條件不完全相同,或許這是一個解決問題的線索。

  XIP執行工程(flexspi_nor_debug),DelayLoop()函數地址8字節對齊:

*******************************************************************************
*** ENTRY LIST
***

Entry                       Address   Size  Type      Object
-----                       -------   ----  ----      ------
DelayLoop               0x3000'3169    0xa  Code  Lc  fsl_common.o [1]

  TCM執行工程(debug工程),DelayLoop()函數地址4字節對齊:

*******************************************************************************
*** ENTRY LIST
***

Entry                       Address   Size  Type      Object
-----                       -------   ----  ----      ------
DelayLoop                    0x314d    0xa  Code  Lc  fsl_common.o [1]

三、找到計時差異本質

  前面找到DelayLoop()函數鏈接地址差異是一個線索,那我們就針對這個線索做測試,不再讓鏈接器自動分配DelayLoop()函數地址,改為在鏈接文件里指定地址去鏈接,下面代碼是IAR環境下的示例,我們使用debug工程(即在TCM執行)來做測試。

  C源文件中在DelayLoop()函數定義前加#pragma location = “.myFunc”,即將該函數定義為.myFunc的段,然後在鏈接文件icf中用place at語句指定.myFunc段到固定地址m_text_func_start處開始鏈接:

#if defined(__ICCARM__)
#pragma location = ".myFunc"
static void DelayLoop(uint32_t count)
{
    // ...
}
#endif
define symbol m_text_func_start        = 0x00004000;

place at address mem: m_text_func_start     { readonly section .myFunc };

define symbol m_text_start             = 0x00002400;
define symbol m_text_end               = 0x0003FFFF;

place in TEXT_region                        { readonly };

  根據鏈接起始地址m_text_func_start的不同,我們得到了不同的結果,如下錶所示。至此真相大白,造成DelayMs()函數執行時間不同的根本原因不是XIP/TCM執行差異,而是鏈接地址對齊差異,8字節對齊的函數更容易觸發CM7指令雙發射,相比4字節對齊的函數在性能上能提升24.8% 。

m_text_func_start值 鏈接地址對齊 函數調用語句 實際執行時間
0x00004000 8n字節 DelayMs(100) 100ms
0x00004002 2字節,未能鏈接 N/A N/A
0x00004004 4字節 DelayMs(100) 133ms
0x00004008 8字節 DelayMs(100) 100ms

  現在我們得到了一個有趣的結論,Cortex-M7上將函數鏈接到8字節對齊的地址有利於指令雙發射,這就是進一步提升代碼執行性能的秘密。

  至此,i.MXRT上進一步提升代碼執行性能的經驗痞子衡便介紹完畢了,掌聲在哪裡~~~

歡迎訂閱

文章會同時發布到我的 博客園主頁、CSDN主頁、微信公眾號 平台上。

微信搜索”痞子衡嵌入式“或者掃描下面二維碼,就可以在手機上第一時間看了哦。

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

淺談Unity的腳本執行順序

一、添加腳本的順序

這是一張官方的腳本順序圖

 

一般,當我們把腳本綁定在遊戲對象上時,或者點擊綁定好的腳本的reset按鈕時,會調用Reset()

當我們初始化一個對象時,會先調用Awake()在調用OnEnable()

GameObject.instantiate(o);

start()在第一次調用update()前調用,有時候start()會在Awake(),OnEnable()執行完后立刻執行,處於同一幀

onDisable()和onDestroy()的執行順序有點特別:

第一種情況:

  對於不同對象上的不同的腳本,執行是沒有先後規律的,且必須成對執行(調用完了onDisable()必須再調用onDestroy())

  eg:

gameobject1:
    sc1.cs
    sc2.cs
gameobject2:
    sc3.cs
    sc4.cs
gameobject3:
    sc5.cs
    sc6.cs

gameobject1.sc1.onDisable()->gameobject1.sc1.onDestroy()->gameobject3.sc5.onDisable()->

gameobject3.sc5.onDestroy()

[如果按前面的規則的話,應該先執行gameobject3的腳本,但這裏不是]

第二種情況:

在同一個對象的不同的腳本上,這兩個方法按照腳本

在m_component中的索引按順序執行的,而且不是成對執行,索引自小到大執行,最早加入的最早調用onDisable()和onDestroy()

gemeobject2.sc3.onDisable()->gameobject2.sc4.onDisable()->

gameobject2.sc3.onDestroy->gameobject2.sc4.onDestroy()

這裏這些腳本先依次調用onDisable()才調用onDestroy()。

二、如何自定義多個腳本的執行順序

 

 划重點:在unity裏面可以點擊edit->project settings->Script->Execution Order中自定義腳本執行順序,

當我們沒有設置腳本執行順序時,腳本按照Default Time的標準執行(你可以把unity裏面hireachy裏面添加對象和在inspector裏面添加腳本

視作一個棧,按照先進先出原則,優先執行后添加的對象的先添加的Script。

可以單擊+號添加對象到執行列表中

1是腳本的執行順序,當你的值execution order越小時,越先執行,在default time上方的在執行default time前執行,在default time下方

的在default time后執行

上面的設置,是通過修改每一個腳本對應的meta文件(去自己的代碼目錄看一下)的excution order來實現的。

 

 三、腳本執行順序的本質

在我們的場景文件.unity,場景文件是一個YAML文檔

 

 每一個腳本對應一個fileid,fileid越小越有先執行

 4.運用

如果你的腳本A會用到另一個腳本B中的某一個對象c,為了防止你在B初始化之前去調用c,出現空指針,你必須讓腳本B在A之後掛載(這樣B就會先初始化),

還有一個很重要的地方,你必須把對象c的初始化寫在腳本B的Awake()方法裏面,因為start()並不是在初始化就立刻調用的,而是在當前更新一幀時,start()

會在update()第一次調用前調用一次,這樣的話如果你把對象c的初始化寫在腳本B的start()方法裏面,很有可能所有腳本的awake()都跑完了,對象c卻沒有初始化,

這樣就出現了空指針。因此,把對象c的初始化在腳本B的awake()方法,而把調用對象c寫在腳本A的start()足夠安全

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

【其他文章推薦】

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

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

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

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

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

pytorch入門2.2構建回歸模型初體驗(開始訓練)

pytorch入門2.x構建回歸模型系列:
pytorch入門2.0構建回歸模型初體驗(數據生成)
pytorch入門2.1構建回歸模型初體驗(模型構建)
pytorch入門2.2構建回歸模型初體驗(開始訓練)

經過上面兩個部分,我們完成了數據生成、網絡結構定義,下面我們終於可以小試牛刀,訓練模型了!
首先,我們先定義一些訓練時要用到的參數:

EPOCH = 1000  # 就是要把數據用幾遍
LR = 0.1  # 優化器的學習率,類似爬山的時候應該邁多大的步子。
# BATCH_SIZE=50

其次,按照定義的模型類實例化一個網絡:

if torch.cuda.is_available():  # 檢查機器是否支持GPU計算,如果支持GPU計算,那麼就用GPU啦,快!
    model = LinearRegression().cuda()  # 這裏的這個.cuda操作就是把模型放到GPU上
else:
    model = LinearRegression()  # 如果不支持,那麼用cpu也可以哦
# 定義損失函數,要有個函數讓模型的輸出知道他做的對、還是錯,對到什麼程度或者錯到什麼程度,這就是損失函數。
loss_fun = nn.MSELoss()  # loss function
# 定義優化器,就是告訴模型,改如何優化內部的參數、還有該邁多大的步子(學習率LR)。
optimizer = torch.optim.SGD(model.parameters(), lr=LR)  # opimizer

下面終於可以開始訓練了,但是訓練之前解釋一下EPOCH,比如我們有300個樣本,訓練的時候我們不會把300個樣本放到模型裏面訓練一遍,就停止了。即在模型中我們每個樣本不會只用一次,而是會使用多次。這300個樣本到底要用多少次呢,就是EPOCH的值的意義。

for epoch in range(EPOCH):
    # 此處類似前面實例化模型是,我們把模型放到GPU上來跑道理是一樣的。此處,我們要把變量放到GPU上,跑的快!如果不行, 那就放到CPU上吧。
    # 其中x是輸入數據,y是訓練集的groundtruth。為什麼要有y呢?因為我們要知道我們算的對不對,到底有多對(這裏由損失函數控制)
    if torch.cuda.is_available():
        x = Variable(x_train).cuda()
        y = Variable(y_train).cuda()
    else:
        x = Variable(x_train)
        y = Variable(y_train)
    # 我們把x丟進模型,得到輸出y。哇,是不是好簡單,這樣我們就得到結果了呢?但是不要高興的太早,我們只是把輸入數據放到一個啥都不懂(參數沒有訓練)的模型中,得到的結果肯定不準啊。不準的結果怎麼辦,看下一步。
    out = model(x)
    # 拿到模型輸出的結果,我們就要看看模型算的準不準,就是計算損失函數了。
    loss = loss_fun(out,y)
    # 好了好了,我已經知道模型算的準不準了,那麼就該讓模型自己去朝着好的方向優化了。模型,你已經是個大孩子了,應該會自己優化的。
    optimizer.zero_grad()  # 在優化之前,我們首先要清空優化器的梯度。因為每次循環都要靠這個優化器呢,不能翻舊賬,就只算這次我們怎麼優化。
    loss.backward()  # 優化開始,首先,我們要把算出來的誤差、損失倒着傳回去。(是你們這些模塊給我算的這個值,現在這個值有錯誤,錯了這麼多,返回給你們,你們自己看看自己錯哪了)

    optimizer.step()  # 按照優化器的方式,一步一步優化吧。

    if (epoch+1)%100==0:  # 中間每循環100次,偷偷看看結果咋樣。
        print('Epoch[{}/{}],loss:{:.6f}'.format(epoch+1,EPOCH,loss.data.item()))

上面我們訓練了1000(EPOCH=1000)次,應該差不多了。是時候看看訓練的咋樣啦!其實我們已經知道訓練的咋樣了,就是上面輸出的損失值,只不過是在訓練集上的。
下面我們就要看看在測試集上表現咋樣呢?

model.eval()  # 開啟模型的測試模式
# 拿到測試集中x的值,放到GPU上
if  torch.cuda.is_available():
    x = x_test.cuda()
#通過把x的值輸入模型,得到預測結果
predict = model(x)
# 那預測結果的值取出來,因為預測結果是封裝好的,現在h只要它的值。
predict = predict.cpu().data.numpy()
#畫個圖看看,到底擬合成啥樣了?
plt.plot(x.cpu().numpy(),y_test.cpu().numpy(),'ro',label='original data')
plt.plot(sorted(x.cpu().numpy()),sorted(predict),label='fitting line')
plt.show()

看看圖,結果還湊合吧,要想結果更好需要進一步對模型的結構、超參數進行設置,我們之後在學。
到此為止,我們用pytorch就已經建立完,並且訓練完一個線性回歸模型了,我們可以回顧下,多看幾遍,仔細回想一下這裏面到底發生了什麼。
完整的代碼地址如下:github

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

SpringCloud之Security

Spring Security是Spring提供的一個安全框架,提供認證和授權功能,最主要的是它提供了簡單的使用方式,同時又有很高的靈活性,簡單,靈活,強大。

我個人博客系統採用的權限框架就是Spring Security,正好整合到SpringCloud裏面。

一般系統里關於角色方面通常有這麼幾張表,角色表、用戶-角色表、菜單表、角色-菜單表等。

不過我個人博客系統主要以wordpress作為參考,沿用其12張表,如圖:

 

 

一、導入Maven依賴

<properties>
       <jjwt.version>0.9.0</jjwt.version>
       <spring-security-jwt.version>1.0.9.RELEASE</spring-security-jwt.version>
</properties>
 <!-- springsecurity-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-security</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.security</groupId>
           <artifactId>spring-security-jwt</artifactId>
           <version>${spring-security-jwt.version}</version>
       </dependency>
       <dependency>
           <groupId>io.jsonwebtoken</groupId>
           <artifactId>jjwt</artifactId>
           <version>${jjwt.version}</version>
       </dependency>

二、編寫Spring Security配置類

package com.springcloud.blog.admin.config;
import com.springcloud.blog.admin.security.UserAuthenticationProvider;
import com.springcloud.blog.admin.security.UserPermissionEvaluator;
import com.springcloud.blog.admin.security.handler.*;
import com.springcloud.blog.admin.security.jwt.JWTAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;

/**
 * SpringSecurity配置類
 * @Author youcong
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //開啟權限註解,默認是關閉的
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 自定義登錄成功處理器
     */
    @Autowired
    private UserLoginSuccessHandler userLoginSuccessHandler;
    /**
     * 自定義登錄失敗處理器
     */
    @Autowired
    private UserLoginFailureHandler userLoginFailureHandler;
    /**
     * 自定義註銷成功處理器
     */
    @Autowired
    private UserLogoutSuccessHandler userLogoutSuccessHandler;
    /**
     * 自定義暫無權限處理器
     */
    @Autowired
    private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
    /**
     * 自定義未登錄的處理器
     */
    @Autowired
    private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
    /**
     * 自定義登錄邏輯驗證器
     */
    @Autowired
    private UserAuthenticationProvider userAuthenticationProvider;

    /**
     * 加密方式
     * @Author youcong
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
    /**
     * 注入自定義PermissionEvaluator
     */
    @Bean
    public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new UserPermissionEvaluator());
        return handler;
    }

    /**
     * 配置登錄驗證邏輯
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth){
        //這裏可啟用我們自己的登陸驗證邏輯
        auth.authenticationProvider(userAuthenticationProvider);
    }
    /**
     * 配置security的控制邏輯
     * @Author youcong
     * @Param  http 請求
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                // 不進行權限驗證的請求或資源(從配置文件中讀取)
                .antMatchers(JWTConfig.antMatchers.split(",")).permitAll()
                // .antMatchers("/*").permitAll()
                // 其他的需要登陸后才能訪問
                .anyRequest().authenticated()
                .and()
                // 配置未登錄自定義處理類
                .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
                .and()
                // 配置登錄地址
                .formLogin()
                .loginProcessingUrl("/login/userLogin")
                // 配置登錄成功自定義處理類
                .successHandler(userLoginSuccessHandler)
                // 配置登錄失敗自定義處理類
                .failureHandler(userLoginFailureHandler)
                .and()
                // 配置登出地址
                .logout()
                .logoutUrl("/login/userLogout")
                // 配置用戶登出自定義處理類
                .logoutSuccessHandler(userLogoutSuccessHandler)
                .and()
                // 配置沒有權限自定義處理類
                .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
                .and()
                // 開啟跨域
                .cors()
                .and()
                // 取消跨站請求偽造防護
                .csrf().disable();
        // 基於Token不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 禁用緩存
        http.headers().cacheControl();
        // 添加JWT過濾器
        http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
    }
}

三、編寫JWTConfig和application.yml增加jwt相關配置

package com.springcloud.blog.admin.config;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * JWT配置類
 * @Author youcong
 */
@Getter
@Component
@ConfigurationProperties(prefix = "jwt")
public class JWTConfig {
    /**
     * 密鑰KEY
     */
    public static String secret;
    /**
     * TokenKey
     */
    public static String tokenHeader;
    /**
     * Token前綴字符
     */
    public static String tokenPrefix;
    /**
     * 過期時間
     */
    public static Integer expiration;
    /**
     * 不需要認證的接口
     */
    public static String antMatchers;


    public void setSecret(String secret) {
        this.secret = secret;
    }

    public void setTokenHeader(String tokenHeader) {
        this.tokenHeader = tokenHeader;
    }

    public void setTokenPrefix(String tokenPrefix) {
        this.tokenPrefix = tokenPrefix;
    }

    public void setExpiration(Integer expiration) {
        this.expiration = expiration * 1000;
    }

    public void setAntMatchers(String antMatchers) {
        this.antMatchers = antMatchers;
    }


}

application.yml增加如下內容:

# JWT配置
jwt:
  # 密匙KEY
  secret: JWTSecret
  # HeaderKEY
  tokenHeader: Authorization
  # Token前綴字符
  tokenPrefix: challenger-
  # 過期時間 單位秒 1天後過期=86400 7天後過期=604800
  expiration: 86400
  # 配置不需要認證的接口
  antMatchers: /index/**,/login/**,/favicon.ico
  # 有效時間
  validTime: 7

四、編寫過濾器處理類

1.UserLoginSuccessHandler.java

package com.springcloud.blog.admin.security.handler;


import com.springcloud.blog.admin.config.JWTConfig;
import com.springcloud.blog.admin.security.entity.SelfUserEntity;
import com.springcloud.blog.admin.utils.AccessAddressUtil;
import com.springcloud.blog.admin.utils.JWTTokenUtil;
import com.springcloud.blog.admin.utils.RedisUtil;
import com.springcloud.blog.admin.utils.ResultUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description 登錄成功處理類
 * @Author youcong
 */
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {


    /**
     * 登錄成功返回結果
     * @Author youcong
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
        // 組裝JWT
        SelfUserEntity selfUserEntity =  (SelfUserEntity) authentication.getPrincipal();
        String token = JWTTokenUtil.createAccessToken(selfUserEntity);
        token = JWTConfig.tokenPrefix + token;

        // 封裝返回參數
        Map<String,Object> resultData = new HashMap<>();
        resultData.put("code","200");
        resultData.put("msg", "登錄成功");
        resultData.put("token",token);
        ResultUtil.responseJson(response,resultData);
    }
}

2.UserLoginFailureHandler.java

package com.springcloud.blog.admin.security.handler;


import com.springcloud.blog.admin.utils.ResultUtil;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Description 登錄失敗處理類
 * @Author youcong
 */
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
    /**
     * 登錄失敗返回結果
     * @Author youcong
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){
        // 這些對於操作的處理類可以根據不同異常進行不同處理
        if (exception instanceof UsernameNotFoundException){
            System.out.println("【登錄失敗】"+exception.getMessage());
            ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶名不存在"));
        }
        if (exception instanceof LockedException){
            System.out.println("【登錄失敗】"+exception.getMessage());
            ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶被凍結"));
        }
        if (exception instanceof BadCredentialsException){
            System.out.println("【登錄失敗】"+exception.getMessage());
            ResultUtil.responseJson(response,ResultUtil.resultCode(500,"密碼錯誤"));
        }
        ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登錄失敗"));
    }
}

3.UserLogoutSuccessHandler.java

package com.springcloud.blog.admin.security.handler;


import com.springcloud.blog.admin.utils.DateUtil;
import com.springcloud.blog.admin.utils.RedisUtil;
import com.springcloud.blog.admin.utils.ResultUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * 登出成功處理類
 * @Author youcong
 */
@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
    

    /**
     * 用戶登出返回結果
     * 這裏應該讓前端清除掉Token
     * @Author youcong
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){

        Map<String,Object> resultData = new HashMap<>();
        resultData.put("code","200");
        resultData.put("msg", "登出成功");
        SecurityContextHolder.clearContext();
        ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData));
    }
}

4.UserAuthAccessDeniedHandler.java

package com.springcloud.blog.admin.security.handler;
import com.springcloud.blog.admin.utils.ResultUtil;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Description 暫無權限處理類
 * @Author youcong
 */
@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {
    /**
     * 暫無權限返回結果
     * @Author youcong
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){
        ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授權"));
    }
}

5.UserAuthenticationEntryPointHandler.java

package com.springcloud.blog.admin.security.handler;


import com.springcloud.blog.admin.utils.ResultUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 用戶未登錄處理類
 * @Author youcong
 */
@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {
    /**
     * 用戶未登錄返回結果
     * @Author youcong
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){
        ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登錄"));
    }
}

6.UserAuthenticationProvider.java

自定義登錄驗證這個類,需要根據實際情況重寫。通常來說改動不大。

package com.springcloud.blog.admin.security;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.springcloud.blog.admin.entity.Usermeta;
import com.springcloud.blog.admin.entity.Users;
import com.springcloud.blog.admin.security.entity.SelfUserEntity;
import com.springcloud.blog.admin.service.UsermetaService;
import com.springcloud.blog.admin.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 自定義登錄驗證
 *
 * @Author youcong
 */
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {


    @Autowired
    private UsersService usersService;

    @Autowired
    private UsermetaService usermetaService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 獲取表單輸入中返回的用戶名
        String userName = (String) authentication.getPrincipal();
        // 獲取表單中輸入的密碼
        String password = (String) authentication.getCredentials();
        // 查詢用戶是否存在
         SelfUserEntity userInfo = usersService.getUserInfo(userName);


        if (userInfo.getUsername() == null || userInfo.getUsername() == "") {
            throw new UsernameNotFoundException("用戶名不存在");
        }


        // 我們還要判斷密碼是否正確,這裏我們的密碼使用BCryptPasswordEncoder進行加密的
        if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) {
            throw new BadCredentialsException("密碼不正確");
        }
        // 還可以加一些其他信息的判斷,比如用戶賬號已停用等判斷
        if (userInfo.getStatus().equals("1")) {
            throw new LockedException("該用戶已被凍結");
        }
        // 角色集合
        Set<GrantedAuthority> authorities = new HashSet<>();

        EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>();
        roleWrapper.eq("user_id",userInfo.getUserId());
        roleWrapper.eq("meta_key","wp_user_level");
        // 查詢用戶角色
        List<Usermeta> sysRoleEntityList = usermetaService.selectList(roleWrapper);
        for (Usermeta sysRoleEntity: sysRoleEntityList){
            authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getMetaValue()));
        }
        userInfo.setAuthorities(authorities);
        // 進行登錄
        return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}

7.UserPermissionEvaluator.java

package com.springcloud.blog.admin.security;


import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.springcloud.blog.admin.entity.Usermeta;
import com.springcloud.blog.admin.service.UsermetaService;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 自定義權限註解驗證
 * @Author youcong
 */
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {

    @Autowired
    private UsermetaService usermetaService;

    /**
     * hasPermission鑒權方法
     * 這裏僅僅判斷PreAuthorize註解中的權限表達式
     * 實際中可以根據業務需求設計數據庫通過targetUrl和permission做更複雜鑒權
     * 當然targetUrl不一定是URL可以是數據Id還可以是管理員標識等,這裏根據需求自行設計
     * @Author youcong
     * @Param  authentication  用戶身份(在使用hasPermission表達式時Authentication參數默認會自動帶上)
     * @Param  targetUrl  請求路徑
     * @Param  permission 請求路徑權限
     * @Return boolean 是否通過
     */
    @Override
    public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
        // 獲取用戶信息
        Usermeta selfUserEntity =(Usermeta) authentication.getPrincipal();
        // 查詢用戶權限(這裏可以將權限放入緩存中提升效率)
        Set<String> permissions = new HashSet<>();
        EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>();
        roleWrapper.eq("user_id",selfUserEntity.getUserId());
        roleWrapper.eq("meta_key","wp_user_level");
        List<Usermeta> sysMenuEntityList = usermetaService.selectList(roleWrapper);
        for (Usermeta sysMenuEntity:sysMenuEntityList) {
            permissions.add(sysMenuEntity.getMetaValue());
        }
        // 權限對比
        if (permissions.contains(permission.toString())){
            return true;
        }
        return true;
    }
    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

五、編寫實體類

package com.springcloud.blog.admin.security.entity;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;

/**
 * SpringSecurity用戶的實體
 * 注意:這裏必須要實現UserDetails接口
 *
 * @Author youcong
 */
public class SelfUserEntity implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;

    /**
     * 用戶ID
     */
    private Long userId;
    /**
     * 用戶名
     */
    private String username;
    /**
     * 密碼
     */
    private String password;
    /**
     * 狀態
     */
    private String status;


    /**
     * 显示名稱
     */
    private String displayName;


    /**
     * 用戶參數
     */
    private Map<String, String> userParamMap;


    /**
     * 用戶角色
     */
    private Collection<GrantedAuthority> authorities;
    /**
     * 賬戶是否過期
     */
    private boolean isAccountNonExpired = false;
    /**
     * 賬戶是否被鎖定
     */
    private boolean isAccountNonLocked = false;
    /**
     * 證書是否過期
     */
    private boolean isCredentialsNonExpired = false;
    /**
     * 賬戶是否有效
     */
    private boolean isEnabled = true;


    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


    public void setAuthorities(Collection<GrantedAuthority> authorities) {
        this.authorities = authorities;
    }


    public void setEnabled(boolean enabled) {
        isEnabled = enabled;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }

    public String getDisplayName() {
        return displayName;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public Map<String, String> getUserParamMap() {
        return userParamMap;
    }

    public void setUserParamMap(Map<String, String> userParamMap) {
        this.userParamMap = userParamMap;
    }

    @Override
    public Collection<GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return isAccountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return isAccountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return isCredentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return isEnabled;
    }


}

六、編寫JWT接口請求攔截器

package com.springcloud.blog.admin.security.jwt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.springcloud.blog.admin.config.JWTConfig;
import com.springcloud.blog.admin.security.entity.SelfUserEntity;
import com.springcloud.blog.admin.utils.CollectionUtil;
import com.springcloud.blog.admin.utils.JWTTokenUtil;
import com.springcloud.blog.admin.utils.RedisUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * JWT接口請求校驗攔截器
 * 請求接口時會進入這裏驗證Token是否合法和過期
 *
 * @Author youcong
 */
public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {

    public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 獲取請求頭中JWT的Token
        String tokenHeader = request.getHeader(JWTConfig.tokenHeader);

        if (null != tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {
            try {


                // 截取JWT前綴
                String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");
                // 解析JWT
                Claims claims = Jwts.parser()
                        .setSigningKey(JWTConfig.secret)
                        .parseClaimsJws(token)
                        .getBody();
                // 獲取用戶名
                String username = claims.getSubject();
                String userId = claims.getId();


                if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(userId)) {
                    // 獲取角色
                    List<GrantedAuthority> authorities = new ArrayList<>();
                    String authority = claims.get("authorities").toString();
                    if (!StringUtils.isEmpty(authority)) {
                        List<Map<String, String>> authorityMap = JSONObject.parseObject(authority, List.class);
                        for (Map<String, String> role : authorityMap) {
                            if (!StringUtils.isEmpty(role)) {
                                authorities.add(new SimpleGrantedAuthority(role.get("authority")));
                            }
                        }
                    }
                    //組裝參數
                    SelfUserEntity selfUserEntity = new SelfUserEntity();
                    selfUserEntity.setUsername(claims.getSubject());
                    selfUserEntity.setUserId(Long.parseLong(claims.getId()));
                    selfUserEntity.setAuthorities(authorities);
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            } catch (ExpiredJwtException e) {
                System.out.println("Token過期");
            } catch (Exception e) {
                System.out.println("Token無效");
            }
        }
        filterChain.doFilter(request, response);
        return;
    }
}

七、SpringSecurity用戶的業務實現

package com.springcloud.blog.admin.security.service;


import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.springcloud.blog.admin.entity.Users;
import com.springcloud.blog.admin.security.entity.SelfUserEntity;
import com.springcloud.blog.admin.service.UsersService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

/**
 * SpringSecurity用戶的業務實現
 *
 * @Author youcong
 */
@Component
public class SelfUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersService usersService;

    /**
     * 查詢用戶信息
     *
     * @Author youcong
     * @Param username  用戶名
     * @Return UserDetails SpringSecurity用戶信息
     */
    @Override
    public SelfUserEntity loadUserByUsername(String username) throws UsernameNotFoundException {

        EntityWrapper<Users> wrapper = new EntityWrapper<>();


        //郵箱正則表達式
        String expr = "^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})$";

        //是否為郵箱
        if (username.matches(expr)) {
            wrapper.eq("user_email", username);
        } else {
            wrapper.eq("user_login", username);
        }

        // 查詢用戶信息
        Users sysUserEntity = usersService.selectOne(wrapper);
        if (sysUserEntity != null) {
            // 組裝參數
            SelfUserEntity selfUserEntity = new SelfUserEntity();
            BeanUtils.copyProperties(sysUserEntity, selfUserEntity);
            return selfUserEntity;
        }
        return null;
    }
}

八、Spring Security常用註解

1.@Secured

當@EnableGlobalMethodSecurity(securedEnabled=true)的時候,@Secured可以使用。

@PostMapping("/helloUser")
@Secured({"ROLE_normal","ROLE_admin"})
public Map<String, Object> initDashboard() {
        Map<String, Object> result = new HashMap<>();
        result.put(ResponseDict.RESPONSE_TITLE_KEY, "儀錶盤初始化");
        result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard());
        return ResultUtil.resultSuccess(result);
    }

說明:擁有normal或者admin角色的用戶都可以方法helloUser()方法。另外需要注意的是這裏匹配的字符串需要添加前綴“ROLE_“。

2.@PreAuthorize

Spring的 @PreAuthorize/@PostAuthorize 註解更適合方法級的安全,也支持Spring 表達式語言,提供了基於表達式的訪問控制。

當@EnableGlobalMethodSecurity(prePostEnabled=true)的時候,@PreAuthorize可以使用:

@PostMapping("/initDashboard")
@PreAuthorize("hasRole('100')")
public Map<String, Object> initDashboard() {
    Map<String, Object> result = new HashMap<>();
    result.put(ResponseDict.RESPONSE_TITLE_KEY, "儀錶盤初始化");
    result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard());
    return ResultUtil.resultSuccess(result);
}

3.@PostAuthorize

@PostAuthorize 註解使用並不多,在方法執行后再進行權限驗證,適合驗證帶有返回值的權限,Spring EL 提供 返回對象能夠在表達式語言中獲取返回的對象returnObject。

當@EnableGlobalMethodSecurity(prePostEnabled=true)的時候,@PostAuthorize可以使用:

@GetMapping("/getUserInfo")
@PostAuthorize(" returnObject!=null &&  returnObject.username == authentication.name")
public User getUserInfo() {
        Object pricipal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        User user;
        if("anonymousUser".equals(pricipal)) {
            user = null;
        }else {
            user = (User) pricipal;
        }
        return user;
}

九、測試

(1)登錄測試,拿到token,如圖:

 

 

(2)請求中如果不攜帶token的話,請求其它接口就會显示沒有登錄的提示,如圖:

 

 

(3)正確的請求應當攜帶token,就像下面這樣,如圖:

 

 

(4)沒有權限請求,如圖:

 

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

28歲能買這三款車 最能彰顯你與眾不同!

值得一提的是2。0T車型的百公里加速成績在6。93秒左右。在同級中表現優秀。編者點評:速派外觀設計自成一格,而且動力和油耗表現不錯,1。4T自動擋車型的車主口碑油耗在7。3左右,而1。8T車型則為9。5L。它的底盤調教側重於舒適性,濾振效果也不錯。

中型車在過去被賦予了太多的商務定義

所以設計一向都以大氣、沉穩為主

因為老闆們做生意

總是要求座駕夠穩重

這樣做生意能有許多便利

(圖為大型車 大眾輝騰)

而現在中型車已不再完全向商務性傾斜

因為買家的年齡逐漸下降!

那麼今天我們就來聊聊28歲到30歲之間買什麼樣的合資中型車,最能體現年輕人的個性、與眾不同!

一汽馬自達-阿特茲

指導價:17.58-23.98萬

阿特茲的外觀設計盡顯東方設計之靈性,在日系中型車中是最有個性的!它極具動感的外觀、內飾設計讓許多年輕消費者為之買單。

它採用了帶創馳藍天技術的動力總成,2.0L和2.5L發動機均採用直噴的供油方式,還帶有雙可變氣門正時控制系統!

而車身長寬高為4870*1840*1450mm,軸距達2830mm。車身尺寸屬於中等水平,而它的乘坐空間其實也充足夠用。

編者點評:

阿特茲的運動底子好,這是它的一大優勢,在轉向精準性、動力響應性、底盤運動性方面都有不錯的表現!而且車主反饋它的平均百公里油耗在8.1-8.9L左右(含2.0L和2.5L車型),這麼低的油耗是一系列創馳藍天技術協調運作的結果!

上汽大眾斯柯達-速派

指導價:16.98-27.68萬

鋒利、前衛的外觀,讓速派吸引了很多年輕、熱愛個性的消費者關注!它的車身長寬高為4861*1865*1489mm,軸距達2841mm。

動力系統方面,速派採用了1.4T、1.8T、2.0T三款渦輪增壓發動機,搭配5擋手動、7擋雙離合變速箱。值得一提的是2.0T車型的百公里加速成績在6.93秒左右!在同級中表現優秀。

編者點評:

速派外觀設計自成一格,而且動力和油耗表現不錯,1.4T自動擋車型的車主口碑油耗在7.3左右,而1.8T車型則為9.5L。它的底盤調教側重於舒適性,濾振效果也不錯。

廣汽本田-雅閣

指導價:16.98-27.98萬

雅閣在改款之後,走起了前衛、運動路線,這樣的設計讓它的銷量一下子得到了提升!它的車身尺寸為:4915*1845*1470mm,軸距為2775mm。雖然軸距比起阿特茲和速派不佔優勢,但是雅閣的車身長度卻是最長的,實際的空間表現也不差。

雅閣搭載2.0L、2.4L兩款發動機,搭配CVT變速箱 ,而且還推出了混合動力車型。

編者點評:

雅閣擁有炫麗的外觀、充足的空間,而且對比起凱美瑞、天籟這兩款車,雅閣更具有運動性。它的2.4L車型百公里加速成績在8.68秒左右,口碑油耗則為9.1L,在這兩方面表現都比較良好。

最後總結:

這三款車當中,操控性最好的車型當屬阿特茲,而且它也擁有着更加運動化的外觀。而雅閣和速派的舒適性表現則更好。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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