介紹
概念
面向切面編程AOP與面向?qū)ο缶幊蘋OP有所不同,AOP不是對OOP的替換,而是對OOP的一種補充,AOP增強了OOP。
假設(shè)我們有幾個業(yè)務(wù)代碼,都調(diào)用了某個方法,按照OOP的思想,我們就會將此方法封裝在一個類中,之后通過對象.方法名調(diào)用
我們可以看作我們的業(yè)務(wù)代碼被其他代碼入侵或者是業(yè)務(wù)代碼被其他與業(yè)務(wù)不相關(guān)的代碼入侵了
這個時候,如果我們使用AOP進(jìn)行編寫代碼,我們的業(yè)務(wù)代碼就可以不需要寫其他與業(yè)務(wù)相關(guān)的代碼,這樣就可以保證業(yè)務(wù)代碼的純潔性
AOP運行流程
通過配置文件,給各個業(yè)務(wù)方法標(biāo)識切入點(PointCut),即切入點方法。
之后當(dāng)程序運行到切入點方法的時候,就會發(fā)出一個通知(Advice),去通知執(zhí)行某個切面方法(Aspect)
專業(yè)術(shù)語
| 項 | 描述 |
|---|---|
| Aspect | 一個模塊具有一組提供橫切需求的 APIs。例如,一個日志模塊為了記錄日志將被 AOP 方面調(diào)用。應(yīng)用程序可以擁有任意數(shù)量的方面,這取決于需求。 |
| Join point | 在你的應(yīng)用程序中它代表一個點,你可以在插件 AOP 方面。你也能說,它是在實際的應(yīng)用程序中,其中一個操作將使用 Spring AOP 框架。 |
| Advice | 這是實際行動之前或之后執(zhí)行的方法。這是在程序執(zhí)行期間通過 Spring AOP 框架實際被調(diào)用的代碼。 |
| Pointcut | 這是一組一個或多個連接點,通知應(yīng)該被執(zhí)行。你可以使用表達(dá)式或模式指定切入點正如我們將在 AOP 的例子中看到的。 |
| Introduction | 引用允許你添加新方法或?qū)傩缘浆F(xiàn)有的類中。 |
| Target object | 被一個或者多個方面所通知的對象,這個對象永遠(yuǎn)是一個被代理對象。也稱為被通知對象。 |
| Weaving | Weaving 把方面連接到其它的應(yīng)用程序類型或者對象上,并創(chuàng)建一個被通知的對象。這些可以在編譯時,類加載時和運行時完成。 |
Advice通知
| 通知 | 類型 |
|---|---|
| 前置通知(Before Advice) | 在切入點方法執(zhí)行之前,執(zhí)行通知 |
| 環(huán)繞通知(Around Advice) | 在切入點方法執(zhí)行的整個過程都可以執(zhí)行通知 |
| 后置通知(After Returning Advice) | 在切入點方法執(zhí)行之后,只有在方法成功執(zhí)行時,才能執(zhí)行通知。 |
| 最終通知(After Finally Advices) | 在一個方法執(zhí)行之后,不管是方法是否成功執(zhí)行 ,執(zhí)行通知 |
| 異常通知(After Throwing Advice) | 在一個方法執(zhí)行之后,只有在方法退出拋出異常時,才能執(zhí)行通知。 |
PS:其實,這些通知就是相當(dāng)于你可以在業(yè)務(wù)方法的執(zhí)行前(前置通知)、執(zhí)行中(環(huán)繞通知)、執(zhí)行成功之后(后置通知)、發(fā)生異常(異常通知)、不管方法是發(fā)生異常還是執(zhí)行成功(最終通知),執(zhí)行某些與業(yè)務(wù)功能無關(guān)的功能代碼。
這樣就可以降低業(yè)務(wù)功能代碼的入侵和污染
下面使用兩種不同的方式來實現(xiàn)一個方法日志打印的簡單例子
后置通知例子
下面的通知是基于xml配置的
1.添加依賴
除了之前的spring的jar包,還需要兩個jar包,aopalliance.jar和aspectjweaver.jar
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>RELEASE</version>
</dependency>
<!-- aop需要的jar -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>RELEASE</version>
</dependency>
2.業(yè)務(wù)代碼
我編寫了一個TeacherDao類,里面只有add和delete方法
package com.wan;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:34
* @description
*/
public class TeacherDao {
public void add(Teacher teacher) {
System.out.println("往數(shù)據(jù)庫中插入一條數(shù)據(jù)");
}
public void delete(Teacher teacher) {
System.out.println("從數(shù)據(jù)庫中刪除一條數(shù)據(jù)");
}
}
3.編寫MyLogging.java
前面說過了通知具有五種類型,我們根據(jù)需要,選擇合適的通知類型,讓某個類實現(xiàn)通知對應(yīng)的接口,這里其實就是相當(dāng)于編寫切面方法
| 通知類型 | 接口 | 接口方法 | 接口方法參數(shù)說明 |
|---|---|---|---|
| 前置通知 | org.springframework.aop.MethodBeforeAdvice | before(Method method, Object[] args, Object target) | method是方法,args是方法的參數(shù),target是目標(biāo)對象 |
| 環(huán)繞通知 | org.aopalliance.intercept.MethodInterceptor | invoke(MethodInvocation invocation) | invocation對象中包含有method,方法參數(shù)和目標(biāo)對象 |
| 后置通知 | org.springframework.aop.AfterReturningAdvice | afterReturning(Object returnValue, Method method, Object[] args, Object target) | returnValue是方法的返回值,其他的參數(shù)和前置通知一樣 |
| 最終通知 | org.springframework.aop.AfterAdvice | 無 | 無 |
| 異常通知 | org.springframework.aop.ThrowsAdvice | 無 | 無 |
我們?nèi)罩据敵?,選擇后置通知,也就是方法執(zhí)行完成之后調(diào)用
MyLogging.java
package com.wan;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
public class MyLogging implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
String methodName = method.getName();//方法名
int size = args.length;//參數(shù)個數(shù)
System.out.println("調(diào)用了"+target+"的"+methodName+"方法,該方法的參數(shù)個數(shù)有"+size+"個");
}
}
4.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="mylog" class="com.wan.MyLogging"/>
<bean id="teacherdao" class="com.wan.TeacherDao"/>
<aop:config>
<aop:pointcut id="mypointcut" expression="execution(public void add(com.wan.Teacher))"/>
<aop:advisor advice-ref="mylog" pointcut-ref="mypointcut"/>
</aop:config>
</beans>
這里和之前一樣,也需要引用aop命名空間,IDEA可以智能幫我們導(dǎo)入,輸入<aop:,之后就會彈出提示
各元素和屬性說明:
| 子元素/屬性 | 含義 |
|---|---|
| aop:pointcut | 切入點,當(dāng)執(zhí)行當(dāng)切入點方法的時候,就會根據(jù)通知(Advice)的類型,從而執(zhí)行非業(yè)務(wù)功能的代碼 |
| id | 切入點的唯一表示,下面pointcut-ref屬性需要引用此id |
| expression | 表達(dá)式,只要是符合此表達(dá)式的方法,都會被當(dāng)作切入點 |
| aop:advisor | 通知 |
| pointcut-ref | 引用切入點的id |
| advice-ref | 引用切入點接口類的bean的id |
補充,關(guān)于expression的例子:
| 例子 | 說明 |
|---|---|
| public boolean addTeacher(com.wan.Teacher) | 所有返回類型為boolean,參數(shù)類型為com.wan.Teacher,方法名為addTeacher的方法 |
| public void com.wan.TeacherDao.add(com.wan.Teacher) | 方法存在TeacherDao類中,返回類型為空,參數(shù)類型為Teacher,方法名為add的方法 |
| public * addTeacher(com.wan.Teacher) | 所有返回類型為任意類型,參數(shù)類型為com.wan.Teacher,方法名為addTeacher的方法 |
| public boolean *(com.wan.Teacher) | 所有返回類型為任意類型,參數(shù)類型為Teacher,方法名任意的方法 |
| public boolean addTeacher(..) | 所有返回類型為任意類型,參數(shù)類型和個數(shù)不限,方法名為addTeacher的方法 |
| * com.wan.*.*(..) | 在com.wan包下面的所有方法(不包括子包) |
| * com.wan..*.*(..) | 在com.wan包下面的所有方法(包括子包) |
表達(dá)式要寫在execution()的括號里面,多個條件可以使用or連接
5.測試
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
TeacherDao teacherdao = (TeacherDao) context.getBean("teacherdao");
teacherdao.add(new Teacher());
其他類型通知Advice使用
前置通知
前置通知和后置通知一樣,也是實現(xiàn)對應(yīng)的接口,然后重寫before方法,這里就不過多說明了
異常通知
異常通知有點特殊,因為此接口是不需要重寫方法的,但是,我們想要實現(xiàn)異常通知,得按照它定義的規(guī)則來
afterThrowing([Method method,Object[] args,Object target],Throwable ex)
- 方法名必須是afterThrowing
- 參數(shù)列表中的最后一個參數(shù)必須存在,可以是Throwable或者Throwable的子類
- 方法列表的前三個參數(shù)要么都存在,要么一個都不存在
環(huán)繞通知
此通知是spring的最強擴(kuò)展,因為環(huán)繞通知可以攔截方法,對方法的傳入?yún)?shù)的數(shù)值、返回值進(jìn)行更改,或者是決定方法是否執(zhí)行,也可以對目標(biāo)進(jìn)行異常處理。
如果對破解有所了解的話,環(huán)繞通知還可以被稱為hook,像Android的Xposed框架就是通過hook原理,來達(dá)到自由更改系統(tǒng)目的。
實現(xiàn)MethodInterceptor接口,重寫其的invoke方法
invoke方法可以獲得像之前的前置通知的三個參數(shù),method,target,args,也可以獲得返回值returnValue
package com.wan;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
public class MyLogging implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object target = invoke.getThis();
Method method = invoke.getMethod();
Object[] args = invoke.getArguments();
//執(zhí)行方法,獲得返回值
Object returnValue = invoke.proceed();
}
}
基于注解配置使用
上面說的幾個例子都是基于xml配置文件,我們可以使用注解,從而達(dá)到簡化的目的
| 注解 | 說明 |
|---|---|
| @Aspect | 標(biāo)注切入點 |
| @Before | 標(biāo)注前置通知 |
| @Around | 標(biāo)注環(huán)繞通知 |
| @AfterReturning | 標(biāo)注后置通知 |
| @After | 標(biāo)注最終通知 |
| @AfterThrowing | 標(biāo)注異常通知 |
步驟
1. 導(dǎo)入相關(guān)jar(之前導(dǎo)入的那兩個jar包)
2. 使用注解,標(biāo)注類和方法
3. 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="teacherdao" class="com.wan.TeacherDao"/>
<aop:aspectj-autoproxy/>
<bean class="com.wan.MyLogging"/>
</beans>
之后的測試代碼和之前的一樣
前置通知
package com.wan;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
@Aspect
public class MyLogging {
@Before("execution(public void add(com.wan.Teacher))")
public void sayHello() {
System.out.println("這是前置通知");
}
}
注解使用挺簡單的,大概看一下示例代碼就能知道怎么使用了
獲得三個參數(shù)target、args、method
AOP中有個JoinPoint的接口,此接口可以獲得target、args、method這三個參數(shù)
| 方法名 | 說明 |
|---|---|
| getTarget() | 獲得目標(biāo)對象 |
| getSignature() | 獲得目標(biāo)方法的Signature對象,由此對象的getName可以獲得方法名 |
| getArgs() | 獲得參數(shù)列表 |
package com.wan;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
@Aspect
public class MyLogging {
@AfterReturning(pointcut="execution(public boolean add(com.wan.Teacher))",returning="returnValue")
public void test(JoinPoint jp,Object returnValue) {
//上面的注解的returning屬性把方法的返回值賦值給了參數(shù)returnValue
}
}
環(huán)繞通知
環(huán)繞通知有個特殊的接口ProceedingJoinPoint,此接口是JoinPoint的子接口,比JoinPoint接口多了一個proceed方法,用于執(zhí)行目的對象的方法獲得返回值
package com.wan;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
@Aspect
public class MyLogging {
@Around("execution(public boolean add(com.wan.Teacher))")
public void test(ProceedingJoinPoint jp) {
Object returnValue = jp.proceed();
}
}
異常通知
package com.wan;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
@Aspect
public class MyLogging {
@AfterThorwing(pointcut="execution(public boolean add(com.wan.Teacher))",throwing="e")
public void test(JoinPoint jp,NullPointException e) {
//上面的注解的throwing屬性把異常賦值給了參數(shù)e
//參數(shù)中指定了異常為空指針異常,所有,發(fā)生異常為空指針異常時候,異常通知才會調(diào)用此方法
}
}
PS:除以上兩種方式可以實現(xiàn)AOP,還有一種使用Schema進(jìn)行配置,我看了一下步驟,覺得比上面兩種還要繁瑣,在這里就補充了
|轉(zhuǎn)載請注明來源地址:蜘蛛池出租 http://www.wholesalehouseflipping.com/專注于SEO培訓(xùn),快速排名黑帽SEO https://www.heimao.wiki
