1 代理
代理(Proxy)是一种设计模式,即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作。
通过代理模式主要有两种功能:
- 功能增强,在原来的基础上,额外增加功能。
- 访问控制,让访问不能直接到目标对象,必须到代理对象。
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需要修改,可以通过代理的方式来扩展该方法。
2 静态代理
在静态代理中,对目标对象(需要增强的类)的每个方法的增强都是手动的,这也符合我们最开始的认知(需要对目标对象进行增强,那么直接对这个目标进行修改)。
静态代理的优缺点:
- 优点:容易理解,实现简单
- 缺点:非常不灵活,比如目标对象实现了一个接口,当接口发生变化时,目标对象和代理对象都需要进行修改。
从JVM的角度来说,静态代理在编译的时候就已经把接口、接口实现类、代理类变成了class文件。
静态代理的实现步骤:
- 定义一个接口及实现类。
- 创建一个代理类,同样实现这个接口。
- 将目标对象注入代理类,然后在代理类中调用目标类中的方法,并在其前后增加一些功能。
下面举一个例子:
package org.example;
// 定义一个任务接口
public interface SendMessage {
void send(String msg);
}
package org.example;
// 接口实现类
public class SendMessageImpl implements SendMessage {
@Override
public 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;
}
// 实现方法
@Override
public 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;
}
@Override
public 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 用于调用原始方法
*/
@Override
public 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时,会引发内存溢出。