Java高級(jí)主題(五)——?jiǎng)討B(tài)代理

代理可以分為靜態(tài)代理、動(dòng)態(tài)代理翔冀,動(dòng)態(tài)代理又可以分為 jvm的動(dòng)態(tài)代理 和 cglib的動(dòng)態(tài)代理。像spring框架的AOP的底層就使用了動(dòng)態(tài)代理的技術(shù)。
代理(Proxy)是一種設(shè)計(jì)模式,提供了對(duì)目標(biāo)對(duì)象另外的訪問方式;即通過代理對(duì)象訪問目標(biāo)對(duì)象.這樣做的好處是:可以在目標(biāo)對(duì)象實(shí)現(xiàn)的基礎(chǔ)上,增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能.
這里使用到編程中的一個(gè)思想:不要隨意去修改別人已經(jīng)寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴(kuò)展該方法

舉個(gè)例子來說明代理的作用:假設(shè)我們想邀請(qǐng)一位明星,那么并不是直接連接明星,而是聯(lián)系明星的經(jīng)紀(jì)人,來達(dá)到同樣的目的.明星就是一個(gè)目標(biāo)對(duì)象,他只要負(fù)責(zé)活動(dòng)中的節(jié)目,而其他瑣碎的事情就交給他的代理人(經(jīng)紀(jì)人)來解決.這就是代理思想在現(xiàn)實(shí)中的一個(gè)例子
用圖表示如下:


image.png

代理模式的關(guān)鍵點(diǎn)是:代理對(duì)象與目標(biāo)對(duì)象.代理對(duì)象是對(duì)目標(biāo)對(duì)象的擴(kuò)展,并會(huì)調(diào)用目標(biāo)對(duì)象

一蚁署、靜態(tài)代理

靜態(tài)代理在使用時(shí),需要定義接口或者父類,被代理對(duì)象與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者是繼承相同父類.

下面舉個(gè)案例來解釋:
模擬保存動(dòng)作,定義一個(gè)保存動(dòng)作的接口:IUserDao.java,然后目標(biāo)對(duì)象實(shí)現(xiàn)這個(gè)接口的方法UserDao.java,此時(shí)如果使用靜態(tài)代理方式,就需要在代理對(duì)象(UserDaoProxy.java)中也實(shí)現(xiàn)IUserDao接口.調(diào)用的時(shí)候通過調(diào)用代理對(duì)象的方法來調(diào)用目標(biāo)對(duì)象.
需要注意的是,代理對(duì)象與目標(biāo)對(duì)象要實(shí)現(xiàn)相同的接口,然后通過調(diào)用相同的方法來調(diào)用目標(biāo)對(duì)象的方法
代碼示例:
接口:IUserDao.java

/**
 * 接口
 */
public interface IUserDao {

    void save();
}

目標(biāo)對(duì)象:UserDao.java

/**
 * 接口實(shí)現(xiàn)
 * 目標(biāo)對(duì)象
 */
public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已經(jīng)保存數(shù)據(jù)!----");
    }
}

代理對(duì)象:UserDaoProxy.java

/**
 * 代理對(duì)象,靜態(tài)代理
 */
public class UserDaoProxy implements IUserDao{
    //接收保存目標(biāo)對(duì)象
    private IUserDao target;
    public UserDaoProxy(IUserDao target){
        this.target=target;
    }

    public void save() {
        System.out.println("開始事務(wù)...");
        target.save();//執(zhí)行目標(biāo)對(duì)象的方法
        System.out.println("提交事務(wù)...");
    }
}

測(cè)試類:App.java

/**
 * 測(cè)試類
 */
public class App {
    public static void main(String[] args) {
        //目標(biāo)對(duì)象
        UserDao target = new UserDao();

        //代理對(duì)象,把目標(biāo)對(duì)象傳給代理對(duì)象,建立代理關(guān)系
        UserDaoProxy proxy = new UserDaoProxy(target);

        proxy.save();//執(zhí)行的是代理的方法
    }
}

靜態(tài)代理總結(jié):
1.可以做到在不修改目標(biāo)對(duì)象的功能前提下,對(duì)目標(biāo)功能擴(kuò)展.
2.缺點(diǎn):因?yàn)榇韺?duì)象需要與目標(biāo)對(duì)象實(shí)現(xiàn)一樣的接口,所以會(huì)有很多代理類,類太多.同時(shí),一旦接口增加方法,目標(biāo)對(duì)象與代理對(duì)象都要維護(hù).

如何解決靜態(tài)代理中的缺點(diǎn)呢?答案是可以使用動(dòng)態(tài)代理方式

二、動(dòng)態(tài)代理

2.1蚂四、JDK接口動(dòng)態(tài)代理

2.1.1 動(dòng)態(tài)代理特點(diǎn)

1.代理對(duì)象,不需要實(shí)現(xiàn)接口
2.代理對(duì)象的生成,是利用JDK的API,動(dòng)態(tài)的在內(nèi)存中構(gòu)建代理對(duì)象(需要我們指定創(chuàng)建代理對(duì)象/目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型)
3.動(dòng)態(tài)代理也叫做:JDK代理,接口代理

JDK中生成代理對(duì)象的API
代理類所在包:java.lang.reflect.Proxy
JDK實(shí)現(xiàn)代理只需要使用newProxyInstance方法,但是該方法需要接收三個(gè)參數(shù),完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意該方法是在Proxy類中是靜態(tài)方法,且接收的三個(gè)參數(shù)依次為:

  • ClassLoader loader,:指定當(dāng)前目標(biāo)對(duì)象使用類加載器,獲取加載器的方法是固定的
  • Class<?>[] interfaces,:目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型
  • InvocationHandler h:事件處理,執(zhí)行目標(biāo)對(duì)象的方法時(shí),會(huì)觸發(fā)事件處理器的方法,會(huì)把當(dāng)前執(zhí)行目標(biāo)對(duì)象的方法作為參數(shù)傳入
2.1.2 代碼示例

接口類IUserDao.java以及接口實(shí)現(xiàn)類,目標(biāo)對(duì)象UserDao是一樣的,沒有做修改.在這個(gè)基礎(chǔ)上,增加一個(gè)代理工廠類(ProxyFactory.java),將代理類寫在這個(gè)地方,然后在測(cè)試類(需要使用到代理的代碼)中先建立目標(biāo)對(duì)象和代理對(duì)象的聯(lián)系,然后代用代理對(duì)象的中同名方法

代理工廠類:ProxyFactory.java

/**
 * 創(chuàng)建動(dòng)態(tài)代理對(duì)象
 * 動(dòng)態(tài)代理不需要實(shí)現(xiàn)接口,但是需要指定接口類型
 */
public class ProxyFactory{

    //維護(hù)一個(gè)目標(biāo)對(duì)象
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }

   //給目標(biāo)對(duì)象生成代理對(duì)象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("開始事務(wù)2");
                        //執(zhí)行目標(biāo)對(duì)象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事務(wù)2");
                        return returnValue;
                    }
                }
        );
    }

}

測(cè)試類:App.java

/**
 * 測(cè)試類
 */
public class App {
    public static void main(String[] args) {
        // 目標(biāo)對(duì)象
        IUserDao target = new UserDao();
        // 【原始的類型 class cn.itcast.b_dynamic.UserDao】
        System.out.println(target.getClass());

        // 給目標(biāo)對(duì)象光戈,創(chuàng)建代理對(duì)象
        IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
        // class $Proxy0   內(nèi)存中動(dòng)態(tài)生成的代理對(duì)象
        System.out.println(proxy.getClass());

        // 執(zhí)行方法   【代理對(duì)象】
        proxy.save();
    }
}

總結(jié):
代理對(duì)象不需要實(shí)現(xiàn)接口(需實(shí)現(xiàn)InvocationHandler接口),但是目標(biāo)對(duì)象一定要實(shí)現(xiàn)接口,否則不能用動(dòng)態(tài)代理。

2.1.3 原理分析

首先是定義一個(gè)Person接口:

/**
 * 創(chuàng)建Person接口
 * @author Gonjan
 */
public interface Person {
    //上交班費(fèi)
    void giveMoney();
}

創(chuàng)建需要被代理的實(shí)際類:

public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }

    @Override
    public void giveMoney() {
        try {
          //假設(shè)數(shù)錢花了一秒時(shí)間
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       System.out.println(name + "上交班費(fèi)50元");
    }
}

再定義一個(gè)檢測(cè)方法執(zhí)行時(shí)間的工具類遂赠,在任何方法執(zhí)行前先調(diào)用start方法久妆,執(zhí)行后調(diào)用finsh方法,就可以計(jì)算出該方法的運(yùn)行時(shí)間跷睦,這也是一個(gè)最簡(jiǎn)單的方法執(zhí)行時(shí)間檢測(cè)工具筷弦。

public class MonitorUtil {

    private static ThreadLocal<Long> tl = new ThreadLocal<>();

    public static void start() {
        tl.set(System.currentTimeMillis());
    }

    //結(jié)束時(shí)打印耗時(shí)
    public static void finish(String methodName) {
        long finishTime = System.currentTimeMillis();
        System.out.println(methodName + "方法耗時(shí)" + (finishTime - tl.get()) + "ms");
    }
}

創(chuàng)建StuInvocationHandler類,實(shí)現(xiàn)InvocationHandler接口抑诸,這個(gè)類中持有一個(gè)被代理對(duì)象的實(shí)例target烂琴。InvocationHandler中有一個(gè)invoke方法,所有執(zhí)行代理對(duì)象的方法都會(huì)被替換成執(zhí)行invoke方法哼鬓。

再再invoke方法中執(zhí)行被代理對(duì)象target的相應(yīng)方法监右。當(dāng)然,在代理過程中异希,我們?cè)谡嬲龍?zhí)行被代理對(duì)象的方法前加入自己其他處理健盒。這也是Spring中的AOP實(shí)現(xiàn)的主要原理,這里還涉及到一個(gè)很重要的關(guān)于java反射方面的基礎(chǔ)知識(shí)称簿。

public class StuInvocationHandler<T> implements InvocationHandler {
   //invocationHandler持有的被代理對(duì)象
    T target;

    public StuInvocationHandler(T target) {
       this.target = target;
    }

    /**
     * proxy:代表動(dòng)態(tài)代理對(duì)象
     * method:代表正在執(zhí)行的方法
     * args:代表調(diào)用目標(biāo)方法時(shí)傳入的實(shí)參
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執(zhí)行" +method.getName() + "方法");
     */   
        //代理過程中插入監(jiān)測(cè)方法,計(jì)算該方法耗時(shí)
        MonitorUtil.start();
        Object result = method.invoke(target, args);
        MonitorUtil.finish(method.getName());
        return result;
    }
}

做完上面的工作后扣癣,我們就可以具體來創(chuàng)建動(dòng)態(tài)代理對(duì)象了,上面簡(jiǎn)單介紹了如何創(chuàng)建動(dòng)態(tài)代理對(duì)象憨降,我們使用簡(jiǎn)化的方式創(chuàng)建動(dòng)態(tài)代理對(duì)象:

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

        //創(chuàng)建一個(gè)實(shí)例對(duì)象父虑,這個(gè)對(duì)象是被代理的對(duì)象
        Person zhangsan = new Student("張三");

        //創(chuàng)建一個(gè)與代理對(duì)象相關(guān)聯(lián)的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);

        //創(chuàng)建一個(gè)代理對(duì)象stuProxy來代理zhangsan,代理對(duì)象的每個(gè)執(zhí)行方法都會(huì)替換執(zhí)行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler)授药;

       //代理執(zhí)行上交班費(fèi)的方法
        stuProxy.giveMoney();
    }
}

我們執(zhí)行這個(gè)ProxyTest類士嚎,先想一下呜魄,我們創(chuàng)建了一個(gè)需要被代理的學(xué)生張三,將zhangsan對(duì)象傳給了stuHandler中莱衩,我們?cè)趧?chuàng)建代理對(duì)象stuProxy時(shí)爵嗅,將stuHandler作為參數(shù)了的,上面也有說到所有執(zhí)行代理對(duì)象的方法都會(huì)被替換成執(zhí)行invoke方法笨蚁,也就是說睹晒,最后執(zhí)行的是StuInvocationHandler中的invoke方法。所以在看到下面的運(yùn)行結(jié)果也就理所當(dāng)然了括细。

運(yùn)行結(jié)果:

代理執(zhí)行g(shù)iveMoney方法
張三上交班費(fèi)50元
giveMoney方法耗時(shí)1001ms

上面說到伪很,動(dòng)態(tài)代理的優(yōu)勢(shì)在于可以很方便的對(duì)代理類的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個(gè)代理類中的方法奋单。是因?yàn)樗斜淮韴?zhí)行的方法锉试,都是通過在InvocationHandler中的invoke方法調(diào)用的,所以我們只要在invoke方法中統(tǒng)一處理辱匿,就可以對(duì)所有被代理的方法進(jìn)行相同的操作了键痛。例如,這里的方法計(jì)時(shí)匾七,所有的被代理對(duì)象執(zhí)行的方法都會(huì)被計(jì)時(shí)絮短,然而我只做了很少的代碼量。

動(dòng)態(tài)代理的過程昨忆,代理對(duì)象和被代理對(duì)象的關(guān)系不像靜態(tài)代理那樣一目了然丁频,清晰明了。因?yàn)閯?dòng)態(tài)代理的過程中邑贴,我們并沒有實(shí)際看到代理類席里,也沒有很清晰地的看到代理類的具體樣子,而且動(dòng)態(tài)代理中被代理對(duì)象和代理對(duì)象是通過InvocationHandler來完成的代理過程的拢驾,其中具體是怎樣操作的奖磁,為什么代理對(duì)象執(zhí)行的方法都會(huì)通過InvocationHandler中的invoke方法來執(zhí)行。帶著這些問題繁疤,我們就需要對(duì)java動(dòng)態(tài)代理的源碼進(jìn)行簡(jiǎn)要的分析咖为,弄清楚其中緣由。
**1.Java 動(dòng)態(tài)代理創(chuàng)建出來的動(dòng)態(tài)代理類 **
上面我們利用Proxy類的newProxyInstance方法創(chuàng)建了一個(gè)動(dòng)態(tài)代理對(duì)象稠腊,查看該方法的源碼躁染,發(fā)現(xiàn)它只是封裝了創(chuàng)建動(dòng)態(tài)代理類的步驟(紅色標(biāo)準(zhǔn)部分):

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

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

其實(shí),我們最應(yīng)該關(guān)注的是 Class

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
        String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理類class文件寫入成功");
        } catch (Exception e) {
           System.out.println("寫文件錯(cuò)誤");
        }

對(duì)這個(gè)class文件進(jìn)行反編譯架忌,我們看看jdk為我們生成了什么樣的內(nèi)容:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;

public final class $Proxy0 extends Proxy implements Person
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;

  /**
  *注意這里是生成代理類的構(gòu)造方法吞彤,方法參數(shù)為InvocationHandler類型,看到這,是不是就有點(diǎn)明白
  *為何代理對(duì)象調(diào)用方法都是執(zhí)行InvocationHandler中的invoke方法饰恕,而InvocationHandler又持有一個(gè)
  *被代理對(duì)象的實(shí)例挠羔,不禁會(huì)想難道是....? 沒錯(cuò)埋嵌,就是你想的那樣褥赊。
  *
  *super(paramInvocationHandler),是調(diào)用父類Proxy的構(gòu)造方法莉恼。
  *父類持有:protected InvocationHandler h;
  *Proxy構(gòu)造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  //這個(gè)靜態(tài)塊本來是在最后的,我把它拿到前面來速那,方便描述
   static
  {
    try
    {
      //看看這兒靜態(tài)塊兒里面有什么俐银,是不是找到了giveMoney方法。請(qǐng)記住giveMoney通過反射得到的名字m3端仰,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }

  /**
  * 
  *這里調(diào)用代理對(duì)象的giveMoney方法捶惜,直接就調(diào)用了InvocationHandler中的invoke方法,并把m3傳了進(jìn)去荔烧。
  *this.h.invoke(this, m3, null);這里簡(jiǎn)單吱七,明了。
  *來鹤竭,再想想踊餐,代理對(duì)象持有一個(gè)InvocationHandler對(duì)象,InvocationHandler對(duì)象持有一個(gè)被代理的對(duì)象臀稚,
  *再聯(lián)系到InvacationHandler中的invoke方法吝岭。嗯,就是這樣吧寺。
  */
  public final void giveMoney()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  //注意窜管,這里為了節(jié)省篇幅,省去了toString稚机,hashCode幕帆、equals方法的內(nèi)容。原理和giveMoney方法一毛一樣赖条。

}

jdk為我們的生成了一個(gè)叫$Proxy0(這個(gè)名字后面的0是編號(hào)失乾,有多個(gè)代理類會(huì)一次遞增)的代理類,這個(gè)類文件時(shí)放在內(nèi)存中的谋币,我們?cè)趧?chuàng)建代理對(duì)象時(shí)仗扬,就是通過反射獲得這個(gè)類的構(gòu)造方法,然后創(chuàng)建的代理實(shí)例蕾额。通過對(duì)這個(gè)生成的代理類源碼的查看早芭,我們很容易能看出,動(dòng)態(tài)代理實(shí)現(xiàn)的具體過程诅蝶。

我們可以對(duì)InvocationHandler看做一個(gè)中介類退个,中介類持有一個(gè)被代理對(duì)象募壕,在invoke方法中調(diào)用了被代理對(duì)象的相應(yīng)方法。通過聚合方式持有被代理對(duì)象的引用语盈,把外部對(duì)invoke的調(diào)用最終都轉(zhuǎn)為對(duì)被代理對(duì)象的調(diào)用舱馅。

代理類調(diào)用自己方法時(shí),通過自身持有的中介類對(duì)象來調(diào)用中介類對(duì)象的invoke方法刀荒,從而達(dá)到代理執(zhí)行被代理對(duì)象的方法代嗤。也就是說,動(dòng)態(tài)代理通過中介類實(shí)現(xiàn)了具體的代理功能缠借。

生成的代理類:$Proxy0 extends Proxy implements Person干毅,我們看到代理類繼承了Proxy類,所以也就決定了java動(dòng)態(tài)代理只能對(duì)接口進(jìn)行代理泼返,Java的繼承機(jī)制注定了這些動(dòng)態(tài)代理類們無法實(shí)現(xiàn)對(duì)class的動(dòng)態(tài)代理硝逢。
上面的動(dòng)態(tài)代理的例子,其實(shí)就是AOP的一個(gè)簡(jiǎn)單實(shí)現(xiàn)了绅喉,在目標(biāo)對(duì)象的方法執(zhí)行之前和執(zhí)行之后進(jìn)行了處理渠鸽,對(duì)方法耗時(shí)統(tǒng)計(jì)。Spring的AOP實(shí)現(xiàn)其實(shí)也是用了Proxy和InvocationHandler這兩個(gè)東西的柴罐。

2.2 Cglib動(dòng)態(tài)代理

2.2.1 Cglib動(dòng)態(tài)代理特點(diǎn)

上面的靜態(tài)代理和動(dòng)態(tài)代理模式都是要求目標(biāo)對(duì)象是實(shí)現(xiàn)一個(gè)接口的目標(biāo)對(duì)象,但是有時(shí)候目標(biāo)對(duì)象只是一個(gè)單獨(dú)的對(duì)象,并沒有實(shí)現(xiàn)任何的接口,這個(gè)時(shí)候就可以使用以目標(biāo)對(duì)象子類的方式類實(shí)現(xiàn)代理,這種方法就叫做:Cglib代理

Cglib代理,也叫作子類代理,它是在內(nèi)存中構(gòu)建一個(gè)子類對(duì)象從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象功能的擴(kuò)展.

  • JDK的動(dòng)態(tài)代理有一個(gè)限制,就是使用動(dòng)態(tài)代理的對(duì)象必須實(shí)現(xiàn)一個(gè)或多個(gè)接口,如果想代理沒有實(shí)現(xiàn)接口的類,就可以使用Cglib實(shí)現(xiàn).
  • Cglib是一個(gè)強(qiáng)大的高性能的代碼生成包,它可以在運(yùn)行期擴(kuò)展java類與實(shí)現(xiàn)java接口.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
  • Cglib包的底層是通過使用一個(gè)小而塊的字節(jié)碼處理框架ASM來轉(zhuǎn)換字節(jié)碼并生成新的類.不鼓勵(lì)直接使用ASM,因?yàn)樗竽惚仨殞?duì)JVM內(nèi)部結(jié)構(gòu)包括class文件的格式和指令集都很熟悉.

Cglib子類代理實(shí)現(xiàn)方法:

  • 1.需要引入cglib的jar文件,但是Spring的核心包中已經(jīng)包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
  • 2.引入功能包后,就可以在內(nèi)存中動(dòng)態(tài)構(gòu)建子類
  • 3.代理的類不能為final,否則報(bào)錯(cuò)
  • 4.目標(biāo)對(duì)象的方法如果為final/static,那么就不會(huì)被攔截,即不會(huì)執(zhí)行目標(biāo)對(duì)象額外的業(yè)務(wù)方法.
2.2.2 代碼示例

目標(biāo)對(duì)象類:UserDao.java

/**
 * 目標(biāo)對(duì)象,沒有實(shí)現(xiàn)任何接口
 */
public class UserDao {

    public void save() {
        System.out.println("----已經(jīng)保存數(shù)據(jù)!----");
    }
}

Cglib代理工廠:ProxyFactory.java

/**
 * Cglib子類代理工廠
 * 對(duì)UserDao在內(nèi)存中動(dòng)態(tài)構(gòu)建一個(gè)子類對(duì)象
 */
public class ProxyFactory implements MethodInterceptor{
    //維護(hù)目標(biāo)對(duì)象
    private Object target;

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

    //給目標(biāo)對(duì)象創(chuàng)建一個(gè)代理對(duì)象
    public Object getProxyInstance(){
        //1.工具類
        Enhancer en = new Enhancer();
        //2.設(shè)置父類
        en.setSuperclass(target.getClass());
        //3.設(shè)置回調(diào)函數(shù)
        en.setCallback(this);
        //4.創(chuàng)建子類(代理對(duì)象)
        return en.create();

    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("開始事務(wù)...");

        //執(zhí)行目標(biāo)對(duì)象的方法
        Object returnValue = method.invoke(target, args);

        System.out.println("提交事務(wù)...");

        return returnValue;
    }
}

測(cè)試類:

/**
 * 測(cè)試類
 */
public class App {

    @Test
    public void test(){
        //目標(biāo)對(duì)象
        UserDao target = new UserDao();

        //代理對(duì)象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

        //執(zhí)行代理對(duì)象的方法
        proxy.save();
    }
}

在Spring的AOP編程中:
如果加入容器的目標(biāo)對(duì)象有實(shí)現(xiàn)接口,用JDK代理
如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口,用Cglib代理

2.2.3 Cglib代理原理分析

如果我們了解了CGLIB創(chuàng)建代理類的原理徽缚,那么其局限性也就一目了然。我們現(xiàn)在做個(gè)實(shí)驗(yàn)丽蝎,將UserDao類加上final修飾符猎拨,使其不可被繼承:

再次執(zhí)行測(cè)試代碼,這次就報(bào)錯(cuò)了: Cannot subclass final class XXXX屠阻。

所以通過CGLIB成功創(chuàng)建的動(dòng)態(tài)代理红省,實(shí)際是被代理類的一個(gè)子類。那么如果被代理類被標(biāo)記成final国觉,也就無法通過CGLIB去創(chuàng)建動(dòng)態(tài)代理吧恃。

3、通過編譯期提供的API動(dòng)態(tài)創(chuàng)建代理類

假設(shè)我們確實(shí)需要給一個(gè)既是final麻诀,又未實(shí)現(xiàn)任何接口的ProductOwner類創(chuàng)建動(dòng)態(tài)代碼痕寓。除了InvocationHandler和CGLIB外,我們還有最后一招:

我直接把一個(gè)代理類的源代碼用字符串拼出來蝇闭,然后基于這個(gè)字符串調(diào)用JDK的Compiler(編譯期)API呻率,動(dòng)態(tài)的創(chuàng)建一個(gè)新的.java文件,然后動(dòng)態(tài)編譯這個(gè).java文件呻引,這樣也能得到一個(gè)新的代理類礼仗。

package proxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class ProductOwnerSourceCodeProxy  {

    public static void main(String[] arg) throws Exception {
        Class<?> c = getProxyClass();
        Constructor<?>[] constructor = c.getConstructors();
        Object POProxy = constructor[0].newInstance("Ross");
        Method defineBackLog = c.getDeclaredMethod("defineBackLog");
        defineBackLog.invoke(POProxy);
    }

    private static String getSourceCode() {
        String src = "package proxy;\n\n"
                + "public final class ProductOwnerSCProxy {\n" 
                + "\tprivate String name;\n\n"
                + "\tpublic ProductOwnerSCProxy(String name){\n" 
                + "\t\tthis.name = name;\n" + "\t}\n\n"
                + "\t\tpublic void defineBackLog(){\n"
                + "\t\tSystem.out.println(\"PO writes some document before defining BackLog\");"
                + "\t\tSystem.out.println(\"PO: \" + name + \" defines Backlog.\");}}\n";
        return src;
    }

    private static String createJavaFile(String sourceCode) {
        String fileName = "C:\\Users\\i042416\\git\\JavaTwoPlusTwoEquals5\\src\\proxy\\ProductOwnerSCProxy.java";
        File javaFile = new File(fileName);
        Writer writer;
        try {
            writer = new FileWriter(javaFile);
            writer.write(sourceCode);
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fileName;
    }

    private static void compile(String fileName) {
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> iter = sjfm.getJavaFileObjects(fileName);
            CompilationTask ct = compiler.getTask(null, sjfm, null, null, null, iter);
            ct.call();
            sjfm.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static Class<?> loadClass() {
        URL[] urls;
        String path = "file:\\C:\\Users\\i042416\\git\\JavaTwoPlusTwoEquals5\\src\\";
        Class<?> c = null;
        try {
            urls = new URL[] { (new URL(path)) };
            URLClassLoader ul = new URLClassLoader(urls);
            c = ul.loadClass("proxy.ProductOwnerSCProxy");
            ul.close();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return c;
    }

    private static Class<?> getProxyClass() {
        String sourceCode = getSourceCode();
        String javaFile = createJavaFile(sourceCode);
        compile(javaFile);
        return loadClass();
    }
}

參考
Java代理設(shè)計(jì)模式(Proxy)的四種具體實(shí)現(xiàn)
java 代理 理解原理及實(shí)現(xiàn)
Java的三種代理模式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子元践,更是在濱河造成了極大的恐慌韭脊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件单旁,死亡現(xiàn)場(chǎng)離奇詭異沪羔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)象浑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蔫饰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人愉豺,你說我怎么就攤上這事死嗦。” “怎么了粒氧?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)节腐。 經(jīng)常有香客問我外盯,道長(zhǎng),這世上最難降的妖魔是什么翼雀? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任饱苟,我火速辦了婚禮,結(jié)果婚禮上狼渊,老公的妹妹穿的比我還像新娘箱熬。我一直安慰自己,他們只是感情好狈邑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布城须。 她就那樣靜靜地躺著,像睡著了一般米苹。 火紅的嫁衣襯著肌膚如雪糕伐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天蘸嘶,我揣著相機(jī)與錄音良瞧,去河邊找鬼。 笑死训唱,一個(gè)胖子當(dāng)著我的面吹牛褥蚯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播况增,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼赞庶,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起尘执,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤舍哄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后誊锭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體表悬,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年丧靡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蟆沫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡温治,死狀恐怖饭庞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情熬荆,我是刑警寧澤舟山,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站卤恳,受9級(jí)特大地震影響累盗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜突琳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一若债、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拆融,春花似錦蠢琳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至趟脂,卻和暖如春躏碳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背散怖。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工菇绵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镇眷。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓咬最,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親欠动。 傳聞我的和親對(duì)象是個(gè)殘疾皇子永乌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容