mybatis 01: 靜態代理 + jdk動態代理

背景

  • 有時目標對象不可直接訪問,只能通過代理對象訪問

  • 圖示:

image

  • 示例1:
    • 房東 ===> 目標對象
    • 房屋中介 ===> 代理對象
    • 你,我 ===> 客戶端對象
  • 示例2:
    • 運營商(電信,移動,聯通) ===> 目標對象
    • 第三方公司 ===> 代理對象
    • 開發的應用程序需要發送短訊的功能(或者需要支付功能) ===> 客戶端對象

代理模式的作用

  • 控制客戶對目標對象的訪問
  • 增強訪問功能

代理模式的分類

  • 靜態代理
  • 動態代理
    • JDK動態代理
    • CGLib動態代理

靜態代理

特點

  • 目標對象和代理對象實現同一個業務接口
  • 目標對象必須實現接口
  • 代理對象在程序運行前就已經存在

靜態代理示例與原理分析

業務背景

image

分析

  • 定義業務接口:面向接口編程,定義業務
  • 目標對象實現接口:業務的核心功能到底怎麼實現
  • 代理對象(擴展業務 + 核心業務)
    • 實現了目標對象所實現的接口,說明代理對象有資歷進行代理
    • 對核心業務進行擴展
    • 調用目標對象實現核心業務(只能目標對象自己完成)
  • 客戶:無法直接訪問目標對象,要訪問代理對象

代碼實現

  • 面向接口編程

    • 成員變量是接口類型
    • 傳入目標對象,方法的參數設計為接口
    • 調用時,接口指向實現類
  • 靜態代理對象代碼

    package com.example.service.impl;
    
    import com.example.service.Service;
    
    public class Agent implements Service {
    
        //定義接口對象
        public Service target;
    
        public Agent(){}
    
        //傳入接口對象
        public Agent(Service target){
            this.target = target;
        }
    
        @Override
        public void sing() {
            System.out.println("協商演出時間......");
            System.out.println("協商演出地點......");
    
            //目標對象完成核心業務,接口指向實現類,調用實現類的方法
            target.sing();
    
            System.out.println("協商演出費用......");
        }
    }
    

靜態代理優缺點

  • 優點:能夠靈活的進行目標對象的切換
    • 適用於業務固定,目標對象可靈活切換的場景
  • 缺點:無法進行功能的靈活處理,當業務發生改變時,所有涉及到的實現類代碼和代理對象代碼都要改變

動態代理

JDK動態代理

特點

  • 目標對象必須實現業務接口
  • JDK代理對象不需要實現業務接口
  • JDK代理對象在程序運行前不存在,程序運行時動態的在內存中構建(根據受代理的對象動態創建)
  • JDK動態代理可以靈活的進行業務功能的切換

JDK動態代理用到的類和接口

  • 使用現有的工具類完成JDK動態代理
  • 先了解兩個單詞的意思
    • InvocationHandler:調用處理程序
    • invoke:調用

Method類

  • 反射時用的類,用來進行目標對象的目標方法的反射調用
  • method對象,接住我們正在調用的方法 sing(),show()
    • method == sing(),show(),即:待調用的方法
    • method.invoke() ==> 相當於手工調用目標方法 sing(),show();

InvocationHandler接口

  • 用來實現代理和業務功能,我們在調用時使用匿名內部實現
    • 匿名內部實現:new接口的同時,重寫接口中的方法(相當於定義了該接口的一個實現類)

Proxy類

  • 位於:java.lang.reflect.Proxy包下

  • 有一個核心方法:Proxy.newProxyInstance(….),專門獲取動態代理對象,有三個參數

    • 參數1:ClassLoader loader

      • 目標對象的類加載器
      • 目的:獲取類方法等信息,畢竟底層還是要調用受代理對象所實現的方法
      • 傳入:targetObj.getClass().getClassLoader();
    • 參數2:Class<?>[] interfaces

      • 目標對象實現的所有接口,類的接口可以有多個
      • 目的:獲取目標對象實現的所有接口以及接口的相關信息,畢竟底層要知道目標對象都可以完成哪些業務操作
      • 傳入:targetObj.getClass().getInterfaces();
    • 上面兩個參數為代理對象動態的創建和調用目標對象的方法提供了數據支持,第3個參數相當於調用程序

    • 參數3:InvocationHandler

      • 實現代理功能的接口,這裡代理功能包括:擴展的功能 + 核心業務功能,傳入的匿名內部實現如下
      new InvocationHandler() {
          @Override
          public Object invoke(
                  Object obj,
                  //用來反射調用方法
                  Method method,
                  //待調用方法需要的參數
                  Object[] args)
                  throws Throwable {
      
              //擴展業務
              System.out.println("協商演出時間......");
              System.out.println("協商演出地點......");
      
              //核心業務,具體調用什麼方法根據外層業務來反射調用對應方法
              Object res = method.invoke(target, args);
      
              //擴展業務
              System.out.println("協商演出費用......");
      
              //目標對象執行的目標方法的返回值
              return res;
          }
      }
      

JDK動態代理示例

  • 代理工廠代碼

    package com.example.proxy;
    
    import com.example.service.Service;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class ProxyFactory {
        //目標對象
        Service target;
    
        public ProxyFactory(){}
    
        public ProxyFactory(Service target){
            this.target = target;
        }
    
        //返回代理對象
        public Object getAgent(){
            return Proxy.newProxyInstance(
                    //需要知道受代理對象的類信息
                    target.getClass().getClassLoader(),
                    //需要知道受代理對象實現的所有接口信息
                    target.getClass().getInterfaces(),
                    //反射調用目標對象的目標方法
                    new InvocationHandler() {
                        @Override
                        public Object invoke(
                                Object obj,
                                //用來反射調用方法
                                Method method,
                                //待調用方法需要的參數
                                Object[] args)
                                throws Throwable {
    
                            //擴展業務
                            System.out.println("協商演出時間......");
                            System.out.println("協商演出地點......");
    
                            //核心業務,具體調用什麼方法根據外層業務來反射調用對應方法
                            Object res = method.invoke(target, args);
    
                            //擴展業務
                            System.out.println("協商演出費用......");
    
                            //目標對象執行的目標方法的返回值
                            return res;
                        }
                    }
            );
        }
    }
    
  • 測試代碼示例

    package com.example.proxy;
    
    import com.example.service.Service;
    import com.example.service.impl.SuperStarZhou;
    import org.junit.Test;
    
    public class TestProxyFactory {
        @Test
        public void testGetProxy(){
            //確定客戶需求
            ProxyFactory factory = new ProxyFactory(new SuperStarZhou());
            //根據需求動態返回對應類型的代理對象
            Service agent = (Service) factory.getAgent();
            //依託對應類型的動態代理對象完成業務:擴展業務(動態代理對象完成) + 核心業務(目標對象完成)
            agent.sing();
        }
    
        @Test
        public void testGetProxy2(){
            ProxyFactory factory = new ProxyFactory(new SuperStarZhou());
            Service agent = (Service) factory.getAgent();
            String res = (String) agent.show(60);
            System.out.println(res);
        }
    }
    

注意

  • 可被代理的方法應該是受代理對象實現的所有接口中的方法與其所有實體方法的交集

    • 本類中獨有的方法不被代理
  • 類型的轉變

     @Test
     public void testGetProxy2() {
         ProxyFactory factory = new ProxyFactory(new SuperStarZhou());
         Service agent = (Service) factory.getAgent();
         Service liu = new SuperStarLiu();
         System.out.println("類型1: " + liu.getClass());
         System.out.println("類型2: " + agent.getClass());
     }
     /*
      輸出結果:
      類型1: class com.example.service.impl.SuperStarLiu
      類型2: class com.sun.proxy.$Proxy7
     */
    
Tags: