AOP
動態代理
代理設計模式的原理:使用一個代理將原本對象包裝起來,然後用該代理對象”取代”原始對象。任何對原始對象的調用都要通過代理。代理對象決定是否以及何時將方法調用轉到原始對象上。
代理模式的三要素:
-
代理主題接口
-
代理者
-
被代理者
代理模式的主要優點
-
代理模式在客戶端與目標對象之間起到一箇中介作用和保護目標對象的作用;
-
代理對象可以擴展目標對象的功能;
-
代理模式能將客戶端與目標對象分離,在一定程度上降低了系統的耦合度;
其主要缺點
-
在客戶端和目標對象之間增加一個代理對象,會造成請求處理速度變慢;
-
增加了系統的複雜度;
動態代理的方式
靜態代理類只能替一個主題接口進行代理工作
基於接口實現動態代理: JDK動態代理
基於繼承實現動態代理: Cglib、Javassist動態代理
JDK動態代理步驟:
* 1、編寫主題接口
* 2、編寫被代理類
* 3、編寫代理工作處理器:即代理類要替被代理類做什麼事情(有參構造器)
* 要求:必須實現InvocationHandler,重寫
* Object invoke(Object proxy, Method method, Object[] args)
* 第一個參數:代理類對象
* 第二個參數:被代理類和代理類 要執行的方法
* 第三個參數:要執行方法的實參列表
* 這個invoke方法不是程序員調用,當代理類對象執行對應的代理方法時,自動調用的
* 4、創建代理類及其對象
* 需要:Proxy:提供用於創建動態代理類和實例的靜態方法,它還是由這些方法創建的所有動態代理類的超類。
* static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* 第一個參數:被代理類的類加載器,我們希望被代理和代理類使用同一個類加載器
* 第二個參數:被代理類實現的接口們
* 第三個參數:代理工作處理器對象
* 5、調用被代理的方法
注意:代理對象和實現類對象,都實現了相同的接口。屬於兄弟關係。(不能強制轉換爲,實現類對象)
AOP概述
1) AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論,是對傳統 OOP(ObjectOrientedProgramming),面向對象編程)的補充。
面向對象 縱向繼承機制
面向切面 橫向抽取機制
2) AOP編程操作的主要對象是切面(aspect),而切面用於橫切關注點。
3) 在應用AOP編程時,仍然需要定義公共功能,但可以明確的定義這個功能應用在哪裏,以什麼方式應用,並且不必修改受影響的類。這樣一來橫切關注點就被模塊化到特殊的類裏——這樣的類我們通常稱之爲“切面”。
4) AOP的好處
① 每個事物邏輯位於一個位置,代碼不分散,便於維護和升級
② 業務模塊更簡潔,只包含核心業務代碼
AOP術語
1.橫切關注點
從每個方法中抽取出來的同一類非核心業務。
2.切面(Aspect)
封裝橫切關注點信息的類,每個關注點體現爲一個通知方法。
3.通知(Advice)
切面必須要完成的各個具體工作
4.目標(Target)
被通知的對象
5.代理(Proxy)
向目標對象應用通知之後創建的代理對象
6. 連接點(Joinpoint)
橫切關注點在程序代碼中的具體體現,對應程序執行的某個特定位置。例如:類某個方法調用前、調用後、方法捕獲到異常後等。
7. 切入點(pointcut):
定位連接點的方式。每個類的方法中都包含多個連接點,所以連接點是類中客觀存在的事物。
如果把連接點看作數據庫中的記錄,那麼切入點就是查詢條件——AOP可以通過切入點定位到特定的連接點。
切點通過org.springframework.aop.Pointcut 接口進行描述,它使用類和方法作爲連接點的查詢條件。
AspectJ
啓用AspectJ註解支持
1、導入JAR包
2、引入aop名稱空間
3、配置:<aop:aspectj-autoproxy>
當Spring IOC容器偵測到bean配置文件中的<aop:aspectj-autoproxy>元素時,會自動爲與AspectJ切面匹配的bean創建代理
用AspectJ註解聲明切面
在Spring中聲明AspectJ切面爲bean實例
初始化AspectJ切面之後,容器就會爲那些與 AspectJ切面相匹配的bean創建代理
在AspectJ註解中,切面只是一個帶有@Aspect註解的Java類
通知是標註有某種註解的Java方法
5種類型的通知註解:@Before(value="切入點表達式")
① @Before:前置通知,在方法執行之前執行
② @After:後置通知,在方法執行之後執行,即無論連接點是正常返回還是拋出異常,後置通知都會執行
③ @AfterRunning:返回通知,在方法返回結果之後執行 (如果異常,不執行 )
在返回通知中訪問連接點的返回值,如果只想在連接點返回的時候記錄日誌,應使用返回通知代替後置通知
①在返回通知中,只要將returning屬性添加到@AfterReturning註解中,就可以訪問連接點的返回值。
②必須在通知方法的簽名中添加一個同名參數。在運行時Spring AOP會通過這個參數傳遞返回值
③原始的切點表達式需要出現在pointcut屬性中
④ @AfterThrowing:異常通知,在方法拋出異常之後執行 (如果無異常,不執行)
將throwing屬性添加到@AfterThrowing註解中,在異常通知方法可以捕獲到任何錯誤和異常。
也可以將參數聲明爲其他異常的參數類型。然後通知就只在拋出這個類型及其子類的異常時才被執行
⑤ @Around:環繞通知,圍繞着方法執行
能夠全面地控制連接點,甚至可以控制是否執行連接點。
連接點的參數類型必須是ProceedingJoinPoint。它是 JoinPoint的子接口,允許控制何時執行,是否執行連接點。
需要明確調用ProceedingJoinPoint的proceed()方法來執行被代理的方法。
注意:環繞通知的方法需要返回目標方法執行之後的結果,即調用 joinPoint.proceed();的返回值,否則會出現空指針異常
@Around(value = "rePointCut()") public Object aroundMethod(ProceedingJoinPoint pjp) { Object obj = null; try { //前置通知 System.out.println("前置通知"); obj = pjp.proceed(); //調用目標對象的方法 //返回通知 System.out.println("返回通知,結果:" + obj); } catch (Throwable e) { //異常通知 System.out.println("異常通知,ex:" + e); e.printStackTrace(); } finally { //後置通知 System.out.println("後置通知"); } return obj; }
切入點表達式
通過表達式的方式定位一個或多個具體的連接點。
語法格式
execution([權限修飾符] [返回值類型] [簡單類名/全類名] [方法名]([參數列表]))
表達式: @Pointcut(value="execution(* com.spring.*.*(..))") 含義: ArithmeticCalculator接口中聲明的所有方法。 第一個“*”代表任意修飾符及任意返回值。
第二個“*”代表,任意類的全類名稱|任意類名 第三個“*”代表任意方法。 “..”匹配任意數量、任意類型的參數。 若目標類、接口與該切面類在同一個包中可以省略包名。
切入點表達式可以通過 “&&”、“||”、“!”等操作符結合起來。
重用切入點
在AspectJ切面中,可以通過@Pointcut註解將一個切入點聲明成簡單的方法。切入點的方法體通常是空的
切入點方法的訪問控制符同時也控制着這個切入點的可見性。
在引入這個切入點時,必須將類名也包括在內。如果類沒有與這個切面放在同一個包中,還必須包含包名。
其他通知可以通過方法名稱引入該切入點
//提取表達式 @Pointcut(value="execution(* com.spring.aspectj.*.*(..))") public void rePointCut() {} @Before(value="rePointCut()"):當前類中重用切入點表達式
指定切面的優先級
在同一個連接點上應用不止一個切面時,除非明確指定,否則它們的優先級是不確定的
使用@Order註解,序號出現在註解中
@Aspect @Order(0) //int類型,數值越小,優先級越高。 public class TestAspect{}
XML方式配置切面
基於註解的聲明要優先於基於XML的聲明,通過AspectJ註解,切面可以與AspectJ兼容,而基於XML的配置則是Spring專有的,所以不推薦
在bean配置文件中,所有的Spring AOP配置都必須定義在<aop:config>元素內部。對於每個切面而言,都要創建一個<aop:aspect>元素來爲具體的切面實現引用後端bean實例。
切面bean必須有一個標識符,供<aop:aspect>元素引用。
1)聲明切入點
切入點使用<aop:pointcut>元素聲明。
① 定義在<aop:aspect>元素下:只對當前切面有效
② 定義在<aop:config>元素下:對所有切面都有效
基於XML的AOP配置不允許在切入點表達式中用名稱引用其他切入點
2)聲明通知
通知元素需要使用<pointcut-ref>來引用切入點
method屬性指定切面類中通知方法的名稱
<aop:config> <aop:pointcut id="myPointcut" expression="execution(* com.spring.aspectj_xml.*.*(..))" /> <!-- 定義日誌切面 --> <aop:aspect id="loggingAspect" ref="loggingAspect" order="1"> <aop:before method="beforeMethod" pointcut-ref="myPointcut"/> <aop:after method="afterMethod" pointcut-ref="myPointcut"/> <aop:after-returning method="afterReturnMethod" returning="rs" pointcut-ref="myPointcut"/> <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="myPointcut"/> </aop:aspect> </aop:config>
JdbcTemplate
可以將Spring的JdbcTemplate看作是一個小型的輕量級持久化層框架,JdbcTemplate類是線程安全的
JdbcTemplate所需要的JAR包
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
數據庫驅動和數據源
druid-1.1.9.jar
mysql-connector-java-5.1.7-bin.jar
配置文件中配置相關的bean
<!-- 引入jdbc配置文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!-- 裝配Druid數據源conn--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" p:driverClassName="${jdbc.driverClass}" ></bean> <!-- 通過數據源裝配JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 通過數據源裝配事務管理器--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 啓用事務管理器--> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
持久化操作
1) 增刪改
update(String sql,Object... args)
2) 批處理增刪改
batchUpdate(String sql,List<Object[]> batchArgs)
Object[]封裝了SQL語句每一次執行時所需要的參數
List集合封裝了SQL語句多次執行時的所有參數
3)獲取單個數值型
queryForObject(String sql,Class<T> requiredType,Object... args)
4)獲取單個對象類型
queryForObject(String sql,RowMapper rowMapper,Object... args)
5)獲取多個JavaBean類型
query(String sql,RowMapper rowMapper,Object... args)
RowMapper對象可以使用BeanPropertyRowMapper實現類:注意new對象時指定類型
事務管理
事務就是一組由於邏輯上緊密關聯而合併成一個整體(工作單元)的多個數據庫操作,這些操作要麼都執行,要麼都不執行。
事務的四個屬性(ACID)
①原子性(atomicity):“原子”的本意是“不可再分”,事務的原子性表現爲一個事務中涉及到的多個操作在邏輯上缺一不可。事務的原子性要求事務中的所有操作要麼都執行,要麼都不執行。
②一致性(consistency):“一致”指的是數據的一致,具體是指:所有數據都處於滿足業務規則的一致性狀態。
③隔離性(isolation):隔離性原則要求多個事務在併發執行過程中不會互相干擾。
④持久性(durability):通常情況下,事務對數據的修改應該被寫入到持久化存儲器中。
編程式事務
使用原生的JDBC API實現事務管理是所有事務管理方式的基石,但是需要將事務管理代碼嵌入到業務方法中,事務與業務代碼相耦合,代碼相對分散且混亂。所以:建議使用聲明式事務。
①獲取數據庫連接Connection對象
②取消事務的自動提交
③執行操作
④正常完成操作時手動提交事務
⑤執行失敗時回滾事務
⑥關閉相關資源
聲明式事務
事務管理代碼的固定模式作爲一種橫切關注點,可以通過AOP方法模塊化,進而藉助Spring AOP框架實現聲明式事務管理。它將事務管理代碼從業務方法中分離出來
Spring的核心事務管理抽象是它爲事務管理封裝了一組獨立於技術的方法。無論使用Spring的哪種事務管理策略(編程式或聲明式),事務管理器都是必須的。開發人員可以通過配置的方式進行事務管理。
事務管理器可以以普通的bean的形式聲明在Spring IOC容器中。
事務管理器的主要實現
1) DataSourceTransactionManager:在應用程序中只需要處理一個數據源,而且通過JDBC存取。
2) JtaTransactionManager:在JavaEE應用服務器上用JTA(Java Transaction API)進行事務管理
3) HibernateTransactionManager:用Hibernate框架存取數據庫
實現
1) 配置文件:如上圖
2) 在需要進行事務控制的方法上加註解 @Transactional
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, timeout=3, readOnly=false, noRollbackFor=RuntimeException.class) public void purchase(String username, String isbn) {}
propagation屬性詳解
事務的傳播行爲
當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。事務的傳播行爲可以由傳播屬性指定。Spring定義了7種類傳播行爲。
事務傳播屬性通過在@Transactional註解的propagation屬性中定義。
①REQUIRED傳播行爲
當一個事務方法調用另一個事務方法時,它默認會在現有的事務內運行。因此在整個事務方法的開始和終止邊界內只有一個事務。即:如果當前存在事務,就使用當前事務。如果當前沒事務,就創建一個新的事務,去使用。
②. REQUIRES_NEW傳播行爲
表示該事務方法必須啓動一個新事務,並在自己的事務內運行。如果有事務在運行,就應該先掛起它。即:無論當前是否存在事務,都必須創建新事務,去使用。等新建事務運行結束後,繼續執行被掛起事務
事務的隔離級別
讀未提交(1),存在問題:髒讀
讀已提交(2),存在問題:不可重複讀(建議使用)
可重複讀(4),存在問題:幻讀(建議使用)
串行化 (8),存在問題:效率低
用@Transactional註解聲明式地管理事務時可以在@Transactional的isolation屬性中設置隔離級別
觸發事務回滾的異常
捕獲到RuntimeException或Error時回滾,而捕獲到編譯時異常不回滾。
通過@Transactional 註解
① rollbackFor屬性:指定遇到時必須進行回滾的異常類型,可以爲多個
② noRollbackFor屬性:指定遇到時不回滾的異常類型,可以爲多個
事務的超時和只讀屬性
超時事務屬性:事務在強制回滾之前可以保持多久。這樣可以防止長期運行的事務佔用資源。
只讀事務屬性: 表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務。
readOnly=true,
true:事務只讀,一旦設置只讀屬性,該事務就不能進行:增刪改操作。
false:設置事務爲,不只讀。
timeout=3, 設置事務的“強制回滾”時間秒。
基於xml方式配置聲明式事務
<!-- 配置事務切面 --> <aop:config> <aop:pointcut expression="execution(* com.tx.component.service.BookShopServiceImpl.purchase(..))" id="txPointCut"/> <!-- 將切入點表達式和事務屬性配置關聯到一起 --> <aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/> </aop:config> <!-- 配置基於XML的聲明式事務 --> <tx:advice id="myTx" transaction-manager="transactionManager"> <tx:attributes> <!-- 設置具體方法的事務屬性 --> <tx:method name="find*" read-only="true"/> <tx:method name="get*" read-only="true"/> <tx:method name="purchase" isolation="READ_COMMITTED" no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException" propagation="REQUIRES_NEW" read-only="false" timeout="10"/> </tx:attributes> </tx:advice>