天天看點

Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理

在我另一篇部落格SSM之Spring 02 —— 動态代理、AOP、Spring-MyBatis、聲明式事務中有簡單描述代理模式和AOP原理,當時自己也還是有些沒明白,後面在面試過程中被問到,這裡詳細說明一下。

AOP是什麼就不再說明了,主要解釋下AOP的原理——動态代理。

動态代理

和靜态代理不同,動态代理是動态生成代理對象,克服了靜态代理的問題——不同目标對象都需要寫一個代理類。

動态代理主要有兩種方式:JDK的動态代理和cglib代理。

JDK的動态代理

它是對對象進行代理,底層利用反射機制實作,同時需要我們自定義一個實作了InvocationHandler接口的類,這個接口中的invoke方法就是AOP的本質。

下面我們通過代碼來進行了解。

1、定義好UserDao以及實作類,這是我們常見的方式。

//為了友善就不寫參數了
public interface UserDao {
    boolean addUser();
    boolean deleteUser();
}

public class UserDaoImpl implements UserDao {
    @Override
    public boolean addUser() {
        System.out.println("addUser();");
        return false;
    }

    @Override
    public boolean deleteUser() {
        System.out.println("deleteUser();");
        return true;
    }
}
           

2、寫一個測試,具體解釋看注解。可以看到Proxy和InvocationHandler都是reflect包下的,說明底層肯定是反射。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
      	//建立好目标對象
        UserDao ud = new UserDaoImpl();
      
      	//這個就是我們自定義的一個實作類,裡面invoke方法就是AOP的原理。
      	InvocationHandler ih = new MyInvocationHandler(ud);//将ud作為目标對象傳進去
      
        /*
        		這個方法傳回一個代理對象,我們會在代理對象中做一些其他操作
            newProxyInstance(ClassLoader, interfaces, InvocationHandler)
            1、ClassLoader:目标對象的類加載器,代碼是固定的。
            2、interfaces:目标對象的接口數組,代碼也是固定的,都是通過反射獲得。
            3、InvocationHandler:這是關鍵點,AOP能實作方法的增強就在這個接口裡。需要自己定義。
         */
        Object newProxyInstance = Proxy.newProxyInstance(ud.getClass().getClassLoader(),
                ud.getClass().getInterfaces(), ih);
      
        //執行代理對象被增強後的方法(增強的意思是在原有方法的基礎上添加了其他效果,同時不影響原有業務,同Spring AOP的事務)
        UserDao proxyUserDao = (UserDao)newProxyInstance;
        proxyUserDao.addUser();
        System.out.println("--------------");
        proxyUserDao.deleteUser();
    }
}
           

3、自定義的實作類,這是實作AOP增強方法的關鍵。

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

public class MyInvocationHandler implements InvocationHandler {
    private Object object;

    public MyInvocationHandler(Object object) {
        this.object = object;
    }

    /**
     *
     * @param proxy 代理對象,就是我們從外部傳進來的,但一般不用這個,會用我們自定義的object成員。
     * @param method 目标對象的方法對象,是JDK傳過來的。
     * @param args 目标對象方法的參數對象。
     * @return 對應于我們目标對象的方法的傳回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        String before = "";
        String after = "";
        //這裡通過反射來判斷UserDao的兩個方法,如果不判斷,那麼就會針對目标對象的所有方法進行增強。
        if(method.getName().equals("addUser")){
            before = "前置通知:addUser()方法執行 之前";
            after = "後置通知:addUser()方法執行 之後";
        }
        if(method.getName().equals("deleteUser")){
            before = "前置通知:deleteUser()方法執行 之前";
            after = "後置通知:deleteUser()方法執行 之後";
        }
        //下面這樣就實作了和Spring AOP 一樣的效果:方法的增強,即在方法執行前做一些其他事,且不影響到原有業務。
        System.out.println(before);
        Object result = method.invoke(object, args);//通過反射調用目标對象的方法
        System.out.println(after);
        return result;
    }
}
           

4、運作結果:

Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理

為什麼說JDK是面向接口的?

這裡的接口不是指InvocationHandler,而是指UserDao,我們看看通過

newProxyInstance

得帶的代理類的父類。

Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理

顯然,隻要我們通過JDK的Proxy代理獲得的類,都會繼承Proxy,那麼我們是如何能夠讓這個類有UserDaoImpl的方法的呢?因為這個類繼承了UserDao。

Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理

這也就解釋了,為什麼我們說JDK動态代理是面向接口的。因為Java是單繼承,而我們通過JDK動态代理得到的代理都會繼承Proxy,同時我們要表明這個代理對象是UserDaoImpl的執行個體,那麼就隻能通過接口的方式。

是以,如果我們有實作類(例如UserDaoImpl),就可以考慮到JDK動态代理。這也是Spring AOP的預設實作方式,我們一般都是用實作類。

cglib

和JDK動态代理不同,cglib(code generator library 代碼生成庫)代理的是位元組碼對象(即Class),而JDK動态代理代理的是對象。

Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理

步驟:

  1. cglib生成一個位元組碼對象(即Class對象),内容是空的。
  2. 設定位元組碼對象的父類為目标對象,即UserDaoImpl。
  3. 通過位元組碼對象中繼承了目标對象的方法,即addUser,進行回調,調用父類的addUser,并在執行其方法前完成方法增強。
  4. 建立代理對象,通過代理對象執行Class的方法。

注意:cglib是Spring中的,是以需要導入Spring核心包才能使用。

Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理

導入進來

Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理

下面進行代碼說明:

這裡面CallBack就是AOP的原理,它通過子類調用父類UserDaoImpl的addUser方法,并在調用時完成增強。

package test02_AOP.cglib;

import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;

public class Test {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();//空的位元組碼對象
        enhancer.setSuperclass(UserDaoImpl.class);//設定父類
        Callback callback = new MyCallback();
        enhancer.setCallback(callback);//設定回調

        UserDao userDao = (UserDao) enhancer.create();//用代理類Enhancer 建立代理對象
        userDao.addUser();
    }
}
           
package test02_AOP.cglib;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

//本來是繼承Callback接口,表示回調
//但Callback接口裡什麼都沒有,隻是起到一個标志作用,是以繼承其子類MethodInterceptor
//MethodInterceptor接口僅有一個方法,攔截我們待增強的方法
public class MyCallback implements MethodInterceptor {
    /**
     *
     * @param proxy 代理對象,即前面圖裡面的 class XX extends UserDaoImpl的XX對象
     * @param method 目标對象的方法
     * @param args 目标對象中的 方法對象 的參數對象(相當于addUser方法的參數)
     * @param methodProxy 代理對象中的 方法代理對象(相當于父類UserDaoImpl的addUser方法)
     * @return 傳回代理對象方法的結果
     * @throws Throwable
     */
    @Override
    public Object intercept(
            Object proxy, Method method, Object[] args, MethodProxy methodProxy
                           ) throws Throwable {
        System.out.println("前置通知");
        //執行原來的方法
        Object object = methodProxy.invokeSuper(proxy, args);//這裡不能用invoke,相當于前面圖裡super.addUser()
        System.out.println("後置通知");

        return object;
    }
}
           

結果:

Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理

總結

AOP在Spring中實作:

1、注解方式

  • 有一個注解@EnableAspectJAutoProxy(ProxyTargetClass = false)
  • 預設為false。如果是實作類(實作),就會用jdk,如果沒有實作接口 ,就會用 cglib。
  • 如果是true。無論是否實作接口,都會使用 cglib。
    Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理
Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理

2、XML方式

3、JDK和cglib小結

  • JDK動态代理是面向接口的。
  • cglib是通過位元組碼底層繼承被代理類,然後重寫父類方法(如果被代理類繼承final則會失敗)
  • 如果被代理的對象是一個實作類(例如UserDaoImpl),那麼Spring AOP會預設用JDK動态代理,否則使用cglib。
  • 性能比較:(參考部落格 https://blog.csdn.net/xlgen157387/article/details/82497594)
    • 關于兩者之間的性能的話,JDK動态代理所建立的代理對象,在以前的JDK版本中,性能并不是很高,雖然在高版本中JDK動态代理對象的性能得到了很大的提升,但是他也并不是适用于所有的場景。主要展現在如下的兩個名額中:

      1、CGLib所建立的動态代理對象在實際運作時候的性能要比JDK動态代理高不少,有研究表明,大概要高10倍;

      2、但是CGLib在建立對象的時候所花費的時間卻比JDK動态代理要多很多,有研究表明,大概有8倍的差距;

      3、是以,對于singleton的代理對象或者具有執行個體池的代理,因為無需頻繁的建立代理對象,是以比較适合采用CGLib動态代理,反正,則比較适用JDK動态代理。

    • 經過原部落客的實驗後得出結果:(具體請參考原部落格)

      最終的測試結果大緻是這樣的,在1.6和1.7的時候,JDK動态代理的速度要比CGLib動态代理的速度要慢,但是并沒有教科書上的10倍差距,在JDK1.8的時候,JDK動态代理的速度已經比CGLib動态代理的速度快很多了。

    • 原部落客下的一個評論(曾經的随性的回複)

      jdk的版本優化中主要是針對虛拟機對反射調用的優化,在jdk1.6中,我們采用JDK代理的方式來生成動态代理類,反射方法的調用在15次以内是調用本地方法,即是java到c++代碼轉換的方法,這種方式比直接生成位元組碼檔案要快的多,而在15次之後則開始使用java實作的方式。而在1.8的版本優化中,反射調用的次數達到門檻值[也就是發射調用的類成為熱點時]之後采用位元組碼的方式,因為位元組碼的 方式隻有在第一次生成位元組碼檔案時比較消耗時間。