AOP學習筆記

洛小白發表於2019-04-01

AOP

  1. 什麼是AOP

    1. 在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
    2. AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性程式碼
    3. 經典應用:效能監視,事務管理,安全檢查,快取,日誌等
    4. Spring AOP使用純Java實現,不需要專門的編譯過程和類載入器,在執行期間通過代理方式向目標類織入增強程式碼
    5. AspectJ是一個基於Java語言的AOP框架,從Spring2.0開始,Spring AOP引入對AspectJ的支援,AspectJ擴充套件了Java語言,提供了一個專門的編譯器,子啊編譯時提供橫向程式碼的織入
  2. AOP實現原理

    1. AOP底層將採用代理機制進行實現。
    2. 介面+實現類:Spring 採用JDK的動態代理Proxy
    3. 實現類:Spring採用cglib位元組碼增強
  3. AOP術語

    1. target:目標類,需要被代理的類:如UserService
    2. Joinpoint(連線點):所有連線點是指那些可能被攔截的方法。例如:所有的方法
    3. PoinCut 切入點:已經被增強的連線點。例如:addUser()
    4. advice 通知/增強:增強程式碼。after、before
    5. Weaving(織入):是指把增強advice應用到目標物件target來建立新的代理物件proxy的過程
    6. proxy代理類
    7. Aspect(切面):是切入點pointcut和通知advice的結合
      1. 一個線是一個特殊的面
      2. 一個切入點和一個通知,組成一個特殊的面
  4. 手動代理

    1. JDK動態代理
      1. JDK動態代理對“裝飾著”設計模式簡化。使用前提:必須有介面

        1. 目標類:介面+實現類
        2. 切面類:用於存通知MyAspect
        3. 工廠類:編寫工廠生成代理
        4. 測試
      2. 目標類

        public interface UserService {
            void addUser();
            void updateUser();
            void deleteUser();
        }
        複製程式碼
      3. 切面類

        public class MyAspect {
            public void before(){
                System.out.println("雞頭");
            }
        
            public void after(){
                System.out.println("鳳尾");
            }
        }
        複製程式碼
      4. 工廠

        public class MyBeanFactory {
        
            public static UserService createService() {
        
                //    1,目標類
                final UserService userService = new UserServiceImpl();
        //    2,切面類
                final MyAspect myAspect = new MyAspect();
        /**
         *      3 代理類:將目標類和切面類結合==》切面
         *      Proxy.newProxyInstance
         *          引數1:loader,類載入器,動態代理類執行時建立,任何類都需要載入器將其載入到記憶體
         *                  一般情況:當前類.class.getClassLoader();
         *                           目標類例項.getClass().get...
         *          引數2:interfaces 代理類需要實現的所有介面
         *              方式1:目標例項.getClass().getInterfaces();注意:只能獲得自己介面,不能獲得父類元素介面
         *              方式2:new Class[]{UserService.class}
         *          引數3:InvocationHandler 處理類,介面,必須進行實現類,一般採用匿名內部類
         *              提供invoke方法,代理類的每一個方法執行時,都將呼叫一次invoke
         *                  引數1:Oject proxy:代理物件
         *                  引數2:Method method:代理物件當前執行的方法的描述(反射)
         *                      執行方法名:method.getName()
         *                      執行方法:method.invoke(物件,實際引數)
         *                  引數3:Object[] args:方法實際引數
         */
                UserService proxService = (UserService) Proxy.newProxyInstance(
                        MyBeanFactory.class.getClassLoader(),
                        userService.getClass().getInterfaces(),
                        new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                              // 將目標類和切面類結合
        
                                //前執行
                                myAspect.before();
                                //執行目標方法
                                Object obj =  method.invoke(userService,args);
                                //後方法
                                myAspect.after();
                                return obj;
                            }
                        });
        
                return proxService;
            }
        }
        複製程式碼
      5. 測試

        @Test
        public void demo(){
            UserService userService = MyBeanFactory.createService();
            userService.addUser();
            userService.updateUser();
            userService.deleteUser();
        }
        
        執行結果:
        雞頭
        addUser
        鳳尾
        雞頭
        updateUser
        鳳尾
        雞頭
        deleteUser
        鳳尾
        
        複製程式碼
    2. CGLIB位元組碼增強
      1. 沒有介面,只有實現類

      2. 採用位元組碼增強框架cglib,在執行時,建立目標類的子類,從而對目標類進行增強

      3. 匯入jar包:

        1. 手動方式(連線)
          1. 核心:cglib.jar
          2. 依賴:asm.jar
        2. spring-core.jar:已經整合了這兩個內容
      4. 實現

        public class MyBeanFactory {
        
            public static UserServiceImpl createService() {
        
                //    1,目標類
                final UserServiceImpl userService = new UserServiceImpl();
                //    2,切面類
                final MyAspect myAspect = new MyAspect();
                /**
                 *  3.代理類,採用chlib,底層建立目標類的子類
                 */
        
        
                //      3.1核心類
                Enhancer enhancer = new Enhancer();
                //      3.2 確定父類
                enhancer.setSuperclass(userService.getClass());
                /**      3.3設定回撥函式 等效 invocationHandler
                 * intercept()等效jdk  invoke()
                 * 引數1,2,3與invoke一樣
                 * 引數4:方法的代理
                 */
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    //前方法
                        myAspect.before();
        
                     // 執行目標類的方法
                        Object obj = method.invoke(userService, objects);
                    // 執行代理類的父類,執行目標類(目標類和代理類 父子關係)
                        methodProxy.invokeSuper(o,objects);
                        //後方法
                        myAspect.after();
                        return obj;
                    }
                });
                //      3.4建立代理
                UserServiceImpl proxService = (UserServiceImpl) enhancer.create();
        
                return proxService;
            }
        
        }
        複製程式碼
    3. Spring AOP增強型別

      1. Spring按照通知在Advice在目標類方法的連線點位置,可以分為5類
        1. 前置通知:在目標方法執行前實施增強
        2. 後置通知:在目標方法執行後實施增強
        3. 環繞通知:在目標方法執行前後實施增強
        4. 異常丟擲通知:在方法丟擲異常後實施增強
        5. 引介通知:在目標類中新增一些新的方法和屬性
				//環繞通知,必須手動執行目標方法
				try{

                  //前置通知

                  //執行目標方法

                  //後置通知	

                  } catch(){

                  //丟擲異常通知

                  }
複製程式碼
  1. spring代理:半自動

    1. 讓spring去建立代理物件,從spring容器中手動獲取代理物件

    2. 匯入jar包:

      1. 核心:4+1
      2. AOP:AOP聯盟(規範)、spring-aop.jar
    3. 目標類

      public interface UserService {
          void addUser();
          void updateUser();
          void deleteUser();
      }
      
      複製程式碼
    4. 切面類

      /**
       * 切面類中確定通知,需要實現不同的介面,介面就是規範,從而確定方法名稱。
       * MethodInterceptor 環繞通知
       */
      public class MyAspect implements MethodInterceptor {
      
      
          @Override
          public Object invoke(MethodInvocation methodInvocation) throws Throwable {
              System.out.println("前");
              //手動執行目標方法
              Object obj = methodInvocation.proceed();
              System.out.println("後");
              return obj;
          }
      }
      複製程式碼
    5. xml配置

      <!--建立目標類-->
      <bean id="userServiceId" class="com.adolph.AOP.jdk.UserServiceImpl"></bean>
      
      <!--建立切面類-->
      <bean id="myAspectId" class="com.adolph.AOP.jdk.MyAspect"></bean>
      
        <!--建立代理類
              * 使用工廠bean Factory Bean ,底層呼叫getObject() 放回特殊bean
              ProxyFactoryBean 用於建立代理工廠bean,生成特殊代理物件
                  interfaces:確定介面們
                      通過<array>可以設定多個值
                      只有一個值時:value=""
                  target:確定目標類
                  interceptorNames:通知切面類的名稱,型別String[],如果設定一個值value=""
                  optimize:強制使用cglib
      
               底層機制
                  如果目標類有介面,採用jdk動態代理
                  如果沒有介面,採用cglib位元組碼增強
                  如果宣告optimize = true,無論是否有介面,都採用cglib
          -->
          <bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactoryBean">
              <property name="interfaces" value="com.adolph.AOP.jdk.UserService"></property>
              <property name="target" ref="userServiceId"></property>
              <property name="interceptorNames" value="myAspectId"></property>
              <property name="optimize" value="true"></property>
      複製程式碼
    6. 測試

      @Test
      public void demo(){
          String xmlPath = "com/adolph/AOP/jdk/beans.xml";
          ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
          UserServiceImpl userService = applicationContext.getBean("proxyServiceId",UserServiceImpl.class);
          userService.addUser();
          userService.updateUser();
          userService.deleteUser();
      }
      複製程式碼
  2. spring aop程式設計:全自動【掌握】

    1. 從spring容器中獲得目標類,如果配置了aop,spring將自動生成代理

    2. 要確定目標類,aspectj 切入點表示式,導jar包

    3. xml

      <!--建立目標類-->
      <bean id="userService" class="com.adolph.AOP.jdk.UserServiceImpl"></bean>
      
      <!--建立切面類-->
      <bean id="myAspect" class="com.adolph.AOP.jdk.MyAspect"></bean>
      
      <!--aop程式設計
              使用<aop:config>進行配置
              proxy-target-class="true":使用cglib代理
               <aop:pointcut>切入點,從目標物件獲得具體方法
               <aop:advisor> 特殊的切面,只有一個通知和一個切入點
                  advice-ref:通知引用
                  pointcut-ref:切入點引用
                切入點表示式:
                  execution(* com.adolph.AOP.jdk.*.*(..))
                  選擇方法 返回值任意  包 類名任意 方法名任意 引數任意
          -->
          <aop:config proxy-target-class="true">
              <aop:pointcut id="myPointCut" expression="execution(* com.adolph.AOP.jdk.*.*(..))"/>
              <aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/>
          </aop:config>
      複製程式碼
    4. 測試

      @Test
      public void demo(){
          String xmlPath = "com/adolph/AOP/jdk/beans.xml";
          ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
      	//返回的時目標類,但是獲得的時代理類
          UserServiceImpl userService = applicationContext.getBean("userService",UserServiceImpl.class);
          userService.addUser();
          userService.updateUser();
          userService.deleteUser();
      }
      複製程式碼

相關文章