代理模式是指给某个对象提供一个代理对象,由代理对象控制对原对象的访问。代理对象可以在调用原对象方法的前后进行其他操作,是对原对象方法的一种增强实现。
为什么会存在代理模式?
- 原对象无法直接引用,这时候需要代理对象起到中介作用,需要代理类和被代理类实现相同的接口
- 想增强被代理类的功能且不修改委托类的代码,可以使用代理类,符合开闭原则
代理模式一般分为两种:静态代理和动态代理。在代理模式中,通常存在三个角色:
- 被代理接口,定义了被代理类的方法。
- 被代理类,定义了被代理接口方法的具体逻辑。
- 代理类,实现了被代理接口,并在实现方法里对被代理类的方法进行了增强
接下来,我们以租房子为例,依次介绍静态代理、JDK动态代理、cglib动态代理
静态代理
首先,先介绍不通过中介租房的代码实例:
定义IRent接口,定义租房的行为:
public interface IRenter {
void rentHouse();
}
实现IRent接口:
public class Renter implements IRenter {
@Override
public void rentHouse() {
System.out.println("客户签订租房合同");
}
}
创建业务类Test:
public class Test {
public static void main(String[] args) {
IRenter renter = new Renter();
renter.rentHouse();
}
}
客户签订租房合同
我们知道,靠自己找房子太困难了,我们需要找中介帮我找房子,签合同,那么在符合开闭原则的情况下,静态代理的代码实例如下:
public class RenterProxy implements IRenter {
private IRenter iRenter;
public RenterProxy(IRenter iRenter) {
this.iRenter = iRenter;
}
@Override
public void rentHouse() {
System.out.println("中介帮忙寻找房子");
iRenter.rentHouse();
System.out.println("客户入住");
}
}
中介帮忙寻找房子
客户签订租房合同
客户入住
代理类同样实现了IRent接口,并在实现方法里调用被代理类的方法且对该方法进行了增强,增加了中介行为的描述。
JDK动态代理
但是问题又来了,如果被代理接口改变了,或者接口里的方法改变了,代理类和业务类都要做出相应的改变,所以,静态代理模式不符合开闭原则,业务层和底层的耦合度很高。我们可以通过JDK动态代理来解决这个问题,代码如下:
public class RenterProxy implements InvocationHandler {
//被代理的对象引用
private Object target;
public RenterProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("中介帮忙寻找房子");
Object object = method.invoke(target, args);
System.out.println("客户入住");
return object;
}
}
public class Test {
public static void main(String[] args) {
IRenter renter = new Renter();
Class clazz = renter.getClass();
IRenter renterProxy = (IRenter) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new RenterProxy(renter));
renterProxy.rentHouse();
}
}
代理类实现了InvocationHandler 接口,重写了invoke方法,invoke方法中的method.invoke(target, args)就是调用原对象的rentHouse方法,最后通过Proxy类的静态方法newProxyInstance来返回一个代理对象。
当我们要更换接口或者修改接口方法的时候,我们只需要在业务类进行修改即可,符合开闭原则。JDK动态代理采用字节重组,重新生成对象代理原始对象,生成对象的步骤如下:
- 获取被代理对象的引用,并且获取它的所有接口,反射获取。
- JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口。
- 动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用。
- 编译新生成的Java代码.class文件。
- 重新加载到JVM中运行
cglib动态代理
JDK动态代理只能代理实现了接口的类,那么没有接口的类怎么办呢?我们可以使用cglib动态代理,其代码实例如下:
创建没有实现接口的被代理类:
public class Renter {
public void rentHouse() {
System.out.println("客户签订租房合同");
}
}
创建代理类:
public class RenterProxy implements MethodInterceptor {
private Object target;
public RenterProxy(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("中介帮忙寻找房子");
Object object = method.invoke(target, objects);
System.out.println("客户入住");
return object;
}
}
创建业务类:
public class Test {
public static void main(String[] args) {
Renter renter = new Renter();
Renter renterProxy = (Renter) Enhancer.create(renter.getClass(), new RenterProxy(renter));
renterProxy.rentHouse();
}
}
代理类实现了MethodInterceptor 接口并重写了intercept方法,方法体的代理与JDK动态代理相同,都是通过method.invoke(target, objects)来调用被代理类的方法,其中target是被代理对象,objects是方法参数。
然后在业务类中通过Enhancer.create方法来创建代理类。
JDK动态代理和cglib动态代理对比
JDK动态代理实现了被代理对象的接口,cglib动态代理继承了被代理对象
- 二者都在运行期生成字节码,JDK动态代理直接写class字节码,cglib动态代理使用ASM框架写class字节码,生成代理类的过程更复杂,效率低。
- JDK动态代理调用代理方法是通过反射机制调用的,cglib动态代理通过FastClass机制直接调用方法,其原理就是:为代理类和被代理类各生成一个类,这个类会为代理类和被代理类的方法分配一个index,FastClass就可以直接通过index定位要调用的方法,省去了反射调用,所以cglib动态代理的执行效率更高。