本来肝完通信编程的文章后想紧接着来一篇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篇