【Spring】面向切面編程AOP


AOP

概念

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

底層原理

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

      創建接口實現類代理對象,增強類的方法

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

      創建子類的代理對象,增強類的方法

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;
          } 
      }

術語

  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.nemo.dao.BookDao 類里面的 add 進行增強
      execution(* com.nemo.dao.BookDao.add(..))
      舉例 2:對 com.nemo.dao.BookDao 類里面的所有的方法進行增強
      execution(* com.nemo.dao.BookDao.* (..))
      舉例 3:對 com.nemo.dao 包里面所有類,類里面所有方法進行增強
      execution(* com.nemo.dao.. (..))

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.nemo.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.nemo.spring5.aopanno.User.add(..))")
          public void before() {
              System.out.println("before.........");
          }
          //后置通知(返回通知)
          @AfterReturning(value = "execution(* com.nemo.spring5.aopanno.User.add(..))")
          public void afterReturning() {
              System.out.println("afterReturning.........");
          }
          //最終通知
          @After(value = "execution(* com.nemo.spring5.aopanno.User.add(..))")
          public void after() {
              System.out.println("after.........");
          }
          //異常通知
          @AfterThrowing(value = "execution(* com.nemo.spring5.aopanno.User.add(..))")
          public void afterThrowing() {
              System.out.println("afterThrowing.........");
          }
          //環繞通知
          @Around(value = "execution(* com.nemo.spring5.aopanno.User.add(..))")
          public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
              System.out.println("環繞之前.........");
              //被增強的方法執行
              proceedingJoinPoint.proceed();
              System.out.println("環繞之后.........");
          }
      }
  5. 相同的切入點抽取

    //相同切入點抽取
    @Pointcut(value = "execution(* com.nemo.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.nemo"})
      @EnableAspectJAutoProxy(proxyTargetClass = true)
      public class ConfigAop {
      }

AspectJ 配置文件

  1. 創建兩個類,增強類和被增強類,創建方法
  2. 在 spring 配置文件中創建兩個類對象

    <!--創建對象-->
    <bean id="book" class="com.nemo.spring5.aopxml.Book"></bean>
    <bean id="bookProxy" class="com.nemo.spring5.aopxml.BookProxy"></bean>

    3、在 spring 配置文件中配置切入點

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM