1. 程式人生 > 其它 >Oracle 運維篇+修改表/索引的記憶體載入屬性

Oracle 運維篇+修改表/索引的記憶體載入屬性

代理模式

代理模式顧名思義,就是以你的名義幫你處理一些瑣事,例如:使用者A 近期準備結婚,他需要預定飯店,佈置會場,邀請親朋,制定選單等等等等.... 這樣處理事情就比較麻煩,針對這些瑣事就可以找 婚慶公司 來處理,他們可以解決婚禮過程中的大部分事情,這樣使用者A 只需要面對結婚這件事兒本身就可以,而不需要考慮其他的事情。

上面這個例子中 婚慶公司的定位就是代理,Java 中有些地方也需要使用代理模式進行解決,例如事務,日誌等等,我們希望 service 中只需要負責業務邏輯本身而不要摻雜其他的程式碼,這裡就可以用到代理模式

代理模式總結起來一句話:可以做到在不修改目標物件的功能前提下,對目標功能擴充套件

我們需要了解掌握三種代理模式:靜態代理,JDK 動態代理,CGLIB 動態代理,這裡通過日誌的方式進行了解

靜態代理模式

我們需要準備 service 介面以及代理類和被代理類:

正常執行新增日誌

// service介面
public interface UserService {
    // 新增使用者的方法
    int insertUser(String username, String password);
}
// 實現類
public class UserServiceImpl implements UserService {
    @Override
    public int insertUser(String username, String password) {
        int count = 1;
        // 假裝有DAO層
        // count = userDao.insertUser(username, password);
        return count;
    }
}
// 測試類
public class AppTest{
    public static void main(String[] args) {
        UserService us = new UserServiceImpl();
        System.out.println(us.insertUser("老八", "祕製小漢堡"));
    }
}

當我們執行測試類的 main 方法後發現控制檯只輸出了數字 1,如果我們想要實現日誌功能的話,需要在實現類中新增列印語句,如下所示:

// 【改】實現類
public class UserServiceImpl implements UserService {
    @Override
    public int insertUser(String username, String password) {
        int count = 1;
        // 假裝有DAO層
        // count = userDao.insertUser(username, password);
        System.out.println("使用者【" + username + "】插入成功!");
        return count;
    }
}

日誌新增完成!但是按照之前的說法,我們不希望在 service 中摻雜其他的程式碼,它僅僅負責業務邏輯即可,所以我們要移除掉實現類中的輸出語句,新建一個代理類來完成這部分功能

使用靜態代理新增日誌

// 代理類
public class UserServiceProxy implements UserService {
    private UserService us;
    public UserServiceProxy( UserService us ){
        this.us = us;
    }
    @Override
    public int insertUser(String username, String password) {
        int count = this.us.insertUser(username, password);
        System.out.println("使用者【" + username + "】插入成功!");
        return count;
    }
}
// 測試類
public class AppTest{
    public static void main(String[] args) {
    	// 這裡建立代理類,將被代理類的例項作為引數傳遞過去,返回代理物件
        UserService us = new UserServiceProxy( new UserServiceImpl() );
        System.out.println(us.insertUser("老八", "祕製小漢堡"));
    }
}

建立代理類的物件,將被代理的例項作為引數傳過去,讓代理類以自己的名義幫自己實現日誌功能,這樣實現類中只需要負責書寫業務邏輯就可以了。這就是 靜態代理模式

JDK動態代理

靜態代理可以做到在不修改原始碼的情況下新增新的功能,但是如果 service 比較多的話靜態代理的弊端就出來了,每個 service 都對應一個代理類,這樣程式碼量就大大增加,這裡就可以使用 JDK動態代理來解決這個問題:

程式碼實現

JDK動態代理依靠 java.lang.reflect.Proxy 實現,保持介面和實現類不動,新建代理類:

public class LogProxy {
    public static Object getInstance( Object obj){
        // 獲取被代理類的類載入器
        ClassLoader classLoader = obj.getClass().getClassLoader();
        // 獲取被代理的類實現的所有介面
        Class<?>[] classes = obj.getClass().getInterfaces();
        // 在這裡接管目標物件執行函式的過程
        // proxy不用管,method:被執行的函式,args:傳遞的引數
        InvocationHandler invocationHandler = 
            (Object proxy, Method method, Object[] args)->{
            	Object result = method.invoke(obj, args);
                System.out.println("使用者【" + Arrays.asList(args) + "】插入成功!");
                return result;
            };
        Object obj = Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        return obj;
    }
}

我們通過 Proxy.newProxyInstance 來獲取到代理物件,呼叫這個方法需要傳遞三個引數:

  • 【classLoader】被代理的類的類載入器
  • 【classes】被代理的類所實現的所有介面
  • 【invocationHandler】具有回撥功能的函式式介面,這裡直接通過 lambda 表示式建立物件即可,當被代理的類執行某個方法的時候,真正的執行過程會在這裡觸發,通過 lambda 建立該類的例項需要三個形參:
    • 【proxy】暫時不理解...不用管
    • 【method】被代理的類執行的方法的例項
    • 【args】被代理類執行方法時傳入的引數
    • 最後記得將 invoke 執行函式後的結果返回,不然呼叫者那邊會收不到返回結果

JDK動態代理的缺點

我們通過 Proxy.newProxyInstance 傳參成功返回了代理物件 ,然後在測試類中成功轉換為 UserService 例項進行日誌增強,現在我們嘗試列印一下轉換之前的代理物件的 class 是什麼樣子的:

// 代理類
public class LogProxy {
    public static Object getInstance(Object obj) {
        // 省略多餘程式碼
        ClassLoader classLoader = obj.getClass().getClassLoader();
        Class<?>[] classes = obj.getClass().getInterfaces();
        InvocationHandler invocationHandler = 
            (Object proxy, Method method, Object[] args) -> method.invoke(obj, args);
        // 獲取代理物件並進行列印
        Object proxy = 
            Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        System.out.println(proxy.getClass());
        return proxy;
    }
}

列印結果為:class com.sun.proxy.$Proxy0,並不是我們熟悉的型別,那為什麼他可以轉換為 UserService 型別呢?因為他獲取了被代理目標類實現的所有介面:obj.getClass().getInterfaces(),讓我們列印一下代理物件實現的介面是什麼樣子的:

public class LogProxy {
    public static Object getInstance(Object obj) {
        // 省略多餘程式碼
        ClassLoader classLoader = obj.getClass().getClassLoader();
        Class<?>[] classes = obj.getClass().getInterfaces();
        InvocationHandler invocationHandler = 
            (Object proxy, Method method, Object[] args) -> method.invoke(obj, args);
        // 獲取代理物件並進行列印
        Object proxy = 
            Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        System.out.println(Arrays.asList(proxy.getClass().getInterfaces()));
        return proxy;
    }
}

列印結果為:[interface site.hanzhe.proxy.jdkProxy.UserService],代理物件實現了和被代理物件一模一樣的介面,所以他可以轉換為 UserService,那麼如果我們被代理的類沒有實現任何介面會怎麼樣呢?

// 修改被代理的類,讓他不實現任何介面
public class UserServiceImpl /* implements UserService */ {
    // @Override
    public int insertUser(String username, String password) {
        int count = 1;
        // 假裝有DAO層
        // count = userDao.insertUser(username, password);
        System.out.println(username + "--" + password);
        return count;
    }
}

被代理類修改完成了,代理類保持不變,在測試類中修改強制轉換型別,然後進行測試

public class AppTest{
    public static void main(String[] args) {
        UserServiceImpl us =
            	(UserServiceImpl)LogProxy.getInstance(new UserServiceImpl());
        us.insertUser("老八", "祕製小漢堡");
    }
}

系統丟擲了 java.lang.ClassCastException 異常,詳細資訊為 com.sun.proxy.$Proxy0 cannot be cast to site.hanzhe.proxy.jdkProxy.UserServiceImpl,告訴我們無法強制轉換為 UserServiceImpl 型別的物件,這樣一來我們就測出了一個問題,JDK動態代理要求 被代理的物件必須實現至少一個介面

CGLIB動態代理

在之前的靜態代理中,代理類與被代理類實現了同一個介面,從而達到對每個函式進行代理的目的,而在 JDK動態代理中要求被代理的類必須實現至少一個介面,那麼如果我們被代理的類沒有實現介面的話如何代理?

CGLIB動態代理模式,以被代理物件作為父類,動態建立一個子類作為代理物件並返回,CGLIB動態代理底層使用了 ASM位元組碼框架來生成子類,相比較比 Java反射效率要高

想要使用 CGLIB動態代理需要引入 CGLIB 和 ASM 的 jar 包,這裡記錄一下兩個 jar 包所對應的 maven 座標:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.0</version>
</dependency>

程式碼實現

// 一個沒有實現任何介面的 service 類
public class UserService {
    // 新增使用者的方法
    public int insertUser(String username, String password){
        int count = 1;
        // 假裝有DAO層
        // count = userDao.insertUser(username, password);
        System.out.println(username + "--" + password);
        return count;
    }
}
// 代理類程式碼
public class LogProxy {
    public static Object getInstance(Object obj){
        // 建立CGLIB的工具類
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(
            (InvocationHandler)(Object proxy, Method method, Object[] args)->{
                Object result = method.invoke(obj, args);
                System.out.println("使用者【" + Arrays.asList(args) + "】插入成功!");
                return result;
            }
        );
        return enhancer.create();
    }
}

代理類程式碼說明:

  • 【Enhancer】CGLIB中的工具類,通過工具類來動態建立一個新的例項
    • 【setSuperclass】設定物件繼承的父類,該方法需要傳入被代理類的 class 物件
    • 【setCallback】通過回撥的方式實現代理功能,該方法需要傳入一個 Callback 物件, 這裡使用他下面子介面 InvocationHandler,也就是 JDK動態代理時手動建立的那個物件,由於型別原因在使用的時候使用了強制型別轉換
      • 【InvocationHandler】函式式介面的三個引數這裡就不解釋了,記得將 invoke 結果返回即可