代理模式
代理模式简介
代理模式是一种结构性设计模式。用于无法直接控制和访问目标对象时,通过给目标对象提供一个代理对象,通过代理对象来间接操作目标对象。
代理模式的应用场景很多,比如Spring AOP、RPC远程调用、服务器反向代理等,通常用来不改变目标类的情况下,对目标对象进行保护或拓展。
代理模式的分类
代理模式按照使用场景进行分类,可分为以下几类:
- 远程代理。
- 虚拟代理。
- 保护代理。
- Cache代理。
- 同步化代理。
- 智能引用代理。
由于各种编程语言的语言特性不尽相同,在不同的语言中代理模式的实现也有所不同。由于在Java可以在运行时在JVM中创建新的字节码,所以可以根据代理类字节码创建的时机,将代理模式分为静态代理和动态代理,静态代理是指在编译期就生成代理类,动态代理是指在运行期生成代理类。其中,动态代理又由生成字节码方式的不同分为JDK动态代理和CGLib动态代理。
静态代理
静态代理实现
Subject是对代理行为的抽象,是代理类和目标类的公共接口。
public interface Subject {
void request();
}
RealSubject是目标类,这里实现真正的doSomeThing的行为
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("Do something");
}
}
Proxy是代理类,对目标类的行为进行封装和增强。
public class Proxy implements Subject {
private Subject target; // 被代理的对象
Proxy(Subject target) {
this.target = target;
}
@Override
public void request() {
before(); // 对目标类增强的行为
target.request(); // 调用目标对象的方法
after(); // 对目标类增强的行为
}
private void before() {
System.out.println("Before ...");
}
private void after() {
System.out.println("After ...");
}
}
在客户端类中使用代理对象来替代目标对象
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject(); // 目标对象
Proxy proxy = new Proxy(realSubject); // 代理对象
proxy.request(); // 通过代理对象去操作目标对象
}
}
运行结果
Before ...
Do something
After ...
静态代理优缺点
优点
静态代理是代理模式实现方式的一种,所以它拥有代理模式的优点,在不改变目标类的情况下对目标类进行保护和增强。
缺点
- 当需要代理的行为要改变时,目标类和代理类都有进行修改,不宜维护。
- 一个代理类只能为一个接口服务,程序开发过程中会出现很多代理类。
- 当需要对大量行为进行同样的扩展的时候(比如在所有的目标对象方法中都要添加同样的after和before),需要在代理类中为每个行为写很多相同的代码。
JDK动态代理
在JDK中,提供了Proxy.newProxyInstance()方法来生成代理类,使用方法如下。
JDK动态的使用
代理接口
public interface Subject {
void request();
}
RealSubject是目标类,这里实现真正的doSomeThing的行为
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("Do something");
}
}
SubjectInvocationHandler
getProxy()函数中通过Proxy.newProxyInstance()方法返回通过反射生成的代理类的对象,Proxy.newProxyInstance()方法的三个参数含义如下。
- ClassLoader loader:代理类的类加载器
- Class<?>[] interfaces:代理类需要实现的接口
InvocationHandler handler:代理对象方法调用的实际处理者,所有接口中的方法以及hashCode()、equals()、toString()调用时都会调用handler的invoke()方法。
public class SubjectInvocationHandler implements InvocationHandler {
// 目标对象
private Subject target;
public SubjectInvocationHandler(Subject target) {
this.target = target;
}
public Subject getProxy() {
// 通过反射生成代理类的代理对象。
return (Subject) Proxy.newProxyInstance(
// 指定生成的代理类的类加载器
Thread.currentThread().getContextClassLoader(),
// 这是获取target的接口,也可以直接传入接口,比如这里传入new Class[]{Subject.class}
this.target.getClass().getInterfaces(),
//要绑定的InvocationHandler
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 调用目标对象的方法
after();
return result; // 返回目标对象方法的返回值
}
private void before() {
System.out.println("Before ...");
}
private void after() {
System.out.println("After ...");
}
}
public class Client {
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
RealSubject realSubject = new RealSubject(); // 目标对象
SubjectInvocationHandler handler = new SubjectInvocationHandler(realSubject);
Subject proxy = handler.getProxy(); // 真正的代理对象
proxy.request(); // 通过代理对象去操作目标对象
}
}
JDK动态代理的原理
JDK动态代理会生成一个java.lang.reflect.Proxy类的子类,然后由指定的类加载器加载到方法区中,这个子类名为”com.sun.proxy.$ProxyN“,并实现了目标接口的方法,并在接口方法的函数体中通过反射去调用Object invoke(Object proxy, Method method, Object[] args) 方法,这样,代理接口中所有方法以及Object中的hashCode()、equals()、toString()调用时都会进入到 invoke(Object proxy, Method method, Object[] args)方法中。
具体原理解析见JDK动态代理源码分析
JDK动态代理的使用限制
由于JDK动态代理的实现是运行时在JVM中生成一个名为com.sun.proxy.$ProxyN的Class,这个Class继承了java.lang.reflect.Proxy类,而Java中只能单继承,所以JDK动态代理必须将代理的行为定义在一个或多个接口中,如果目标类没有实现接口则无法使用JDK动态代理。
其次,由于JDK动态代理的实现是在com.sun.proxy.$ProxyN实现的接口中通过反射调用InvocationHandler的invoke方法,所以会存在性能的问题。
CGLib动态代理
CGLib动态代理的使用
目标类RealSubject
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("Do something");
}
}
方法拦截器类,继承MethodInterceptor并实现intercept方法,在intercept中进行拦截。
intercept的参数如下。
- Object object:代理的目标对象
- Method method:拦截的方法
- Object[] objects:参数参数列表
MethodProxy methodProxy:方法代理
public class Interceptor implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
// 这里使用方法代理的invokeSuper方法调用目标类的方法,目标类是代理类的子类。
// 使用invoke是调用代理类的方法,会造成死循环。
Object result = methodProxy.invokeSuper(object, objects);
after();
return result; // 返回代理后的结果
}
private void before() {
System.out.println("Before ...");
}
private void after() {
System.out.println("After ...");
}
}
客户端类
public class Client {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class); // 设置超类,cglib是通过继承来实现的
enhancer.setCallback(new Interceptor());
RealSubject realSubject = (RealSubject)enhancer.create(); // 创建代理类并返回一个代理类对象
realSubject.request(); // 通过代理类调用方法
}
}
CGLib动态代理的原理
CGLib动态代理和JDK动态代理都是在代理类方法中进行拦截,不过二者在动态生成代理类的方法不同,JDK通过创建一个Proxy类的子类并实现代理接口的方法来生成代理类,CGLib通过ASM生成目标类的子类(子类重写目标类的所有非final方法)来生成代理类。
CGLib动态代理的使用限制
- 由于Cglib是通过生成目标类的子类来实现代理类,所以Cglib无法对声明为final的目标类进行代理。
CGLib在创建代理对象时花费的时间比JDK多很多,对于需要频繁创建对象的情况,使用CGLib代价会比较大。
三种代理方式的比较
从代理类生成时间上来看,静态代理的代理类在编译期就生成了,JDK和CGLib在运行时生成,前者不需要消耗JVM运行时间,但后者具有动态性,更加灵活。
- 从代理类生成方式上来看,静态代理的代理类人为实现(可接口,可继承)生成代理类,JDK代理通过创建一个Proxy类的子类并实现代理接口的方法来生成代理类,CGLib代理通过ASM生成目标类的子类来生成代理类。
- 从使用限制上来看,静态代理和CGLib对目标类没有要求(可接口,可为类),JDK的目标类需要额外的接口。
- 从性能消耗上来看,静态代理的带运行时没有额外的性能消耗,JDK动态代理在方法调用时(反射)性能消耗较大,CGLib在生成对象时性能消耗较大。