1铲汪、Spring的AOP面向切面編程
1.1、AOP
AOP 為 Aspect Oriented Programming 的縮寫,意思為面向切面編程柠傍,是通過**預(yù)編譯方式和運(yùn)行期動態(tài)代理**實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。
AOP 是 OOP 的延續(xù)辩稽,是軟件開發(fā)中的一個(gè)熱點(diǎn)携兵,也是Spring框架中的一個(gè)重要內(nèi)容,是**函數(shù)式編程的一種衍生范型**搂誉。利用AOP可以對業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離徐紧,從而使得業(yè)務(wù)邏輯各部分之間的**耦合度降低,提高程序的可重用性炭懊,同時(shí)提高了開發(fā)的效率**并级。
1.2、AOP作用及優(yōu)勢
作用:在程序運(yùn)行期間侮腹,在**不修改源代碼**的情況下對**目標(biāo)進(jìn)行功能增強(qiáng)**嘲碧,完成程序代碼之間的**松耦合。**
優(yōu)勢:減少重復(fù)代碼父阻,提高開發(fā)效率愈涩,并且便于維護(hù)。
1.3加矛、AOP底層實(shí)現(xiàn):通過spring動態(tài)代理技術(shù)
實(shí)際上履婉,AOP 的底層是通過 Spring 提供的的動態(tài)代理技術(shù)實(shí)現(xiàn)的。在運(yùn)行期間斟览,Spri6ng通過動態(tài)代理技術(shù)動態(tài)的生成代理對象毁腿,代理對象方法執(zhí)行時(shí)進(jìn)行增強(qiáng)功能的介入,在去調(diào)用目標(biāo)對象的方法苛茂,從而完成功能的增強(qiáng)已烤。
1.4、AOP動態(tài)代理技術(shù)(面試題)
默認(rèn)判斷類是否有接口妓羊,
**JDK代理**:基于接口的動態(tài)代理技術(shù)胯究,有接口時(shí)使用
**cglib**:基于父類的動態(tài)代理技術(shù)
[圖片上傳失敗...(image-7c5fcd-1566476285391)]
1.5、JDK的動態(tài)代理
java
//1)躁绸、目標(biāo)類接口
public interface TargetInterface {
public void method();
}
//2)裕循、實(shí)現(xiàn)目標(biāo)接口 的 目標(biāo)類
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("目標(biāo)運(yùn)行....");
}
}
//3)臣嚣、動態(tài)代理代碼, 配置方式增強(qiáng)原有對象费韭,底層是動態(tài)代理茧球,增強(qiáng)代理
final Target target = new Target(); //創(chuàng)建目標(biāo)對象
//創(chuàng)建代理對象
//返回值,動態(tài)生成的代理對象. jdk是基于接口的,所以返回值的類型是接口的子實(shí)現(xiàn)類
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {//調(diào)用代理對象的任何方法,實(shí)際執(zhí)行的invoke方法
@Override
public Object invoke(Object proxy,
Method method/*正在執(zhí)行目標(biāo)的方法*/,
Object[] args/*正在執(zhí)行的方法的參數(shù)*/)
throws Throwable {
System.out.println("前置增強(qiáng)代碼...");
//啟用日志
Object invoke = method.invoke(target, args);
System.out.println("后置增強(qiáng)代碼...");
return invoke;
}
}
);
//4)、調(diào)用代理對象的方法測試
//執(zhí)行目標(biāo)和實(shí)際參數(shù)
method,invoke(useDao.args);
proxy.method();
1.6星持、cglib的動態(tài)代理 沒有接口時(shí)基于父類
java
//1抢埋、目標(biāo)類
public class Target {
public void method() {
System.out.println("Target running....");
}
}
//2、動態(tài)代理代碼
Target target = new Target(); //創(chuàng)建目標(biāo)對象
Enhancer enhancer = new Enhancer(); //創(chuàng)建增強(qiáng)器 cglib提供
enhancer.setSuperclass(Target.class); //設(shè)置父類(目標(biāo)) target.class
enhancer.setCallback(new MethodInterceptor() { //設(shè)置回調(diào)
@Override
public Object intercept(Object o, //proxy代理
Method method,
Object[] objects, //args參數(shù)
MethodProxy methodProxy
) throws Throwable {
System.out.println("前置代碼增強(qiáng)....");
advice.before();前置
Object invoke = method.invoke(target, objects);//執(zhí)行目標(biāo)
System.out.println("后置代碼增強(qiáng)....");
advice.after();后置
return invoke;
}
});
//創(chuàng)建代理對象
Target proxy = (Target) enhancer.create();
//3督暂、調(diào)用代理對象的方法測試
//測試,當(dāng)調(diào)用接口的任何方法時(shí)揪垄,代理對象的代碼都無需修改
proxy.method();
1.7、AOP相關(guān)概念B呶獭<⑴!
spring的AOP實(shí)現(xiàn)底層就是對之前的JDK動態(tài)代理和cglib動態(tài)代理的代碼進(jìn)行封裝八回,之后我們需要關(guān)注部分代碼的編寫酷愧,通過配置文件完成對執(zhí)行目標(biāo)的方法進(jìn)行增強(qiáng)
AOP常見相關(guān)術(shù)語
- Target(目標(biāo)對象):代理的目標(biāo)對象
- Proxy(代理):類被AOP織入增強(qiáng)后,產(chǎn)生的結(jié)果代理類缠诅。
- JoinPoint(連接點(diǎn)):可以被增強(qiáng)的方法(候選人)溶浴,spring只支持方法類型的連接點(diǎn)。
- PointCut(切入點(diǎn)/切點(diǎn)):真正配置被增強(qiáng)的方法(連接點(diǎn))管引。
- Advice(通知/增強(qiáng)):增強(qiáng)連接點(diǎn)的方法士败,包含增強(qiáng)的業(yè)務(wù)邏輯封裝的advice。before前置褥伴,after后置
- Aspect(切面):切點(diǎn)和通知的結(jié)合
- Weaving(織入):就是將切點(diǎn)和增強(qiáng)的過程谅将,通過配置文件體現(xiàn),配置的過程可以理解為織入的過程 把增強(qiáng)應(yīng)用到目標(biāo)對象 來創(chuàng)建新的代理對象的過程重慢。spring采用動態(tài)代理織入饥臂,而aspect采用編譯期織入和類裝載期織入
1.8、AOP開發(fā)明確的事項(xiàng)
1)伤锚、需要編寫的內(nèi)容
- 編寫核心業(yè)務(wù)代碼(目標(biāo)類的目標(biāo)方法)
- 編寫切面類擅笔,切面類中有通知(增強(qiáng)功能的方法)
- 在配置文件中,配置織入關(guān)系屯援,即將哪些通知(增強(qiáng))與哪些連接點(diǎn)(切點(diǎn))進(jìn)行結(jié)合
2)、AOP技術(shù)實(shí)現(xiàn)的內(nèi)容
Spring 框架監(jiān)控切入點(diǎn)方法的執(zhí)行念脯。一旦監(jiān)控到切入點(diǎn)方法被運(yùn)行狞洋,使用代理機(jī)制,動態(tài)創(chuàng)建目標(biāo)對象的代理對象绿店,根據(jù)通知類別(前置,后置等)吉懊,在代理對象的對應(yīng)位置庐橙,將通知對應(yīng)的功能進(jìn)行相應(yīng)的織入,完成完整的代碼邏輯運(yùn)行借嗽。
3)态鳖、AOP底層使用哪種代理方式
在 spring 中,框架會根據(jù)目標(biāo)類是否實(shí)現(xiàn)了接口來決定采用哪種動態(tài)代理的方式
1.9恶导、小結(jié)知識要點(diǎn)
- aop:面向切面編程
- aop底層實(shí)現(xiàn):基于JDK或者基于cglib的動態(tài)代理
- aop重要概念:
- pointcut(切入點(diǎn)):被增強(qiáng)的方法
- Advice(通知/增強(qiáng)):封裝增強(qiáng)業(yè)務(wù)邏輯的方法
- Aspect(切面):切點(diǎn)+通知
- Weaving(織入):將切點(diǎn)與通知增強(qiáng)結(jié)合的過程
- 開發(fā)明確事項(xiàng):
- 誰是切點(diǎn)(切點(diǎn)表達(dá)式的配置)
- 誰是通知(切面類中的增強(qiáng)方法)
- 將切點(diǎn)和通知增強(qiáng)進(jìn)行織入配置
2浆竭、基于XML的AOP開發(fā)
2.1、開發(fā)步驟
①導(dǎo)入 AOP 相關(guān)坐標(biāo)
②創(chuàng)建目標(biāo)接口和目標(biāo)類(內(nèi)部有切點(diǎn))
③創(chuàng)建切面類(內(nèi)部有增強(qiáng)方法)
④將目標(biāo)類和切面類的對象創(chuàng)建權(quán)交給 spring
⑤在 applicationContext.xml 中配置織入關(guān)系
⑥測試代碼
2.2惨寿、代碼演示
//1) pom文件導(dǎo)入AOP相關(guān)坐標(biāo)
<!--導(dǎo)入spring的context坐標(biāo)邦泄,context依賴aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的織入 -->
<dependency>
<groupId>org.aspectj</groupId>//第三方的aop坐標(biāo)
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
//2)、創(chuàng)建目標(biāo)接口和實(shí)現(xiàn)該接口的目標(biāo)類(內(nèi)部有切點(diǎn))
public interface TargetInterface {
public void method();
}
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
//3)裂垦、創(chuàng)建切面類(增強(qiáng)方法)
public class MyAspect {
//前置增強(qiáng)方法
public void before(){
System.out.println("前置代碼增強(qiáng).....");
}
}
//4)顺囊、將目標(biāo)類和切面類的對象創(chuàng)建交給spring
<!--配置目標(biāo)類-->
<bean id="target" class="com.ppvir.aop.Target"></bean>
<!--配置切面類-->
<bean id="myAspect" class="com.ppvir.aop.MyAspect"></bean>
//5)、在applicationContext.xml中配置織入關(guān)系
//導(dǎo)入aop命名空間
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
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.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
//配置切點(diǎn)表達(dá)式和前置增強(qiáng)的織入關(guān)系
<!--配置織入,告訴框架,哪些方法(切點(diǎn))需要進(jìn)行哪些增強(qiáng)(前置,后置)-->
<aop:config>
<!--引用myAspect的Bean為切面對象-->
<!--聲明切面-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法執(zhí)行時(shí)要進(jìn)行myAspect的before方法前置增強(qiáng)-->
<!--切面, 切點(diǎn)+通知-->
<aop:before method="before" pointcut="execution(public void com.ppvir.aop.Target.method())"></aop:before>
</aop:aspect>
</aop:config>
<beans>
//6)蕉拢、測試代碼
@RunWith(SpringJUnit4ClassRunner.class) //RunWith測試
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired//測試誰就依賴注入誰
private TargetInterface target;
@Test
public void test1(){
target.method();
}
}
2.2特碳、Xml配置AOP詳解
1)、切點(diǎn)表達(dá)式的縮略寫法
表達(dá)式語法:
execution([修飾符] 返回值類型 包名.類名.方法名(參數(shù)))
- 訪問修飾符可以省略
- 返回值類型晕换、包名午乓、類名、方法名可以使用星號* 代表任意
- 包名與類名之間一個(gè)點(diǎn) . 代表當(dāng)前包下的類届巩,兩個(gè)點(diǎn) .. 表示當(dāng)前包及其子包下的類
- 參數(shù)列表可以使用兩個(gè)點(diǎn) .. 表示任意個(gè)數(shù)硅瞧,任意類型的參數(shù)列表
示例:
- execution(public void com.ppvir.aop.Target.method())
- execution(void com.ppvir.aop.Target.*(..))
- execution(* com.ppvir.aop..(..))
- execution(* com.ppvir.aop...(..))
- execution(* ...*(..))
2)、通知(增強(qiáng))的類型
通知的配置語法:
<aop:通知類型 method=“切面類中方法名” pointcut=“切點(diǎn)表達(dá)式"></aop:通知類型>
名稱 | 標(biāo)簽 | 說明 |
---|---|---|
前置通知 | <aop:before> |
用于配置前置通知恕汇,指定增強(qiáng)的方法在切點(diǎn)方法之前執(zhí)行 |
后置通知 | <aop:after-returning> |
用于配置后置通知腕唧,指定增強(qiáng)的方法在切點(diǎn)方法之后執(zhí)行 |
環(huán)繞通知 | <aop:around> |
用于配置環(huán)繞通知,指定增強(qiáng)的方法在切點(diǎn)方法之前和之后都執(zhí)行 |
異常拋出通知 | <aop:throwing> |
用于配置異常拋出通知瘾英,指定增強(qiáng)的方法在出現(xiàn)異常時(shí)執(zhí)行 |
最終通知 | <aop:after> |
用于配置最終通知枣接,無論增強(qiáng)方式執(zhí)行是否有異常都會執(zhí)行 |
需要使用方法執(zhí)行前和執(zhí)行后的參數(shù),優(yōu)先使用環(huán)繞通知
環(huán)繞=前置+后置+異常+最終
3)缺谴、切點(diǎn)表達(dá)式的抽取
當(dāng)多個(gè)增強(qiáng)的切點(diǎn)表達(dá)式相同時(shí)但惶,可以將切點(diǎn)表達(dá)式進(jìn)行抽取,在增強(qiáng)中使用 pointcut-ref 屬性代替 pointcut 屬性來引用抽取后的切點(diǎn)表達(dá)式湿蛔。
<aop:config>
<!--引用myAspect的Bean為切面對象-->
<aop:aspect ref="myAspect">
<aop:pointcut id="myPointcut" expression="execution(* com.ppvir.aop.*.*(..))"/>
<aop:before method="before" pointcut-ref="myPointcut"></aop:before>
</aop:aspect>
</aop:config>
3膀曾、基于注解的AOP開發(fā)
3.1 基于注解的AOP開發(fā)步驟:
①創(chuàng)建目標(biāo)接口和目標(biāo)類(內(nèi)部有切點(diǎn))
②創(chuàng)建切面類(內(nèi)部有增強(qiáng)方法)
③將目標(biāo)類和切面類的對象創(chuàng)建權(quán)交給 spring
④在切面類中使用注解配置織入關(guān)系
⑤在配置文件中開啟組件掃描和 AOP 的自動代理
⑥測試
3.2 代碼演示
1)、創(chuàng)建目標(biāo)接口和目標(biāo)類(含有切點(diǎn))
public interface TargetInterface {
public void method();
}
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
2)阳啥、創(chuàng)建切面類(含有增強(qiáng)方法)
public class MyAspect {
//前置增強(qiáng)方法
public void before(){
System.out.println("前置代碼增強(qiáng).....");
}
}
3)添谊、將目標(biāo)類和切面類的對象創(chuàng)建交給spring
@Component("target")
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
@Component("myAspect")
public class MyAspect {
public void before(){
System.out.println("前置代碼增強(qiáng).....");
}
}
4)、在切面類中使用注解配置織入關(guān)系
@Component("myAspect")
@Aspect //切面
public class MyAspect {
@Before("execution(* com.ppvir.aop.*.*(..))")
public void before(){
System.out.println("前置代碼增強(qiáng).....");
}
}
5)察迟、在 配置文件中開啟組件掃描和AOP的自動代理
<!--組件掃描-->
<context:component-scan base-package="com.ppvir.aop"/>
<!--aop的自動代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
6)斩狱、測試代碼
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
}
}
3.2耳高、注解配置AOP詳解
1)、注解通知的類型
通知的配置語法:@通知注解(“切點(diǎn)表達(dá)式”)
名稱 | 注解 | 說明 |
---|---|---|
前置通知 | @Before |
用于配置前置通知所踊,指定增強(qiáng)的方法在切點(diǎn)方法之前執(zhí)行 |
后置通知 | @AfterReturning |
用于配置后置通知泌枪,指定增強(qiáng)的方法在切點(diǎn)方法之后執(zhí)行 |
環(huán)繞通知 | @Around |
用于配置環(huán)繞通知,指定增強(qiáng)的方法在切點(diǎn)方法之前和之后都執(zhí)行 |
異常拋出通知 | @AfterThrowing |
用于配置異常拋出通知秕岛,指定增強(qiáng)的方法在出現(xiàn)異常時(shí)執(zhí)行 |
最終通知 | @After |
用于配置最終通知碌燕,無論增強(qiáng)方式執(zhí)行是否有異常都會執(zhí)行 |
- 切點(diǎn)表達(dá)式的抽取
同 xml配置aop 一樣,我們可以將切點(diǎn)表達(dá)式抽取瓣蛀。抽取方式是在切面內(nèi)定義方法陆蟆,在該方法上使用@Pointcut注解定義切點(diǎn)表達(dá)式,然后在在增強(qiáng)注解中進(jìn)行引用惋增。
具體如下:
@@Component("myAspect")
@Aspect
public class MyAspect {
@Before("MyAspect.myPoint()")
public void before(){
System.out.println("前置代碼增強(qiáng).....");
}
@Pointcut("execution(* com.ppvir.aop.*.*(..))")
public void myPoint(){}
}
3.3叠殷、小結(jié)知識要點(diǎn)
- 注解aop開發(fā)步驟;
1.使用@Aspect標(biāo)注切面類
2.使用@通知注解標(biāo)注通知方法
3.在配置文件中配置aop自動代理<aop:aspectj-autoproxy/>
- 通知注解類型
詳3.2表格