Java中的代理
代理模式
模式定义
为其他对象提供一种代理以控制对这个对象的访问。对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。
模式结构
代理模式的结构图如下所示:
用代码描述就是:
Subject
1 | public interface Subject { |
RealSubject
RealSubject实现了Subject接口,并重写了Request方法
1 | public class RealSubject implements Subject { |
SubjectProxy
SubjectProxy也实现了Subject接口,重写了Request,并且它还保存着一个RealSubject的引用。
1 | public class SubjectProxy implements Subject { |
Test 测试类
最后是一个测试类,说明代理模式的用法
1 | public class Test { |
程序的输出就是
1 | Before call RealSubject Request |
静态代理
静态代理就是,在程序运行之前,就是编译之前就已经确定了代理类和委托类的关系。它可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
上文的例子就是一个静态代理的例子。
它的优点呢,就是可以在不修改目标对象的前提下扩展目标对象的功能。
缺点也显而易见:
- 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
- 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
如果Subject又增加了一个Response方法,那么代理类和被代理类都需要修改,牵一发而动全身。
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(SubjectProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
实现动态代理有两种方式,利用JDK自带的API和使用cglib库。
JDK动态代理
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
接下来我们把上面的例子改造成动态代理
Subject
还是老样子,没有变化
1 | public interface Subject { |
RealSubject
也没有变化
1 | public class RealSubject implements Subject { |
SubjectDynamicProxy
动态代理类,注意它的实现,匿名内部类,重写了invoke方法
1 | public class SubjectDynamicProxy { |
SubjectDynamicProxy还可以通过继承接口InvocationHandler来实现,本质上是一样的。
1 | public class SubjectDynamicProxy implements InvocationHandler { |
Test
测试类
1 | public class Test { |
输出
1 | class com.proxy.dynamic.RealSubject |
静态代理和动态代理的区别主要在:
- 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
- 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
- 动态代理不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理
cglib动态代理
cglib是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口
- CGLIB是一个强大的高性能的代码生承包,它可以在运行期扩展Java类与实现Java接口
- CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类
CGLIB和动态代理最大的区别就是
- 使用动态代理的对象必须实现一个或多个接口
- 使用cglib代理的对象则无需实现接口,达到代理类无侵入
下面看一个例子
RealSubject
1 | public class RealSubject { |
CglibSubjectProxy
1 | public class CglibSubjectProxy implements MethodInterceptor { |
Test
1 | public class Test { |
输出
1 | class com.proxy.cglib.RealSubject |
总结
- 静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。
- JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。
- 动态代理生成的类为class com.sun.proxy.$Proxy0,cglib代理生成的类为class com.cglib.UserDao$$EnhancerByCGLIB$$a5790549。
- 静态代理在编译时产生class字节码文件,可以直接使用,效率高。
- 动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
- cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。