1. 程式人生 > 實用技巧 >【Spring】面向切面程式設計AOP

【Spring】面向切面程式設計AOP

AOP(概念)

  1. 什麼是 AOP
    1. 面向切面程式設計(方面),利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
    2. 通俗描述:不通過修改原始碼方式,在主幹功能裡面新增新功能
    3. 使用登入例子說明 AOP

AOP(底層原理)

  1. AOP 底層使用動態代理
    有兩種情況動態代理
    1. 第一種 有介面情況,使用 JDK 動態代理

      建立介面實現類代理物件,增強類的方法

    2. 第二種 沒有介面情況,使用 CGLIB 動態代理

      建立子類的代理物件,增強類的方法

AOP(JDK 動態代理)

  1. 使用 JDK 動態代理,使用 Proxy 類裡面的方法建立代理物件
    1. 呼叫 newProxyInstance 方法
      方法有三個引數:
      1. 第一引數,類載入器
      2. 第二引數,增強方法所在的類,這個類實現的介面,支援多個介面
      3. 第三引數,實現這個介面 InvocationHandler,建立代理物件,寫增強的部分
  2. 編寫 JDK 動態代理程式碼
    1. 建立介面,定義方法

      public interface UserDao {
          public int add(int a,int b);
          public String update(String id);
      }
    2. 建立介面實現類,實現方法

      public class UserDaoImpl implements UserDao {
          @Override
          public int add(int a, int b) {
              return a+b;
          }
          @Override
          public String update(String id) {
              return id;
          } 
      }
    3. 使用 Proxy 類建立介面代理物件

      public class JDKProxy {
          public static void main(String[] args) {
              //建立介面實現類代理物件
              Class[] interfaces = {UserDao.class};
          // Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
          //     @Override
          //     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          //          return null;
          //     }
          // });
              UserDaoImpl userDao = new UserDaoImpl();
              UserDao dao = 
                  (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
              int result = dao.add(1, 2);
               System.out.println("result:"+result);
          } 
      }
      //建立代理物件程式碼
      class UserDaoProxy implements InvocationHandler {
          //1 把建立的是誰的代理物件,把誰傳遞過來
          //有引數構造傳遞
          private Object obj;
          public UserDaoProxy(Object obj) {
              this.obj = obj;
          }
          //增強的邏輯
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              //方法之前
              System.out.println("方法之前執行...."+method.getName()+" :傳遞的引數..."+ Arrays.toString(args));
              //被增強的方法執行
              Object res = method.invoke(obj, args);
              //方法之後
              System.out.println("方法之後執行...."+obj);
              return res;
          } 
      }

AOP(術語)

  1. 連線點:類裡面哪些方法可以被增強,這些方法稱為連線點
  2. 切入點:實際被真正增強的方法,稱為切入點
  3. 通知(增強):
    1. 實際增強的邏輯部分稱為通知(增強)
    2. 通知有多種型別
      • 前置通知
      • 後置通知
      • 環繞通知
      • 異常通知
      • 最終通知
  4. 切面:是動作,把通知應用到切入點的過程

AOP 操作(準備工作)

  1. Spring 框架一般都是基於 AspectJ 實現 AOP 操作
    1. AspectJ 不是 Spring 組成部分,獨立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,進行 AOP 操作
  2. 基於 AspectJ 實現 AOP 操作
    1. 基於 xml 配置檔案實現
    2. 基於註解方式實現(使用)
  3. 在專案工程裡面引入 AOP 相關依賴
  4. 切入點表示式
    1. 切入點表示式作用:知道對哪個類裡面的哪個方法進行增強
    2. 語法結構: execution([許可權修飾符] [返回型別] [類全路徑] 方法名稱)

      舉例 1:對 com.atguigu.dao.BookDao 類裡面的 add 進行增強
      execution(* com.atguigu.dao.BookDao.add(..))
      舉例 2:對 com.atguigu.dao.BookDao 類裡面的所有的方法進行增強
      execution(* com.atguigu.dao.BookDao.* (..))
      舉例 3:對 com.atguigu.dao 包裡面所有類,類裡面所有方法進行增強
      execution(* com.atguigu.dao.. (..))

AOP 操作(AspectJ 註解)

  1. 建立類,在類裡面定義方法

    public class User {
        public void add() {
            System.out.println("add.......");
        } 
    }
  2. 建立增強類(編寫增強邏輯)
    1. 在增強類裡面,建立方法,讓不同方法代表不同通知型別

      //增強的類
      public class UserProxy {
          public void before() {//前置通知
              System.out.println("before......");
          } 
      }
  3. 進行通知的配置
    1. 在 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.atguigu.spring5.aopanno"></context:component-scan>
    2. 使用註解建立 User 和 UserProxy 物件

    3. 在增強類上面添加註解 @Aspect

      //增強的類
      @Component
      @Aspect //生成代理物件
      public class UserProxy {
    4. 在 spring 配置檔案中開啟生成代理物件

      <!-- 開啟 Aspect 生成代理物件-->
      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  4. 配置不同型別的通知
    1. 在增強類的裡面,在作為通知方法上面新增通知型別註解,使用切入點表示式配置

      //增強的類
      @Component
      @Aspect //生成代理物件
      public class UserProxy {
          //前置通知
          //@Before 註解表示作為前置通知
          @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
          public void before() {
              System.out.println("before.........");
          }
          //後置通知(返回通知)
          @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
          public void afterReturning() {
              System.out.println("afterReturning.........");
          }
          //最終通知
          @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
          public void after() {
              System.out.println("after.........");
          }
          //異常通知
          @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
          public void afterThrowing() {
              System.out.println("afterThrowing.........");
          }
          //環繞通知
          @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
          public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
              System.out.println("環繞之前.........");
              //被增強的方法執行
              proceedingJoinPoint.proceed();
              System.out.println("環繞之後.........");
          }
      }
  5. 相同的切入點抽取

    //相同切入點抽取
    @Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void pointdemo() {
    }
    //前置通知
    //@Before 註解表示作為前置通知
    @Before(value = "pointdemo()")
    public void before() {
        System.out.println("before.........");
    }
  6. 有多個增強類多同一個方法進行增強,設定增強類優先順序
    1. 在增強類上面添加註解 @Order(數字型別值),數字型別值越小優先順序越高

      @Component
      @Aspect
      @Order(1)
      public class PersonProxy
  7. 完全使用註解開發
    1. 建立配置類,不需要建立 xml 配置檔案

      @Configuration
      @ComponentScan(basePackages = {"com.atguigu"})
      @EnableAspectJAutoProxy(proxyTargetClass = true)
      public class ConfigAop {
      }

AOP 操作(AspectJ 配置檔案)

  1. 建立兩個類,增強類和被增強類,建立方法
  2. 在 spring 配置檔案中建立兩個類物件

    <!--建立物件-->
    <bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
    <bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>

    3、在 spring 配置檔案中配置切入點

    <!--配置 aop 增強--> <aop:config>
        <!--切入點-->
        <aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/>
        <!--配置切面-->
        <aop:aspect ref="bookProxy">
            <!--增強作用在具體的方法上-->
            <aop:before method="before" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>