今天聊聊平时工作中很常见的Java设计模式–代理模式。
什么是代理模式?
为其他对象提供一种代表,以控制对这个对象的访问。
代理对象起到中介的作用,用户与代理对象打交道,不直接接触实际对象。
使用代理模式创建代表对象,让代理对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或者需要安全控制的对象。
常见的代理模式:
远程代理–为不同地理的对象,提供局域网对象(监控全国分店)
虚拟代理–将资源消耗很大的对象进行延迟加载,真正需要的时候进行创建(加载图片时,先用默认图片代替)
保护代理–控制用户的访问权限
- 智能引用代理–提供对目标对象额外服务(火车站代售点,不止代售车票)
静态代理
代理和被代理的对象在代理之前是确定的。他们都实现相同的接口或继承相同的抽象类。
静态代理模式类图:
从代理模式的类图可以看出,RealSubject和Proxy都实现了Subject接口,Proxy代理类持有Subject的引用,控制着对Subject的实现类RealSubject的访问。
举个栗子:
public class StaticProxyDemo {
// 接口
static interface Subject{
public void request();
}
// 实现类
static class RealSubject implements Subject{
@Override
public void request() {
System.out.println("I am a request...");
}
}
// 代理类
static class Proxy implements Subject{
private Subject realSubject;
public Proxy(Subject realSubject){
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("enter proxy...");
realSubject.request();
System.out.println("out proxy...");
}
}
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject proxySubject = new Proxy(subject);
proxySubject.request();
}
}
程序输出:
enter proxy...
I am a request...
out proxy...
静态代理很简单,代码无需多说,下面直接进入重点动态代理。
动态代理
何为动态?动态是指运行时才把类创建出来,根据传入接口参数创建的被代理类。
利用reflect反射包,在运行时动态创建一个代理类,实现一个或多个接口,并把方法的调用转发到所指定的类。这个过程称为动态代理。
JDK动态代理
而JDK动态代理是运行时生产的class,该class需要实现一组interface,使用动态代理类时,必须实现invocationHandler接口。
JDK动态代理类图:
从类图可以看到,新增了InvocationHandler接口和实现,Proxy的任何方法调用都会被传入此类,InvocationHandler控制着对RealSubject的访问。
我们用动态代理实现之前的栗子:
public class DynamicProxyDemo {
// 接口
static interface Subject {
public void request();
}
// 实现类
static class RealSubject implements Subject {
@Override
public void request() {
System.out.println("I am a request...");
}
}
// 代理类
static class InvocationHandlerDemo implements InvocationHandler {
private Subject realSubject;
public InvocationHandlerDemo(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("DynamicProxy enter " + method.getName());
Object result = method.invoke(realSubject, args);
System.out.println("DynamicProxy out " + method.getName());
return result;
}
}
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject proxySubject = (Subject) Proxy.newProxyInstance(subject.getClass()
.getClassLoader(), new Class<?>[] { Subject.class },
new InvocationHandlerDemo(subject));
proxySubject.request();
}
}
通过代码,我们看到,Subject接口和实现都没变,但是代理对象proxySubject的创建方式变了,proxySubject是由reflect反射包中的Proxy的静态方法newProxyInstance来创建的,该方法声明如下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
ClassLoader loader
:表示类加载器,不同的类加载器所加载同一个类时都会产生不同的class文件,所以这里要使用与Subject一样的类加载器。
Class<?>[] interfaces
:表示接口数组,即代理类要实现的接口数组,注意这里只能传接口类型,不能是其实现类。我们这里传入了Subject接口类型。
InvocationHandler h
:表示InvocationHandler接口类型,这里我们传入了InvocationHandlerDemo,InvocationHandler的实现类作为参数,它只定义了一个方法invoke,对代理接口所有方法的调用都会转发给该方法。
Proxy的静态方法newProxyInstance的返回值类型是Object类型,我们将返回值强制转换成了Subject类型,注意返回值类型不能强制转换为类类型,比如其子类RealSubject,因为它的实际代理对象类型是Subject接口类型。
基本原理
上面例子中创建proxySubject的代码可以用如下代码代替:
Class<?> proxyCls = Proxy.getProxyClass(Subject.class.getClassLoader(),
new Class<?>[] { Subject.class });
Constructor<?> ctor = proxyCls.getConstructor(new Class<?>[] { InvocationHandler.class });
InvocationHandler handler = new InvocationHandlerDemo(subject);
Subject proxySubject = (Subject) ctor.newInstance(handler);
分为三步:
1、通过Proxy.getProxyClass创建代理类定义,类定义会被缓存
2、获取代理类的构造方法,构造方法有一个InvocationHandler类型的参数
3、创建InvocationHandler对象,创建代理类对象
CGLIB动态代理
由于JDK动态代理只能代理接口,返回的代理对象也只能转换到某个接口类型,如果一个类没有接口,或者希望代理不是接口中定义的方法,那么JDK动态代理将无能为力。但是CGLIB动态代理可以实现针对类的代理。
举个栗子:
public class DynamicProxyDemo3 {
static class RealSubject {
public void request() {
System.out.println("I am a request...");
}
}
// 代理类
static class InterceptorDemo implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] objects,
MethodProxy proxy) throws Throwable {
System.out.println("CGLIB DynamicProxy enter " + method.getName());
Object result = proxy.invokeSuper(object, objects);
System.out.println("CGLIB DynamicProxy out " + method.getName());
return result;
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new InterceptorDemo());
RealSubject proxySubject = (RealSubject) enhancer.create();
proxySubject.request();
}
}
RealSubject 表示被代理的类,它既不是接口也没有实现接口。Enhancer对象为一个类生成代理对象,这个代理对象可以安全的转换为被代理类的类型,setSuperclass设置被代理的类,setCallback设置被代理类的public非final方法被调用时的处理类,Enhancer支持多种类型,这里使用的类实现了MethodInterceptor接口,它与JDK动态代理的InvocationHandler有点类似。
基本原理
CGLIB的实现机制与JDK不同,它是通过继承实现的,它也是动态创建了一个类,但这个类的父类是被代理的类,代理类重写了父类的所有public非final方法,改为调用Callback中的相关方法。
JDK动态代理和CGLIB动态代理的比较
JDK动态代理只能代理一个接口或一组接口,它为这些接口动态创建了实现类,接口的具体逻辑由实现了InvocationHandler接口的实现类而定,可以根据情况适当的选择被代理的对象。而CGLIB代理面向的是一个具体的类,动态地创建了一个新类,继承了被代理类,重写了其方法。
总结
动态代理是一种强大的功能,它可以在运行时动态创建一个类,实现一个或多个接口,可以在不修改原有类的基础上动态为通过该类获取的对象添加方法、修改行为。这些特性使得它广泛应用于各种框架中,比如Spring,MyBatis等,了解并深入理解动态代理,有助于我们更深刻的理解这些开源框架。