JDK动态代理和CGLIB动态代理
代理
代理模式是一种常用的设计模式, 代表性的有Spring中的AOP.
代理模式就像是一个中介一样, 以租房为例.
房东作为被代理类, 租客作为调用方, 中介就是租客和房东中间的代理. 在中介的操作下, 租客租下了房间, 中介在中间做了一些事情.
代理分为静态代理和动态代理.
静态代理
静态代理就是预先写好的代理类.
举个例子:
// IRoom.java
public interface IRoom {
void rent();
}
// Room.java
public class Room implements IRoom {
@Override
public void rent() {
System.out.println("房东收租金100块");
}
}
// Client.java
public class Client {
public static void main(String[] args) {
Room room = new Room();
room.rent();
}
}
输出:
房东收租金100块
很简单一个例子, 直接找房东租房, 房东直接收租金100块.
加上代理:
// IRoom.java
public interface IRoom {
void rent();
}
// Room.java
public class Room implements IRoom {
@Override
public void rent() {
System.out.println("房东收租金100块");
}
}
// RoomProxy.java
public class RoomProxy implements IRoom {
private IRoom iRoom;
public RoomProxy(IRoom iRoom) {
this.iRoom = iRoom;
}
@Override
public void rent() {
System.out.println("中介收中介费10块");
iRoom.rent();
System.out.println("中介收手续费1块");
}
}
// Client.java
public class Client {
public static void main(String[] args) {
RoomProxy roomProxy = new RoomProxy(new Room());
roomProxy.rent();
}
}
输出:
中介收中介费10块
房东收租金100块
中介收手续费1块
可以看到, 代理的含义就是在一个代理类中, 调用被代理类的方法, 在调用中可以做一些额外的事情.
动态代理
动态代理大概可以分两类:
- JDK动态代理
- CGLIB动态代理
JDK动态代理
// 被代理类实现的接口
public interface ITeacherDao {
void teach();
}
// 被代理类
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("teachDao teach");
}
}
// 代理类工厂
public class ProxyFactory {
private Object teacherDao;
public ProxyFactory(Object teacherDao) {
this.teacherDao = teacherDao;
}
/**
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
*
* 1. ClassLoader loader : 指定当前目标对象使用的类加载器获取加载器的方法固定
* 2. Class<?>[] interfaces : 目标对象实现的接口类型, 使用泛型的方式确认类型
* 3. InvocationHandler h : 事件处理, 执行目标对象的方法时,会触发事件处理器的方法, 会把当前执行的目标对象方法作为参数传入
*
* @return
*/
public Object getNewProxyInstance(){
return Proxy.newProxyInstance(teacherDao.getClass().getClassLoader(), teacherDao.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk代理开始");
Object returnVal = method.invoke(teacherDao, args);
System.out.println("jdk代理结束");
return returnVal;
}
});
}
}
// 调用方
public class Client {
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
ProxyFactory proxyFactory = new ProxyFactory(new TeacherDao());
ITeacherDao newProxyInstance = (ITeacherDao)proxyFactory.getNewProxyInstance();
newProxyInstance.teach();
System.out.println(newProxyInstance.getClass());
}
}
可以看到, 虽然添加了一个ProxyFactory
的类, 但是该类持有的是一个Object类型的对象, 也就是说, 该类可以创建’任何’类的代理对象.
为什么要把[任何]两个字打双引号呢?
主要看这一句代码:
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
这里面有三个参数:
- ClassLoader loader: 指定使用哪个类加载器加载该生成的代理对象
- Class<?>[] interfaces: 传入该类实现的接口
- InvocationHandler h: 执行方法时的事件处理器
可以看到, 第二个参数Class<?>[] interfaces
是要求该被代理类必须要实现接口的, 因此JDK的动态代理只能代理实现了接口的类.
// ProxyGenerator
private byte[] generateClassFile() {
// 添加hashCode(),equals(),toString()方法
this.addProxyMethod(hashCodeMethod, Object.class);
this.addProxyMethod(equalsMethod, Object.class);
this.addProxyMethod(toStringMethod, Object.class);
// 获取代理类的接口
Class[] var1 = this.interfaces;
int var2 = var1.length;
int var3;
Class var4;
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
// 获取接口中的方法
Method[] var5 = var4.getMethods();
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
Method var8 = var5[var7];
// 添加代理方法
this.addProxyMethod(var8, var4);
}
}
Iterator var11 = this.proxyMethods.values().iterator();
List var12;
while(var11.hasNext()) {
var12 = (List)var11.next();
checkReturnTypes(var12);
}
Iterator var15;
try {
// 将方法添加进class文件中
// 添加生成的构造方法
this.methods.add(this.generateConstructor());
var11 = this.proxyMethods.values().iterator();
while(var11.hasNext()) {
var12 = (List)var11.next();
var15 = var12.iterator();
while(var15.hasNext()) {
ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
this.methods.add(var16.generateMethod());
}
}
this.methods.add(this.generateStaticInitializer());
} catch (IOException var10) {
throw new InternalError("unexpected I/O Exception", var10);
}
if (this.methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
} else if (this.fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
} else {
this.cp.getClass(dotToSlash(this.className));
this.cp.getClass("java/lang/reflect/Proxy");
var1 = this.interfaces;
var2 = var1.length;
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
this.cp.getClass(dotToSlash(var4.getName()));
}
this.cp.setReadOnly();
ByteArrayOutputStream var13 = new ByteArrayOutputStream();
DataOutputStream var14 = new DataOutputStream(var13);
try {
var14.writeInt(-889275714);
var14.writeShort(0);
var14.writeShort(49);
this.cp.write(var14);
var14.writeShort(this.accessFlags);
var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
var14.writeShort(this.interfaces.length);
Class[] var17 = this.interfaces;
int var18 = var17.length;
for(int var19 = 0; var19 < var18; ++var19) {
Class var22 = var17[var19];
var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
}
var14.writeShort(this.fields.size());
var15 = this.fields.iterator();
while(var15.hasNext()) {
ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
var20.write(var14);
}
var14.writeShort(this.methods.size());
var15 = this.methods.iterator();
while(var15.hasNext()) {
ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
var21.write(var14);
}
var14.writeShort(0);
return var13.toByteArray();
} catch (IOException var9) {
throw new InternalError("unexpected I/O Exception", var9);
}
}
}
从上面的代码可以看到, JDK中添加代理类是使用获取接口, 从接口获取方法再添加到class文件中.
因此JDK中动态代理的劣势就是要求被代理类必须要实现接口.
保存动态生成的代理类
在代码中添加下面一行代码就可以将生成的代理类保存下来, 默认保存地址是当前工作路径的 com.sun.proxy
包下.
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
CGLIB动态代理
CGLIB动态代理就规避了这个问题, 不再要求被代理类必须实现接口, 而且写起来更加简单, 只需要在pom文件里添加cglib的引用即可.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
代码:
// 被代理类
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("teachDao rent");
}
}
// 调用方
public class Client {
public static void main(final String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TeacherDao.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开始cglib代理");
Object invoke = methodProxy.invokeSuper(o, objects);
System.out.println("结束cglib代理");
return invoke;
}
});
TeacherDao o = (TeacherDao)enhancer.create();
o.teach();
}
}
输出:
开始cglib代理
teachDao rent
结束cglib代理
可以看到, enhancer.setSuperclass(TeacherDao.class);
设置的是SuperClass,
而调用中Object invoke = methodProxy.invokeSuper(o, objects);
也是使用invokeSuper
的方式调用的.
因此CGLIB生成的代理类是被代理类的子类.
如果该被代理类被声明为final, 就无法使用CGLIB生成代理类了.