1. AOP
- AOP:面向切面(方面)編程,通俗的理解就是:擴展功能不通過修改源代碼實現
- AOP:採用橫向抽取機制,取代了傳統 縱向 繼成體系 重用代碼(性能監視,事務管理,安全檢查,緩存)
2. AOP實現機制 – 代理機制:
- Spring 的 AOP 的底層用到兩種代理機制:
- JDK 的動態代理 :針對實現了接口的類產生代理.
- Cglib 的動態代理 :針對沒有實現接口的類產生代理. 應用的是底層的字節碼增強的技術 生成當前類
的子類對象.
3. AOP的相關概念
-
PointCut(切入點)
切入點實際上是用來定義橫切邏輯規則的組件;
所謂切入點是指我們要對哪些Joinpoint進行攔截的定義;
【糯米藕】切藕的規則:距藕節3~4cm; -
Target(目標對象)
代理的目標對象 (要增強的類)
根據切入點(pointcut)的規則,找出來的需要被增強的類 / 對象。
【糯米藕】根據切藕的規則,找出來的符號條件的藕; -
JoinPoint(連接點)
所謂連接點是指那些被攔截到的點,在Spring中,這些點指的是方法,因爲Spring只支持方法類型的連接點。
連接點是根據切入點(pointcut)的規則,在目標對象(Target)上要進行切面的那個位置,代碼中體現爲一個特定的方法;
【糯米藕】具體的一節藕上,下刀的位置; -
Advice(通知/增強)
所謂通知是指攔截到連接點之後所要做的事情就是通知;
通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知 (切面要完成的任務)
通知(Advice)可以看做是添加到目標對象(Target)上的新的功能;
通知體現爲類的方法;
【糯米藕】米; -
Aspect(切面)
切面(Aspect)是切入點和通知(引介)的結合
代碼中的切面是一個理解性的概念;
【糯米藕】在藕上下刀,形成的橫截面; -
Introduction(引介)
引介是一種特殊的通知(Advice),在不修改類的代碼的前提下,Introduction可以在運行期間爲類動態添加一些方法或者Field
-
Weaving(織入)
把增強的應用到目標過程。【把advice應用到target的過程】
織入在開發過程中,需要進行xml或註解配置;
【糯米藕】在藕上下刀,把米灌入藕的全過程; -
Proxy(代理)
一個類被AOP織入增強後,就產生一個結果代理類
【糯米藕】糯米藕;
4. Spring 使用 AspectJ 進行 AOP 的開發: XML 的方式
4.1 引入jar包
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.21.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
4.2 引入 Spring 的配置文件
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
4.3 編寫目標類
import com.hxzy.service.TeacherService;
public class TeacherServiceImpl implements TeacherService{
@Override
public void dianMing() {
System.out.println("TeacherServiceImpl ... dianMing()");
teacherDao.dianMing();
}
}
4.4 整合 Junit 單元測試
- 引入 spring-test.jar
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.21.RELEASE</version>
</dependency>
4.5 通知類型
- 前置通知 : 在目標方法執行之前執行.
- 後置通知 : 在目標方法執行之後執行
- 環繞通知 : 在目標方法執行前和執行後執行
- 異常拋出通知: 在目標方法執行出現 異常的時候 執行
- 最終通知 : 無論目標方法是否出現異常 最終通知都會 執行.
4.6 切入點表達式
語法結構:
execution( 【方法修飾符】 方法返回值 方法所屬類 匹配方法名 ( 方法中的形參表 ) 方法申明拋出的異常 )
其中 方法返回值、匹配方法名、 方法中的形參表 的部分時不能省略的,各部分都支持通配符 “*” 來匹配全部。
比較特殊的爲形參表部分,其支持兩種通配符
“*”:代表一個任意類型的參數;
“…”:代表零個或多個任意類型的參數。
例如:
()匹配一個無參方法
(…)匹配一個可接受任意數量參數和類型的方法
(*)匹配一個接受一個任意類型參數的方法
(*,Integer)匹配一個接受兩個參數的方法,第一個可以爲任意類型,第二個必須爲Integer。
分類 | 示例 | 描述 |
通過方法簽名定義切入點 | execution(public * * (..)) | 匹配所有目標類的public方法,第一個*爲返回類型,第二個*爲方法名 |
execution(* save* (..)) | 匹配所有目標類以save開頭的方法,第一個*代表返回類型 | |
execution( * *product(*,String)) | 匹配目標類所有以product結尾的方法,並且其方法的參數表第一個參數可爲任意類型,第二個參數必須爲String | |
通過類定義切入點 | execution(* aop_part.Demo1.service.*(..)) | 匹配service接口及其實現子類中的所有方法 |
通過包定義切入點 | execution(* aop_part.*(..)) | 匹配aop_part包下的所有類的所有方法,但不包括子包 |
execution(* aop_part..*(..)) | 匹配aop_part包下的所有類的所有方法,包括子包。(當".."出現再類名中時,後面必須跟“*”,表示包、子孫包下的所有類) | |
execution(* aop_part..*.*service.find*(..)) | 匹配aop_part包及其子包下的所有後綴名爲service的類中,所有方法名必須以find爲前綴的方法 | |
通過方法形參定義切入點 | execution(*foo(String,int)) | 匹配所有方法名爲foo,且有兩個參數,其中,第一個的類型爲String,第二個的類型爲int |
execution(* foo(String,..)) | 匹配所有方法名爲foo,且至少含有一個參數,並且第一個參數爲String的方法(後面可以有任意個類型不限的形參) |
4.7 編寫一個切面類
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 增強 Advice
*
* @author Administrator
*/
public class Aspect01 {
Logger logger = Logger.getLogger(Aspect01.class);
/**
* 前置增強
*/
public void before(JoinPoint jp) {
logger.debug("前置增強:before");
// jp.getTarget() : 獲得類名 , 獲得目標對象(Target)
// jp.getSignature().getName() : 獲得方法名
// jp.getArgs() : 獲得參數數組
logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入參:"
+ Arrays.toString(jp.getArgs()));
}
/**
* 後置增強
*/
public void afterReturn(JoinPoint jp, Object returnValue) {
logger.debug("後置增強:afterReturn ... ");
// jp.getTarget() : 獲得類名
// jp.getSignature().getName() : 獲得方法名
// jp.getArgs() : 獲得參數數組
logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法," + "方法入參:"
+ Arrays.toString(jp.getArgs()) + " ,方法返回值:" + returnValue);
}
/**
* 環繞增強
*
* @return
*/
public Object around(ProceedingJoinPoint jp) {
logger.debug("環繞增強 begin");
// jp.getTarget() : 獲得類名 , 獲得目標對象(Target)
// jp.getSignature().getName() : 獲得方法名
// jp.getArgs() : 獲得參數數組
logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入參:"
+ Arrays.toString(jp.getArgs()));
// 在環繞增強中,可以使用連接點的對象jp,直接調用目標方法,並得到返回值。
Object returnValue = null; // 返回值
try {
returnValue = jp.proceed();
System.out.println("返回值是:" + returnValue);
} catch (Throwable e) {
e.printStackTrace();
}
logger.debug("環繞增強 end ");
return returnValue;
}
/**
* 異常增強
* @param jp
* @param e
*/
public void afterThrowing(JoinPoint jp, RuntimeException e) {
logger.info(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法發生異常,異常信息是:" + e.getMessage());
}
/**
* 最終增強
* @return
*/
public void after(JoinPoint jp) {
logger.debug("最終增強 ");
logger.debug(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法結束執行。");
}
}
4.8 配置完成增強
<!-- 配置切面類(增強) -->
<bean id="aspect01" class="com.hxzy.aop.Aspect01"></bean>
<!-- 進行 aop 的配置 -->
<aop:config>
<!-- 切入點:指明哪些類的哪些方法需要進行增強,是一個規則 -->
<!-- [方法訪問修飾符] 方法返回值 包名.類名.方法名(方法的參數) -->
<aop:pointcut expression="execution( * com.hxzy.service..*.*(..))" id="pointcut"/>
<!-- 配置切面 , 織入 -->
<aop:aspect ref="aspect01">
<!-- 前置增強 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 後置增強 -->
<aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="returnValue"/>
<!-- 環繞增強 -->
<aop:around method="around" pointcut-ref="pointcut"/>
<!-- 異常增強 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<!-- 最終增強 -->
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
5. Spring 使用 AspectJ 進行 AOP 的開發: 註解配置
5.1 Spring配置文件中開啓aop註解的自動代理
<!-- 掃描帶有註解的增強,需要引入aop的命名空間 -->
<aop:aspectj-autoproxy />
5.2 AspectJ的AOP註解
- 通知類型
- @Before : 前置增強
- @AfterReturning : 後置增強
- @Around : 環繞增強
- @AfterThrowing : 異常拋出增強
- @After : 最終增強
- 切入點
- @Pointcut
5.3 增強類中使用註解獲得連接點信息
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 增強 Advice
* @author Administrator
*/
@Component // 註冊bean , 配置切面
@Aspect //配置切面類
public class Aspect02 {
Logger logger = Logger.getLogger(Aspect02.class);
// 切入點
@Pointcut(value = "execution( * com.hxzy.service.impl.*.*(..))")
private void anyMethod() {}
/**
* 前置增強
*/
// @Before(value="execution( * com.hxzy.service.impl.*.*(..))")
// @Before("execution( * com.hxzy.service.impl.*.*(..))")
@Before("anyMethod()")
public void before(JoinPoint jp) {
logger.debug("前置增強:before");
logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入參:"
+ Arrays.toString(jp.getArgs()));
}
/**
* 後置增強
*/
@AfterReturning(pointcut = "anyMethod()",returning = "returnValue")
public void afterReturn(JoinPoint jp, Object returnValue) {
logger.debug("後置增強:afterReturn ... ");
logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法," + "方法入參:"
+ Arrays.toString(jp.getArgs()) + " ,方法返回值:" + returnValue);
}
/**
* 環繞增強
* @return
*/
@Around("anyMethod()")
public Object around(ProceedingJoinPoint jp) {
logger.debug("環繞增強 begin");
logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入參:"
+ Arrays.toString(jp.getArgs()));
// 在環繞增強中,可以使用連接點的對象jp,直接調用目標方法,並得到返回值。
Object returnValue = null; // 返回值
try {
returnValue = jp.proceed();
System.out.println("返回值是:" + returnValue);
} catch (Throwable e) {
e.printStackTrace();
}
logger.debug("環繞增強 end ");
return returnValue;
}
/**
* 異常增強
* @param jp
* @param e
*/
@AfterThrowing(pointcut = "anyMethod()" , throwing = "e")
public void afterThrowing(JoinPoint jp, RuntimeException e) {
logger.info(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法發生異常,異常信息是:" + e.getMessage());
}
/**
* 最終增強
* @return
*/
@After("anyMethod()")
public void after(JoinPoint jp) {
logger.debug("最終增強 ");
logger.debug(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法結束執行。");
}
}