1 基本概念
什麼是AOP?
- 面向切面程式設計,利用AOP可以對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高開發的效率。
- 不通過修改源代碼的方式,在主幹功能中添加新功能。
- 登入例子。
基本術語
- 連接配接點:在被增強類中,那些方法可以被增強,就稱為連接配接點。
- 切入點:實際增強的方法,稱為切入點。
- 通知(增強):實際增強的邏輯部分稱為通知。通知有五種類型:前置通知、後置通知、環繞通知、異常通知、最終通知。
- 前置通知:增強方法之前執行的通知。
- 後置通知:增強方法之後執行的通知。
- 環繞通知:增強方法之前和之後執行的通知。
- 異常通知:增強方法出現異常執行的通知。
- 最終通知:類似
中的try-catch-finally
,無論是否出異常,最終都會執行的通知。finally
- 切面:是一個動作,把通知應用到切入點的過程。
2 底層原理
AOP底層使用動态代理方式增強源代碼。
有接口情況,使用JDK動态代理。建立接口實作類的代理對象。
沒有接口情況,使用CGLIB動态代理。建立目前子類的代理對象。
3 JDK動态代理
類。
Proxy
包中,用于建立代理對象。
java.lang
loader
:目前類加載器。
interfaces
:增強類所在的接口,可以寫多個接口。
是個接口,傳入實作對象,其中寫增強方法。
h:InvocationHandler
代碼實作
(1) 建立接口
public interface UserDao {
int add(int a, int b);
String update(String id);
}
(2) 建立接口實作類
public class UserDaoImpl implements UserDao {
public int add(int a, int b) {
return a + b;
}
public String update(String id) {
return id;
}
}
(3) 使用
Proxy
類建立接口代理對象
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
// 三個參數:第一個目前類的加載器,第二個被增強類實作的接口,InvocationHandler的實作類對象(用于增強方法)
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
int result = dao.add(1,2);
System.out.println(result);
}
static class UserDaoProxy implements InvocationHandler{
// 把被增強對象傳遞過來,即UserDaoImpl
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
// 增強的邏輯
// 三個參數:第一個表示被增強類,第二個目前增強的方法,第三個被增強方法的參數
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 可以在方法執行之前,通過method判斷是add還是update方法,進而對不同的方法進行不同的增強操作。
// 方法之前
System.out.println("方法之前執行" + method.getName() + Arrays.toString(args));
// 執行被增強方法,兩個參數,所屬對象,方法所需的參數。res是原方法的傳回值。
Object res = method.invoke(obj,args);
// 方法之後
System.out.println("方法之後執行" + obj);
return res;
}
}
}
(4) 輸出結果。add執行了這句需要加載
UserDaoImpl
的add方法中,否則不會輸出。
4 Spring中的AOP操作
4.1 準備工作
Spring架構一般基于 AspectJ
實作AOP操作。
AspectJ
不是Spring的組成部分,獨立AOP架構,一般把
AspectJ
和
Spring
架構一起使用,進行AOP操作。
基于 AspectJ
實作AOP操作
(1) 基于xml配置檔案實作
(2) 基于注解方式實作(較常使用)
在項目中引入AOP相關依賴
<dependencies>
<!-- Beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<!-- Core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<!-- Context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<!-- aop相關 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.cglib</groupId>
<artifactId>com.springsource.net.sf.cglib</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aopalliance/com.springsource.org.aopalliance -->
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/com.springsource.org.aspectj.weaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.weaver</artifactId>
</dependency>
<!-- 日志相關 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
</dependencies>
切入點表達式:知道對哪個類中的哪個方法進行增強。
文法結構:
excution([權限修飾符(可以寫*代表所有權限)][傳回類型(可省略)][類全路徑][方法名稱][參數清單(可用..代表兩個參數)])
增強
com.wit.dao.BookDao
中的
add
方法:
excution(* com.wit.dao.BookDao.add(..))
增強
com.wit.dao.BookDao
中的所有方法:
excution(* com.wit.dao.BookDao.*(..))
4.2 基于注解方式實作
(1) 建立類,在類裡面定義方法
public class User {
public int add(int a,int b){
System.out.println("add....");
}
}
(2) 建立增強類(編寫增強邏輯),在增強類裡面,建立方法,讓不同方法代表不同的通知類型。
// 增強類
public class UserProxy {
// 前置通知
public void before(){
System.out.println("Before...");
return a + b;
}
}
(3) 配置通知
在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: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.xsd">
<context:component-scan base-package="com.wit.aop"/>
</beans>
使用IOC建立和
User
UserProxy
在增強類上面添加注解 @Aspect
在spring配合檔案中開啟生成代理對象
<!-- 開啟aspectj生成代理對象 -->
<aop:aspectj-autoproxy/>
(4) 配置不同類型的通知
在增強類裡面,作為通知方法上面添加通知類型注解,使用切入點表達式配置。
// 增強類
@Component
@Aspect
public class UserProxy {
// 前置通知
@Before(value = "execution(* com.wit.aop.User.add(..))")
public void before(){
System.out.println("Before...");
}
}
(5) 測試
@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user", User.class);
user.add(1,2);
}
不同通知類型測試:
// 增強類
@Component
@Aspect
public class UserProxy {
// 前置通知
@Before(value = "execution(* com.wit.aop.User.add(..))")
public void before(){
System.out.println("before...");
}
// 後置通知
// 有異常則不會通知
@AfterReturning(value = "execution(* com.wit.aop.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
// 最終通知
// 任何情況下都會執行
@After(value = "execution(* com.wit.aop.User.add(..))")
public void after(){
System.out.println("after...");
}
// 異常通知
// 有異常才會通知
@AfterThrowing(value = "execution(* com.wit.aop.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
// 環繞通知
@Around(value = "execution(* com.wit.aop.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("環繞之前...");
// 被增強的方法
proceedingJoinPoint.proceed();
System.out.println("環繞之後...");
}
}
測試結果與spring版本有。5.2.8是分界線,5.2.8之前
5.2.8及5.2.8之後。相比之下5.2.8之後更加符合認知。有兩點:第一,環繞通知包含其他通知,更加符合環繞的概念。第二,
after
在
afterReturning
之後輸出,
after
是最終通知,
afterReturning
是後置通知,最終通知更想finally塊,異常發生之後仍然執行。
公共切入點提取
上面測試中,多個測試方法切入表達式是相同的,可以對切入點表達式進行提取。
// 切入點提取
@Pointcut(value = "execution(* com.wit.aop.User.add(..))")
public void pointDemo(){
}
// 如何調用
// 前置通知
@Before(value = "pointDemo()")
public void before(){
System.out.println("before...");
}
增強類優先級
一個類有多個增強類,可以對增強類的優先級進行設定。在增強類上添加 @Order
,數字類型越小優先級越高。
建立
USerProxy2
,設定
@Order(1)
,設定
UserProxy
中為
@Order(3)
。
@Component
@Aspect
@Order(1)
public class UserProxy2 {
@Before(value = "execution(* com.wit.aop.User.add(..))")
public void before(){
System.out.println("Order 1 before...");
}
}
測試結果
全注解開發
把配置在xml檔案中的
<aop:aspectj-autoproxy/>
配置在配置類上,加注解
@EnableAspectJAutoProxy(proxyTargetClass = true)
4.3 基于xml方式實作
使用較少,一般使用注解方式。xml方法不需要配置 <aop:aspectj-autoproxy/>
(1) 建立兩個類,增強類和被增強類,建立方法
public class Student {
public void work(){
System.out.println("work.......");
}
}
public class StudentProxy {
public void before(){
System.out.println("before......");
}
}
(2) 在spring配置檔案中建立兩個類對象
<bean id="student" class="com.wit.aop.Student"/>
<bean id="studentProxy" class="com.wit.aop.StudentProxy"/>
(3) 在spring配置檔案中配置切入點
<!-- 配置aop增強 -->
<aop:config>
<!-- 切入點 -->
<aop:pointcut id="p" expression="execution(* com.wit.aop.Student.work(..))"/>
<!-- 配置切面,應用到方法的過程 -->
<aop:aspect ref="studentProxy">
<!-- 增強作用在具體的方法上 -->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
(4)測試