古朗月行
- 代理模式
- JDK动态代理
- 代码示例
- 原码分析
- cglib动态代理
- 代码示例
- 源码分析
- JDK cglib动态代理对比
- ClassLoader
- 类的生命周期:
- 参考资料
唐 李白
小时不识月,呼作白玉盘。
又疑瑶台镜,飞在青云端。
仙人垂两足,桂树何团团。
白兔捣药成,问言与谁餐?
蟾蜍蚀圆影,大明夜已残。
羿昔落九乌,天人清且安。
阴精此沦惑,去去不足观。
忧来其如何,凄怆摧心肝
代理模式
In computer programming, the proxy pattern is a software design pattern. A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate. In short, a proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy, extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked. For the client, usage of a proxy object is similar to using the real object, because both implement the same interface.
在计算机编程中,代理模式是一个软件设计模式。
JDK动态代理
动态代理的本质就是在运行时动态的生成一个代理类,这个代理类的加载同样遵循JVM类加载机制那一套东西,涉及到动态生成代理类的字节码并将其加载到JVM中。
代码示例
package org.example.proxy.jdk;
public interface IService {
void myselfMethod();
}
package org.example.proxy.jdk.impl;
import org.example.proxy.jdk.IService;
/**
* 真的对象服务
*
* @author Samson Bruce
* @since 2024/11/26:11:33
*/
public class RealObjectService implements IService {
@Override
public void myselfMethod() {
System.out.println("RealObjectService test .......");
}
}
/**
* 动态代理处理类
*
* @author samson bruce
* @since 2024/11/26:11:34
* 1.拿到被代理对象的引用,然后获取它的接口
* 2.jdk代理重新生成一个类,同时实现我们个额的代理对象所实现的接口
* 3.把被代理对象的引用也拿到了
* 4.重新动态生成一个class字节码
* 5.然后编译
*/
@Slf4j
public class DynamicInvocationHandler implements InvocationHandler {
private Object target;
public Object getInstance(IService target) throws Exception {
this.target = target;
Class<?> clazz = target.getClass();
log.info("被代理对象的class是:{}", clazz);
//一旦代理类被加载和链接,就可以使用 newProxyInstance 方法返回的 Constructor 对象来创建代理实例。
// 这个构造器的 newInstance 方法接收一个 InvocationHandler 实例作为参数,并返回代理对象。
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("开始进行方法增强,(记录日志、校验、性能统计等)……");
Object result = method.invoke(target, args);
log.info("方法增强完毕,(记录日志、校验、性能统计等)……");
return result;
}
}
package org.example.proxy.jdk;
import org.example.proxy.jdk.impl.RealObjectService;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @FileName: MainTest
* @Description:
* @Author: liulianglin
* @Date: 2024/11/26:12:10
*/
public class JDKProxyExampleMain {
public static void printClassInfo(Executable[] targets) {
for (Executable target : targets) {
// 构造器/方法名称
String name = target.getName();
StringBuilder sBuilder = new StringBuilder(name);
// 拼接左括号
sBuilder.append('(');
Class<?>[] clazzParams = target.getParameterTypes();
// 拼接参数
for (Class<?> clazzParam : clazzParams) {
sBuilder.append(clazzParam.getName()).append(',');
}
//删除最后一个参数的逗号
if (clazzParams.length != 0) {
sBuilder.deleteCharAt(sBuilder.length() - 1);
}
//拼接右括号
sBuilder.append(')');
//打印 构造器/方法
System.out.println(sBuilder.toString());
}
}
/**
* 将动态代理生成的代理类的字节码保存到本地磁盘,方便调试查看
* @param path
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IOException
*/
public static void saveProxyClassFile(String path) {
Class cl = null;
FileOutputStream fileOutputStream = null;
try {
// Java11开始ProxyGenerator,不再public,改为了private,无法直接使用,所以采用反射的方式获取它
cl = Class.forName("java.lang.reflect.ProxyGenerator");
Method m =cl.getDeclaredMethod("generateProxyClass",String.class,Class[].class);
m.setAccessible(true);
byte[] $proxy1 = (byte[]) m.invoke(null, "$proxy1", RealObjectService.class.getInterfaces());
System.out.println($proxy1.length);
fileOutputStream = new FileOutputStream((path + "$Proxy.class"));
fileOutputStream.write($proxy1);
}catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e){
e.printStackTrace();
} finally {
if (fileOutputStream !=null) {
try {
fileOutputStream.flush();
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
try {
IService iService = (IService) new DynamicInvocationHandler().getInstance(new RealObjectService());
iService.myselfMethod();
// 将生成的动态代理类的字节码文件保存到本地
saveProxyClassFile("./");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
原码分析
- 确定类加载器
首先,Proxy.newProxyInstance 方法接收一个 ClassLoader 参数,这个参数指定了用于加载代理类的类加载器。通常,这个类加载器是被代理对象的类加载器,但也可以是其他任意的类加载器。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null? null: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
* Returns the Constructor object of a proxy class that takes a single argument of type InvocationHandler, given a class loader and an array of interfaces.
* The returned constructor will have the accessible flag already set.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
Returns the Constructor object of a proxy class that takes a single argument of type InvocationHandler, given a class loader and an array of interfaces.
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces)
{
// optimization for single interface
if (interfaces.length == 1) {
//如果只有一个接口,那么首先获取该接口
Class<?> intf = interfaces[0];
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
//使用 proxyCache 缓存来获取或生成代理类的构造器。
//proxyCache 是一个多层次的缓存结构,其中 sub(intf) 获取或创建与接口相关的子缓存,
//computeIfAbsent 方法确保对于给定的类加载器 loader,只生成一次代理类。
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
// interfaces cloned
final Class<?>[] intfsArray = interfaces.clone();
if (caller != null) {
checkProxyAccess(caller, loader, intfsArray);
}
final List<Class<?>> intfs = Arrays.asList(intfsArray);
return proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
}
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (caller != null) {
checkNewProxyPermission(caller, cons.getDeclaringClass());
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
}
}
- 生成代理类的字节码(核心)
JDK动态代理通过以下关键步骤生成代理类的字节码:
- 创建代理类名称:代理类的名称是由 “$Proxy” + 一个数字序号 构成的,例如 P r o x y 0 、 Proxy0、 Proxy0、Proxy1 等。这个序号是为了保证代理类的名称唯一性。
- 实现接口:代理类实现了传入 newProxyInstance 方法的所有接口。
- 添加构造方法:代理类有一个私有的构造方法,它接受一个 InvocationHandler 类型的参数。
- 生成方法实现:对于每个接口方法,代理类都会生成一个实现,该实现在调用目标方法之前和之后会调用 InvocationHandler 的 invoke 方法。
- 字节码生成:JDK内部使用 ProxyBuilder 类(在 JDK 内部,不是公开API)来生成代理类的字节码。
- 加载代理类(JVM的那一套)
生成的字节码被传递给类加载器,由类加载器负责将字节码加载到JVM中,创建代理类的 Class 对象。这个过程包括:
- 定义类:类加载器使用 defineClass 方法将字节码定义为一个类。
- 链接类:链接过程包括验证字节码、准备类、解析符号引用等步骤。
- 初始化类:对于非抽象的代理类,JVM会执行类的初始化,包括执行类构造器 ()。
-
创建代理实例(使用过程)
一旦代理类被加载和链接,就可以使用 newProxyInstance 方法返回的 Constructor 对象来创建代理实例。这个构造器的 newInstance 方法接收一个 InvocationHandler 实例作为参数,并返回代理对象。 -
方法调用和拦截(使用过程)
当代理对象的方法被调用时,JVM会跳转到代理类中的方法实现,这些实现会委托给 InvocationHandler 的 invoke 方法来处理。invoke 方法负责执行实际的逻辑,包括调用目标对象的方法和添加额外的处理。
cglib动态代理
代码示例
源码分析
JDK cglib动态代理对比
JDK动态代理和CGLIB动态代理是Java中常用的两种代理技术,它们有以下几个主要区别:
- 代理方式的不同
JDK动态代理:JDK动态代理依赖于接口,它只能对实现了接口的类进行代理。通过 java.lang.reflect.Proxy 类生成代理对象。即被代理类必须实现至少一个接口。
CGLIB动态代理:CGLIB(Code Generation Library)是通过继承被代理类来创建代理对象的。CGLIB可以对没有实现接口的类进行代理。它使用字节码生成技术,在运行时动态地生成代理类。 - 是否需要接口
JDK动态代理:要求被代理的类必须实现接口,因此不能代理没有实现接口的类。
CGLIB动态代理:不需要接口,可以代理没有实现接口的类。 - 代理类的生成方式
JDK动态代理:通过 Proxy.newProxyInstance() 方法生成代理类,该方法使用反射机制来创建代理对象。
CGLIB动态代理:通过继承被代理类,并通过字节码技术生成代理类。CGLIB通过修改类的字节码实现代理,代理类是被继承的。 - 性能
JDK动态代理:因为它使用反射机制,性能相对较低,尤其是在大量调用代理方法时。
CGLIB动态代理:因为它是通过继承生成代理类,性能通常比JDK动态代理高。 - 代理目标的限制
JDK动态代理:只能代理接口,不能代理类。如果目标类没有接口,JDK动态代理不能使用。
CGLIB动态代理:能够代理没有接口的类,但它有一些限制。例如,如果目标类的方法是 final 或 private,CGLIB就无法代理该方法,因为CGLIB通过继承生成代理类,而 final 或 private 方法不能被重写。 - 使用场景
JDK动态代理:适用于目标类实现了接口的场景,特别是当接口较多时,JDK动态代理更加清晰和灵活。
CGLIB动态代理:适用于目标类没有接口或目标类的方法较多且没有接口的场景,CGLIB在这些场景中更具优势。 - Spring框架中的使用
在Spring框架中,Spring默认使用JDK动态代理进行AOP代理(前提是目标类实现了接口)。如果目标类没有实现接口,Spring则使用CGLIB来创建代理对象。
ClassLoader
类加载是Java中将类的字节码读入内存并准备好供程序使用的过程,这个过程由类加载器(Class Loader)负责。
类的生命周期:
- 加载 Loading 找到class文件
- 链接 Linking 链接是指将编译后生成的一个或多个目标文件(以及所需的库文件)合并为一个可执行文件的过程。这个过程由链接器(Linker)负责执行。
验证 Verification 检查字节码的正确性和安全性
准备 Preparation 分配内存并初始化类变量(静态变量)
解析 Analysis 将符号引用转换为直接引用,解决类间的依赖关系 - 初始化 Initialization 执行类的初始化方法,初始化类变量
- 使用 Using
- 卸载 Unloading
Java中的类加载器有多种类型,包括:
- 引导类加载器(Bootstrap Class Loader):加载Java核心类库。
- 扩展类加载器(Extension Class Loader):加载Java扩展库。
- 应用类加载器(Application Class Loader):加载应用程序的类。
类加载机制使得Java具有动态性和灵活性,可以在运行时加载类,从而支持多种应用场景。
参考资料
添加链接描述
Spring AOP
【深度思考】聊聊CGLIB动态代理原理