Spring5知識整理(二)
-
Spring的表示式語言spEL
- Spring表示式語言(SpEL):是一個支援執行時查詢和操作物件圖的強大表示是語言,是一種可以與一個基於spring的應用程式中的執行時物件互動的東西。總得來說SpEL表示式是一種簡化開發的表示式,通過使用表示式來簡化開發,減少一些邏輯、配置的編寫。
- 語法類似於 EL:SpEL 使用 #{...} 作為定界符 , 所有在大括號中的字元都將被認為是 SpEL , SpEL 為 bean 的屬性進行動態賦值提供了便利。
-
通過 SpEL 可以實現:
- 通過 bean 的 id 對 bean 進行引用,用了SpEL在bean標籤中可以用value代替ref。
- 可以像EL一樣用點運算子呼叫方法以及物件中的屬性。
- 計算表示式的值
- 正則表示式的匹配。
-
SpEL 字面量,意義不大,spring內部本身有資料型別的自動轉換機制,直接寫值就好了,不必用SqEL,瞭解:
- 整數:#{8}
- 小數:#{8.8}
- 科學計數法:#{1e4}
- String:可以使用單引號或者雙引號作為字串的定界符號。
- Boolean:#{true}
-
SpEL引用bean , 呼叫它屬性和方法:
- 引用其他物件:#{car}
- 引用其他物件的屬性:#{car.price}
- 呼叫其它方法 , 還可以鏈式操作:#{person.pet.toString()}
- 呼叫靜態方法靜態屬性:#{T(java.lang.Math).PI}
- Spring EL 操作List、Map集合取值
-
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的生命週期
- 生命週期圖解:
- 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屬性,會自動呼叫其配置的銷燬方法。
- BeanPostProcessor介面演示:對BeanPostProcessor介面,做一個實現類,演示一下這些介面如果要在專案中自定義的話應該怎麼用,生命週期的前後置處理方法的執行情況
-
Spring通過工廠方法進行配置
-
在Spring的世界中, 我們通常會利用 xml配置檔案 或者 annotation註解方式來配置bean例項!
在第一種利用 xml配置檔案 方式中, 還包括如下三小類
- 反射模式(我們前面的所有配置都是這種模式)
- 工廠方法模式
- Factory Bean模式
上面bean 裡面的class屬性就是全類名, Spring利用java反射機制建立這個bean object。 - 工廠方法模式 在工廠方法模式中, Spring不會直接利用反射機制建立bean物件, 而是會利用反射機制先找到Factory類,然後利用Factory再去生成bean物件。 而Factory Mothod的具體使用方式也分兩種, 分別是靜態工廠方法 和 例項工廠方法。
-
靜態工廠方法方式所謂靜態工廠方式就是指Factory類不本身不需要例項化, 這個Factory類中提供了1個靜態方法來生成bean物件
裡面定義了1個靜態的bean 容器map. 然後提供1個靜態方法根據Car 的id 來獲取容器裡的car物件。
xml配置:
小結 由上面的例子, 靜態工廠方法方式是非常適用於作為1個bean容器, 只不過bean集合定義在工廠類裡面而不是專案xml配置檔案裡面。 缺點也比較明顯, 把資料寫在class裡面而不是配置檔案中違反了我們程式猿的常識和spring的初衷。當然優點就是令人噁心的xml配置檔案更加簡潔。所以,工廠方法的配置,瞭解一下就行了,個人建議不要在專案中使用。 -
例項工廠方法方式
所謂例項工廠方式也很容易看懂, 就是工廠類裡面的getBean 方法不是靜態的, 也就是說要先例項1個工廠的物件, 才能依靠這個工廠物件去呼叫getBean 方法獲得bean 物件。
小結:顯然,例項化工廠方法比靜態工廠方法,要靈活一些,沒把資料寫死在工廠類裡,但是實際開發中,用的最多的還是反射模式!
-
用註解的方式來配置Bean
- 在實際專案開發中,可能使用註解配置Bean,使用的還要廣泛一些,因為更方便簡潔!
- 什麼是註解:
傳統的Spring做法是使用.xml檔案來對bean進行注入或者是配置aop、事務,這麼做有兩個缺點: 1、如果所有的內容都配置在.xml檔案中,那麼.xml檔案將會十分龐大;如果按需求分開.xml檔案,那麼.xml檔案又會非常多。總之這將導致配置檔案的可讀性與可維護性變得很低 2、在開發中在.java檔案和.xml檔案之間不斷切換,是一件麻煩的事,同時這種思維上的不連貫也會降低開發的效率 為了解決這兩個問題,Spring引入了註解,通過"@XXX"的方式,讓註解與Java Bean緊密結合,既大大減少了配置檔案的體積,又增加了Java Bean的可讀性與內聚性。 - 註解方式配置:
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容器自動裝配哪個物件 - 示例:不使用註解
- 使用註解:
也可以:
@Resource的裝配順序: 1、@Resource後面沒有任何內容,預設通過name屬性去匹配bean,找不到再按type去匹配 2、指定了name或者type則根據指定的型別去匹配bean 3、指定了name和type則根據指定的name和type去匹配bean,任何一個不匹配都將報錯
-
@Autowired
-
@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 即可:
- @Autowired介面注入
上面的比較簡單,我們只是簡單注入一個Java類,那麼如果有一個介面,有多個實現,Bean裡引用的是介面名,又該怎麼做呢?比如有一個Car介面:
這樣做的話就會報錯, Car介面有兩個實現類,Spring並不知道應當引用哪個實現類。這種情況通常有兩個解決辦法: 1、刪除其中一個實現類,Spring會自動去base-package下尋找Car介面的實現類,發現Car介面只有一個實現類,便會直接引用這個實現類 2、實現類就是有多個該怎麼辦?此時可以使用@Qualifier註解,指明你要spring裝載那個物件:
- @inject:
功能和@Autowired差不多的一個註解@inject,它是jsr330規範的註解,用它的話要匯入相應的jar包,推薦使用@Autowired <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
-
component-scan標籤詳解
-
base-package: 指定spring掃描註解的類所在的包。當需要掃描多個包的時候,可以使用逗號分隔。如果只希望掃描特定的類,不是掃描包裡的所有類的時候,可以使用resource-pattern屬性來指定只掃描的包。這標籤是需要context的名稱空間的。
只是這樣配置,上面test可以訪問到所有的有註解的物件!加上resource-pattern來指定只掃描的包:
這樣配置,除了User的物件,其他都找不到了!
-
子標籤<context:exclude-filter type="annotation" expression=""/>配置在不掃描的類,可以有很多個這樣的子標籤。
這樣配置,@controller註解的類的物件就找不到了!
-
子標籤<context:include-filter type="annotation" expression=""/>配置要掃描的類,也可以有多個。
除了包含的註解以外的註解的類的物件都找不到了! - 上面都是用的type=annotation,下面再看一下assignable
排除UserDao這個介面以及這個介面的實現類!
-
泛型的依賴注入
- 泛型依賴注入就是允許我們在使用spring進行依賴注入的同時,利用泛型的優點對程式碼進行精簡,將可重複使用的程式碼全部放到一個類之中,方便以後的維護和修改。同時在不增加程式碼的情況下增加程式碼的複用性。
-
Spring的切面程式設計概述
- AOP:AOP(Aspect Oriented Programming),即面向切面程式設計,可以說是OOP(Object Oriented Programming,面向物件程式設計)的補充和完善。OOP引入封裝、繼承、多型等概念來建立一種物件層次結構,用於模擬公共行為的一個集合。不過OOP允許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。日誌程式碼往往橫向地散佈在所有物件層次中,這種散佈在各處的與具體業務無關的程式碼被稱為橫切(cross cutting),在OOP設計中,它導致了大量程式碼的重複,而不利於各個模組的重用。
- 用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程式碼實現試驗
- 基於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實現後置、返回、異常和環繞通知
- 後置通知: 在切入點的目標方法執行後(無論有異常丟擲沒的),都會執行這個通知方法!
如果想要在通知方法裡訪問到目標方法返回的結果,可以用返回通知, - 返回通知:是在目標方法執行之後沒有異常,並且返回結果後才執行通知方法:
- 異常通知:當目標物件丟擲異常的時候執行通知方法,新增一個div除法方法
- 環繞通知:就是把前面4中通知全給整合在一起,環繞目標方法的所有通知的意味。
使用它必須要求: 1、必須要帶引數ProceedingJoinPoint型別的引數,這個引數可以直接呼叫原來的目標方法。 2、環繞通知方法必須有返回值,這個反正值就是目標方法的返回值。
當同一個目標方法有多個切面的時候,哪個切面先執行,取決於在切面類上的註解@order(值小的先執行)
-
基於xml配置檔案的AOP使用
- 前面的例子中,配置切面類,AopTest類還是用註解,我們主要看有切面的通知方法在xml裡該怎麼配,方法上的註解刪除乾淨。