代理模式和动态代理

356 阅读14分钟

文章开始

上一篇文章为大家介绍了spring中scoped-proxy的一些源码,其中分析到ProxyFactory.getProxy()方法就没有继续往下分析了,因为在分析spring的生成代理对象之前,为了方便大家理解,我们先来了解一下这个代理是怎么玩的,再此之后再去看源码会好接受一些。

代理模式

是什么?

在目标对象的行为范围内,代理对象代替目标对象执行目标对象的行为。

先举个现实生活中的例子,去租房子,但是没办法短时间内直接找到房东去找到租房信息,你只能去网上找,但是找到的信息都是中介发布的信息。 只能去跟中介租房。

: 客户端

中介 代理对象(和目标对象具有一样的行为)

房东 目标对象

从角色上来看,代理对象中介和目标对象房东具有一致的行为时,中介才能叫做房东的代理,即代理对象需和目标对象的拥有一致的行为。

为什么

为什么需要代理?

  • 1 保护目标对象

    房东而言,我可能有一些自己得其他行为,比如说打麻将,这种行为并不开放给租客,但是一旦租客直接拿到了房东对象的话,那么租客就有可能会调用房东的打麻将方法。但是有了中介之后就不一样了,房东只暴露了租房方法给中介,所以租客中介再怎么打交道,最终只会调用房东的租房方法,租客根本接触不到房东,何来调用房东的打麻将方法呢?

  • 2 扩展目标对象功能

    中介而言,我不是免费的替房东出租房屋的,所以我可以在房东租房的方法前后加上自己的需求。比如说,房东租房 1000块/1月,我可以租给租客 1200块/1月,有200块可以落我自己腰包。

  • 3 客户端与目标对象分离解耦

    房东来说,有了中介之后,我只用告诉他我租房的价格和其他要求,剩余的时间我就该打麻将打麻将了,只要中介每月把钱打我卡上就行。对于租客来说,只要中介的价格合适,而且房间满足我的要求,我就不用担心该房间的真实房东到底是谁了。

怎么用?

由于代理对象需要保证和目标对象具有一样的对象行为,所以需要代理目标对象实现同一个接口,由于代理又需要调用目标对象的真实行为,所以代理类里面再聚合一个目标对象即可(就是塞一个目标对象的属性)类图如下

上代码:

租房接口

public interface Renting {
    void rentRoom();
}

房东类

public class HouseOwner implements Renting{

    @Override
    public void rentRoom() {
        System.out.println("我是房东,租房价格 1000元/1月");
    }
}

代理类

public class HouseOwnerProxy implements Renting{
	
    //这里用租房接口来接收房东对象,是因为代理只对房东的租房方法感兴趣,如果房东对象还有一个公开的打麻将方法,那也是房东类自己的事情。
    private Renting renting;
    
    @Override
    public void rentRoom() {
        System.out.println("我是中介,我要在房东租房之前干点什么");
        renting.rentRoom();
        System.out.println("我是中介,我要在房东租房之后干点什么");
    }
	//使用代理之前得先传入一个真正的目标对象,这里就是房东
    public HouseOwnerProxy(Renting renting) {
        this.renting = renting;
    }
}

测试类

public class ProxyTest {
    public static void main(String[] args) {
        //创建房东类
        HouseOwner houseOwner = new HouseOwner();

        //创建中介类,并将房东传入作为中介内部的一个对象
        HouseOwnerProxy houseOwnerProxy = new HouseOwnerProxy(houseOwner);

        //调用中介的租房方法
        houseOwnerProxy.rentRoom();
    }
}

测试结果

ok,代理对象已经能够在房东租房前后干自己任何自己想干的事情了,相信大家也不难理解,接下来我们来瞅瞅动态代理。

动态代理

ok,上面我们介绍了静态代理的及用法,但是我们的需求小姐姐有一天突发奇想,想要在基本上每一个方法上面都加一个代理,再目标对象方法前后做一些接口调用记录,统计一下接口调用次数和频率,找出哪些接口是客户关心的。如果按照我们上面的方法,我们是不是要为程序里面原本的每一个原本写好的对象都加上一个代理类呢?

显而易见不会这样搞的,但是为了小姐姐的需求又不能不实现, 有没有办法就是我只写一份代码,可以为每一个原来的类里面的方法都生成一个代理,再代理的方法里面再去统计接口调用次数和频率呢?这个时候就该我动态代理出马了

代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(HouseOwnerProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。

相比于静态代理, 动态代理的优势在于可以很方便的对目标对象的函数进行统一的处理,而不用修改每个目标对象中的方法。其中动态代理又分为JDK动态代理和Cglib动态代理,接下来详说两种模式。

jdk动态代理

上代码

创建代理提供者,实现一个获取代理的方法

//实现InvocationHandler接口,并实现invoke方法
//中介动态代理
public class HouseOwnerDynamicProxy implements InvocationHandler {
	
    //目标对象
    private Object object;
	
    //使用构造器将目标对象传入
    public HouseOwnerDynamicProxy(Object object) {
        this.object = object;
    }

    //三个参数分别为:代理对象,目标方法,目标方法参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("目标方法执行之前");
        Object invokeResult = method.invoke(object, args);
        System.out.println("目标方法执行之后");

        return invokeResult;
    }

    public Object getProxyInstance(){
        
        //传入目标对象的ClassLoader,目标对象的接口,和当前的实例对象(实际上是要传入InvocationHandler接口的实现类,因为当前类实现了InvocationHandler接口)
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
    }
    
    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }
}
public class JdkDynamicProxyTest {
    public static void main(String[] args) {

        //创建目标对象
        HouseOwner houseOwner = new HouseOwner();

        //创建InvocationHandler实例
        HouseOwnerDynamicProxy dynamicProxy = new HouseOwnerDynamicProxy(houseOwner);

        //根据动态代理类获取到代理对象
        Renting renting = (Renting) dynamicProxy.getProxyInstance();
        //调用代理的租房方法
        renting.rentRoom();

    }
}

看起来jdk动态代理的和我们上面的硬编码方式好像没多少区别,都需要硬编码,不要着急,我们再来看看下面的代码,或许你会发现些什么?

新增一个卖房接口

public interface SellHouse {
    void SellHouse();
}

添加一个卖房接口的实现类

public class HouseSeller implements SellHouse{

    @Override
    public void SellHouse() {
        System.out.println("我是卖房人员,我现在要卖房子");
    }
}

重新编写我们的测试类

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

        //创建目标对象
        HouseOwner houseOwner = new HouseOwner();

        //创建InvocationHandler实例
        HouseOwnerDynamicProxy dynamicProxy = new HouseOwnerDynamicProxy(houseOwner);

        //根据动态代理类获取到代理对象
        Renting rentingProxy = (Renting) dynamicProxy.getProxyInstance();

        rentingProxy.rentRoom();

        System.out.println("----------我是隔断线------------");

        //将InvocationHandler实例中的目标对象set成售房人员对象
        dynamicProxy.setObject(new HouseSeller());

        //获取售房人员的代理
        SellHouse sellHouseProxy = (SellHouse) dynamicProxy.getProxyInstance();
        //售房人员代理的售房方法
        sellHouseProxy.SellHouse();

    }
}

查看控制台输出

有没有发现,我们写了一个InvocationHandler的实现类,但是该类可以帮我们代理不同的接口(案例中是Renting租房接口和SellHouse卖房接口)。这样的话就为我们一些通用的切面功能提供了无限可能。比如说有很多很多个对象(jdk代理得实现接口,jdk动态代理需要这个)都需要前后都需要做处理,那么我只需要该一份代码,就能为这些很多很多个对象生成代理类,代码里面只要用到代理类,就能一直做前置或后置处理了。

分析一波怎么实现的

我们获取代理类的时候都调用了dynamicProxy.getProxyInstance()方法,而该方法又调用了Proxy类的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

三个入参分别为

Parameters:

  • loader - the class loader to define the proxy class(类加载器)

  • interfaces - the list of interfaces for the proxy class to implement(代理类需要实现的接口)

  • h - the invocation handler to dispatch method invocations to (InvocationHandler实现类,通过该接口方法转发到目标对象的方法,可在目标对象方法前后做些什么东东)

我们这里传入的是目标对象的类加载器,和目标对象的接口,及我们自己实现的InvocationHandler的实现类, 我们再仔细看一下该方法

    //三个参数分别为:代理对象,目标方法,目标方法参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("目标方法执行之前");
        //使用反射得到的Method对象调用invoke()方法,传入目标对象(相当于目标对象调用了一次方法,具体是哪个方法封装在Method实例中)
        Object invokeResult = method.invoke(object, args);
        
        System.out.println("目标方法执行之后");
        
        return invokeResult;
    }

看到这里可能还看不明白些什么东西,我们在得到我们的代理类之前的JdkDynamicProxyTest.main()方法加上一句代码

 //保存生成的代理类的class文件,路劲为工程路径下/com/sun/proxy
 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

我们打开代理的Class文件

//代理类继承了Proxy对象,实现了Renting接口(和静态代理一样,代理类和目标类继承自同一个接口)
public final class $Proxy0 extends Proxy implements Renting {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
	
    //向父类中填充InvocationHandler实例,我们这里填充的就是HouseOwnerDynamicProxy实例
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    
    //使用反射将生成的代理类的四个Mthod属性 m0,m1,m2,m3
    static {
        try {
        	//生成代理类的equals方法Mehtod
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            //生成代理类的rentRoom方法Method
            m3 = Class.forName("com.hundsun.akun.designpattern.proxy.example.Renting").getMethod("rentRoom");
            //生成代理类的toString()方法Method
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            //生成代理类的hashCode()方法Method
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
	
    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
	
    //代理对象自己的rentRoom()方法,因为和目标对象一样事项了Renting接口,所以要实现
    该方法
    public final void rentRoom() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    
}

生成的代理类和我们静态代理一样,代理对象和目标对象实现了同一个接口。而且同样实现了rentRoom的方法,我们来单独瞅一眼该方法:

public final void rentRoom() throws  {
        try {
        	代用父类的InvocationHandler实例的invoke方法,传入当前代理实例,m3,和方法入参
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

该方法代用父类的InvocationHandler实例的invoke方法,传入当前代理实例,m3,和方法入参 super.h实际上是InvocationHandler实例,这里就是我们的HouseOwnerDynamicProxy实例, 其中入参为,当前代理对象,m3在静态方法里面已经初始化过,实际上就是Renting接口的rentRoom()方法对应的Method对象。

看到这里的时候是不是大概就明白了,

JDK动态代理的关键在于:

  • 使用反射为目标对象的接口方法生成Method对象
  • 使用InvocationHandler,当调用代理对象的方法时,实际上调用InvocationHandlerinvoke方法,invoke方法又转发到目标对象的去执行(其中使用Method.invoke方法,对此不了解的同学可以先了解一下java反射),只是在执行前后可以加入自己的逻辑。

我们画幅图来总结下流程:

cglib动态代理

看完了jdk的动态代理,我们再来看看cglib的动态代理,老样子,先上代码

创建代理提供者,实现一个获取代理的方法

public class CglibDynamicProxy {

    private Object object;

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

	//定义一个MethodInterceptor,并实现了intercept方法。
    //该方法和jdk代理里面的InvocationHandler本质上一样的,都起到一个转发到目标对象执行目标方法的作用。jdk代理里面实现了InvocationHandler,而这里是直接new出来一个对象
    private MethodInterceptor methodInterceptor = new MethodInterceptor() {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("目标方法执行前做点什么");
            method.invoke(object,args);
            System.out.println("目标方法执行后做点什么");
            return null;
        }
    };

    public Object getProxyInstance(){
    	//创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        //设置Enhancer对象的父类为目标对象,这里是HouseOwner
        enhancer.setSuperclass(object.getClass());
        //试着方法拦截器即自定义MethodInterceptor
        enhancer.setCallback(this.methodInterceptor);
        //创建代理类,实际上是HouseOwner的子类
        return enhancer.create();

    }

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }
}

编写测试类

public class CglibDynamicProxyTest {

    public static void main(String[] args) {
        //创建目标对象房东
        HouseOwner owner = new HouseOwner();

        //创建代理提供者
        CglibDynamicProxy proxyProvider = new CglibDynamicProxy(owner);

        //创建房东的代理
        HouseOwner houseOwnerproxy = (HouseOwner) proxyProvider.getProxyInstance();

        //调用代理的租房方法
        houseOwnerproxy.rentRoom();

        System.out.println("--------------我是分隔线---------------");

        //将目标对象变成售房人员
        proxyProvider.setObject(new HouseSeller());

        //创建售房人员的代理
        HouseSeller houseSellerProxy = (HouseSeller) proxyProvider.getProxyInstance();
        //调用售房人员代理的售房方法
        houseSellerProxy.SellHouse();
        
    }
}

查看运行结果

查看运行结果时,我们发现,运行结果和jdk动态代理是一样的,都是同一个类,针对不同的目标对象,可以生成不同的代理。 两边都是需要一个方法执行转发器,不过一个叫InvocationHandler,一个叫做MethodInterceptor,不同就不同在创建的代理对象。我们先不讨论如何使用Enhancer对象怎么生成代理对象的,我们先看下生成的代理对象是什么样子的,依然再测试的main方法加上一行代码 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\proxy"); 再运行我们的测试main方法,这时在D盘proxy路径下面会生成文件夹,我们在当中找到了生成的代理的class文件 我们打开该class文件

//生成的Class类继承自HouseOwner
public class HouseOwner$$EnhancerByCGLIB$$db21b668 extends HouseOwner implements Factory {
	//属性有删减
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$rentRoom$0$Method;
    private static final MethodProxy CGLIB$rentRoom$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
  
    static {
        CGLIB$STATICHOOK1();
    }
    static void CGLIB$STATICHOOK1() {
    	//源码有删减,重要的保留了
        Class var0 = Class.forName("com.hundsun.akun.designpattern.proxy.example.HouseOwner$$EnhancerByCGLIB$$db21b668");
        Class var1;
        //和jdk一样,Method对象实际上代表的是HouseOwner的rentRoom()方法
        CGLIB$rentRoom$0$Method = ReflectUtils.findMethods(new String[]{"rentRoom", "()V"}, (var1 = Class.forName("com.hundsun.akun.designpattern.proxy.example.HouseOwner")).getDeclaredMethods())[0];
        //创建了一个rentRoom()的Method对象的代理MethodProxy对象
        CGLIB$rentRoom$0$Proxy = MethodProxy.create(var1, var0, "()V", "rentRoom", "CGLIB$rentRoom$0");
    }

    final void CGLIB$rentRoom$0() {
        super.rentRoom();
    }

    public final void rentRoom() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$rentRoom$0$Method, CGLIB$emptyArgs, CGLIB$rentRoom$0$Proxy);
        } else {
            super.rentRoom();
        }
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case -508378822:
            if (var10000.equals("clone()Ljava/lang/Object;")) {
                return CGLIB$clone$4$Proxy;
            }
            break;
        case 733512513:
            if (var10000.equals("rentRoom()V")) {
                return CGLIB$rentRoom$0$Proxy;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return CGLIB$equals$1$Proxy;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return CGLIB$toString$2$Proxy;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return CGLIB$hashCode$3$Proxy;
            }
        }
        return null;
    }

    public HouseOwner$$EnhancerByCGLIB$$db21b668() {
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        HouseOwner$$EnhancerByCGLIB$$db21b668 var1 = (HouseOwner$$EnhancerByCGLIB$$db21b668)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        HouseOwner$$EnhancerByCGLIB$$db21b668 var10000 = new HouseOwner$$EnhancerByCGLIB$$db21b668();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        HouseOwner$$EnhancerByCGLIB$$db21b668 var10000 = new HouseOwner$$EnhancerByCGLIB$$db21b668();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        HouseOwner$$EnhancerByCGLIB$$db21b668 var10000 = new HouseOwner$$EnhancerByCGLIB$$db21b668;
        switch(var1.length) {
        case 0:
            var10000.<init>();
            CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
            return var10000;
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        MethodInterceptor var10000;
        switch(var1) {
        case 0:
            var10000 = this.CGLIB$CALLBACK_0;
            break;
        default:
            var10000 = null;
        }

        return var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch(var1) {
        case 0:
            this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
        default:
        }
    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    
}

其中因为该代理类继承了HouseOwner,而该代理类里面又有rentRoom()方法,所以算是重写了父类的方法

 public final void rentRoom() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
        	//调用MethodInterceptor的intercept方法,即我们在CglibDynamicProxy的MethodInterceptor
            //传入当前的代理对象,目标方法Method对象,目标方法实参,目标方法Method对象的代理对象
            var10000.intercept(this, CGLIB$rentRoom$0$Method, CGLIB$emptyArgs, CGLIB$rentRoom$0$Proxy);
        } else {
            super.rentRoom();
        }
    }

看到这里其实差不多应该能看明白了,cglib动态代理实际上利用字节码技术,生成了目标对象的子类,并重写了目标对象的目标方法,和jdk代理类似利用MethodInterceptor接口转发到目标对象的方法里面去。

附录

给读者留下点儿疑问

  • jdk动态代理中如果目标对象有两个方法A和B,A方法里面调用了B方法,则生成代理之后,InvocationHandlerinvoke方法会执行几次?

  • cglib动态代理中MethodInterceptorintercept方法入参中还有一个MethodProxy实例,该实例有个invoke方法,有一个invokeSuper方法,有什么区别?和Method.invoke又有何异同?如果放在第一个疑问的情形下,intercept方法又会执行几次?