Java中的静态代理与动态代理

本来肝完通信编程的文章后想紧接着来一篇RPC的文章的,但是一想 RPC的话,还涉及到动态代理的知识,所以先来理一下动态代理的知识。

代理模式想必大家耳熟能详,一个代理类持有目标对象的引用,在执行目标方法前后加一点别的逻辑。其实现实中也有很多代理场景,像专利代理,房产中介代理等,今天就以房产中介为例来讲述下代理模式。

静态代理

直接用一个java类来代理另一个java类的形式就叫做静态代理,需要我们手动将代理的类编写出来。

以租房举例,租客就是目标对象,负责签字,付款。中介就是代理对象,负责带看、讲价、准备合同等工作。

首先定义一个租房的接口,租客和中介都要实现这个接口

public interface RentHouse {
    void rent();
}

租客类

public class Renter implements RentHouse{
    @Override
    public void rent() {
        System.out.println("签字,交押金、租金");
    }
}

中介类

public class RentHouseProxy implements RentHouse {

    private RentHouse renter;

    public RentHouseProxy(RentHouse renter) {
        this.renter = renter;
    }

    @Override
    public void rent() {
        System.out.println("带看房源,砍价,准备合同");
        renter.rent();
        System.out.println("交易完成");
    }

}

可以看到,中介类里拥有租客对象的引用,两个类都实现了RentHouse接口,所以都实现了rent方法。中介负责代理整个租房流程,带看、砍价、准备合同、让租客交钱、完成交易。

用一个测试方法来执行下整个流程

@Test
public void staticProxyTest() {
    Renter renter = new Renter();
    RentHouseProxy rentHouseProxy = new RentHouseProxy(renter);
    rentHouseProxy.rent();
}

输出结果:

带看房源,砍价,准备合同
签字,交押金、租金
交易完成

这就是一个简单的静态代理,优点是逻辑很明了,缺点是扩展性差。比如中介不仅仅代理租房的业务,还代理买卖房子的业务,要实现这个业务就需要再搞一个接口出来

买房接口

public interface BuyHouse {
    void buy();
}

买方类

public class Buyer implements BuyHouse {
    @Override
    public void buy() {
        System.out.println("签字,交房款");
    }
}

中介类

public class BuyHouseProxy implements BuyHouse {

    private BuyHouse buyer;

    public BuyHouseProxy(BuyHouse buyer) {
        this.buyer = buyer;
    }

    @Override
    public void buy() {
        System.out.println("带看房源,砍价,准备合同");
        buyer.buy();
        System.out.println("交易完成");
    }

}

用一个测试方法来执行下整个流程

@Test
public void staticProxyTest() {
    Renter renter = new Renter();
    RentHouseProxy rentHouseProxy = new RentHouseProxy(renter);
    rentHouseProxy.rent();

    Buyer buyer = new Buyer();
    BuyHouseProxy houseAgent = new BuyHouseProxy(buyer);
    houseAgent.buy();
}

输出结果

带看房源,砍价,准备合同
签字,交押金、租金
交易完成
带看房源,砍价,准备合同
签字,交房款
交易完成

看,单从我们这段代码来看,中介干的事其实差不多。但是为了代理租房和买房的业务,却编写了两个代理类出来。

如果中介又有了新的功能,需要再编写第三个,第四个代理类出来。有没有更好的实现方式呢?有,那就是动态代理!

动态代理

Java中动态代理最常见的就是JDK动态代理CGLIB动态代理。来看看怎么用这两种方式分别实现中介代理类。

JDK动态代理

JDK动态代理是JDK自带的实现,被代理的类需要实现一个接口,才能使用JDK动态代理。这个代理模式创建代理对象的方法为

需要三个参数

  • loader:创建代理类需要的类加载器
  • interfaces:需要代理的接口,所以说JDK动态代理必须要有接口才行
  • h:InvocationHandler接口,需要一个具体的实现类来编写代理逻辑

InvocationHandler只有一个方法需要实现

参数说明

  • proxy:代理对象(不是被代理的对象)
  • method:当前执行的方法
  • args:当前执行方法附带的参数

来看一下中介代理类的实现

public class JdkProxy implements InvocationHandler {

    private Object target;

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

    /**
     * 调用代理人对象的所有方法都会导向到下面的invoke方法
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("带看房源,砍价,准备合同");
        Object result = method.invoke(target, args);
        System.out.println("交易完成");
        return result;
    }
}

target是被代理的对象,可以是租客也可以是买房者。

用一个测试方法来编写租房和买房的逻辑

@Test
public void jdkProxyTest() {
    BuyHouse buyer = new Buyer();
    BuyHouse proxyBuyer = (BuyHouse) Proxy.newProxyInstance(Buyer.class.getClassLoader(), new Class[]{BuyHouse.class}, new JdkProxy(buyer));
    proxyBuyer.buy();

    RentHouse renter = new Renter();
    RentHouse rentHouse = (RentHouse) Proxy.newProxyInstance(Renter.class.getClassLoader(), new Class[]{RentHouse.class}, new JdkProxy(renter));
    rentHouse.rent();
}

输出结果:

带看房源,砍价,准备合同
签字,交房款
交易完成
带看房源,砍价,准备合同
签字,交押金、租金
交易完成

结果和使用两个静态代理类一样,但是我们只实实在在 在代码里编写了一个代理类。

另外,JDK代理也可以只代理接口,无需有实现类,这种方式在实现客户端RPC调用时比较常见。伪代码如下:

public class JdkProxyV2 implements InvocationHandler {

    /**
     * 调用代理人对象的所有方法都会导向到下面的invoke方法
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String interfaceName = method.getClass().getName();

        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setClassName(interfaceName);
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParameterTypes(method.getParameterTypes());
        rpcRequest.setParameters(args);
        // 调用远程服务获取方法的执行结果
        Object result = RemoteCall.send(rpcRequest);
        return result;
    }
}

真实可用的代码,我会放到下篇文章编写RPC Demo时再介绍,敬请关注!

CGLIB动态代理

JDK动态代理有一个特性就是,需要有接口存在才能创建代理对象,如果没有接口的话就用不了了。而CGLIB代理就可以直接代理一个类,代理出来的类就是被代理类的子类。

由于JDK中没有CGLIB的功能,所以需要单独引入cglib的包,maven工程的话引入如下依赖

另外Spring框架中也有自己的cglib实现,如果你是在一个Spring项目中编写demo,那就省事了,不用额外引入cglib的包了。

编写cglib代理类需要实现MethodInterceptor接口,此时,中介代理类如下

public class CglibProxy implements MethodInterceptor {

    private Object target;

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

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("带看房源,砍价,准备合同");
        Object result = method.invoke(target, args);
        System.out.println("交易完成");
        return result;
    }

    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }
    
}

target是目标对象,可以是租客,也可以是买房者。

intercept方法就是编写代理逻辑的方法

getProxy是我自定义的方法,这个方法演示了如何通过Enhancer来创建一个代理类。

用一个测试方法来编写租房和买房的逻辑

@Test
public void cglibProxyTest() {
    BuyHouse buyer = new Buyer();
    BuyHouse buyerProxy = new CglibProxy(buyer).getProxy();
    buyerProxy.buy();

    RentHouse renter = new Renter();
    RentHouse renterProxy = new CglibProxy(renter).getProxy();
    renterProxy.rent();
}

输出结果

带看房源,砍价,准备合同
签字,交房款
交易完成
带看房源,砍价,准备合同
签字,交押金、租金
交易完成

输出结果与静态代理和JDK动态代理并无差别。但是代理类也是只有一个

动态代理在Spring的AOP实现和众多RPC框架中非常常见,明白了使用方法不至于你在看源码时一脸懵逼。

好,Java中的代理就讲述到这里,敬请期待下篇文章,RPC篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值