本来肝完通信编程的文章后想紧接着来一篇RPC的文章的,但是一想 RPC的话,还涉及到动态代理的知识,所以先来理一下动态代理的知识。
代理模式想必大家耳熟能详,一个代理类持有目标对象的引用,在执行目标方法前后加一点别的逻辑。其实现实中也有很多代理场景,像专利代理,房产中介代理等,今天就以房产中介为例来讲述下代理模式。
静态代理
直接用一个java类来代理另一个java类的形式就叫做静态代理,需要我们手动将代理的类编写出来。
以租房举例,租客就是目标对象,负责签字,付款。中介就是代理对象,负责带看、讲价、准备合同等工作。
首先定义一个租房的接口,租客和中介都要实现这个接口
public interface RentHouse {void rent();
}
租客类
public class Renter implements RentHouse{@Overridepublic void rent() {System.out.println("签字,交押金、租金");}
}
中介类
public class RentHouseProxy implements RentHouse {private RentHouse renter;public RentHouseProxy(RentHouse renter) {this.renter = renter;}@Overridepublic 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 {@Overridepublic void buy() {System.out.println("签字,交房款");}
}
中介类
public class BuyHouseProxy implements BuyHouse {private BuyHouse buyer;public BuyHouseProxy(BuyHouse buyer) {this.buyer = buyer;}@Overridepublic 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方法*/@Overridepublic 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方法*/@Overridepublic 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;}@Overridepublic 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篇