1. 程式人生 > 程式設計 >el-tree文字顯示不全的解決辦法

el-tree文字顯示不全的解決辦法

1. 代理模式

代理模式是一種比較好理解的設計模式。簡單來說就是 我們使用代理物件來代替對真實物件(real object)的訪問,這樣就可以在不修改原目標物件的前提下,提供額外的功能操作,擴充套件目標物件的功能。

代理模式的主要作用是擴充套件目標物件的功能,比如說在目標物件的某個方法執行前後你可以增加一些自定義的操作。

舉個例子:你找了小紅來幫你問話,小紅就可以看作是代理你的代理物件,代理的行為(方法)是問話。

https://medium.com/@mithunsasidharan/understanding-the-proxy-design-pattern-5e63fe38052a

代理模式有靜態代理和動態代理兩種實現方式,我們 先來看一下靜態代理模式的實現。

2. 靜態代理

靜態代理中,我們對目標物件的每個方法的增強都是手動完成的(後面會具體演示程式碼),非常不靈活(比如介面一旦新增加方法,目標物件和代理物件都要進行修改)且麻煩(需要對每個目標類都單獨寫一個代理類)。 實際應用場景非常非常少,日常開發幾乎看不到使用靜態代理的場景。

上面我們是從實現和應用角度來說的靜態代理,從 JVM 層面來說, 靜態代理在編譯時就將介面、實現類、代理類這些都變成了一個個實際的 class 檔案。

靜態代理實現步驟:

  1. 定義一個介面及其實現類;
  2. 建立一個代理類同樣實現這個介面
  3. 將目標物件注入進代理類,然後在代理類的對應方法呼叫目標類中的對應方法。這樣的話,我們就可以通過代理類遮蔽對目標物件的訪問,並且可以在目標方法執行前後做一些自己想做的事情。

下面通過程式碼展示!

1.定義傳送簡訊的介面

public interface SmsService {
    String send(String message);
}

2.實現傳送簡訊的介面

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3.建立代理類並同樣實現傳送簡訊的介面

public class SmsProxy implements SmsService {

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
        //呼叫方法之前,我們可以新增自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        //呼叫方法之後,我們同樣可以新增自己的操作
        System.out.println("after method send()");
        return null;
    }
}

4.實際使用

public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

執行上述程式碼之後,控制檯打印出:

before method send()
send message:java
after method send()

可以輸出結果看出,我們已經增加了 SmsServiceImplsend()方法。

3. 動態代理

相比於靜態代理來說,動態代理更加靈活。我們不需要針對每個目標類都單獨建立一個代理類,並且也不需要我們必須實現介面,我們可以直接代理實現類( CGLIB 動態代理機制)。

從 JVM 角度來說,動態代理是在執行時動態生成類位元組碼,並載入到 JVM 中的。

說到動態代理,Spring AOP、RPC 框架應該是兩個不得不的提的,它們的實現都依賴了動態代理。

動態代理在我們日常開發中使用的相對較小,但是在框架中的幾乎是必用的一門技術。學會了動態代理之後,對於我們理解和學習各種框架的原理也非常有幫助。

就 Java 來說,動態代理的實現方式有很多種,比如 JDK 動態代理CGLIB 動態代理等等。

guide-rpc-framework 使用的是 JDK 動態代理,我們先來看看 JDK 動態代理的使用。

另外,雖然 guide-rpc-framework 沒有用到 CGLIB 動態代理 ,我們這裡還是簡單介紹一下其使用以及和JDK 動態代理的對比。

3.1. JDK 動態代理機制

3.1.1. 介紹

在 Java 動態代理機制中 InvocationHandler 介面和 Proxy 類是核心。

Proxy 類中使用頻率最高的方法是:newProxyInstance() ,這個方法主要用來生成一個代理物件。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ......
    }

這個方法一共有 3 個引數:

  1. loader :類載入器,用於載入代理物件。
  2. interfaces : 被代理類實現的一些介面;
  3. h : 實現了 InvocationHandler 介面的物件;

要實現動態代理的話,還必須需要實現InvocationHandler 來自定義處理邏輯。 當我們的動態代理物件呼叫一個方法時候,這個方法的呼叫就會被轉發到實現InvocationHandler 介面類的 invoke 方法來呼叫。

public interface InvocationHandler {

    /**
     * 當你使用代理物件呼叫方法的時候實際會呼叫到這個方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke() 方法有下面三個引數:

  1. proxy :動態生成的代理類
  2. method : 與代理類物件呼叫的方法相對應
  3. args : 當前 method 方法的引數

也就是說:你通過Proxy 類的 newProxyInstance() 建立的代理物件在呼叫方法的時候,實際會呼叫到實現InvocationHandler 介面的類的 invoke()方法。 你可以在 invoke() 方法中自定義處理邏輯,比如在方法執行前後做什麼事情。

3.1.2. JDK 動態代理類使用步驟

  1. 定義一個介面及其實現類;
  2. 自定義 InvocationHandler 並重寫invoke方法,在 invoke 方法中我們會呼叫原生方法(被代理類的方法)並自定義一些處理邏輯;
  3. 通過 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法建立代理物件;

3.1.3. 程式碼示例

這樣說可能會有點空洞和難以理解,我上個例子,大家感受一下吧!

1.定義傳送簡訊的介面

public interface SmsService {
    String send(String message);
}

2.實現傳送簡訊的介面

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3.定義一個 JDK 動態代理類

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author shuang.kou
 * @createTime 2020年05月11日 11:23:00
 */
public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理類中的真實物件
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        //呼叫方法之前,我們可以新增自己的操作
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //呼叫方法之後,我們同樣可以新增自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}

invoke() 方法: 當我們的動態代理物件呼叫原生方法的時候,最終實際上呼叫到的是 invoke() 方法,然後 invoke() 方法代替我們去呼叫了被代理物件的原生方法。

4.獲取代理物件的工廠類

public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目標類的類載入
                target.getClass().getInterfaces(),  // 代理需要實現的介面,可指定多個
                new DebugInvocationHandler(target)   // 代理物件對應的自定義 InvocationHandler
        );
    }
}

getProxy() :主要通過Proxy.newProxyInstance()方法獲取某個類的代理物件

5.實際使用

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

執行上述程式碼之後,控制檯打印出:

before method send
send message:java
after method send

3.2. CGLIB 動態代理機制

3.2.1. 介紹

JDK 動態代理有一個最致命的問題是其只能代理實現了介面的類。

為了解決這個問題,我們可以用 CGLIB 動態代理機制來避免。

CGLIB(Code Generation Library)是一個基於ASM的位元組碼生成庫,它允許我們在執行時對位元組碼進行修改和動態生成。CGLIB 通過繼承方式實現代理。很多知名的開源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模組中:如果目標物件實現了介面,則預設採用 JDK 動態代理,否則採用 CGLIB 動態代理。

在 CGLIB 動態代理機制中 MethodInterceptor 介面和 Enhancer 類是核心。

你需要自定義 MethodInterceptor 並重寫 intercept 方法,intercept 用於攔截增強被代理類的方法。

public interface MethodInterceptor
extends Callback{
    // 攔截被代理類中的方法
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}
  1. obj :被代理的物件(需要增強的物件)
  2. method :被攔截的方法(需要增強的方法)
  3. args :方法入參
  4. methodProxy :用於呼叫原始方法

你可以通過 Enhancer類來動態獲取被代理類,當代理類呼叫方法的時候,實際呼叫的是 MethodInterceptor 中的 intercept 方法。

3.2.2. CGLIB 動態代理類使用步驟

  1. 定義一個類;
  2. 自定義 MethodInterceptor 並重寫 intercept 方法,intercept 用於攔截增強被代理類的方法,和 JDK 動態代理中的 invoke 方法類似;
  3. 通過 Enhancer 類的 create()建立代理類;

3.2.3. 程式碼示例

不同於 JDK 動態代理不需要額外的依賴。CGLIB(Code Generation Library) 實際是屬於一個開源專案,如果你要使用它的話,需要手動新增相關依賴。

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

1.實現一個使用阿里雲傳送簡訊的類

package github.javaguide.dynamicProxy.cglibDynamicProxy;

public class AliSmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

2.自定義 MethodInterceptor(方法攔截器)

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 自定義MethodInterceptor
 */
public class DebugMethodInterceptor implements MethodInterceptor {


    /**
     * @param o           代理物件(增強的物件)
     * @param method      被攔截的方法(需要增強的方法)
     * @param args        方法入參
     * @param methodProxy 用於呼叫原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //呼叫方法之前,我們可以新增自己的操作
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        //呼叫方法之後,我們同樣可以新增自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }

}

3.獲取代理類

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 建立動態代理增強類
        Enhancer enhancer = new Enhancer();
        // 設定類載入器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 設定被代理類
        enhancer.setSuperclass(clazz);
        // 設定方法攔截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 建立代理類
        return enhancer.create();
    }
}

4.實際使用

AliSmsService aliSmsService = 
    (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class); aliSmsService.send("java");

執行上述程式碼之後,控制檯打印出:

before method send
send message:java
after method send

3.3. JDK 動態代理和 CGLIB 動態代理對比

  1. JDK 動態代理只能只能代理實現了介面的類或者直接代理介面,而 CGLIB 可以代理未實現任何介面的類。 另外, CGLIB 動態代理是通過生成一個被代理類的子類來攔截被代理類的方法呼叫,因此不能代理宣告為 final 型別的類和方法。
  2. 就二者的效率來說,大部分情況都是 JDK 動態代理更優秀,隨著 JDK 版本的升級,這個優勢更加明顯。

4. 靜態代理和動態代理的對比

  1. 靈活性 :動態代理更加靈活,不需要必須實現介面,可以直接代理實現類,並且可以不需要針對每個目標類都建立一個代理類。另外,靜態代理中,介面一旦新增加方法,目標物件和代理物件都要進行修改,這是非常麻煩的!
  2. JVM 層面 :靜態代理在編譯時就將介面、實現類、代理類這些都變成了一個個實際的 class 檔案。而動態代理是在執行時動態生成類位元組碼,並載入到 JVM 中的。

來自:點選這裡