1 代理

代理(Proxy)是一种设计模式,即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作。
通过代理模式主要有两种功能:

  • 功能增强,在原来的基础上,额外增加功能。
  • 访问控制,让访问不能直接到目标对象,必须到代理对象。

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需要修改,可以通过代理的方式来扩展该方法。

2 静态代理

在静态代理中,对目标对象(需要增强的类)的每个方法的增强都是手动的,这也符合我们最开始的认知(需要对目标对象进行增强,那么直接对这个目标进行修改)。

静态代理的优缺点:

  • 优点:容易理解,实现简单
  • 缺点:非常不灵活,比如目标对象实现了一个接口,当接口发生变化时,目标对象和代理对象都需要进行修改。

从JVM的角度来说,静态代理在编译的时候就已经把接口、接口实现类、代理类变成了class文件。

静态代理的实现步骤:

  • 定义一个接口及实现类。
  • 创建一个代理类,同样实现这个接口。
  • 将目标对象注入代理类,然后在代理类中调用目标类中的方法,并在其前后增加一些功能。

下面举一个例子:

  1. package org.example;
  2. // 定义一个任务接口
  3. public interface SendMessage {
  4. void send(String msg);
  5. }
  1. package org.example;
  2. // 接口实现类
  3. public class SendMessageImpl implements SendMessage {
  4. @Override
  5. public void send(String msg) {
  6. System.out.println("发送消息:" + msg);
  7. }
  8. }
  1. package org.example;
  2. // 创建代理类
  3. public class SendMessageProxy implements SendMessage {
  4. // 私有属性:实现的接口(不使用接口实现类,是为了代码的复用性。当接口实现类变化时,不需要再改动代码)
  5. private SendMessage sendMessage;
  6. // 带参构造函数
  7. public SendMessageProxy(SendMessage sendMessage) {
  8. this.sendMessage = sendMessage;
  9. }
  10. // 实现方法
  11. @Override
  12. public void send(String msg) {
  13. // 调用方法前,增加功能
  14. System.out.println("准备发送消息。。。");
  15. sendMessage.send(msg);
  16. // 调用方法后,增加功能
  17. System.out.println("消息发送完毕。。。");
  18. }
  19. }
  1. package org.example;
  2. public class SendMsg {
  3. public static void main(String[] args) {
  4. // 创建目标对象
  5. SendMessage sendMessage = new SendMessageImpl();
  6. // 创建代理对象,传入创建好的目标对象
  7. SendMessageProxy sendMessageProxy = new SendMessageProxy(sendMessage);
  8. // 目标对象
  9. sendMessage.send("hello");
  10. System.out.println("==============");
  11. // 代理对象
  12. sendMessageProxy.send("hello");
  13. }
  14. }

输出结果:可以看到,已经对send方法进行了增强。

  1. 发送消息:hello
  2. ==============
  3. 准备发送消息。。。
  4. 发送消息:hello
  5. 消息发送完毕。。。

Java 中的静态代理类似于装饰者模式,静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。

3 动态代理

动态,指的是代理类在程序运行时创建的,而不是在程序运行前手动编码来定义代理类的。这些动态代理类是在运行时候根据我们在 JAVA 代码中的指示动态生成的。使用动态代理可以避免静态代理的缺点。

从JVM的角度来说,动态代理是在运行时动态生成类字节码,然后加载到JVM中的。

动态代理在许多框架中都有运用:比如Spring AOP,RPC框架等。

动态代理的实现方式有很多,常见的有JDK动态代理和CGLIB动态代理。后面将详细介绍。

4 JDK动态代理

JDK动态代理主要用到了:

  • InvocationHandler: 实现了该接口的类,相当于代理类。
  • Method:表示目标类中的方法。
  • Proxy:用其中的静态方法 newProxyInstance 来创建代理对象。

接下来对这三个类进行逐一介绍。

4.1 InvocationHandler

public interface InvocationHandler
代理类需要实现这个接口,接口中只有一个invoke方法:表示代理类需要执行的功能代码,代理类需要增强的代码也在这个方法中。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

  • Object proxy:代理对象,jdk创建并赋值。
  • Method method:目标类中的方法,jdk赋值。
  • Object[] args:目标类中方法的参数:jdk提供。

我们主要使用的是第二个参数Method,下面将对其进行进一步介绍。

4.2 Method

Method method:目标类中的方法。通过Method可以执行目标类中的方法:method.invoke(目标对象,方法的参数)。
method.invoke()方法就是用来执行目标类中的方法的。相当于前面静态代理中在代理类中执行的sendMessage.send(msg)

4.3 Proxy

Proxy类,就是用来创建代理对象的。
Proxy.newProxyInstance:创建代理对象,效果相当于Student stu = new Student()

  1. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  2. throws IllegalArgumentException
  • ClassLoader loader:目标对象的类加载器,通过反射机制可以获得。
  • Class<?>[] interfaces:目标对象实现的接口,同样通过反射机制获得。
  • InvocationHandler h:自己实现并创建的InvocationHandler接口实现类(相当于代理对象)。
  • 返回值:动态创建出来的代理对象,默认是Object类型,需要自行强转为目标接口的类型才能使用。

关于获得目标对象的类加载器和实现的接口,方法如下:
假设创建目标对象为target,那么获取类加载器:target.getClass().getClassLoader();同样的,获取接口target.getClass().getInterfaces()
这么做的原因:每个类都继承自Object类,而Object类中有一个getClass方法可以获得类对象在运行是的Class对象;而在Class类中有一个getClassLoader方法,可以获得该Class类的类加载器;有一个getInterfaces方法,可以获得该Class类实现的接口。

4.4 JDK动态代理的步骤

  • 创建接口,以及接口实现类
  • 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
  • 使用Proxy.newProxyInstance静态方法,创建代理对象,注意需要把返回值转换为目标接口的类型。
  • 使用代理对象。

具体例子如下:

  1. // 接口和接口实现类和静态代理一致
  1. package org.example;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. // 创建InvocationHandler的实现类,即代理类
  5. public class MyHandle implements InvocationHandler {
  6. // 传入的是哪个对象,就给哪个对象创建代理
  7. Object target = null;
  8. public MyHandle(Object object) {
  9. this.target = object;
  10. }
  11. @Override
  12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  13. Object res = null;
  14. // 执行方法前增强功能
  15. System.out.println("动态代理之前");
  16. res = method.invoke(target, args);
  17. // 执行方法前增强功能
  18. System.out.println("动态代理之后");
  19. return res;
  20. }
  21. }
  1. package org.example;
  2. import java.lang.reflect.Proxy;
  3. public class SendMsg2 {
  4. public static void main(String[] args) {
  5. // 创建目标对象
  6. SendMessage sendMessage = new SendMessageImpl();
  7. // 创建handle对象,传入创建好的目标对象
  8. MyHandle handle = new MyHandle(sendMessage);
  9. // 通过Proxy的静态方法创建代理对象
  10. // 传入的参数包括:目标对象的类加载器、目标对象的接口、handle对象
  11. SendMessage proxyInstance = (SendMessage) Proxy.newProxyInstance(sendMessage.getClass().getClassLoader(),
  12. sendMessage.getClass().getInterfaces(),
  13. handle);
  14. // 代理对象执行增强后的方法
  15. proxyInstance.send("hello world");
  16. }
  17. }

输出结果:

  1. 动态代理之前
  2. 发送消息:hello world
  3. 动态代理之后

5 CGLIB 动态代理

JDK动态代理存在一个严重的问题,那就是只能代理实现了接口的类。对于没有使用接口的类,如果该类可以被继承,那么可以采用CGLIB动态代理。
CGLIB动态代理是基于继承实现的,被广泛的运用到许多 AOP 的框架,例如 Spring AOP 和 synaop,为他们提供方法的 interception (拦截)。 Cglib 包的底层是通过使用一个小而快的字节码处理框架 ASM 来转换字节码并生成新的类。不鼓励直接使用 ASM ,因为它要求你必须对 JVM 内部结构包括 class 文件的格式和指令集都很熟悉。
CGLIB动态代理机制中,主要的有两个类:

  • MethodInterceptor接口
  • Enhancer

下面将对这两个类进行进一步的介绍。

5.1 MethodInterceptor

  1. /**
  2. *
  3. /**
  4. * @param var1 被代理的对象(需要增强的对象)
  5. * @param var2 被拦截的方法(需要增强的方法)
  6. * @param var3 方法参数
  7. * @param var4 用于调用原始方法
  8. */
  9. public interface MethodInterceptor extends Callback {
  10. Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4)
  11. throws Throwable;
  12. }

MethodInterceptor接口中只有一个intercept方法,包含了四个参数:

  • var1 被代理的对象(需要增强的对象)。
  • var2 被拦截的方法(需要增强的方法)。
  • var3 方法参数。
  • var4 用于调用原始方法。

其作用类似于JDK动态代理中InvocationHandler接口中的invoke方法。
我们需要自定义一个拦截器并实现MethodInterceptor接口。在intercept方法中完成对目标方法的增强和调用。

5.2 Enhancer

Enhancer类的作用是用来设置并创建代理类。需要设置的有:

  • 类加载器。
  • 被代理对象。
  • 方法拦截器(就是前面自定义的拦截器)。

来看一个简单的例子:

  1. // 代理类的创建工厂
  2. public class CglibProxyFactory {
  3. // 静态方法,获取代理类
  4. // 传入的参数为目标类的Class对象
  5. public static Object getProxy(Class<?> clazz) {
  6. // 创建动态代理增强类
  7. Enhancer enhancer = new Enhancer();
  8. // 设置类加载器
  9. enhancer.setClassLoader(clazz.getClassLoader());
  10. // 设置被代理类
  11. enhancer.setSuperclass(clazz);
  12. // 设置方法拦截器
  13. enhancer.setCallback(new SendMessageServiceInterceptor());
  14. // 创建并返回代理类
  15. return enhancer.create();
  16. }
  17. }

5.3 CGLIB动态代理的步骤

  • 添加cglib依赖。
  • 定义一个目标类。
  • 定义一个方法拦截器,实现MethodInterceptor接口。
  • 定义一个代理类工厂,获取代理对象。
  • 使用代理对象。

下面是具体演示。

  1. <!--在maven项目的pom文件中添加cglib依赖-->
  2. <dependency>
  3. <groupId>cglib</groupId>
  4. <artifactId>cglib</artifactId>
  5. <version>3.3.0</version>
  6. </dependency>
  1. package org.example.cglibProxy;
  2. // 目标类
  3. public class SendMessageService {
  4. public String send(String string) {
  5. System.out.println("发送信息:" + string);
  6. return string;
  7. }
  8. }
  1. package org.example.cglibProxy;
  2. import net.sf.cglib.proxy.MethodInterceptor;
  3. import net.sf.cglib.proxy.MethodProxy;
  4. import java.lang.reflect.Method;
  5. // 方法拦截器
  6. public class SendMessageServiceInterceptor implements MethodInterceptor {
  7. /**
  8. *
  9. /**
  10. * @param o 被代理的对象(需要增强的对象)
  11. * @param method 被拦截的方法(需要增强的方法)
  12. * @param objects 方法参数
  13. * @param methodProxy 用于调用原始方法
  14. */
  15. @Override
  16. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  17. // 在方法前增加功能
  18. System.out.println("准备发送消息");
  19. // 调用目标类中的方法,注意这里调用的是invokeSuper方法,区别于JDK动态代理中的invoke方法。
  20. methodProxy.invokeSuper(o, objects);
  21. // 在方法后增加功能
  22. System.out.println("消息发送完毕!");
  23. return null;
  24. }
  25. }
  1. package org.example.cglibProxy;
  2. import net.sf.cglib.proxy.Enhancer;
  3. // 代理类的创建工厂
  4. public class CglibProxyFactory {
  5. // 静态方法,获取代理类
  6. public static Object getProxy(Class<?> clazz) {
  7. // 创建动态代理增强类
  8. Enhancer enhancer = new Enhancer();
  9. // 设置类加载器
  10. enhancer.setClassLoader(clazz.getClassLoader());
  11. // 设置被代理类
  12. enhancer.setSuperclass(clazz);
  13. // 设置方法拦截器
  14. enhancer.setCallback(new SendMessageServiceInterceptor());
  15. // 创建并返回代理类
  16. return enhancer.create();
  17. }
  18. }
  1. package org.example.cglibProxy;
  2. public class CglibProxyTest {
  3. public static void main(String[] args) {
  4. // 获取目标对象
  5. SendMessageService sendMessageService = new SendMessageService();
  6. // 获取代理对象
  7. SendMessageService sendMessageServiceProxy = (SendMessageService) CglibProxyFactory.getProxy(SendMessageService.class);
  8. // 执行原方法
  9. sendMessageService.send("hello");
  10. System.out.println("================");
  11. // 执行增强后的方法
  12. sendMessageServiceProxy.send("world");
  13. }
  14. }

输出结果:

  1. 发送信息:hello
  2. ================
  3. 准备发送消息
  4. 发送信息:world
  5. 消息发送完毕!

6 CGLIB和JDK的区别

JDK CGLIB
实现的要求 必须实现了接口,或者直接代理接口 可以没有实现接口,但必须不是final类型的类和方法
效率 1.6和1.7时,JDK比CGLIB慢 JDK1.8时,JDK速度比CGLIB块

关于两种代理方式的效率问题,可以参考这篇文章:Spring AOP中的JDK和CGLib动态代理哪个效率更高?

7 动态代理用多了之后对内存方面有什么影响

基于JDK的动态代理,不会存在内存溢出问题。
基于 cglib 的动态代理,在设置用户缓存为 true 时不会产生内存溢出;设置为false时,会引发内存溢出。

参考