1 代理
代理(Proxy)是一种设计模式,即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作。
通过代理模式主要有两种功能:
- 功能增强,在原来的基础上,额外增加功能。
- 访问控制,让访问不能直接到目标对象,必须到代理对象。
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需要修改,可以通过代理的方式来扩展该方法。
2 静态代理
在静态代理中,对目标对象(需要增强的类)的每个方法的增强都是手动的,这也符合我们最开始的认知(需要对目标对象进行增强,那么直接对这个目标进行修改)。
静态代理的优缺点:
- 优点:容易理解,实现简单
- 缺点:非常不灵活,比如目标对象实现了一个接口,当接口发生变化时,目标对象和代理对象都需要进行修改。
从JVM的角度来说,静态代理在编译的时候就已经把接口、接口实现类、代理类变成了class文件。
静态代理的实现步骤:
- 定义一个接口及实现类。
- 创建一个代理类,同样实现这个接口。
- 将目标对象注入代理类,然后在代理类中调用目标类中的方法,并在其前后增加一些功能。
下面举一个例子:
package org.example;// 定义一个任务接口public interface SendMessage {void send(String msg);}
package org.example;// 接口实现类public class SendMessageImpl implements SendMessage {@Overridepublic void send(String msg) {System.out.println("发送消息:" + msg);}}
package org.example;// 创建代理类public class SendMessageProxy implements SendMessage {// 私有属性:实现的接口(不使用接口实现类,是为了代码的复用性。当接口实现类变化时,不需要再改动代码)private SendMessage sendMessage;// 带参构造函数public SendMessageProxy(SendMessage sendMessage) {this.sendMessage = sendMessage;}// 实现方法@Overridepublic void send(String msg) {// 调用方法前,增加功能System.out.println("准备发送消息。。。");sendMessage.send(msg);// 调用方法后,增加功能System.out.println("消息发送完毕。。。");}}
package org.example;public class SendMsg {public static void main(String[] args) {// 创建目标对象SendMessage sendMessage = new SendMessageImpl();// 创建代理对象,传入创建好的目标对象SendMessageProxy sendMessageProxy = new SendMessageProxy(sendMessage);// 目标对象sendMessage.send("hello");System.out.println("==============");// 代理对象sendMessageProxy.send("hello");}}
输出结果:可以看到,已经对send方法进行了增强。
发送消息:hello==============准备发送消息。。。发送消息:hello消息发送完毕。。。
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()。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)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静态方法,创建代理对象,注意需要把返回值转换为目标接口的类型。 - 使用代理对象。
具体例子如下:
// 接口和接口实现类和静态代理一致
package org.example;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;// 创建InvocationHandler的实现类,即代理类public class MyHandle implements InvocationHandler {// 传入的是哪个对象,就给哪个对象创建代理Object target = null;public MyHandle(Object object) {this.target = object;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res = null;// 执行方法前增强功能System.out.println("动态代理之前");res = method.invoke(target, args);// 执行方法前增强功能System.out.println("动态代理之后");return res;}}
package org.example;import java.lang.reflect.Proxy;public class SendMsg2 {public static void main(String[] args) {// 创建目标对象SendMessage sendMessage = new SendMessageImpl();// 创建handle对象,传入创建好的目标对象MyHandle handle = new MyHandle(sendMessage);// 通过Proxy的静态方法创建代理对象// 传入的参数包括:目标对象的类加载器、目标对象的接口、handle对象SendMessage proxyInstance = (SendMessage) Proxy.newProxyInstance(sendMessage.getClass().getClassLoader(),sendMessage.getClass().getInterfaces(),handle);// 代理对象执行增强后的方法proxyInstance.send("hello world");}}
输出结果:
动态代理之前发送消息:hello world动态代理之后
5 CGLIB 动态代理
JDK动态代理存在一个严重的问题,那就是只能代理实现了接口的类。对于没有使用接口的类,如果该类可以被继承,那么可以采用CGLIB动态代理。
CGLIB动态代理是基于继承实现的,被广泛的运用到许多 AOP 的框架,例如 Spring AOP 和 synaop,为他们提供方法的 interception (拦截)。 Cglib 包的底层是通过使用一个小而快的字节码处理框架 ASM 来转换字节码并生成新的类。不鼓励直接使用 ASM ,因为它要求你必须对 JVM 内部结构包括 class 文件的格式和指令集都很熟悉。
CGLIB动态代理机制中,主要的有两个类:
MethodInterceptor接口Enhancer类
5.1 MethodInterceptor
/***/*** @param var1 被代理的对象(需要增强的对象)* @param var2 被拦截的方法(需要增强的方法)* @param var3 方法参数* @param var4 用于调用原始方法*/public interface MethodInterceptor extends Callback {Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4)throws Throwable;}
MethodInterceptor接口中只有一个intercept方法,包含了四个参数:
- var1 被代理的对象(需要增强的对象)。
- var2 被拦截的方法(需要增强的方法)。
- var3 方法参数。
- var4 用于调用原始方法。
其作用类似于JDK动态代理中InvocationHandler接口中的invoke方法。
我们需要自定义一个拦截器并实现MethodInterceptor接口。在intercept方法中完成对目标方法的增强和调用。
5.2 Enhancer
Enhancer类的作用是用来设置并创建代理类。需要设置的有:
- 类加载器。
- 被代理对象。
- 方法拦截器(就是前面自定义的拦截器)。
来看一个简单的例子:
// 代理类的创建工厂public class CglibProxyFactory {// 静态方法,获取代理类// 传入的参数为目标类的Class对象public static Object getProxy(Class<?> clazz) {// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(clazz.getClassLoader());// 设置被代理类enhancer.setSuperclass(clazz);// 设置方法拦截器enhancer.setCallback(new SendMessageServiceInterceptor());// 创建并返回代理类return enhancer.create();}}
5.3 CGLIB动态代理的步骤
- 添加cglib依赖。
- 定义一个目标类。
- 定义一个方法拦截器,实现
MethodInterceptor接口。 - 定义一个代理类工厂,获取代理对象。
- 使用代理对象。
下面是具体演示。
<!--在maven项目的pom文件中添加cglib依赖--><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
package org.example.cglibProxy;// 目标类public class SendMessageService {public String send(String string) {System.out.println("发送信息:" + string);return string;}}
package org.example.cglibProxy;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;// 方法拦截器public class SendMessageServiceInterceptor implements MethodInterceptor {/***/*** @param o 被代理的对象(需要增强的对象)* @param method 被拦截的方法(需要增强的方法)* @param objects 方法参数* @param methodProxy 用于调用原始方法*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 在方法前增加功能System.out.println("准备发送消息");// 调用目标类中的方法,注意这里调用的是invokeSuper方法,区别于JDK动态代理中的invoke方法。methodProxy.invokeSuper(o, objects);// 在方法后增加功能System.out.println("消息发送完毕!");return null;}}
package org.example.cglibProxy;import net.sf.cglib.proxy.Enhancer;// 代理类的创建工厂public class CglibProxyFactory {// 静态方法,获取代理类public static Object getProxy(Class<?> clazz) {// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(clazz.getClassLoader());// 设置被代理类enhancer.setSuperclass(clazz);// 设置方法拦截器enhancer.setCallback(new SendMessageServiceInterceptor());// 创建并返回代理类return enhancer.create();}}
package org.example.cglibProxy;public class CglibProxyTest {public static void main(String[] args) {// 获取目标对象SendMessageService sendMessageService = new SendMessageService();// 获取代理对象SendMessageService sendMessageServiceProxy = (SendMessageService) CglibProxyFactory.getProxy(SendMessageService.class);// 执行原方法sendMessageService.send("hello");System.out.println("================");// 执行增强后的方法sendMessageServiceProxy.send("world");}}
输出结果:
发送信息:hello================准备发送消息发送信息:world消息发送完毕!
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时,会引发内存溢出。
