1 AOP
簡單地說,就是把程序重複的代碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改源碼的基礎上,對已有的方法增強。
1.1 AOP術語
1.1.1 Joinpoint
連接點
指被攔截到的點。在spring中,這些點指的是方法,因爲 spring 只支持方法類型的連接點。
1.1.2 Pointcut
切入點
指要對哪些 Jointpoint 進行攔截的定義。
1.1.3 Advice
通知/增強
- 指攔截到 jointpoint 之後所要做的事情
- 通知類型:前置通知、後置通知、異常通知、最終通知、環繞通知
- 在環繞通知中有明確的切入點方法調用
1.1.4 Introduction
引介
一種特使的通知,在不修改類代碼的前提下,Introduction可以在運行期位類動態地添加一些方法或者 field
1.1.5 target
目標對象
代理的目標對象
1.1.6 weaving
織入
指把增強應用到目標對象來創建新的代理對象的過程。
- spring 採用動態代理織入,而 aspectJ採用編譯期織入和類裝載期織入。
1.1.7 proxy
代理
一個類被AOP織入增強後,就產生一個結果代理類
1.1.8 aspect
切面
是切入點和通知(引介)的結合
1.2 學習spring中的AOP要明確的事
- 開發階段(我們做的)
(1)編寫核心業務代碼(開發主線):大部分程序員來做,要求熟悉業務需求。
(2)把公用代碼抽取出來,製作成通知。(開發階段最後再做):AOP編程人員來做。
(3)在配置文件中,聲明切入點與通知間的關係,即切面。:AOP編程人員來做。 - 運行階段(Spring框架完成的)
Spring框架監控切入點方法的執行。一旦監控到切入點方法被運行,使用代理機制,動態創建目標對象的代理對象,根據通知類別,在代理對象的對應位置,將通知對應的功能織入,完成完整的代碼邏輯運行。
1.3 代理的選擇
在spring中,框架會根據目標類是否實現了接口來決定採用哪種動態代理的方式。
2 基於 xml 的AOP配置
2.1 pom
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<spring-version>5.0.2.RELEASE</spring-version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
2.2 Service
public interface IAccountService {
/**
* 模擬保存賬戶
*
*/
void saveAccount();
void updateAccount(int i);
int deleteAccount();
}
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("執行了保存");
}
@Override
public void updateAccount(int i) {
System.out.println("執行了更新 " + i);
}
@Override
public int deleteAccount() {
System.out.println("執行了刪除");
return 0;
}
}
2.3 日誌記錄類
package com.tzb.myutils;
/**
* 用於記錄日誌的工具類
*/
public class Logger {
/**
* 打印日誌,計劃讓其在切入點方法之前執行
* 切入點方法就是業務層方法
*/
public void printLog(){
System.out.println("Logger類中的 printLog 開始記錄日誌。。。");
}
}
2.5 bean.xml
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
<!--配置 spring 的 IOC-->
<bean id="accountService" class="com.tzb.service.impl.AccountServiceImpl"></bean>
<!--AOP 配置步驟
1、把通知 Bean 也交給 spring 管理
2、使用 aop:config 標籤表明開始 AOP 的配置
3、使用 aop:aspect 標籤表明開始配置切面
id屬性:是給切面提供一個唯一標識
ref屬性:指定通知類 bean 的id
4、在 aop:aspect 標籤的內部使用對應的標籤來配置通知的類型
現在的案例是讓 printlog 方法在切入點方法執行之前執行,所以是前置通知
aop:brfore 配置前置通知
method屬性,指定 Logger 類中哪個方法是前置通知
pointcut屬性:指定切入點表達式,指對業務類中哪個方法增強
切入點表達式的寫法:
關鍵字: execution
表達式:
訪問修飾符 返回值 包名.包名...類名.方法名(參數列表)
-->
<!--配置 Logger 類-->
<bean id="logger" class="com.tzb.myutils.Logger"></bean>
<!-- 配置 AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知類型,且建立通知方法和切入點的關聯-->
<aop:before method="printLog" pointcut="execution(public void com.tzb.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
2.6 單元測試
public class AOPTest {
public static void main(String[] args) {
// 1.獲取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:bean.xml");
// 2. 獲取對象
IAccountService as = (IAccountService) ac.getBean("accountService");
// 3.執行方法
as.saveAccount();
}
}
3 切入點表達式寫法
public class AOPTest {
public static void main(String[] args) {
// 1.獲取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:bean.xml");
// 2. 獲取對象
IAccountService as = (IAccountService) ac.getBean("accountService");
// 3.執行方法
as.saveAccount();
as.updateAccount(1);
as.deleteAccount();
}
}
3.1 全通配寫法
* *..*.*(..)
3.2 訪問修飾符可以省略
void com.tzb.service.impl.AccountServiceImpl.saveAccount()
3.3 返回值使用通配符,表示任意返回值
* com.tzb.service.impl.AccountServiceImpl.saveAccount()
3.4 包名可以使用通配符,表示任意包
- 但是有幾級包,就需要寫幾個
*.
3.5 包名可以使用..
,表示當前包及其子包
* *..AccountServiceImpl.saveAccount()
3.6 類名和方法名都可以使用 *
實現通配
* *..*.*()
3.7 參數列表
- 可以直接寫參數類型:基本類型直接寫名稱;引用類型寫
包名.類名
* *..*.*(int)
- 可以使用通配符表示任意類型,但是必須有參數
* *..*.*(*)
- 可以使用
..
,表示有參數或者無參數
3.8 實際開發中切入點表達式的通常寫法
切到業務層實現類下的所有方法
* com.tzb.service.impl.*.*(..)