1. 程式人生 > >AOP面向切面編程筆記

AOP面向切面編程筆記

本質 join 2.0 jdb ntc 通知 nim arrays win

1.AOP概念:Aspect Oriented Programming 面向切面編程

2.作用:本質上來說是一種簡化代碼的方式
繼承機制
封裝方法
動態代理
……


3.情景舉例
①數學計算器接口[MathCalculator]
int add(int i,int j);
int sub(int i,int j);
int mul(int i, int j);
int div(int i,int j);
②提供簡單實現[EasyImpl]
③在簡單實現的基礎上讓每一個計算方法都能夠打印日誌[LoginImpl]

④缺陷
[1]手動添加日誌繁瑣,重復
[2]統一修改不便
[3]對目標方法本來要實現的核心功能有幹擾,使程序代碼很臃腫,不易於開發維護

⑤使用動態代理實現
[1]創建一個類,讓這個類能夠提供一個目標對象的代理對象
[2]在代理對象中打印日誌



4.AOP術語![參見圖例和doc文檔]
AOP概述
●AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論,
是對傳統 OOP(Object-Oriented Programming,面向對象編程)的補充。
●參見圖例和doc文檔解釋AOP的各個術語!
●Spring的AOP既可以使用xml配置的方式實現,也可以使用註解的方式來實現!

5.在Spring中使用AOP實現日誌功能
①Spring中可以使用註解或XML文件配置的方式實現AOP。
②導入jar包
com.springsource.net.sf.cglib -2.2.0.jar
com.springsource.org.aopalliance-1.0.0 .jar
com.springsource.org.aspectj.weaver-1.6.8 .RELEASE.jar
commons-logging-1.1.3. jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE. jar

③開啟基於註解的AOP功能
< aop:aspectj-autoproxy />
④聲明一個切面類,並把這個切面類加入到IOC容器中
@Aspect//表示這是一個切面類
@Component//加入IOC容器
public class LogAspect {}

⑤在切面類中聲明通知方法
[1]前置通知:@Before
[2]返回通知:@AfterReturning
[3]異常通知:@AfterThrowing
[4]後置通知:@After
[5]環繞通知:@Around :環繞通知是前面四個通知的集合體!

@Aspect//表示這是一個切面類
@Component//將本類對象加入到IOC容器中!
public class LogAspect {
@Before(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
public void showBeginLog(){
System.out.println("AOP日誌開始");
}
@After(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
public void showReturnLog(){
System.out.println("AOP方法返回");
}
@AfterThrowing(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
public void showExceptionLog(){
System.out.println("AOP方法異常");
}
@AfterReturning(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
public void showAfterLog(){
System.out.println("AOP方法結束");
}
}

⑥被代理的對象也需要加入IOC容器
@Component//加入IOC容器
public class MathCalculatorImpl {

public int add(int i,int j){
int result = i+j;
return result;
}
public int sub(int i,int j){
int result = i-j;
return result;
}
public int multi(int i,int j){
int result = i*j;
return result;
}
public int divide(int i,int j){
int result = i/j;
return result;
}
}

6.切入點表達式:
1.上述案例通過junit測試,會發現,我們調用目標類的四個方法只有add方法被加入了4個通知,如果想所有的方法都加上這些通知,可以
在切入點表達式處,將execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int)) 換成:
execution(public int com.neuedu.aop.target.MathCalculatorImpl.*(int, int))這樣只要是有兩個參數,且
參數類型為int的方法在執行的時候都會執行其相應的通知方法!

2.①切入點表達式的語法格式[參見第5章AOP細節]
execution([權限修飾符] [返回值類型] [簡單類名/全類名] [方法名]([參數列表]))

參見第5章AOP細節:演示驗證
1.任意參數,任意類型
2.任意返回值
3.用@PointCut註解統一聲明,然後在其它通知中引用該統一聲明即可!
需要註意的是:權限是不支持寫通配符的,當然你可以寫一個*表示所有權限所有返回值!


最詳細的切入點表達式:
execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
最模糊的切入點表達式:
execution (* *.*(..))

7.統一聲明切入點表達式
@Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))")
public void myPointCut(){}


8.通知方法的細節
①在通知中獲取目標方法的方法名和參數列表
[1]在通知方法中聲明一個JoinPoint類型的形參
[2]調用JoinPoint對象的getSignature()方法獲取目標方法的簽名
[3]調用JoinPoint對象的getArgs()方法獲取目標方法的實際參數列表

②在返回通知中獲取方法的返回值
[1]在@AfterReturning註解中添加returning屬性
@AfterReturning (value="myPointCut()", returning= "result")
[2]在返回通知的通知方法中聲明一個形參,形參名和returning屬性的值一致
showReturnLog(JoinPoint joinPoint, Object result)
③在異常通知中獲取異常對象
[1]在@ AfterThrowing註解中添加throwing屬性
@AfterThrowing (value="myPointCut()",throwing= "throwable" )
[2]在異常通知的通知方法中聲明一個形參,形參名和throwing屬性值一致
showExceptinLog(JoinPoint joinPoint, Throwable throwable)


9.根據接口類型獲取target對象時,實際上真正放在IOC容器中的對象是代理對象,而並不是目標對象本身!


10.環繞通知:@Around
1.環繞通知需要在方法的參數中指定JoinPoint的子接口類型ProceedingJoinPoint為參數
@Around(value="pointCut()")
public void around(ProceedingJoinPoint joinPoint){
}
2.環繞通知會將其他4個通知能幹的,自己都給幹了!
註意:@Around修飾的方法一定要將方法的返回值返回!本身相當於代理!

@Around(value="pointCut()")
public Object around(ProceedingJoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
List<Object> list = Arrays.asList(args);
Object result = null;
try {
//目標方法之前要執行的操作
System.out.println("[環繞日誌]"+methodName+"開始了,參數為:"+list);
//調用目標方法
result = joinPoint.proceed(args);

//目標方法正常執行之後的操作
System.out.println("[環繞日誌]"+methodName+"返回了,返回值為:"+result);
} catch (Throwable e) {
//目標方法拋出異常信息之後的操作
System.out.println("[環繞日誌]"+methodName+"出異常了,異常對象為:"+e);
throw new RuntimeException(e.getMessage());
}finally{
//方法最終結束時執行的操作!
System.out.println("[環繞日誌]"+methodName+"結束了!");
}
return result;
}

11.切面的優先級
對於同一個代理對象,可以同時有多個切面共同對它進行代理。
可以在切面類上通過@Order (value=50)註解來進行設置,值越小優先級越高!
@Aspect
@Component
@Order(value=40)
public class TxAspect {
@Around(value="execution(public * com.neuedu.aop.target.MathCalculatorImpl.*(..))")
public Object around(ProceedingJoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
List<Object> list = Arrays.asList(args);
Object result = null;
try {
//目標方法之前要執行的操作
System.out.println("[事務日誌]"+methodName+"開始了,參數為:"+list);
//調用目標方法
result = joinPoint.proceed(args);

//目標方法正常執行之後的操作
System.out.println("[事務日誌]"+methodName+"返回了,返回值為:"+result);
} catch (Throwable e) {
//目標方法拋出異常信息之後的操作
System.out.println("[事務日誌]"+methodName+"出異常了,異常對象為:"+e);
throw new RuntimeException(e.getMessage());
}finally{
//方法最終結束時執行的操作!
System.out.println("[事務日誌]"+methodName+"結束了!");
}
return result;
}
}

12.註意:上面的AOP都是通過註解實現的,AOP實際上也可以通過xml配置的方式實現!

<!-- 1.將需要加載到IOC容器中的bean配置好 -->
<bean id="logAspect" class="com.neuedu.aop.proxy.LogAspect"></bean>
<bean id="txAspect" class="com.neuedu.aop.target.TxAspect"></bean>
<bean id="calculator" class="com.neuedu.aop.target.MathCalculatorImpl"></bean>

<!-- 2.配置AOP,需要導入AOP名稱空間 -->
<aop:config>
<!-- 聲明切入點表達式 -->
<aop:pointcut expression="execution(* com.neuedu.aop.target.MathCalculatorImpl.*(..))" id="myPointCut"/>
<!-- 配置日誌切面類,引用前面的類 ,通過order屬性控制優先級-->
<aop:aspect ref="logAspect" order="25">
<!-- 通過method屬性指定切面類的切面方法,通過pointcut-ref指定切入點表達式 -->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<aop:after method="showAfterLog" pointcut-ref="myPointCut"/>
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="ex"/>
<aop:after-returning method="showReturnLog" pointcut-ref="myPointCut" returning="result"/>
<aop:around method="around" pointcut-ref="myPointCut"/>
</aop:aspect>

<!-- 配置事務切面類,引用前面的類 -->
<aop:aspect ref="txAspect" order="20">
<aop:around method="around" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>

需要知道的是:事務的管理是和AOP是有很大關系的,即聲明式事務的底層是用事務實現的!


13.批處理(batch)------------>好比快遞員【不能一件一件的送快遞】
- 批處理指的是一次操作中執行多條SQL語句
- 批處理相比於一次一次執行效率會提高很多

- 批處理主要是分兩步:
1.將要執行的SQL語句保存
2.執行SQL語句

- Statement和PreparedStatement都支持批處理操作,這裏我們只需要掌握PreparedStatement的批處理方式:
- 方法:
void addBatch()
- 將要執行的SQL先保存起來,先不執行
- 這個方法在設置完所有的占位符之後調用
int[] executeBatch()
- 這個方法用來執行SQL語句,這個方法會將批處理中所有SQL語句執行

- mysql默認批處理是關閉的,所以我們還需要去打開mysql的批處理:
rewriteBatchedStatements=true
我們需要將以上的參數添加到mysql的url地址中

- 註意:低版本的mysql-jdbc驅動也不支持批處理,一般都是在修改的時候使用批處理,查詢的時候不使用!



案例演示:
1.創建一張新的數據表
CREATE TABLE t_emp(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50)
)
2.反復打開數據庫客戶端,插入語句【相當於每次獲取一個connection連接,執行executeUpdate語句】
INSERT INTO t_emp(NAME) VALUES(‘張三‘);
SELECT * FROM t_emp;

3.引出批處理--->執行效率高,資源利用率好!

@Test//測試批處理
public void testBatch(){
//向t_emp表中插入10000條數據

//準備兩個變量
Connection connection = null;
PreparedStatement ps = null;

try {
//獲取數據庫連接
connection=JDBCUtil.getConnection();
//準備SQL模板
String sql = "INSERT INTO t_emp(NAME) VALUES(?)";
//獲取PrepareStatement
ps = connection.prepareStatement(sql);
//創建一個for循環,來設置占位符
for(int i = 0; i < 10000 ;i++){
//填充占位符
ps.setString(1,"emp"+i);
//添加到批處理方法中,調用無參的,有參的是Statement來調用的!
ps.addBatch();
}

//獲取一個時間戳
long start = System.currentTimeMillis();
//執行批處理
ps.executeBatch();
//獲取一個時間戳
long end = System.currentTimeMillis();
System.out.println("共花費了:"+(end-start));
} catch (SQLException e) {
e.printStackTrace();
}
}



5) 事務(Transaction)


演示銀行轉賬的功能:
1.創建一張表示賬號的表
CREATE TABLE t_account(
id INT PRIMARY KEY AUTO_INCREMENT,
a_name VARCHAR(50),
balance DECIMAL(11,2)
)

2.向表中插入幾個用戶
INSERT INTO t_account(id,a_name,balance) VALUES(NULL,‘sunwukong‘,1000);
INSERT INTO t_account(id,a_name,balance) VALUES(NULL,‘zhubajie‘,1000);
INSERT INTO t_account(id,a_name,balance) VALUES(NULL,‘shaheshang‘,1000);
SELECT * FROM t_account;


3.#sunwukong向shaheshang轉賬100元
#從sunwukong的賬號減去100元
UPDATE t_account SET balance = balance - 100 WHERE a_name=‘sunwukong‘;

#給shaheshang的賬號加上100元
UPDATE t_account SET balance = balance +100 WHERE a_name = ‘shaheshang‘;


重新設置為1000元:
UPDATE t_account SET balance =1000;

4.從java代碼中演示上面的案例:
1.創建Dao類
public class AcountDao {
public void update(String name,double money){
//準備兩個變量
Connection conn = null;
PreparedStatement ps = null;
//準備SQL模板
String sql = "UPDATE t_account SET balance = balance + ? WHERE a_name = ?";

try {
conn = JDBCUtil.getConnection();
//獲取PreparedStatement
ps = conn.prepareStatement(sql);
//填充占位符
ps.setDouble(1, money);
ps.setString(2, name);

//執行SQL語句
ps.executeUpdate();

} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtil.close(conn, ps, null);
}
}
}

2.測試該DAO
public class TestTransaction {
private AcountDao accountDao = new AcountDao();

@Test
public void test() {
//從sunwukong賬戶向shaheshang賬戶轉賬100元!
//1.從sunwukong賬戶扣除100元
accountDao.update("sunwukong", -100);
//2.向shaheshang賬戶添加100元
accountDao.update("shaheshang", 100);
}
}
//顯然上面是可以正常執行的!
但是如果上面的程序在suwukong減去100元之後,shaheshang加錢之前,出現了異常,如下所示:

//從sunwukong賬戶向shaheshang賬戶轉賬100元!
//1.從sunwukong賬戶扣除100元
accountDao.update("sunwukong", -100);
int i =10/0;//添加一個異常
//2.向shaheshang賬戶添加100元
accountDao.update("shaheshang", 100);


- 在開發中我們的一個業務往往需要同時操作多個表,這些操作往往是不可分割,業務中的對數據庫的多次操作,
要麽同時成功,要麽全都失敗。

- 事務的特性(ACID):
原子性(atomicity)
一個事務是一個不可分割的工作單位,事務中包括的諸操作要麽都做,要麽都不做。

一致性(consistency)
事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。

隔離性(isolation)
一個事務的執行不能被其他事務幹擾。
即一個事務內部的操作及使用的數據對並發的其他事務是隔離的,並發執行的各個事務之間不能互相幹擾。

持久性(durability)
持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。
接下來的其他操作或故障不應該對其有任何影響。

- 操作事務的基本步驟:
1.開啟事務
- 開啟事務以後,我們只後的所有操作將都會在同一個事務當中
2.操作數據庫
- 開啟事務以後再去操作數據庫,所有操作將不會直接提交到數據庫中
3.提交事務
- 將修改應用到數據庫
4.回滾事務
- 數據庫操作過程中出現異常了,回滾事務,回滾事務以後,數據庫變成開啟事務之前的狀態

- mysql中的事務控制
#開啟事務
START TRANSACTION
#回滾事務
ROLLBACK
#提交事務
COMMIT

- JDBC中的事務主要通過Connection對象來控制的
1.開啟事務
void setAutoCommit(boolean autoCommit) throws SQLException;
- 設置事務是否自動提交,默認是自動提交
- 設置事務手動提交
conn.setAutoCommit(false);

2.提交事務
void commit() throws SQLException;
- 提交事務
conn.commit()

3.回滾事務
void rollback() throws SQLException;
- 回滾事務
conn.rollback()

- 事務控制的格式:
//創建一個Connection
Connection conn = null;

try{

//獲取Connection
conn = JDBCUtils.getConnection();

//開啟事務
conn.setAutoCommit(false);

//對數據庫進行操作

//操作成功,提交事務
conn.commit();

}catch(Exception e){
e.printStackTrace();

//回滾事務
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}

}finally{
JDBCUtils.close(conn, null, null);
}


- 註意:我們在同一個事務中使用的數據庫連接(Connection)必須是同一個,否則事務還是不作用!
所以此時原來的AcountDAO中的update方法要改為如下所示:
public class AcountDao {
public void update(Connection conn,String name,double money){
//準備兩個變量
PreparedStatement ps = null;
//準備SQL模板
String sql = "UPDATE t_account SET balance = balance + ? WHERE a_name = ?";

try {
//獲取PreparedStatement
ps = conn.prepareStatement(sql);
//填充占位符
ps.setDouble(1, money);
ps.setString(2, name);

//執行SQL語句
ps.executeUpdate();

} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//此時也不能在這裏關閉數據庫連接了,而是在外邊統一關閉
JDBCUtil.close(null, ps, null);
}
}
}



技術分享










AOP面向切面編程筆記