天天看點

第三章 AOP

1 基本概念

什麼是AOP?
  1. 面向切面程式設計,利用AOP可以對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高開發的效率。
  2. 不通過修改源代碼的方式,在主幹功能中添加新功能。
  3. 登入例子。
    第三章 AOP

基本術語

  • 連接配接點:在被增強類中,那些方法可以被增強,就稱為連接配接點。
  • 切入點:實際增強的方法,稱為切入點。
  • 通知(增強):實際增強的邏輯部分稱為通知。通知有五種類型:前置通知、後置通知、環繞通知、異常通知、最終通知。
    • 前置通知:增強方法之前執行的通知。
    • 後置通知:增強方法之後執行的通知。
    • 環繞通知:增強方法之前和之後執行的通知。
    • 異常通知:增強方法出現異常執行的通知。
    • 最終通知:類似

      try-catch-finally

      中的

      finally

      ,無論是否出異常,最終都會執行的通知。
  • 切面:是一個動作,把通知應用到切入點的過程。

2 底層原理

AOP底層使用動态代理方式增強源代碼。

有接口情況,使用JDK動态代理。建立接口實作類的代理對象。

第三章 AOP

沒有接口情況,使用CGLIB動态代理。建立目前子類的代理對象。

第三章 AOP

3 JDK動态代理

Proxy

類。

java.lang

包中,用于建立代理對象。
第三章 AOP

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方法中,否則不會輸出。

第三章 AOP

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

第三章 AOP
第三章 AOP
在增強類上面添加注解

@Aspect

第三章 AOP
在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);
}
           
第三章 AOP

不同通知類型測試:

// 增強類
@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之前

第三章 AOP
第三章 AOP

5.2.8及5.2.8之後。相比之下5.2.8之後更加符合認知。有兩點:第一,環繞通知包含其他通知,更加符合環繞的概念。第二,

after

afterReturning

之後輸出,

after

是最終通知,

afterReturning

是後置通知,最終通知更想finally塊,異常發生之後仍然執行。

第三章 AOP
第三章 AOP

公共切入點提取

上面測試中,多個測試方法切入表達式是相同的,可以對切入點表達式進行提取。
// 切入點提取
@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...");
    }
}
           

測試結果

第三章 AOP

全注解開發

  把配置在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)測試

第三章 AOP