1. 程式人生 > >Spring5知識整理(二)

Spring5知識整理(二)

  • Spring的表示式語言spEL

  1. Spring表示式語言(SpEL):是一個支援執行時查詢和操作物件圖的強大表示是語言,是一種可以與一個基於spring的應用程式中的執行時物件互動的東西。總得來說SpEL表示式是一種簡化開發的表示式,通過使用表示式來簡化開發,減少一些邏輯、配置的編寫。
  2. 語法類似於 EL:SpEL 使用 #{...} 作為定界符 , 所有在大括號中的字元都將被認為是 SpEL , SpEL 為 bean 的屬性進行動態賦值提供了便利。
  3. 通過 SpEL 可以實現:
    • 通過 bean 的 id 對 bean 進行引用,用了SpEL在bean標籤中可以用value代替ref。
    • 可以像EL一樣用點運算子呼叫方法以及物件中的屬性。
    • 計算表示式的值
    • 正則表示式的匹配。
  4. SpEL 字面量,意義不大,spring內部本身有資料型別的自動轉換機制,直接寫值就好了,不必用SqEL,瞭解:
    • 整數:#{8}
    • 小數:#{8.8}
    • 科學計數法:#{1e4}
    • String:可以使用單引號或者雙引號作為字串的定界符號。
    • Boolean:#{true}

  5. SpEL引用bean , 呼叫它屬性和方法:
    • 引用其他物件:#{car}
    • 引用其他物件的屬性:#{car.price}
    • 呼叫其它方法 , 還可以鏈式操作:#{person.pet.toString()}
    • 呼叫靜態方法靜態屬性:#{T(java.lang.Math).PI}
    • Spring EL 操作List、Map集合取值











  6. SpEL支援的運算子號:
    • 算術運算子:+,-,*,/,%,^(加號還可以用作字串連線)
    • 比較運算子:< , > , == , >= , <= , lt , gt , eg , le , ge
    • 邏輯運算子:and , or , not , |
    • if-else 運算子(類似三目運算子):?:(temary), ?:(Elvis)
    • 正則表示式:#{admin.email matches '[a-zA-Z0-9._%+-][email protected][a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}'}



 

  • Spring中的bean的生命週期

  1. 生命週期圖解:


  2. spring生命週期:

    1、例項化一個Bean--也就是我們常說的new;

    2、按照Spring上下文對例項化的Bean進行配置--也就是IOC注入;

    3、如果這個Bean已經實現了BeanNameAware介面,會呼叫它實現的setBeanName(String)方法,此處傳遞的就是Spring配置檔案中Bean的id值

    4、如果這個Bean已經實現了BeanFactoryAware介面,會呼叫它實現的setBeanFactory(setBeanFactory(BeanFactory)傳遞的是Spring工廠自身(可以用這個方式來獲取其它Bean,只需在Spring配置檔案中配置一個普通的Bean就可以);

    5、如果這個Bean已經實現了ApplicationContextAware介面,會呼叫setApplicationContext(ApplicationContext)方法,傳入Spring上下文(同樣這個方式也可以實現步驟4的內容,但比4更好,因為ApplicationContext是BeanFactory的子介面,有更多的實現方法);

    6、如果這個Bean關聯了BeanPostProcessor介面,將會呼叫postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor經常被用作是Bean內容的更改,並且由於這個是在Bean初始化結束時呼叫那個的方法,也可以被應用於記憶體或快取技術;

    7、如果Bean在Spring配置檔案中配置了init-method屬性會自動呼叫其配置的初始化方法。

    8、如果這個Bean關聯了BeanPostProcessor介面,將會呼叫postProcessAfterInitialization(Object obj, String s)方法、;

        注:以上工作完成以後就可以應用這個Bean了,那這個Bean是一個Singleton的,所以一般情況下我們呼叫同一個id的Bean會是在內容地址相同的例項,當然在Spring配置檔案中也可以配置非Singleton,這裡我們不做贅述。

    9、當Bean不再需要時,會經過清理階段,如果Bean實現了DisposableBean這個介面,會呼叫那個其實現的destroy()方法;

    10、最後,如果這個Bean的Spring配置中配置了destroy-method屬性,會自動呼叫其配置的銷燬方法。

  3. BeanPostProcessor介面演示:對BeanPostProcessor介面,做一個實現類,演示一下這些介面如果要在專案中自定義的話應該怎麼用,生命週期的前後置處理方法的執行情況


  • Spring通過工廠方法進行配置

  1. 在Spring的世界中, 我們通常會利用 xml配置檔案 或者 annotation註解方式來配置bean例項! 在第一種利用 xml配置檔案 方式中, 還包括如下三小類
    1. 反射模式(我們前面的所有配置都是這種模式)
    2. 工廠方法模式
    3. Factory Bean模式
    其中反射模式最常見, 我們需要在bean 配置中配置我們需要的bean object的全類名。

     
    上面bean 裡面的class屬性就是全類名, Spring利用java反射機制建立這個bean object。

  2. 工廠方法模式 在工廠方法模式中, Spring不會直接利用反射機制建立bean物件, 而是會利用反射機制先找到Factory類,然後利用Factory再去生成bean物件。 而Factory Mothod的具體使用方式也分兩種, 分別是靜態工廠方法 和 例項工廠方法。
  3. 靜態工廠方法方式所謂靜態工廠方式就是指Factory類不本身不需要例項化, 這個Factory類中提供了1個靜態方法來生成bean物件



    裡面定義了1個靜態的bean 容器map. 然後提供1個靜態方法根據Car 的id 來獲取容器裡的car物件。

    xml配置:



    小結 由上面的例子, 靜態工廠方法方式是非常適用於作為1個bean容器, 只不過bean集合定義在工廠類裡面而不是專案xml配置檔案裡面。 缺點也比較明顯, 把資料寫在class裡面而不是配置檔案中違反了我們程式猿的常識和spring的初衷。當然優點就是令人噁心的xml配置檔案更加簡潔。所以,工廠方法的配置,瞭解一下就行了,個人建議不要在專案中使用。  
  4. 例項工廠方法方式 所謂例項工廠方式也很容易看懂, 就是工廠類裡面的getBean 方法不是靜態的, 也就是說要先例項1個工廠的物件, 才能依靠這個工廠物件去呼叫getBean 方法獲得bean 物件。





    小結:顯然,例項化工廠方法比靜態工廠方法,要靈活一些,沒把資料寫死在工廠類裡,但是實際開發中,用的最多的還是反射模式!

 

  • 用註解的方式來配置Bean

  1. 在實際專案開發中,可能使用註解配置Bean,使用的還要廣泛一些,因為更方便簡潔!
  2. 什麼是註解:
    傳統的Spring做法是使用.xml檔案來對bean進行注入或者是配置aop、事務,這麼做有兩個缺點: 1、如果所有的內容都配置在.xml檔案中,那麼.xml檔案將會十分龐大;如果按需求分開.xml檔案,那麼.xml檔案又會非常多。總之這將導致配置檔案的可讀性與可維護性變得很低 2、在開發中在.java檔案和.xml檔案之間不斷切換,是一件麻煩的事,同時這種思維上的不連貫也會降低開發的效率 為了解決這兩個問題,Spring引入了註解,通過"@XXX"的方式,讓註解與Java Bean緊密結合,既大大減少了配置檔案的體積,又增加了Java Bean的可讀性與內聚性。
  3. 註解方式配置:
    Spring註解配置初始化物件(<bean>): spring中使用註解配置物件前,要在配置檔案中配置context:component-scan 標籤告訴spring框架,配置了註解的類的位置 配置檔案applicationContext.xml: <context:component-scan base-package="cn.example.bean"> </context:component-scan>   註解說明: Component最初spring框架設計的,後來為了標識不同程式碼層,衍生出Controller,Service,Repository三個註解 作用相當於配置檔案的bean標籤,被註解的類,spring始化時,就會建立該物件 @Component("user") 給類註解 @Service("user") // service層 @Controller("user") // web業務層 @Repository("user")//dao層 @Scope(scopeName="singleton") 等同於配置檔案的scope屬性   @Value(value="188") //給值屬性賦值,可以用在方法上或者屬性上 @Resource(name="car") //給物件賦值,該值car必須要已經宣告(在配置檔案中已經配置,或者在類對應中已經註解) @PostConstruct //指定該方法在物件被建立後馬上呼叫 相當於配置檔案中的init-method屬性 @PreDestroy //指定該方法在物件銷燬之前呼叫 相當於配置檔案中的destory-method屬性 @Autowired //自動裝配物件賦值@Qualifier("car2") 一起使用 告訴spring容器自動裝配哪個物件
  4. 示例:不使用註解




  5. 使用註解:




    也可以:






    @Resource的裝配順序: 1、@Resource後面沒有任何內容,預設通過name屬性去匹配bean,找不到再按type去匹配 2、指定了name或者type則根據指定的型別去匹配bean 3、指定了name和type則根據指定的name和type去匹配bean,任何一個不匹配都將報錯

 

  • @Autowired

  1. @Autowired顧名思義,就是自動裝配,其作用是為了消除程式碼Java程式碼裡面的getter/setter與bean屬性中的property。當然,getter看個人需求,如果私有屬性需要對外提供的話,應當予以保留。 因此,引入@Autowired註解,不要忘記配置檔案要寫

    然後才是在JavaBean的屬性上加註解:


    這裡@Autowired註解的意思就是,當Spring發現@Autowired註解時,將自動在程式碼上下文中找到和其匹配(預設是型別匹配)的Bean,並自動注入到相應的地方去。 有一個細節性的問題是,假設此時我把.xml檔案的 <bean id="monkey" class="cn.ybzy.springtest.Monkey" p:monkeyName="mm"></bean> <bean id="tiger" class="cn.ybzy.springtest.Tiger" p:tigerName="tt"></bean> 行兩行給去掉,再執行,會丟擲異常,因為,@Autowired註解要去尋找的是一個Bean,Tiger和 Monkey的Bean定義都給去掉了,Spring容器找不到了自然丟擲異常。那麼,如果屬性找不到對應的物件我不想讓Spring容器拋 出異常,而就是顯示null,可以嗎?可以的,就是將@Autowired註解的required屬性設定為false 即可:
  2. @Autowired介面注入
    上面的比較簡單,我們只是簡單注入一個Java類,那麼如果有一個介面,有多個實現,Bean裡引用的是介面名,又該怎麼做呢?比如有一個Car介面:







    這樣做的話就會報錯, Car介面有兩個實現類,Spring並不知道應當引用哪個實現類。這種情況通常有兩個解決辦法: 1、刪除其中一個實現類,Spring會自動去base-package下尋找Car介面的實現類,發現Car介面只有一個實現類,便會直接引用這個實現類 2、實現類就是有多個該怎麼辦?此時可以使用@Qualifier註解,指明你要spring裝載那個物件:
  3. @inject:
    功能和@Autowired差不多的一個註解@inject,它是jsr330規範的註解,用它的話要匯入相應的jar包,推薦使用@Autowired <dependency>   <groupId>javax.inject</groupId>        <artifactId>javax.inject</artifactId>        <version>1</version> </dependency>
  • component-scan標籤詳解

  1. base-package: 指定spring掃描註解的類所在的包。當需要掃描多個包的時候,可以使用逗號分隔。如果只希望掃描特定的類,不是掃描包裡的所有類的時候,可以使用resource-pattern屬性來指定只掃描的包。這標籤是需要context的名稱空間的。





    只是這樣配置,上面test可以訪問到所有的有註解的物件!加上resource-pattern來指定只掃描的包:

    這樣配置,除了User的物件,其他都找不到了!

  2. 子標籤<context:exclude-filter type="annotation" expression=""/>配置在不掃描的類,可以有很多個這樣的子標籤。

    這樣配置,@controller註解的類的物件就找不到了!

  3. 子標籤<context:include-filter type="annotation" expression=""/>配置要掃描的類,也可以有多個。

    除了包含的註解以外的註解的類的物件都找不到了!
  4. 上面都是用的type=annotation,下面再看一下assignable

    排除UserDao這個介面以及這個介面的實現類!

 

  • 泛型的依賴注入

  1. 泛型依賴注入就是允許我們在使用spring進行依賴注入的同時,利用泛型的優點對程式碼進行精簡,將可重複使用的程式碼全部放到一個類之中,方便以後的維護和修改。同時在不增加程式碼的情況下增加程式碼的複用性。








 

  • Spring的切面程式設計概述

  1. AOP:AOP(Aspect Oriented Programming),即面向切面程式設計,可以說是OOP(Object Oriented Programming,面向物件程式設計)的補充和完善。OOP引入封裝、繼承、多型等概念來建立一種物件層次結構,用於模擬公共行為的一個集合。不過OOP允許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。日誌程式碼往往橫向地散佈在所有物件層次中,這種散佈在各處的與具體業務無關的程式碼被稱為橫切(cross cutting),在OOP設計中,它導致了大量程式碼的重複,而不利於各個模組的重用。
  2. 用log4j這個日誌框架來看一看這種“大量程式碼的重複”的情況:
    1、log4j日誌配置檔案log4j.properties:
    ### set log levels ### log4j.rootLogger = debug , stdout , D ### 輸出到控制檯 ### log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = %n %d %p [%l] %m %n ### 輸出到日誌檔案 ### log4j.appender.D = org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File = ./log.log log4j.appender.D.Append = true ## 只輸出DEBUG級別以上的日誌!!! log4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayout log4j.appender.D.layout.ConversionPattern = %n %d %p [%l] %m %



    AOP技術恰恰相反,它利用一種稱為"橫切"的技術,剖解開封裝的物件內部,並將那些影響了多個類的公共行為封裝到一個可重用模組,並將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻為業務模組所共同呼叫的邏輯或責任封裝起來,便於減少系統的重複程式碼,降低模組之間的耦合度,並有利於未來的可操作性和可維護性。 使用"橫切"技術,AOP把軟體系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處基本相似,比如許可權認證、日誌、事務。AOP的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。 Spring的AOP就可以實現核心關注點和橫切關注點的分離 Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關係也由IOC容器負責管理。因此,AOP代理可以直接使用容器中的其它bean例項作為目標,這種關係可由IOC容器的依賴注入提供。Spring建立代理的規則為: 1、預設使用Java動態代理來建立AOP代理,這樣就可以為任何介面例項建立代理了 2、當需要代理的類不是代理介面的時候,Spring會切換為使用CGLIB代理,也可強制使用CGLIB(CGLIB是一個強大的、高效能的程式碼生成庫) AOP程式設計其實是很簡單的事情,縱觀AOP程式設計,程式設計師只需要參與三個部分: 1、定義普通業務元件 2、定義切入點,一個切入點可能橫切多個業務元件 3、定義增強處理,增強處理就是在AOP框架為普通業務元件織入的處理動作 所以進行AOP程式設計的關鍵就是定義切入點和定義增強處理,一旦定義了合適的切入點和增強處理,AOP框架將自動生成AOP代理,即:代理物件的方法=增強處理+被代理物件的方法。
  • 基於註解的Spring的AOP程式碼實現試驗

  1. 基於AspectJ框架來實現Spring的AOP的: 步驟1: 新增jar包 <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.0.6.RELEASE</version>
    </dependency>   步驟2:寫Spring的配置檔案,把註解掃面開啟,寫介面和實現類,有main方法是測試類








    步驟3:定義切面類,LoggerAspect


    步驟4:spring的配置檔案中,讓@Before註解起作用


    可以看到在指定的切入點,切入了切面類的方法,將分離的日誌程式碼和業務程式碼在執行時合起來了!

    步驟5:繼續完善切面類


    步驟6:一個方法實現了,擴充套件到多個方法,用*代替add方法名,表示任意方法:


    最後,對@Before註解在解釋一下:   @Before和它註解的這個方法在AOP裡叫:通知(advice),這個我們除了@Before這個叫前置通知(在切入目標方法執行之前執行)外,還有: @After後置通知(在目標方法執行之後執行) @AfterReturning返回通知(在目標方法返回結果之後執行) @AfterThrowing異常通知(在目標方法跑出異常之後執行) @Around環繞通知(圍繞著方法執行)   @Before註解後的括號的內容叫AspectJ表示式,這裡還可進一步使用萬用字元*

    public int 換成 * : 任何修飾符和返回值型別


    AopTestImpl換成*,表示cn.ybzy.springdemo包裡的所有類


    還可用兩個點兒表示任意引數


    很多個方法都是相同的切入點表示式,可以像提公因式樣的提出來:

 

  • AOP實現後置、返回、異常和環繞通知

  1. 後置通知: 在切入點的目標方法執行後(無論有異常丟擲沒的),都會執行這個通知方法!


    如果想要在通知方法裡訪問到目標方法返回的結果,可以用返回通知,


  2. 返回通知:是在目標方法執行之後沒有異常,並且返回結果後才執行通知方法:
  3. 異常通知:當目標物件丟擲異常的時候執行通知方法,新增一個div除法方法




  4. 環繞通知:就是把前面4中通知全給整合在一起,環繞目標方法的所有通知的意味。
    使用它必須要求: 1、必須要帶引數ProceedingJoinPoint型別的引數,這個引數可以直接呼叫原來的目標方法。 2、環繞通知方法必須有返回值,這個反正值就是目標方法的返回值。


    當同一個目標方法有多個切面的時候,哪個切面先執行,取決於在切面類上的註解@order(值小的先執行)

 

  • 基於xml配置檔案的AOP使用

  1. 前面的例子中,配置切面類,AopTest類還是用註解,我們主要看有切面的通知方法在xml裡該怎麼配,方法上的註解刪除乾淨。