java面試常見模式問題—代理模式

  • 本篇總結的是 代理設計模式,後續會經常更新~
  • 代理模式最直觀的解釋就是,通過代理,將被代理對象 “增強”!(即,擴展被代理對象的功能)
  • 代理模式分為靜態代理,和動態代理:動態代理的代理類是動態生成的 , 靜態代理的代理類是我們提前寫好的邏輯。
  • Java 中實現動態代理的方式有 2 種:
  • JDK 動態代理
  • CGLIB 動態代理

1、靜態代理

靜態代理角色分析

  • 抽象角色 :一般使用接口或者抽象類來實現。
  • 真實角色 :被代理的角色。
  • 代理角色: 代理真實角色 , 代理真實角色後 ,一般會做一些附屬的操作。
  • 調用方:使用代理角色來進行一些操作。

我們以租客租客租房子為例,涉及到的對象有:租客、中介、房東。(房東即為被代理對象,中介即為代理對象)

租客通過中介之手租住房東的房子,代理對象中介需要尋找租客租房,並從中獲取中介費用。

代碼實現

Rent.java 即抽象角色

// 抽象角色:租房
public interface Rent {
   public void rent();
}

Host.java 即真實角色

// 真實角色: 房東,房東要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

Proxy.java 即代理角色

//代理角色:中介
public class Proxy implements Rent {
   private Host host;
   public Proxy() { }
   public Proxy(Host host) {
       this.host = host;
  }
   // 租房
   public void rent(){
       seeHouse();
       host.rent();
       fare();
  }
   // 看房
   public void seeHouse(){
       System.out.println("帶房客看房");
  }
   // 收中介費
   public void fare(){
       System.out.println("收中介費");
  }
}

Client.java 調用方,即客戶

// 客戶類,一般客戶都會去找代理!
public class Client {
   public static void main(String[] args) {
       // 房東要租房
       Host host = new Host();
       // 中介幫助房東
       Proxy proxy = new Proxy(host);
       // 你去找中介!
       proxy.rent();
  }
}

靜態代理的缺點

需要手動創建代理類,如果需要代理的對象多瞭,那麼代理類也越來越多。

為瞭解決,這個問題,就有瞭動態代理 !

2、動態代理

說到動態代理,面試的時候肯定會問動態代理的兩種實現方式:

先來看公共的 UserService 接口,和 UserServiceImpl 實現類:

/**
 * @author csp
 * @date 2021-06-03
 */
public interface UserService {
    /**
     * 登錄
     */
    void login();
    /**
     * 登出
     */
    void logout();
}
/**
 * @author csp
 * @date 2021-06-03
 */
public class UserServiceImpl implements UserService{
    @Override
    public void login() {
        System.out.println("用戶登錄...");
    }
    @Override
    public void logout() {
        System.out.println("用戶推出登錄...");
    }
}

JDK 動態代理

代碼如下

/**
 * @author csp
 * @date 2021-06-03
 */
public class JDKProxyFactory implements InvocationHandler {
    // 目標對象(被代理對象)
    private Object target;
    public JDKProxyFactory(Object target) {
        super();
        this.target = target;
    }
    /**
     * 創建代理對象
     *
     * @return
     */
    public Object createProxy() {
        // 1.得到目標對象的類加載器
        ClassLoader classLoader = target.getClass().getClassLoader();
        // 2.得到目標對象的實現接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        // 3.第三個參數需要一個實現invocationHandler接口的對象
        Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
        return newProxyInstance;
    }
    /**
     * 真正執行代理增強的方法
     *
     * @param proxy  代理對象.一般不使用
     * @param method 需要增強的方法
     * @param args   方法中的參數
     * @return
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK 動態代理:登錄/登出前邏輯校驗......");
        Object invoke = method.invoke(target, args);
        System.out.println("JDK 動態代理:登錄/登出後日志打印......");
        return invoke;
    }
    public static void main(String[] args) {
        // 1.創建對象
        UserServiceImpl userService = new UserServiceImpl();
        // 2.創建代理對象
        JDKProxyFactory jdkProxyFactory = new JDKProxyFactory(userService);
        // 3.調用代理對象的增強方法,得到增強後的對象
        UserService userServiceProxy = (UserService) jdkProxyFactory.createProxy();
        userServiceProxy.login();
        System.out.println("==================================");
        userServiceProxy.logout();
    }
}

輸出結果如下

JDK 動態代理:登錄/登出前邏輯校驗……
用戶登錄…
JDK 動態代理:登錄/登出後日志打印……
==================================
JDK 動態代理:登錄/登出前邏輯校驗……
用戶推出登錄…
JDK 動態代理:登錄/登出後日志打印……

CGLIB 動態代理

代碼如下:

/**
 * @author csp
 * @date 2021-06-03
 */
public class CglibProxyFactory implements MethodInterceptor {
    // 目標對象(被代理對象)
    private Object target;
    // 使用構造方法傳遞目標對象
    public CglibProxyFactory(Object target) {
        super();
        this.target = target;
    }
    /**
     * 創建代理對象
     *
     * @return
     */
    public Object createProxy() {
        // 1.創建Enhancer
        Enhancer enhancer = new Enhancer();
        // 2.傳遞目標對象的class
        enhancer.setSuperclass(target.getClass());
        // 3.設置回調操作
        enhancer.setCallback(this);
        return enhancer.create();
    }
    /**
     * 真正執行代理增強的方法
     * @param o 代理對象
     * @param method 要增強的方法
     * @param objects 要增強方法的參數
     * @param methodProxy 要增強的方法的代理
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib 動態代理:登錄/登出前邏輯校驗......");
        Object invoke = method.invoke(target, objects);
        System.out.println("cglib 動態代理:登錄/登出後日志打印......");
        return invoke;
    }
    public static void main(String[] args) {
        // 1.創建對象
        UserServiceImpl userService = new UserServiceImpl();
        // 2.創建代理對象
        CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(userService);
        // 3.調用代理對象的增強方法,得到增強後的對象
        UserService userServiceProxy = (UserService) cglibProxyFactory.createProxy();
        userServiceProxy.login();
        System.out.println("==================================");
        userServiceProxy.logout();
    }
}

測試結果如下

cglib 動態代理:登錄/登出前邏輯校驗……
用戶登錄…
cglib 動態代理:登錄/登出後日志打印……
==================================
cglib 動態代理:登錄/登出前邏輯校驗……
用戶推出登錄…
cglib 動態代理:登錄/登出後日志打印……

面試題一:JDK動態代理和CGLIB動態代理區別?

① JDK 動態代理本質上是實現瞭被代理對象的接口,而 CGLib 本質上是繼承瞭被代理對象,覆蓋其中的方法。

② JDK 動態代理隻能對實現瞭接口的類生成代理,CGLib 則沒有這個限制。但是 CGLib 因為使用繼承實現,所以 CGLib 所以無法對 final private 方法static方法進行代理。

③ JDK 動態代理是 JDK 裡自帶的,CGLib 動態代理需要引入第三方的 jar 包。

④ 在調用代理方法上,JDK動態代理是通過反射機制調用,CGLib 是通過 FastClass 機制直接調用。(看過一篇文章,介紹說 FastClass 簡單的理解,就是使用一個 index 下標作為入參,可以直接定位到要調用的方法直接,並進行調用)

在性能上,JDK1.7 之前,由於使用瞭 FastClass 機制,CGLib 在執行效率上比 JDK 快,但是隨著 JDK 動態代理的不斷優化,從 JDK 1.7 開始,JDK 動態代理已經明顯比 CGLib 更快瞭。

面試題二:JDK 動態代理為什麼隻能對實現瞭接口的類生成代理?

根本原因是通過 JDK 動態代理生成的類已經繼承瞭 Proxy 類,所以無法再使用繼承的方式去對類實現代理。

總結

文章會不定時更新,有時候一天多更新幾篇,如果幫助您復習鞏固瞭知識點,還請三連支持一下,後續會億點點的更新!希望大傢多多關註WalkonNet的其他內容!

推薦閱讀: