cglib动态代理实现原理_动态代理的两种方式

cglib动态代理实现原理_动态代理的两种方式CGLib动态代理原理CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法啊,这样也可以保证代理类拥有目标类的同名方法;看一下CGLib的基本结构,下图所示,代理类去继承目标类,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑,结构还是一目了然的;1.CGLib的基本使用使用一下CGLib,在JDK动态代理中提供一个Proxy类来创建代理类,而在CGLib动态代理中也提供了一个类似的类Enhancer;使用的CGLib版本是2.2.2,我是随便找的,不

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

CGLib动态代理原理

CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法啊,这样也可以保证代理类拥有目标类的同名方法;

看一下CGLib的基本结构,下图所示,代理类去继承目标类,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑,结构还是一目了然的;
在这里插入图片描述

1.CGLib的基本使用

使用一下CGLib,在JDK动态代理中提供一个Proxy类来创建代理类,而在CGLib动态代理中也提供了一个类似的类Enhancer;

使用的CGLib版本是2.2.2,我是随便找的,不同的版本有点小差异,建议用3.x版本的…我用的maven项目进行测试的,首先要导入cglib的依赖

<dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.2.2</version>
</dependency>

目标类(一个公开方法,另外一个用final修饰):

package com.wyq.day527;

public class Dog{ 
   
    
    final public void run(String name) { 
   
        System.out.println("狗"+name+"----run");
    }
    
    public void eat() { 
   
        System.out.println("狗----eat");
    }
}

方法拦截器:

package com.wyq.day527;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyMethodInterceptor implements MethodInterceptor{ 
   

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 
   
        System.out.println("这里是对目标类进行增强!!!");
        //注意这里的方法调用,不是用反射哦!!!
        Object object = proxy.invokeSuper(obj, args);
        return object;
    }  
}

测试类:

package com.wyq.day527;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

public class CgLibProxy { 
   
    public static void main(String[] args) { 
   
        //在指定目录下生成动态代理类,我们可以反编译看一下里面到底是一些什么东西
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\java\\java_workapace");
        
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer = new Enhancer();
        //设置目标类的字节码文件
        enhancer.setSuperclass(Dog.class);
        //设置回调函数
        enhancer.setCallback(new MyMethodInterceptor());
        
        //这里的creat方法就是正式创建代理类
        Dog proxyDog = (Dog)enhancer.create();
        //调用代理类的eat方法
        proxyDog.eat();       
    }
}

测试结果:

在这里插入图片描述
使用起来还是很容易的,但是其中有很多小细节我们要注意,下面我们就慢慢的看;

2.生成动态代理类

首先到我们指定的目录下面看一下生成的字节码文件,有三个,一个是代理类的FastClass,一个是代理类,一个是目标类的FastClass,我们看看代理类(Dog

EnhancerByCGLIBEnhancerByCGLIB

a063bd58.class),名字略长~后面会仔细介绍什么是FastClass,这里简单说一下,就是给每个方法编号,通过编号找到方法,这样可以避免频繁使用反射导致效率比较低,也可以叫做FastClass机制
在这里插入图片描述  然后我们可以结合生成的动态代理类来简单看看原理,一个反编译工具

我们就打开xxx.java文件,稍微进行整理一下,我们可以看到对于eat方法,在这个代理类中对应会有eat 和CGLIB$eat 0 这 两 个 方 法 ;     − 其 中 前 者 e a t 则 是 我 们 使 用 代 理 类 时 候 调 用 的 方 法 ,     − 后 者 C G L I B 0这两个方法;   - 其中前者eat 则是我们使用代理类时候调用的方法,   - 后者CGLIB 0  eat使  CGLIBeat 0 是 在 方 法 拦 截 器 里 面 调 用 的 ,   换 句 话 来 说 当 我 们 代 码 调 用 代 理 对 象 的 e a t 方 法 , 然 后 会 到 方 法 拦 截 器 中 调 用 i n t e r c e p t 方 法 , 该 方 法 内 则 通 过 p r o x y . i n v o k e S u p e r 调 用 C G L I B 0是在方法拦截器里面调用的,  换句话来说当我们代码调用代理对象的eat方法,然后会到方法拦截器中调用intercept方法,该方法内则通过proxy.invokeSuper调用CGLIB 0 eatinterceptproxy.invokeSuperCGLIBeat$0这个方法,不要因为方法名字太长了就觉得难,其实原理很简单。。。(顺便一提,不知道大家有没有发现代理类中只有eat方法,没有run方法,因为run方法被final修饰了,不可被重写,所以代理类中就没有run方法,这里要符合java规范!!!)

package com.wyq.day527;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.*;

//可以看到这个代理类是继承我们的目标类Dog,并且顺便实现了一个Factory接口,这个接口就是一些设置回调函数和返回实例化对象的方法
public class Dog$$EnhancerByCGLIB$$fbca2ec6 extends Dog implements Factory{ 
   
    //这里有很多的属性,仔细看一下就是一个方法对应两个,一个是Method类型,一个是MethodProxy类型
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback CGLIB$STATIC_CALLBACKS[];
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$eat$0$Method;
    private static final MethodProxy CGLIB$eat$0$Proxy;
    private static final Object CGLIB$emptyArgs[];
    private static final Method CGLIB$finalize$1$Method;
    private static final MethodProxy CGLIB$finalize$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;
  
    //静态代码块,调用下面静态方法,这个静态方法大概做的就是获取目标方法中每个方法的MethodProxy对象
      static { 
   
          CGLIB$STATICHOOK1();
      }
    
    //无参构造器
    public Dog$$EnhancerByCGLIB$$fbca2ec6()
    { 
   
        CGLIB$BIND_CALLBACKS(this);
    }

    //此方法在上面的静态代码块中被调用
    static void CGLIB$STATICHOOK1(){ 
   
        //注意下面这两个Method数组,用于保存反射获取的Method对象,避免每次都用反射去获取Method对象
        Method[] amethod;
        Method[] amethod1;
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        
        //获取目标类的字节码文件
        Class class1 = Class.forName("com.wyq.day527.Dog$$EnhancerByCGLIB$$fbca2ec6");
        
        //代理类的字节码文件
        Class class2;
        
        //ReflectUtils是一个包装各种反射操作的工具类,通过这个工具类来获取各个方法的Method对象,然后保存到上述的Method数组中
        amethod = ReflectUtils.findMethods(new String[] { 
   
            "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"
        }, (class2 = Class.forName("java.lang.Object")).getDeclaredMethods());
        Method[] _tmp = amethod;
        
        //为目标类的每一个方法都建立索引,可以想象成记录下来目标类中所有方法的地址,需要用调用目标类方法的时候根据地址就能直接找到该方法
        //这就是此处CGLIB$xxxxxx$$Proxy的作用。。。
        CGLIB$finalize$1$Method = amethod[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(class2, class1, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = amethod[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(class2, class1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = amethod[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = amethod[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(class2, class1, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = amethod[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        amethod1 = ReflectUtils.findMethods(new String[] { 
   
            "eat", "()V"
        }, (class2 = Class.forName("com.wyq.day527.Dog")).getDeclaredMethods());
        Method[] _tmp1 = amethod1;
        CGLIB$eat$0$Method = amethod1[0];
        CGLIB$eat$0$Proxy = MethodProxy.create(class2, class1, "()V", "eat", "CGLIB$eat$0");
    }

    //这个方法就是调用目标类的的eat方法
    final void CGLIB$eat$0()
    { 
   
        super.eat();
    }

    //这个方法是我们是我们要调用的,在前面的例子中调用代理对象的eat方法就会到这个方法中
    public final void eat(){ 
   
        //CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
        CGLIB$CALLBACK_0;
        //这里就是判断CGLIB$CALLBACK_0是否为空,也就是我们传入的方法拦截器是否为空,如果不为空就最终到下面的_L4
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 37;
           goto _L3 _L4
_L3:
        break MISSING_BLOCK_LABEL_21;
_L4:
        break MISSING_BLOCK_LABEL_37;
        this;
        CGLIB$eat$0$Method;
        CGLIB$emptyArgs;
        CGLIB$eat$0$Proxy;
        //这里就是调用方法拦截器的intecept()方法
        intercept();
        return;
        super.eat();
        return;
    }
    
    //这里省略finalize,equals,toString,hashCode,clone,因为和上面的eat的两个方法差不多
    //..........
    //...........
    //..........

    public static MethodProxy CGLIB$findMethodProxy(Signature signature)
    { 
   
        String s = signature.toString();
        s;
        s.hashCode();
        JVM INSTR lookupswitch 6: default 140
    // -1574182249: 68
    // -1310345955: 80
    // -508378822: 92
    // 1826985398: 104
    // 1913648695: 116
    // 1984935277: 128;
           goto _L1 _L2 _L3 _L4 _L5 _L6 _L7
_L2:
        "finalize()V";
        equals();
        JVM INSTR ifeq 141;
           goto _L8 _L9
_L9:
        break MISSING_BLOCK_LABEL_141;
_L8:
        return CGLIB$finalize$1$Proxy;
_L3:
        "eat()V";
        equals();
        JVM INSTR ifeq 141;
           goto _L10 _L11
_L11:
        break MISSING_BLOCK_LABEL_141;
_L10:
        return CGLIB$eat$0$Proxy;
_L4:
        "clone()Ljava/lang/Object;";
        equals();
        JVM INSTR ifeq 141;
           goto _L12 _L13
_L13:
        break MISSING_BLOCK_LABEL_141;
_L12:
        return CGLIB$clone$5$Proxy;
_L5:
        "equals(Ljava/lang/Object;)Z";
        equals();
        JVM INSTR ifeq 141;
           goto _L14 _L15
_L15:
        break MISSING_BLOCK_LABEL_141;
_L14:
        return CGLIB$equals$2$Proxy;
_L6:
        "toString()Ljava/lang/String;";
        equals();
        JVM INSTR ifeq 141;
           goto _L16 _L17
_L17:
        break MISSING_BLOCK_LABEL_141;
_L16:
        return CGLIB$toString$3$Proxy;
_L7:
        "hashCode()I";
        equals();
        JVM INSTR ifeq 141;
           goto _L18 _L19
_L19:
        break MISSING_BLOCK_LABEL_141;
_L18:
        return CGLIB$hashCode$4$Proxy;
_L1:
        JVM INSTR pop ;
        return null;
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback acallback[])
    { 
   
        CGLIB$THREAD_CALLBACKS.set(acallback);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback acallback[])
    { 
   
        CGLIB$STATIC_CALLBACKS = acallback;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object obj)
    { 
   
        Dog$$EnhancerByCGLIB$$fbca2ec6 dog$$enhancerbycglib$$fbca2ec6 = (Dog$$EnhancerByCGLIB$$fbca2ec6)obj;
        if(dog$$enhancerbycglib$$fbca2ec6.CGLIB$BOUND) goto _L2; else goto _L1
_L1:
        Object obj1;
        dog$$enhancerbycglib$$fbca2ec6.CGLIB$BOUND = true;
        obj1 = CGLIB$THREAD_CALLBACKS.get();
        obj1;
        if(obj1 != null) goto _L4; else goto _L3
_L3:
        JVM INSTR pop ;
        CGLIB$STATIC_CALLBACKS;
        if(CGLIB$STATIC_CALLBACKS != null) goto _L4; else goto _L5
_L5:
        JVM INSTR pop ;
          goto _L2
_L4:
        (Callback[]);
        dog$$enhancerbycglib$$fbca2ec6;
        JVM INSTR swap ;
        0;
        JVM INSTR aaload ;
        (MethodInterceptor);
        CGLIB$CALLBACK_0;
_L2:
    }

    public Object newInstance(Callback acallback[])
    { 
   
        CGLIB$SET_THREAD_CALLBACKS(acallback);
        CGLIB$SET_THREAD_CALLBACKS(null);
        return new Dog$$EnhancerByCGLIB$$fbca2ec6();
    }

    public Object newInstance(Callback callback)
    { 
   
        CGLIB$SET_THREAD_CALLBACKS(new Callback[] { 
   
            callback
        });
        CGLIB$SET_THREAD_CALLBACKS(null);
        return new Dog$$EnhancerByCGLIB$$fbca2ec6();
    }

    public Object newInstance(Class aclass[], Object aobj[], Callback acallback[])
    { 
   
        CGLIB$SET_THREAD_CALLBACKS(acallback);
        JVM INSTR new #2   <Class Dog$$EnhancerByCGLIB$$fbca2ec6>;
        JVM INSTR dup ;
        aclass;
        aclass.length;
        JVM INSTR tableswitch 0 0: default 35
    // 0 28;
           goto _L1 _L2
_L2:
        JVM INSTR pop ;
        Dog$$EnhancerByCGLIB$$fbca2ec6();
          goto _L3
_L1:
        JVM INSTR pop ;
        throw new IllegalArgumentException("Constructor not found");
_L3:
        CGLIB$SET_THREAD_CALLBACKS(null);
        return;
    }

    public Callback getCallback(int i)
    { 
   
        CGLIB$BIND_CALLBACKS(this);
        this;
        i;
        JVM INSTR tableswitch 0 0: default 30
    // 0 24;
           goto _L1 _L2
_L2:
        CGLIB$CALLBACK_0;
          goto _L3
_L1:
        JVM INSTR pop ;
        null;
_L3:
        return;
    }

    public void setCallback(int i, Callback callback)
    { 
   
        switch(i)
        { 
   
        case 0: // '\0'
            CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
            break;
        }
    }

    public Callback[] getCallbacks()
    { 
   
        CGLIB$BIND_CALLBACKS(this);
        this;
        return (new Callback[] { 
   
            CGLIB$CALLBACK_0
        });
    }

    public void setCallbacks(Callback acallback[])
    { 
   
        this;
        acallback;
        JVM INSTR dup2 ;
        0;
        JVM INSTR aaload ;
        (MethodInterceptor);
        CGLIB$CALLBACK_0;
    }

    

   
}

根据上面的代码我们可以知道代理类中主要有几部分组成:

  1. 重写的父类方法,
  2. CGLIB$eat$0这种奇怪的方法,
  3. Interceptor()方法,
  4. newInstance和get/setCallback方法

3.FastClass机制分析

为什么要用这种机制呢?直接用反射多好啊,但是我们知道反射虽然很好用,但是和直接new对象相比,效率有点慢,于是就有了这种机制, Jdk动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法,下面用一个小例子来说明一下,这样比较直观:

public class test10 { 
   
    public static void main(String[] args){ 
   
        Test tt = new Test();
        Test2 fc = new Test2();
        int index = fc.getIndex("f()V");
        fc.invoke(index, tt, null);
    }
}

class Test{ 
   
    public void f(){ 
   
        System.out.println("f method");
    }
    
    public void g(){ 
   
        System.out.println("g method");
    }
}
class Test2{ 
   
    public Object invoke(int index, Object o, Object[] ol){ 
   
        Test t = (Test) o;
        switch(index){ 
   
        case 1:
            t.f();
            return null;
        case 2:
            t.g();
            return null;
        }
        return null;
    }
    
    public int getIndex(String signature){ 
   
        switch(signature.hashCode()){ 
   
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }
}

上例中,Test2是Test的Fastclass,在Test2中有两个方法getIndex和invoke。在getIndex方法中对Test的每个方法建立索引,并根据入参(方法名+方法的描述符)来返回相应的索引。Invoke根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率。代理类(Target

EnhancerByCGLIBEnhancerByCGLIB

788444a0)中与生成Fastclass相关的代码如下:

Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
localClass2 = Class.forName("net.sf.cglib.test.Target");
CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");

MethodProxy中会对localClass1和localClass2进行分析并生成FastClass,然后再使用getIndex来获取方法g 和 CGLIB$g$0的索引,具体的生成过程将在后续进行介绍,这里介绍一个关键的内部类:

private static class FastClassInfo
    { 
   
        FastClass f1; // net.sf.cglib.test.Target的fastclass
        FastClass f2; // Target$$EnhancerByCGLIB$$788444a0 的fastclass
        int i1; //方法g在f1中的索引
        int i2; //方法CGLIB$g$0在f2中的索引
    }

MethodProxy 中invokeSuper方法的代码如下:

 FastClassInfo fci = fastClassInfo;
    return fci.f2.invoke(fci.i2, obj, args);

当调用invokeSuper方法时,实际上是调用代理类的CGLIB$g 0 方 法 , C G L I B 0方法,CGLIB 0CGLIBg$0直接调用了目标类的g方法。所以,在第一节示例代码中我们使用invokeSuper方法来调用被拦截的目标类方法。

4.简单原理

上面我们看了CGLib动态代理的用法、实际生成的代理类以及FastClass机制,下面我们就以最前面的那个例子中调用eat()方法来看看主要的调用步骤;

第一步:是经过一系列操作实例化出了Enhance对象,并设置了所需要的参数然后enhancer.create()成功创建出来了代理对象,这个就不多说了…

第二步:调用代理对象的eat()方法,会进入到方法拦截器的intercept()方法,在这个方法中会调用proxy.invokeSuper(obj, args);方法

第三步:invokeSuper中,通过FastClass机制调用目标类的方法

方法拦截器中只有一个invoke方法,这个方法有四个参数,obj表示代理对象,method表示目标类中的方法,args表示方法参数,proxy表示代理方法的MethodProxy对象
在这里插入图片描述
在这个方法内部会调用proxy.invokeSuper(obj, args)方法,我们进入.invokeSuper方法内部看看:

在这里插入图片描述

简单看看init()方法:

在这里插入图片描述
 FastClassInfo内部如下图,由此可以看出prxy.invokeSuper()方法中fci.f2.invoke(fci.i2, obj, args),其实就是调用CGLIB e a t eat eat这个方法

在这里插入图片描述
 invoke方法是个抽象方法,我们反编译一下代理类的FastClass(也就是生成的那三个字节码文件名称最长的那个)就可以看到,由于代码比较长,就不复制了…
在这里插入图片描述

5.总结

CGLib动态代理是将继承用到了极致
  这里随便画一个简单的图看看整个过程,当我们去调用方法一的时候,在代理类中会先判断是否实现了方法拦截的接口,没实现的话直接调用目标类的方法一;如果实现了那就会被方法拦截器拦截,在方法拦截器中会对目标类中所有的方法建立索引,其实大概就是将每个方法的引用保存在数组中,我们就可以根据数组的下标直接调用方法,而不是用反射;索引建立完成之后,方法拦截器内部就会调用invoke方法(这个方法在生成的FastClass中实现),在invoke方法内就是调用CGLIB方 法 一 方法一方法一这种方法,也就是调用对应的目标类的方法一;

一般我们要添加自己的逻辑就是在方法拦截器那里。。。。

在这里插入图片描述

学习参考记录:
https://www.cnblogs.com/wyq1995/p/10945034.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/179117.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • IntelliJ IDEA 2021.5 x64 激活码[在线序列号]

    IntelliJ IDEA 2021.5 x64 激活码[在线序列号],https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月18日
    43
  • 转发和重定向的区别是什么

    转发和重定向的区别是什么1 请求次数重定向是浏览器向服务器发送一个请求并收到响应后再次向一个新地址发出请求 转发是服务器收到请求后为了完成响应跳转到一个新的地址 重定向至少请求两次 转发请求一次 2 地址栏不同重定向地址栏会发生变化 转发地址栏不会发生变化 3 是否共享数据重定向两次请求不共享数据 转发一次请求共享数据 在 request 级别使用信息共享 使用重定向必然出错 4 跳转限制重定向可以跳转到任意 URL 转发只能跳转本站点资源 5 发生行为不同重定向是客户端行为 转发是服务器端行为

    2025年10月9日
    3
  • 莫比乌斯反演的两种形式及其证明

    莫比乌斯反演的两种形式及其证明莫比乌斯反演形式一 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 证明 把代入右边的式子 得 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 根据莫比乌斯函数的性质 有定理 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 因此 只有

    2025年6月28日
    3
  • sublime激活码【注册码】

    sublime激活码【注册码】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月18日
    63
  • ai修复照片软件是哪个软件_AI照片修复免费版-AI照片修复软件下载v1.1.0安卓版-西西软件下载…「建议收藏」

    ai修复照片软件是哪个软件_AI照片修复免费版-AI照片修复软件下载v1.1.0安卓版-西西软件下载…「建议收藏」AI照片修复软件是一款专业的照片图片修复处理工具,可以帮助用户将旧照片、老照片以及低像素的照片进行修复,软件界面赶紧操作简单,适合没有经验的伙伴修复使用,除此之外,还有各种美化效果,轻松将照片上色、换底、换像素,来西西下载即可使用哦!AI照片修复软件简介:AI照片修复是一款免费的智能AI照片修复处理工具,包括照片破损修复、黑白照片上色、无损放大、模糊照片清晰增强等实用工具。基于先进的AI图像生成技…

    2022年4月18日
    121
  • DB2的JDBC连接

    DB2的JDBC连接

    2021年5月7日
    129

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号