Spring筆記03_AOP

1. AOP

1.1 AOP介紹

1.1.1 什麼是AOP

  • 在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,經過預編譯方式運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP(面向對象編程)的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。
  • AOP採起橫向抽取機制,取代了傳統縱向繼承體系重複性代碼。以下圖所示:
  • 經典應用:事務管理、性能監視、安全檢查、緩存 、日誌等。
  • Spring AOP使用純Java實現,不須要專門的編譯過程和類加載器,在運行期經過代理方式向目標類織入加強代碼。
  • AspectJ是一個基於Java語言的AOP框架,從Spring2.0開始,Spring AOP引入對Aspect的支持,AspectJ擴展了Java語言,提供了一個專門的編譯器,在編譯時提供橫向代碼的織入。

1.1.2 AOP實現原理

  • aop底層將採用代理機制進行實現
  • 接口+實現類時:Spring採用JDK的動態代理Proxy
  • 只有實現類時:Spring採用cglib字節碼加強。這種底層屬於繼承加強。

1.1.3 AOP術語【掌握】

  1. Target :目標類,須要被代理的類。本例中如:UserDao
  2. Joinpoint(鏈接點) :所謂鏈接點是指那些可能被攔截到的點。在spring中,這些點指的是方法,由於spring只支持方法類型的鏈接點。本例中如:UserDao的全部的方法
  3. PointCut 切入點 :所謂切入點是指咱們要對哪些Joinpoint進行攔截,即已經被加強的鏈接點。例如:save()
  4. Advice :通知/加強,加強的代碼。例如:checkPri()
    所謂通知是指攔截到Joinpoint以後所要作的事情就是通知,通知分爲前置通知、後置通知、異常通知、最終通知、環繞通知(即切面要完成的功能)。
  5. Weaving(織入) :是指把通知/加強advice應用到目標對象target來建立新的代理對象proxy的過程。
    spring採用動態代理織入,而AspectJ採用編譯期織入和類裝在期織入。
  6. Proxy :代理類,一個類被AOP織入加強後,就產生一個結果代理類。
  7. Aspect(切面) : 是切入點Pointcut和通知Advice(引介)的結合。
  8. Introduction(引介) :引介是一種特殊的通知,在不修改類代碼的前提下,Introduction 能夠在運行期爲類動態地添加一些方法或Field。

AOP相關術語

  • 小結:

    一個線是一個特殊的面。
    一個切入點和一個通知,組成成一個特殊的面。
    詳解如圖01:html

    詳解如圖02:java

1.2 AOP的底層實現(瞭解)

  • 動態代理web

    • JDK動態代理:只能對實現了接口的類產生代理
    • Cglib動態代理(第三方代理技術):對沒有實現接口的類產生代理對象,生成子類對象。
    • 代理知識點參考:https://www.cnblogs.com/itzho...

1.2.1 JDK動態代理

  • 準備工做:新建web項目,不須要導包
  • 代理對象UserDaospring

    package com.itzhouq.spring.demo1;
    
    public interface UserDao {
        public void save();
        public void update();
        public void find();
        public void delete();
    }
  • 實現類express

    package com.itzhouq.spring.demo1;
    
    public class UserDaoImpl implements UserDao {
    
        @Override
        public void save() {
            System.out.println("保存用戶");
        }
    
        @Override
        public void update() {
            System.out.println("更新用戶");
        }
    
        @Override
        public void find() {
            System.out.println("查找用戶");
        }
    
        @Override
        public void delete() {
            System.out.println("刪除用戶");
        }
    
    }
  • 使用JDK產生UserDao的代理類
package com.itzhouq.spring.demo1;
  
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.Method;
  import java.lang.reflect.Proxy;
  
  /*
   * 使用JDK動態代理對UserDao產生代理
   */
  public class JDKProxy implements InvocationHandler {
      // 將被加強的對象傳遞到代理總
      private UserDao userDao;
      public JDKProxy(UserDao userDao) {
          this.userDao = userDao;
      }
      /*
       * 產生UserDao代理的方法
       */
      public UserDao createProxy() {
          UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(
                  userDao.getClass().getClassLoader(),
                  userDao.getClass().getInterfaces(),
                  this);
          return userDaoProxy;
      }
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          // 判斷方法名是否是save
          if("save".equals(method.getName())) {
              // 加強
              System.out.println("權限校驗==============");
              return method.invoke(userDao, args);
              
          }
          return method.invoke(userDao, args);
      }
  }
  • 測試類編程

    package com.itzhouq.spring.demo1;
    
    import org.junit.Test;
    
    public class SpringDemo1 {
        
        @Test
        //JDK動態代理
        public void test1() {
            UserDao userDao = new UserDaoImpl();
            // 建立代理
            UserDao proxy = new JDKProxy(userDao).createProxy();
            proxy.save();
            proxy.find();
            proxy.delete();
            proxy.update();
            //權限校驗==============
            //保存用戶
            //查找用戶
            //刪除用戶
            //更新用戶
        }
    }

1.2.2 Cglib動態代理

  • Cglib是第三方開源代碼生成類庫,動態添加類的屬性和方法
  • cglib的運行原理:在運行時建立目標類的子類從而對目標類進行加強。
  • 由於Spring核心包中包含了cglib的包,因此引入Spring的4+2必備包就能夠使用Cglib了

  • 目標類:沒有實現接口緩存

    package com.itzhouq.spring.demo2;
    
    public class CustomerDao {
        public void save() {
            System.out.println("保存客戶");
        }
        public void update() {
            System.out.println("更新客戶");
        }
        public void find() {
            System.out.println("查找客戶");
        }
        public void delete() {
            System.out.println("刪除客戶");
        }
    }
  • Cglib代理類
package com.itzhouq.spring.demo2;
 
 import java.lang.reflect.Method;
 
 import org.springframework.cglib.proxy.Enhancer;
 import org.springframework.cglib.proxy.MethodInterceptor;
 import org.springframework.cglib.proxy.MethodProxy;
 
 /*
  * Cglib動態代理
  */
 public class CglibProxy implements MethodInterceptor {
     
     private CustomerDao customerDao;
 
     public CglibProxy(CustomerDao customerDao) {
         this.customerDao = customerDao;
     }
     
     // 使用Cglib產生代理的方法
     public CustomerDao createProxy() {
         // 1. 建立cglib的核心類對象
         Enhancer enhancer = new Enhancer();
         // 2. 設置父類
         enhancer.setSuperclass(customerDao.getClass());
         // 3. 設置回調(相似於InvocationHandler對象)
         enhancer.setCallback(this);
         // 4. 建立代理對象
         CustomerDao proxy = (CustomerDao) enhancer.create();
         return proxy;
     }
 
     @Override
     public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) 
             throws Throwable {
         // 判斷方法是否爲save
         if("save".equals(method.getName())) {
             // 加強
             System.out.println("權限校驗========");
             return methodProxy.invokeSuper(proxy, arg);
         }
         return methodProxy.invokeSuper(proxy, arg);
     }
 }
  • 測試
package com.itzhouq.spring.demo2;
  
  import org.junit.Test;
  
  public class SpringDemo2 {
      /*
       * cglib的測試
       */
      
      @Test
      public void test1() {
          CustomerDao customerDao = new CustomerDao();
          CustomerDao proxy = new CglibProxy(customerDao).createProxy();
          proxy.save();
          proxy.find();
          proxy.update();
          proxy.delete();
          //權限校驗========
          //保存客戶
          //查找客戶
          //更新客戶
          //刪除客戶
      }
  }

1.2.3 總結

  • Spring在運行期,生成動態代理對象,不須要特殊的編譯器。
  • Spring AOP的底層是經過JDK動態代理或者Cglib動態代理技術爲目標bean執行橫向織入的。安全

    • Spring會優先使用Spring使用JDK代理方式進行代理
    • 若目標對象沒有實現任何接口,Spring容器會使用Cglib動態代理
  • 標記爲final的方法不能被代理,由於沒法進行覆蓋
  • Cglib動態代理,是針對的目標類產生子類,因此目標類不能被final修飾。
  • Spring只支持方法鏈接點,不提供屬性鏈接。

2. Spring的AOP的開發(AspectJ的XML方式)

2.1 Spring的AOP簡介

  • AOP思想最先是由AOP聯盟組織提出。Spring是使用這種思想最好的框架。
  • Spring的AOP有本身的實現方式(很是繁瑣)。AspectJ是一個AOP框架,Spring引入AspectJ做爲自身AOP的開發
  • Spring兩種開發方式app

    • Spring傳統方式(棄用)。
    • Spring基於AspectJ的AOP開發方式(使用)。

2.2 AOP入門開發

2.2.1 準備工程和jar包

  • 除去基本的6個包,在web項目中添加aop開發的相關jar包框架

    • AOP聯盟規範包:..spring相關依賴包spring-framework-3.0.2.RELEASE-dependenciesorg.aopalliancecom.springsource.org.aopalliance1.0.0com.springsource.org.aopalliance-1.0.0.jar
    • AOP包:..spring-framework-4.2.4.RELEASE-distspring-framework-4.2.4.RELEASElibsspring-aop-4.2.4.RELEASE.jar
    • AspectJ包:..spring相關依賴包spring-framework-3.0.2.RELEASE-dependenciesorg.aspectjcom.springsource.org.aspectj.weaver1.6.8.RELEASEcom.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    • Spring和ASpectJ整合包:..spring-framework-4.2.4.RELEASE-distspring-framework-4.2.4.RELEASElibsspring-aspects-4.2.4.RELEASE.jar
    • Spring整合JUnit單元測試包:..spring-framework-4.2.4.RELEASE-distspring-framework-4.2.4.RELEASElibsspring-test-4.2.4.RELEASE.jar

2.2.2 引入Spring的配置文件

  • 引入aop的約束
  • 約束的位置:../spring-framework-4.2.4.RELEASE-dist/spring-framework-4.2.4.RELEASE/docs/spring-framework-reference/html/xsd-configuration.html 40.2.7 the aop schema
<?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 
        http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

2.2.3 編寫目標類並完成配置

  • 目標接口

    package com.itzhouq.spring.demo3;
    
    public interface ProductDao {
        public void save();
        public void update();
        public void find();
        public void delete();
    }
  • 目標類

    package com.itzhouq.spring.demo3;
    
    public class ProductDaoImpl implements ProductDao {
    
        @Override
        public void save() {
            System.out.println("保存商品");
        }
    
        @Override
        public void update() {
            System.out.println("更新商品");
        }
    
        @Override
        public void find() {
            System.out.println("查找商品");
        }
    
        @Override
        public void delete() {
            System.out.println("刪除商品");
        }
    
    }
  • 配置目標對象

    <!-- 配置目標對象:被加強的對象 -->
    <bean id="productDao" class="com.itzhouq.spring.demo3.ProductDaoImpl"></bean>

2.2.4 測試類

package com.itzhouq.spring.demo3;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/*
 * AOP入門
 */
@RunWith(SpringJUnit4ClassRunner.class)    //這兩個註釋就是Spring整合了JUnit單元測試
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {
    
    //注入productDao
    @Resource(name="productDao")
    private ProductDao productDao;
    
    @Test
    public void test1() {
        productDao.save();
        productDao.update();
        productDao.find();
        productDao.delete();
//        保存商品
//        更新商品
//        查找商品
//        刪除商品
    }
}
  • 下面須要對save()方法加強

2.2.5 編寫一個切面類

  • 切面:多個通知和多個切入點的組合。
package com.itzhouq.spring.demo3;
          /*
           * 切面類
           */
          public class MyAspectXML {
              public void checkPri() {
                  System.out.println("權限校驗。。。");
              }
          }

2.2.6 將切面類交給Spring

  • <!-- 將切面類交給Spring管理 -->
          <bean id="myAspect" class="com.itzhouq.spring.demo3.MyAspectXML"></bean>

2.2.7 經過AOP的配置實現動態代理

<!-- 經過AOP的配置完成對目標類產生代理 -->
    <aop:config>
        <!-- 表達式配置哪些類的哪些方法須要進行加強 -->
        <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.save(..))" id="pointcut1"/>
        <!-- 配置切面 -->
        <aop:aspect ref="myAspect">
            <aop:before method="checkPri" pointcut-ref="pointcust1"/>
        </aop:aspect>
    </aop:config>

2.2.8 測試類中運行結果,save實現了加強

@RunWith(SpringJUnit4ClassRunner.class)    //這兩個註釋就是Spring整合了JUnit單元測試
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {
    
    //注入productDao
    @Resource(name="productDao")
    private ProductDao productDao;
    
    @Test
    public void test1() {
        productDao.save();
        productDao.update();
        productDao.find();
        productDao.delete();
//        權限校驗。。。
//        保存商品
//        更新商品
//        查找商品
//        刪除商品

    }
}

2.3 Spring中的通知類型

2.3.1 前置通知

  • 配置

    <!-- 配置切面 -->
    <aop:aspect ref="myAspect">
        <aop:before method="checkPri" pointcut-ref="pointcut1"/>
    </aop:aspect>
  • 在目標方法執行以前進行操做
  • 能夠得到切入點的信息
  • 好比在切面類中加入參數JoinPoint

    public class MyAspectXML {
        
        public void checkPri(JoinPoint joinPoint) {
            System.out.println("權限校驗。。。"+joinPoint);
        }
    }

    測試打印的效果

    //        權限校驗。。。execution(void com.itzhouq.spring.demo3.ProductDao.save())
    //        保存商品
    //        更新商品
    //        查找商品
    //        刪除商品

2.3.2 後置通知:

  • 在目標方法以後操做,能夠得到返回值
  • 修改接口ProductDao和實現類ProductDaoImpl類的delete方法的返回值爲String
  • 配置後置通知

    <!-- 經過AOP的配置完成對目標類產生代理 -->
        <aop:config>
            <!-- 表達式配置哪些類的哪些方法須要進行加強 -->
            <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.save(..))" id="pointcut1"/>
            <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.delete(..))" id="pointcut2"/>
            <!-- 配置切面 -->
            <aop:aspect ref="myAspect">
                <!-- 前置通知============= -->
                <aop:before method="checkPri" pointcut-ref="pointcut1"/>
                <!-- 後置通知============= -->
                <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/>
            </aop:aspect>
        </aop:config>
  • 在後置切面類中添加記錄日誌的方法
/*
       * 後置通知演示
       */
      public void writeLog(Object result) {
          System.out.println("日誌記錄======="+result);
      }
  • 測試

    權限校驗。。。execution(void com.itzhouq.spring.demo3.ProductDao.save())
    //        保存商品
    //        更新商品
    //        查找商品
    //        刪除商品
    //        日誌記錄=======kkk

2.3.3 環繞通知:

  • 在目標方法執行以前 和以後進行操做
  • 環繞通知能夠組織目標方法的執行
  • 舉例:需求---在修改方法update先後添加性能監控
  • 在切面類中添加一個方法用來測試環繞通知
/**
       * 性能監控
       * @throws Throwable 
       */
      public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
          System.out.println("環繞前通知===========");
          Object obj = joinPoint.proceed();//這一步至關於執行目標程序
          System.out.println("環繞後通知=========");
          return obj;
      }
  • 配置環繞通知

    <aop:config>
            <!-- 表達式配置哪些類的哪些方法須要進行加強 -->
            <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.save(..))" id="pointcut1"/>
            <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.delete(..))" id="pointcut2"/>
            <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.update(..))" id="pointcut3"/>
            <!-- 配置切面 -->
            <aop:aspect ref="myAspect">
                <!-- 前置通知============= -->
                <aop:before method="checkPri" pointcut-ref="pointcut1"/>
                <!-- 後置通知============= -->
                <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/>
                <!-- 環繞通知 -->
                <aop:around method="around" pointcut-ref="pointcut3"/>
            </aop:aspect>
        </aop:config>
  • 測試

    //        權限校驗。。。execution(void com.itzhouq.spring.demo3.ProductDao.save())
    //        保存商品
    //        環繞前通知===========
    //        更新商品
    //        環繞後通知=========
    //        查找商品
    //        刪除商品
    //        日誌記錄=======kkk

2.3.4 異常拋出通知:

  • 在程序拋出異常時候進行操做,能夠獲得異常信息
  • 在find方法上模擬一個異常

    @Override
        public void find() {
            System.out.println("查找商品");
            int i = 1 / 0;
        }
  • 切面類中添加一個方法用於測試異常拋出通知
/*
       * 異常拋出通知
       */
      public void afterThrowing(Throwable ex) {
          System.out.println("異常拋出通知=======" + ex);
      }
  • 配置異常通知

    <aop:config>
            <!-- 表達式配置哪些類的哪些方法須要進行加強 -->
            <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.save(..))" id="pointcut1"/>
            <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.delete(..))" id="pointcut2"/>
            <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.update(..))" id="pointcut3"/>
            <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.find(..))" id="pointcut4"/>
            <!-- 配置切面 -->
            <aop:aspect ref="myAspect">
                <!-- 前置通知============= -->
                <aop:before method="checkPri" pointcut-ref="pointcut1"/>
                <!-- 後置通知============= -->
                <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/>
                <!-- 環繞通知 -->
                <aop:around method="around" pointcut-ref="pointcut3"/>
                <!-- 異常拋出通知 -->
                <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>
            </aop:aspect>
        </aop:config>
  • 測試

    //        權限校驗。。。execution(void com.itzhouq.spring.demo3.ProductDao.save())
    //        保存商品
    //        環繞前通知===========
    //        更新商品
    //        環繞後通知=========
    //        查找商品
    //        異常拋出通知=======java.lang.ArithmeticException: / by zero

2.3.5 最終通知:

  • 在切面類中添加方法測試最終通知
/*
       * 最終通知:至關於finally代碼塊中的內容
       */
      public void after() {
          System.out.println("最終通知=========");
      }
  • 配置最終通知

    <!-- 配置最終通知 -->
                <aop:after method="after" pointcut-ref="pointcut4"/>
  • 測試
  • 不管是否有異常,最終通知都會執行。

2.3.6 引介通知(不用會)

2.4 切入點表達式【掌握】

1.execution()  用於描述方法【掌握】
    語法:execution(修飾符  返回值  包.類.方法名(參數) throws異常)
        修飾符,通常省略
            public      公共方法
            *           任意
        返回值,不能省略
            void        返回沒有值
            String      返回值字符串
            *           任意
        包,[能夠省略]
            com.itheima.crm                 固定的包
            com.itheima.crm.*.service       crm包下面的任意子包,固定目錄service(例如:com.itheima.crm.staff.service)
            com.itheima.crm..               crm包下面的全部子包(含本身)
            com.itheima.crm.*.service..     crm包下面的任意子包,固定目錄service,service目錄任意包(含本身)
        類,[能夠省略]
            UserServiceImpl                 指定的類
            *Impl                           以Impl結尾的類
            User*                           以User開頭的類
            *                               任意的類
        方法名,不能省略
            addUser                         固定的方法名
            add*                            以add開頭的方法名
            *Do                             以Do結尾的方法名
            *                               任意的方法名
        (參數)
            ()                              無參
            (int)                           一個整型
            (int, int)                      兩個整型
            (..)                            參數任意
        throws,[能夠省略],通常省略。

    綜合案例1:
        execution(* com.itheima.crm.*.service..*.*(..))
    綜合案例2:
        <aop:pointcut expression="execution(* com.itheima.*WithCommit.*(..)) || 
                                  execution(* com.itheima.*Service.*(..))" id="myPointCut"/>

2.within:匹配包或子包中的方法(瞭解)
    within(com.itheima.aop..*)
3.this:匹配實現了接口的代理對象中的方法(瞭解)
    this(com.itheima.aop.user.UserDAO)
4.target:匹配實現了接口的目標對象中的方法(瞭解)
    target(com.itheima.aop.user.UserDAO)
5.args:匹配參數格式符合標準的方法(瞭解)
    args(int, int)
6.bean(id):對指定的bean全部的方法(瞭解)
    bean('userServiceId')
相關文章
相關標籤/搜索