JDK 動態代理與 CGLIB 動態代理,它倆真的不一樣

語言: CN / TW / HK

摘要: 一文帶你搞懂JDK 動態代理與 CGLIB 動態代理

本文分享自華為雲社群《 一文帶你搞懂JDK 動態代理與 CGLIB 動態代理 》,作者: Code皮皮蝦 。

兩者有何區別

1、Jdk動態代理:利用攔截器(必須實現InvocationHandler介面)加上 反射機制 生成一個代理介面的匿名類,在呼叫具體方法前呼叫InvokeHandler來處理

2、 Cglib動態代理:利用ASM框架,對代理物件類生成的class檔案載入進來,通過 修改其位元組碼生成子類來進行代理

所以:

  • 如果想要實現JDK動態代理那麼代理類必須實現介面,否則不能使用;
  • 如果想要使用CGlib動態代理,那麼代理類不能使用final修飾類和方法;

還有: 在jdk6、jdk7、jdk8逐步對JDK動態代理優化之後,在呼叫次數較少的情況下,JDK代理效率高於CGLIB代理效率,只有當進行大量呼叫的時候,jdk6和jdk7比CGLIB代理效率低一點,但是到jdk8的時候,jdk代理效率高於CGLIB代理。

如何實現

JDK動態代理

UserService介面

public interface UserService {

    void addUser();

    void updateUser(String str);

}

UserServiceImpl實現類

public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("新增使用者");
    }

    @Override
    public void updateUser(String str) {
        System.out.println("更新使用者資訊" + str);
    }
}

UserProxy代理類,實現InvocationHandler介面重寫invoke方法

public class UserProxy implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object res = method.invoke(target, args);

        System.out.println("記錄日誌");

        return res;
    }
}

test測試類

public class test {
    public static void main(String[] args) {

        UserServiceImpl impl = new UserServiceImpl();
        UserProxy userProxy = new UserProxy(impl);
        UserService userService = (UserService) Proxy.newProxyInstance(impl.getClass().getClassLoader(),impl.getClass().getInterfaces(),userProxy);
        userService.addUser();
        userService.updateUser(":我是皮皮蝦");
    }

}

可見實現了增強,打印出記錄日誌

CGlib動態代理

CGlib不像是JDK動態代理,CGlib需要匯入Jar包,那麼我用SpringBoot直接匯入依賴

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

UserServiceImpl被代理類

public class UserServiceImpl {

    public void addUser() {
        System.out.println("添加了一個使用者");
    }

    public void deleteUser() {
        System.out.println("刪除了一個使用者");
    }

}

UserServiceCGlib代理

public class UserServiceCGlib implements MethodInterceptor {
    private Object target;

    public UserServiceCGlib() {
    }

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

    //返回一個代理物件:    是 target物件的代理物件
    public Object getProxyInstance() {
        //1. 建立一個工具類
        Enhancer enhancer = new Enhancer();
        //2. 設定父類
        enhancer.setSuperclass(target.getClass());
        //3. 設定回撥函式
        enhancer.setCallback(this);
        //4. 建立子類物件,即代理物件
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("增強開始~~~");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("增強結束~~~");
        return result;
    }

}

test測試類

public class test {

    public static void main(String[] args) {
        UserServiceCGlib serviceCGlib = new UserServiceCGlib(new UserServiceImpl());
        UserServiceImpl userService = (UserServiceImpl)serviceCGlib.getProxyInstance();
        userService.addUser();
        System.out.println();
        userService.deleteUser();
    }

}

可見實現了增強,打印出記錄日誌

使用場景

到這裡相信各位小夥伴們已經基本掌握了JDK動態代理和CGlib動態代理的區別和實現

但是,如果是在面試過程中,除了要答出以上要點,你還要回答出它們的使用場景,這其實就是面試的加分項

那麼,這兩個動態代理的使用場景是什麼呢???

答案:Spring AOP

以下是Spring AOP建立代理的方法

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {

   if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass == null) {
         throw new AopConfigException("TargetSource cannot determine target class: " +
               "Either an interface or a target is required for proxy creation.");
      }
      //如果
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
         return new JdkDynamicAopProxy(config);
      }
      return new ObjenesisCglibAopProxy(config);
   }
   else {
      return new JdkDynamicAopProxy(config);
   }
}

1、如果目標物件實現了介面, 預設情況下會採用JDK的動態代理

2、如果目標物件實現了介面,也可以 強制使用CGLIB

3、如果目標物件 沒有實現了介面,必須採用CGLIB庫 ,spring會自動在JDK動態代理和CGLIB之間轉換

如果需要強制使用CGLIB來實現AOP,需要配置spring.aop.proxy-target-class=true或@EnableAspectJAutoProxy(proxyTargetClass = true

點選關注,第一時間瞭解華為雲新鮮技術~