第十三讲-JDK代理原理
JDK代理原理
1. JDK代理类的模拟实现
JDK动态代理的原理内部实现在Proxy.newInstance
方法中,其原理是使用了ASM
动态的生成代理类的字节码,我们无法看出对应代理类的Java代码是什么样子的。
因此这里呢,我们模拟一下JDK的代理实现:
我们首先编写一个接口以及接口的实现类:
package com.cherry.chapter1.a12;
public interface Foo {
void foo();
}
class Target implements Foo{
@Override
public void foo() {
System.out.println("target foo()...");
}
}
首先手写一个简单的代理类,该代理类要实现Foo接口
package com.cherry.chapter1.a12;
// 代理类 实现父接口
public class $Proxy0 implements Foo{
@Override
public void foo() {
// 1. 实现功能的增强
System.out.println("Before...");
// 2. 调用目标
new Target().foo();
}
}
编写主方法测试:
package com.cherry.chapter1.a12;
public class A12Test {
public static void main(String[] args) {
Foo proxy = (Foo)new $Proxy0();
// 调用方法
proxy.foo();
}
}
Before...
target foo()...
我们完成了简单的代理。
大家想一想,JDK在生成代理类的时候会不会把功能增强的代码和调用目标的代码固定在代理类的内部?显然是不行的,JDK生成的代理类并不知道功能增强的代码和调用目标的代码具体执行逻辑是什么,这些都是不确定的,因此我们不能写死在这里,也就是说我们不能把不确定的代码逻辑放在代理类的内部中做,我们可以把这种不确定的代码抽象成抽象的方法。如下面的代码:
首先定义一个接口:
interface InvocationHandler{
void invoke();
}
我们直接在代理类中定义一个InvocationHandler,并在实现接口的方法中直接调用invoke
方法即可:
package com.cherry.chapter1.a12;
// 代理类 实现父接口
public class $Proxy0 implements Foo{
private InvocationHandler invocationHandler;
public $Proxy0(InvocationHandler invocationHandler) {
this.invocationHandler = invocationHandler;
}
@Override
public void foo() {
invocationHandler.invoke();
}
}
因此呢,我们可以在我们比较在我们已知的主方法中调用代理类的foo
方法时可以手动增强,且代理类中的代码时固定的!
package com.cherry.chapter1.a12;
public class A12Test {
public static void main(String[] args) {
Foo proxy = (Foo)new $Proxy0(new InvocationHandler() {
@Override
public void invoke() {
// 1. 实现功能的增强
System.out.println("Before...");
// 2. 调用目标
new Target().foo();
}
});
// 调用方法
proxy.foo();
}
}
测试如下:
Before...
target foo()...
我们发现,在不修改代理类的情况下,我们可以任意的对目标方法进行功能增强!代理类只需要知道接口和要执行方法的抽象类或接口即可!
我们继续改进,我们的接口目前只有一个方法,那么有多个方法怎么办呢?如下面的接口代码:
package com.cherry.chapter1.a12;
public interface Foo {
void foo();
void bar()
}
class Target implements Foo{
@Override
public void foo() {
System.out.println("target foo()...");
}
@Override
public void bar() {
System.out.println("target bar()...");
}
}
我们在代理类中同样也要实现bar方法
package com.cherry.chapter1.a12;
// 代理类 实现父接口
public class $Proxy0 implements Foo{
private InvocationHandler invocationHandler;
public $Proxy0(InvocationHandler invocationHandler) {
this.invocationHandler = invocationHandler;
}
@Override
public void foo() {
invocationHandler.invoke();
}
@Override
public void bar() {
invocationHandler.invoke();
}
}
同样的在主方法中修改
package com.cherry.chapter1.a12;
public class A12Test {
public static void main(String[] args) {
Foo proxy = (Foo)new $Proxy0(new InvocationHandler() {
@Override
public void invoke() {
// 1. 实现功能的增强
System.out.println("Before...");
// 2. 调用目标
new Target().foo();
}
});
// 调用方法
proxy.foo();
proxy.bar();
}
}
Before...
target foo()...
Before...
target foo()...
我们发现,虽然都做了Before增强,但是都调用的是foo方法,因此我们应该再做一个改进,就是在InvocationHandler
的invoke
方法执行的时候,我们应该知道当前正在执行的是哪个方法!到底是foo还是bar方法?我们可以使用方法对象来解决,来看下面的解决思路:
我们修改一下我们的代理类,让我们的代理类根据方法对象执行对应的方法
package com.cherry.chapter1.a12;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 代理类 实现父接口
public class $Proxy0 implements Foo{
private InvocationHandler invocationHandler;
public $Proxy0(InvocationHandler invocationHandler) {
this.invocationHandler = invocationHandler;
}
@Override
public void foo() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
// 获取foo方法对象
Method foo = Foo.class.getDeclaredMethod("foo");
// 将方法对象和参数传递过去
invocationHandler.invoke(foo,new Object[0]);
}
@Override
public void bar() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method bar = Foo.class.getDeclaredMethod("bar");
invocationHandler.invoke(bar, new Object[0]);
}
}
其次修改InvocationHandler
类:
interface InvocationHandler{
void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException;
}
最后修改我们的主方法:
package com.cherry.chapter1.a12;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class A12Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Foo proxy = (Foo)new $Proxy0(new InvocationHandler() {
@Override
public void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
// 1. 实现功能的增强
System.out.println("Before...");
// 2. 调用目标
method.invoke(new Target(), args);
}
});
proxy.bar();
proxy.foo();
}
}
运行测试:
Before...
target bar()...
Before...
target foo()...
我们发现,实现了更加灵活的功能增强!这里总结一下:
-
代理类要和目标类实现共同的接口,但是为了代理类的方法内变的更为灵活(并非将逻辑代码写死在代理类的方法内),我们需要设计一个接口(InvocationHandler)来回调(invoke)这段抽象的操作。
-
在代理类的方案内,我们只需要回调invoke方法即可,那么invoke将来怎么知道执行哪个方法呢?我们可以把方法对象(接口中对应方法)传递进去,也可以传递一些参数。
-
这样我们在调用代理类的时候将方法对象传递过去,代理类就知道当前执行的是哪一个方法对象,再根据方法对象反射调用目标方法。
我们再继续改进我们的代理类,首先解决返回值的处理,如下面的接口和实现类有一个返回值的方法:
package com.cherry.chapter1.a12;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public interface Foo {
void foo() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException;
void bar() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException;
int hello();
}
class Target implements Foo{
@Override
public void foo() {
System.out.println("target foo()...");
}
@Override
public void bar() {
System.out.println("target bar()...");
}
@Override
public int hello() {
System.out.println("target hello()...");
return 100;
}
}
再将InvocationHandler
的invoke
方法作为改进,将返回值改为Object,这里的返回值是目标返回什么,这里就返回什么!
interface InvocationHandler{
Object invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException;
}
同样的,我们在代理中实现这个方法,并获取以及返回结果:
package com.cherry.chapter1.a12;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class A12Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Foo proxy = (Foo)new $Proxy0(new InvocationHandler() {
@Override
public Object invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
// 1. 实现功能的增强
System.out.println("Before...");
// 2. 调用目标
int result = (int)method.invoke(new Target(), args);
return result;
}
});
proxy.bar();
proxy.foo();
}
}
我们发现,在代理类方法中,每次都要反射调用获取方法对象,这里显然太麻烦了,我们可以把方法声明为静态成员变量,例如下面:
package com.cherry.chapter1.a12;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 代理类 实现父接口
public class $Proxy0 implements Foo{
private InvocationHandler invocationHandler;
public $Proxy0(InvocationHandler invocationHandler) {
this.invocationHandler = invocationHandler;
}
static Method foo;
static Method bar;
static {
try {
foo = Foo.class.getDeclaredMethod("foo");
bar = Foo.class.getDeclaredMethod("bar");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@Override
public void foo() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
// 获取foo方法对象
Method foo =
// 将方法对象和参数传递过去
invocationHandler.invoke(this, foo,new Object[0]);
}
@Override
public void bar() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
invocationHandler.invoke(this, bar, new Object[0]);
}
@Override
public int hello() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method hello = Foo.class.getDeclaredMethod("hello");
int result = (int)invocationHandler.invoke(this, hello, new Object[0]);
return result;
}
}
我们目前写的代理类已经可JDK的代理类非常的相似了,我们来看一下JDK中的InvocationHandler
方法的源码:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
和我们写的几乎一样!:)<
再看一下JDK提供的阿代理类:
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
private Proxy() {
}
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
}
其实把我们写的$Proxy0继承JDK提供的Proxy,以及完全使用JDK提供的InvocationHandler完全可以运行我们写的示例程序,这里就不再演示了。
2. JDK代理字节码生成
JDK在生成代理类时,并没有经历源码阶段和编译阶段,而是直接生成字节码了,而直接生成字节码的技术叫做ASM
,该技术广泛用于Spring框架,JDK等底层源码中,起作用就是在运行期间动态生成字节码 。这里呢,我们就模拟一下JDK代理字节么生成的过程。
先编写一个接口:
public interface Foo {
public void foo();
}
编写一个代理类代码:
public class $Proxy0 extends Proxy implements Foo{
// 定义一个方法对象
static Method foo;
static {
try {
foo = Foo.class.getDeclaredMethod("foo");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public void foo() {
try {
this.h.invoke(this,foo, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
接下来将我们写的代码编译一下并讲$Proxy0的文件右键如下操作:
反编译后的代码如下,我们大致的分析一下ASM到底干了哪些事情,我们大致看一下:
package com.cherry.chapter1.a13;
import org.springframework.asm.*;
public class $Proxy0Dump implements Opcodes {
public static byte[] dump() throws Exception {
// 通过ClassWriter生成字节码
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
RecordComponentVisitor recordComponentVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;
// 定义一个类:java版本号,类用public修饰,类名叫com/cherry/chapter1/a13/$Proxy0 ,这个类的父类是java/lang/reflect/Proxy
// 这个类实现了"com/cherry/chapter1/a13/Foo"接口
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "com/cherry/chapter1/a13/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"com/cherry/chapter1/a13/Foo"});
classWriter.visitSource("$Proxy0.java", null);
// 定义类的静态成员变量
{
fieldVisitor = classWriter.visitField(ACC_STATIC, "foo", "Ljava/lang/reflect/Method;", null, null);
fieldVisitor.visitEnd();
}
{
// 定义类的构造方法以及构造方法的实现
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null);
methodVisitor.visitParameter("h", 0);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(21, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(22, label1);
methodVisitor.visitInsn(RETURN);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLocalVariable("this", "Lcom/cherry/chapter1/a13/$Proxy0;", null, label0, label2, 0);
methodVisitor.visitLocalVariable("h", "Ljava/lang/reflect/InvocationHandler;", null, label0, label2, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
}
{
// 定义foo方法以及foo方法内的实现
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "foo", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
Label label1 = new Label();
Label label2 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Throwable");
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(27, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "com/cherry/chapter1/a13/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;");
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETSTATIC, "com/cherry/chapter1/a13/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
methodVisitor.visitInsn(ACONST_NULL);
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(30, label1);
Label label3 = new Label();
methodVisitor.visitJumpInsn(GOTO, label3);
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(28, label2);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
methodVisitor.visitVarInsn(ASTORE, 1);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(29, label4);
methodVisitor.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException");
methodVisitor.visitInsn(DUP);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false);
methodVisitor.visitInsn(ATHROW);
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(31, label3);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(RETURN);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLocalVariable("e", "Ljava/lang/Throwable;", null, label4, label3, 1);
methodVisitor.visitLocalVariable("this", "Lcom/cherry/chapter1/a13/$Proxy0;", null, label0, label5, 0);
methodVisitor.visitMaxs(4, 2);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
Label label1 = new Label();
Label label2 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/NoSuchMethodException");
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(14, label0);
methodVisitor.visitLdcInsn(Type.getType("Lcom/cherry/chapter1/a13/Foo;"));
methodVisitor.visitLdcInsn("foo");
methodVisitor.visitInsn(ICONST_0);
methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Class");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/cherry/chapter1/a13/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(17, label1);
Label label3 = new Label();
methodVisitor.visitJumpInsn(GOTO, label3);
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(15, label2);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/NoSuchMethodException"});
methodVisitor.visitVarInsn(ASTORE, 0);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(16, label4);
methodVisitor.visitTypeInsn(NEW, "java/lang/NoSuchMethodError");
methodVisitor.visitInsn(DUP);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/NoSuchMethodException", "getMessage", "()Ljava/lang/String;", false);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V", false);
methodVisitor.visitInsn(ATHROW);
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(18, label3);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitLocalVariable("e", "Ljava/lang/NoSuchMethodException;", null, label4, label3, 0);
methodVisitor.visitMaxs(3, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
// 将生成好的类生成byte数组,这个数组就是代理类最终的字节码文件
return classWriter.toByteArray();
}
}
我们写一个测试类查看一下最终生成的字节码文件:
package com.cherry.chapter1.a13;
import java.io.FileOutputStream;
public class TestProxy {
public static void main(String[] args) throws Exception {
byte[] dump = $Proxy0Dump.dump();
// 查看一下代理类最终生成的字节码文件
FileOutputStream fos = new FileOutputStream("$Proxy0.class");
fos.write(dump,0,dump.length);
fos.close();
}
}
拖到IDEA中反编译:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.cherry.chapter1.a13;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public class $Proxy0 extends Proxy implements Foo {
static Method foo;
public $Proxy0(InvocationHandler h) {
super(h);
}
public void foo() {
try {
this.h.invoke(this, foo, (Object[])null);
} catch (Throwable var2) {
Throwable e = var2;
throw new UndeclaredThrowableException(e);
}
}
static {
try {
foo = Foo.class.getDeclaredMethod("foo");
} catch (NoSuchMethodException var1) {
NoSuchMethodException e = var1;
throw new NoSuchMethodError(e.getMessage());
}
}
}
我们发现从ASM反编译过来的代理类源码和我们模拟写代理类的非常的相似:)>!
接下来我们来加载这个字节码文件来使用这个代理类:
package com.cherry.chapter1.a13;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TestProxy {
public static void main(String[] args) throws Exception {
byte[] dump = $Proxy0Dump.dump();
// 查看一下代理类最终生成的字节码文件
// FileOutputStream fos = new FileOutputStream("$Proxy0.class");
// fos.write(dump,0,dump.length);
// fos.close();
ClassLoader loader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.defineClass(name, dump, 0, dump.length);
}
};
Class<?> proxyClass = loader.loadClass("com.cherry.chapter1.a13.$ProxyDump");
// 创建代理类实例
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
Foo proxy = (Foo) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before...");
System.out.println("调用目标");
return null;
}
});
// 调用接口中的方法
proxy.foo();
}
}
3. JDK反射优化
在上面讲到代理的时候,有一行代码非常的关键,就是偶尔美女每次都要使用反射机制来调用方法。那么对于方法的反射调用,JDK对此有没有做一些优化的处理呢?
这里我们研究一下。我们首先编写一个测试类:
package com.cherry.chapter1.a13;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
// 运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
public class TestMethodInvoke {
public static void main(String[] args) throws Exception {
Method foo = TestMethodInvoke.class.getMethod("foo", int.class);
for (int i = 1; i <= 17; i++) {
show(i, foo);
foo.invoke(null, i);
}
System.in.read();
}
// 方法反射调用时, 底层 MethodAccessor 的实现类
private static void show(int i, Method foo) throws Exception {
Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");
getMethodAccessor.setAccessible(true);
Object invoke = getMethodAccessor.invoke(foo);
if (invoke == null) {
System.out.println(i + ":" + null);
return;
}
Field delegate = Class.forName("jdk.internal.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");
delegate.setAccessible(true);
System.out.println(i + ":" + delegate.get(invoke));
}
public static void foo(int i) {
System.out.println(i + ":" + "foo");
}
}
我们发现前16次反射调用采用的是基于Java Native 本地API实现的,因此性能是比较低的,从第17次开始,把反射调用的实现给换了,把反射调用换成了代理调用,因此反射调用的性能大大提高了!
当然代价是什么?代价就是为了优化一个方法反射的调用,会生成一个代理类。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构