【JavaEE學習筆記】Spring_03_IoC的其他配置方式,AOP淺析
Spring_03
A.IoC的其他配置方式
1.xml+Annotation
bean
package org.wpf.spr_01; import org.springframework.stereotype.Component; @Component("aa") public class Demo { }
配置類
package org.wpf.spr_01; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) // 這個註解可以用於類上進行宣告 @Retention(RetentionPolicy.RUNTIME) // 這個註解會一直保持到執行時 @Documented public @interface Compentent { // 引數value是字串型別,預設值為空串;如果一個註解中有且僅有一個屬性為value時,可以不寫value= // 這個註解用於宣告一個受管bean,其中的value引數為受管bean的id名稱 String value() default ""; }
配置檔案
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="aa" class="org.wpf.spr_01.Demo" /> </beans>
測試類
手動配置Spring自動掃描發現受管beanpackage org.wpf.spr_01; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Demo demo = ac.getBean("aa", Demo.class); System.out.println(demo); } }
另外Spring提供的其它註解:@Controller用於宣告一個控制器受管bean
@Service用於宣告一個業務受管bean
@Repository用於宣告一個DAO型別的受管bean
@Component用於宣告一個無法明確區分用途的受管bean
2.基於JavaConfig配置
配置類
package org.wpf.spr_01; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration // 用於說明當前類是一個配置類 public class DemoConfig { @Bean // 定義受管bean,方法名稱就是受管bean的名稱 public Demo aa() { return new Demo(); } }
測試類
package org.wpf.spr_01; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ac = new AnnotationConfigApplicationContext(DemoConfig.class); Demo demo = ac.getBean("aa", Demo.class); System.out.println(demo); } }
3.bean之間的關係
繼承和依賴
Spring 允許繼承 bean 的配置, 被繼承的 bean 稱為父 bean, 繼承這個父 Bean 的 Bean 稱為子 Bean
子 Bean 從父 Bean 中繼承配置, 包括 Bean 的屬性配置
子 Bean 也可以覆蓋從父 Bean 繼承過來的配置
父 Bean 可以作為配置模板, 也可以作為 Bean 例項, 若只想把父 Bean 作為模板, 可以設定 <bean> 的abstract 屬性為 true, 這樣 Spring 將不會例項化這個 Bean
並不是 <bean> 元素裡的所有屬性都會被繼承. 比如: autowire, abstract 等
也可以忽略父 Bean 的 class 屬性,讓子 Bean 指定自己的類, 而共享相同的屬性配置, 但此時 abstract 必須設為 true
Spring 允許使用者通過 depends-on 屬性設定 Bean 前置依賴的Bean,前置依賴的 Bean 會在本 Bean 例項化之前建立好<bean id="now" class="java.util.Date" /> <!-- 沒有對應的類定義,只是用於簡化繼承於它的受管bean的配置 --> <bean id="myParent" p:birth-ref="now" abstract="true" /> <bean id="aa" class="com.wpf.ioc04.A" parent="myParent" /> <!-- 這裡實際上也需要注入birth,但是不需要再次配置定義,因為parent="myParent",表示這個受管bean繼承了myParent的配置 --> <bean id="bb" class="com.wpf.ioc04.B" parent="myParent" />
如果前置依賴於多個 Bean,則可以通過逗號,空格或的方式配置 Bean 的名稱
預設情況下按照配置的順序構建受管bean物件
執行順序為:B.B() A.A() B.setA()<!-- 設定要求先建立aa然後再建立bb.注意這裡並沒有將aa物件注入到bb物件中 --> <bean id="bb" class="com.yan.ioc05.B" depends-on="aa" /> <bean id="aa" class="com.yan.ioc05.A" /> <!-- ------------------------------------------------ --> <bean id="bb" class="com.yan.ioc05.B" p:a-ref="aa" /> <!-- 注入被依賴的物件可以保證一定會被注入,但是不保證構建aa的順序 --> <bean id="aa" class="com.yan.ioc05.A" />
4.使用外部屬性檔案和spEL表示式
在配置檔案裡配置 Bean 時,有時需要在 Bean 的配置裡混入系統部署的細節資訊
分離配置的思想(例如檔案路徑, 資料來源配置資訊等)
而這些部署細節實際上需要和 Bean 配置相分離
在src目錄下建立properties檔案,這裡包含資料庫連線的相關配置資訊
配置檔案jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/test jdbc.username=root jdbc.password=123456
spEl<!-- 使用context名空間可以實現讀取並解析特定的資原始檔,並在其它配置的地方通過SpEL【Spring表示式語言】進行呼叫,以簡化配置資訊 --> <context:property-placeholder location="classpath:database.properties" /> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" p:driverClass="${jdbc.driver}" p:jdbcUrl="${jdbc.url}" p:user="${jdbc.username}" p:password="${jdbc.password}" destroy-method="close" /> <!-- 在xml配置中只管理連線,不包含資料庫相關配置資訊 --> <bean id="aa" class="com.yan.ioc06.A" p:driver="${jdbc.driver}" p:ds-ref="dataSource" />
Spring 表示式語言,簡稱SpEL:是一個支援執行時查詢和操作物件圖的強大的表示式語言。
語法類似於 EL:SpEL 使用 #{…} 作為定界符,所有在大框號中的字元都將被認為是 SpEL
SpEL 為 bean 的屬性進行動態賦值提供了便利
B.AOP淺析(瞭解)
1.引入
業務需求:完成使用者功能的處理程式碼
系統需求:日誌,安全,事務等
如果把系統需求加入到處理業務需求的類中,那麼業務類就會變得很複雜和零亂
甚至可能會喧賓奪主,而且以後要變更系統需求的時候,將給軟體帶來很大的維護量
程式碼混亂:越來越多的非業務需求(日誌和驗證等)加入後, 原有的業務方法急劇膨脹. 每個方法在處理核心邏輯的同時還必須兼顧其他多個關注點.
程式碼分散: 以日誌需求為例, 只是為了滿足這個單一需求, 就不得不在多個模組(方法)裡多次重複相同的日誌程式碼. 如果日誌需求發生變化, 必須修改所有模組
底層原理是代理模式
2.分散關注思路
將通用需求功能從不相關類之中分離出來
能夠使得很多類共享一個行為,一旦行為發生變化,不必修改很多類,只要修改這個行為就可以
AOP就是這種實現分散關注的程式設計方法,它將“關注”封裝在“方面aspect”中
3.AOP
AOP即Aspect-Oriented Programming,面向切面程式設計:是一種新的方法論,是一種技術,不是設計模式,是對傳統 OOP(Object-Oriented Programming, 面向物件程式設計) 的補充
AOP 的主要程式設計物件是切面(aspect),而切面模組化橫切關注點
在應用 AOP 程式設計時,仍然需要定義公共功能,但可以明確的定義這個功能在哪裡,以什麼方式應用,並且不必修改受影響的類,這樣一來橫切關注點就被模組化到特殊的物件(切面)裡
AOP 的好處
每個事物邏輯位於一個位置,程式碼不分散,便於維護和升級
業務模組更簡潔,只包含核心業務程式碼
4.AOP 與IoC的聯絡與區別
相同點:都是尋求呼叫者與被呼叫者的解耦
不同點:
IoC關注的是物件的建立、維護職責與物件的使用權(該使用權與呼叫者的功能緊密相關)解耦
AOP關注的是功能本身的解耦
5.Spring AOP的優點
實現了分散關注,將通用需求功能從不相關類之中分離出來;
同時,能夠使得很多類共享一個行為,一旦行為發生變化,不必修改很多類,只要修改這個行為就可以
允許開發者使用宣告式企業服務,比如事務服務、安全性服務
開發者可以開發滿足業務需求的自定義方面
開發Spring AOP Advice 很方便,可以藉助代理類快速搭建Spring AOP 應用
6.AOP術語
連線點(Joinpoint):程式執行的某個特定位置:如類某個方法呼叫前、呼叫後、方法丟擲異常後等。連線點由兩個資訊確定:方法表示的程式執行點;相對點表示的方位
例如 ArithmethicCalculator#add() 方法執行前的連線點,執行點為 ArithmethicCalculator#add(); 方位為該方法執行前的位置
通知(Advice): 在連線點需要追加的程式處理
切入點pointcut:連線點的物件化表示,比如Filter的<url-pattern>
切面(Aspect): 切入點和通知定義在一個類中,這個類就是切面類
目標(Target): 被呼叫的程式
代理(Proxy): 向目標物件應用通知之後建立的物件
7.Spring的通知型別
Spring通知型別按切面功能呼叫的不同時刻,可以分為提供了5種Advice型別
前置通知Before advice:在某連線點之前執行的通知,但這個通知不能阻止連線點之前的執行流程(除非它丟擲一個異常)
後置通知After returning advice:在某連線點正常完成後執行的通知
異常通知After throwing advice:在方法丟擲異常退出時執行的通知
最終通知After (finally) advice:當某連線點退出的時候執行的通知(不論是正常返回還是異常退出)
環繞通知(Around Advice):包圍一個連線點的通知,如方法呼叫。這是最強大的一種通知型別。環繞通知可以在方法呼叫前後完成自定義的行為。它也會選擇是否繼續執行連線點或直接返回它自己的返回值或丟擲異常來結束執行
8.通知型別的選擇
環繞通知是最常用的通知型別
推薦使用盡可能簡單的通知型別來實現需要的功能
如果你只是需要一個方法的返回值來更新快取,最好使用後置通知而不是環繞通知
用最合適的通知型別可以使得程式設計模型變得簡單,並且能夠避免很多潛在的錯誤9.SpringAOP的原理
10.前置通知Spring框架中的AOP攔截技術是POJO的方法層面的攔截【攔截的顆粒度較粗】
其底層實現原理是動態代理技術
對於面向介面的方法攔截,依賴於jdk的動態代理技術,即java.lang.reflect.Proxy#newProxyInstance,將對被代理的目標物件的呼叫,委託到代理物件,觸發攔截通知;而當被攔截的方法, 不是在介面中定義時,使用的是cglib,對位元組碼進行動態增強,生成被代理類的子物件,以實現代理
spring實現aop,動態代理技術的兩種實現是jdk動態代理、cglib代理,根據被通知的方法是否為介面方法,來選擇使用哪種代理生成策略
目標介面,注意JDK動態代理要求必須有對應的介面
否則不能使用 JDK動態代理,只能使用CGLIb動態代理
package org.wpf.spr_01; public interface IHelloServ { void sayHello(); }
目標實現類---具體的業務邏輯處理類
package org.wpf.spr_01; public class HelloServImpl implements IHelloServ { @Override public void sayHello() { System.out.println("業務邏輯處理開始........"); System.out.println("hello"); System.out.println("業務邏輯處理結束........"); } }
定義前置通知類
package org.wpf.spr_01; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; // 這是一個前置通知類。定義通知常見的方法有2種: // 方法1:實現特定的介面進行定義,但是Spring2+版本中沒有最終通知,實現介面定義會導致侵入性,不推薦使用 // 方法2:添加註解進行定義 public class BeforeHelloAdvice implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("前置通知開始。。。。。。"); System.out.println("呼叫的方法為:" + arg0.getName()); if (arg1 != null && arg1.length > 0) for (Object temp : arg1) System.out.println("\t\t呼叫的引數為:" + temp); System.out.println("呼叫的目標物件為" + arg2); System.out.println("前置通知結束。。。。。。"); } }
通過使用IoC將通知和業務處理進行組裝---代理
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 定義目標物件 --> <bean id="hello" class="org.wpf.spr_01.HelloServImpl" /> <!-- 定義通知物件 --> <bean id="bef" class="com.wpf.spr_01.BeforeHelloAdvice" /> <!-- 定義代理物件,Spring的AOP是依賴於代理將通知織入到目標程式 --> <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 設定具體的目標物件 --> <property name="target" ref="hello" /> <!-- 定義代理的介面 --> <property name="interfaces"> <value>org.wpf.spr_01.IHelloServ</value> </property> <!-- 定義攔截物件的名稱,系統根據事先的介面進行區分不同的執行時機 --> <property name="interceptorNames"> <value>bef</value> </property> </beans>
測試呼叫
package org.wpf.spr_01; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ac = new AnnotationConfigApplicationContext("applicationContext.xml"); // 獲取的不是具體的業務邏輯處理物件,而是通過Spring提供的代理工廠生成的代理物件 IHelloServ hello = ac.getBean("proxy", IHelloServ.class); hello.sayHello(); } }