Java动态代理

1. 代理模式

代理在我们生活中很常见,比如超市卖烟酒的柜台,他们自己并不生产烟酒,而是一些品牌烟酒的代理商,他们售卖商品,但是商品并不一定是他们直接生产的;房屋中介,房东将需要出租或售卖的房屋委托给中介,中介将房屋信息发布,发布时可能添加一些形容词,比如临街旺铺、学区房等吸引顾客上门。这些都是代理行为,那为什么不能直接和烟酒生产厂商或房东进行直接交易呢?在很多情况下我们是没法直接和这样的对象直接交易的,烟酒生产商不会因为你买一瓶酒就给你直接谈合作,房东为了省事而更想把房子交给中介去出租或售卖,只要花钱就能少操心。
那什么是代理模式呢?代理模式就是为其它对象提供了一种代理以控制这个对象的访问,顾客不是直接和某个具体对象直接打交道,而是和代理进行交互,但是最终实际做事的还是被代理的对象,比如中介卖房(租房),最终签订售房或租房合同的还是和房东进行签订。
代理模式设计的角色如下:


代理模式
  • 被代理的接口Subject,用户并不在乎具体是谁提供了功能,只关心接口的功能,比如一个想租房的人只会找租房中介而不招卖车中介,这是因为客户知道从卖车中介哪里租不来房子,用户也不会关心租房中介到底有什么样的房子出租,但是知道这个租房中介能帮我租到房子,具体租到什么样的房子取决于该中介手里的房子是不是适合我;
  • 接口的一个实现类RealSubject,这里可以是一个房东要出租的具体的房子;
  • 代理类Proxy,实现了Subject接口的类,这里是房屋中介且是具有出租房子能力的中介;
  • 客户端类,这里就是想租房的租户,直接和房屋中介打交道;
    对于代理的使用场景还有很多,比如我们不能直接访问对象A,但是有个中间对象B有直接访问对象A,那我们就让对象B帮我们去和对象A交互,这样我们就像和对象A直接交互一样,用户可能并不知道有中间对象B的存在,给用户一种直接和A交互的假象。

2. 静态代理

上面我们知道了什么是代理,对于马上要说的Java里的动态代理,我们先来看看什么是静态代理,静态代理就是代理关系在代码编译时就确定了,静态代理实现比较简单,适合那种被代理的类比较少且事先就确定的情况。我们使用代码实现房屋中介的例子;
首先我们需要有一个接口叫Rent,代表了房屋出租能力:

public interface Rent {
    void houseRent();
}

然后需要有一个实现了Rent接口的类,这个类是需要被代理的类,该类实现了houseRent方法,表示房屋出租,当调用该方法时,为租户提供房屋。

public class RealHouse implements Rent{
    @Override
    public void houseRent() {
        System.out.println("出租一间70平米次卧");
    }
}

下面是一个房屋中介类

public class HouseAgency implements Rent {
    private RealHouse house;

    public HouseAgency(RealHouse house){
        this.house = house;
    }

    @Override
    public void houseRent() {
        System.out.println("租户需要缴纳房租10%的中介费");
        house.houseRent();
        System.out.println("房租到期租户需结清水电费用");
    }
}

我们来编写测试代码:

public class TestProxy {
    public static void main(String[] args){
        RealHouse realHouse = new RealHouse();
        Rent houseProxy = new HouseAgency(realHouse);
        houseProxy.houseRent();
    }
}

输出:

租户需要缴纳房租10%的中介费
出租一间70平米次卧
房租到期租户需结清水电费用

3. 动态代理

我们上面使用传统的方式实现了代理模式,我们的代理类在事先就关联上了一间70平米的次卧,这样在需要被代理的类较少且确定的情况下是没有问题的,这样在代码编译期间代理关系就确定了,如果我们想要在运行期间动态的创建一些代理来代理不同的目标对象怎么办,动态代理就是代理类不用我们手动去创建,而是在程序运行期间动态的创建出来的,我们用一个超市卖酒的例子来学习动态代理,并分别使用JDK和CGLIB来实现。
想象一下,在超市的柜台上,摆放这各种品牌的烟酒,我们拿酒水来说,酒分为很多种类型,柜台具有售卖烟酒的能力,但是卖烟酒的柜台可能并不生产酒,只是代理商。

4. Java动态代理

4.1 JDK原生动态代理

我们首先使用JDK的动态代理来实现卖酒,之后再来回顾JDK动态代理的实现过程。
首先还是定义一个具有买酒功能的接口,表示我这个柜台具有买酒的能力

public interface SellWine {
    void sellWine();
}

我们第一款酒准备卖青岛啤酒,于是我们有了实现买酒接口的青岛啤酒类

/**
 * 青岛啤酒
 * */
public class TsingTaoBeer implements SellWine{

    @Override
    public void sellWine() {
        System.out.println("售卖青岛啤酒");
    }
}

现在我们有了具体某个品牌的酒,我们还需要一个柜台来摆放我们的酒,于是定义一个实现了InvocationHandler接口的柜台类,实现了该接口的invoke方法

public class Booth implements InvocationHandler {
    private Object brand;

    public Booth(Object brand){
        this.brand = brand;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("本柜台销售有烟酒副食");
        method.invoke(brand, args);
        System.out.println("销售结束");
        return null;
    }
}

最后我们来编写测试代码,销售青岛啤酒

public class TestProxy {
    public static void main(String[] args){
        TsingTaoBeer beer = new TsingTaoBeer();
        InvocationHandler booth1 = new Booth(beer);

        SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(TsingTaoBeer.class.getClassLoader(),
                TsingTaoBeer.class.getInterfaces(), booth1);
        dynamicProxy.sellWine();
    }

输出:

本柜台销售有烟酒副食
售卖青岛啤酒
销售结束

我们在这个例子中,没有定义代理类,而是通过Proxy类动态生成的代理对象。JDK动态代理的语法:
JDK动态代理一个非常重要的类是Proxy,通过该类的静态方法nnewProxyInstance()来创建动态代理,下面是该静态方法的定义:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
  • loader是一个类加载器
  • interfaces是要代理的接口,我们这里是SellWine()接口
  • 实现InvocationHandler 接口的具体类的对象
    InvocationHandler 是一个接口,每一个代理的实例都会有一个与之关联的InvocationHandler实现类,如果代理的方法被调用,那么代理就会将转发给内部的InvocationHandler实现类来处理,其实就是调用invoke方法,
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}
  • proxy是代理的对象
  • method调用代理对象的方法
  • args为调用方法的参数
    调用代理的方法其实真正的执行者就是InvocationHandler实现类。
    卖更多商品
    我们柜台既然能卖酒,我还想卖烟怎么办?我们再定义一个卖烟的接口:
public interface SellSmoke {
    void sellSmoke();
}

我们打算售卖宇宙牌香烟

public class CosmicCigarette implements SellSmoke {
    @Override
    public void sellSmoke() {
        System.out.println("售卖宇宙牌香烟");
    }
}

接着编写测试代码:

public class TestProxy {
        public static void main(String[] args){
            TsingTaoBeer beer = new TsingTaoBeer();
            InvocationHandler booth1 = new Booth(beer);

            CosmicCigarette cigarette = new CosmicCigarette();
            InvocationHandler booth2 = new Booth(cigarette);

            SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(TsingTaoBeer.class.getClassLoader(),
                    TsingTaoBeer.class.getInterfaces(), booth1);
            dynamicProxy.sellWine();

            SellSmoke dynamicProxy2 = (SellSmoke) Proxy.newProxyInstance(CosmicCigarette.class.getClassLoader(),
                    CosmicCigarette.class.getInterfaces(), booth2);
            dynamicProxy2.sellSmoke();
        }
}

输出:

本柜台销售有烟酒副食
售卖青岛啤酒
销售结束
本柜台销售有烟酒副食
售卖宇宙牌香烟
销售结束

从运行结果可以看到,我们通过Proxy的newProxyInstance方法动态的产生了SellWine和SellSmoke两种接口的实现类代理,而不需要我们去手动编写对应的代理类。
JDK的动态代理依赖接口,被代理的类必须实现接口,下面我们介绍实现动态代理的另一种方式,使用CGLIB库,它能对类进行代理,而不强制类实现实现接口。

4.2 CGLIB动态代理

对于上面的例子,我们使用CGLIB进行改写,CGLIB实现动态代理需要实现MethonInterceptor接口的类,对方法进行拦截。
同样我们定义需要被代理的类,青岛啤酒类

public class TsingTaoBeer {
    public void sellWine(){
        System.out.println("售卖青岛啤酒");
    }
}

然后定义一个实现了MethodInterceptor接口的类

public class SellInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("本柜台售卖烟酒");
        methodProxy.invokeSuper(o, objects);
        System.out.println("售卖结束");
        return null;
    }
}

编写测试代码

public class TestProxy {
    public static void main(String[] args){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TsingTaoBeer.class);
        enhancer.setCallback(new SellInterceptor());

        TsingTaoBeer beerProxy = (TsingTaoBeer) enhancer.create();
        beerProxy.sellWine();

    }
}

使用CGLIB实现动态代理不需要实现接口。

5. 待完善

分析JDK动态代理和CGLIB动态代理详细过程。

推荐阅读更多精彩内容