代理、静态代理、动态代理

以下内容是建立在理解的基础上从各博客整体所得,对各类代理有了一个比较清楚地认识。

代理模式

给目标对象提供一个代理类以控制对目标对象的访问,在不修改目标对象基础上,对目标对象进行功能扩展。代理对象和目标对象(真实业务对象)实现共同的接口或继承于同一个类。代理模式具体包括静态代理与动态代理,在程序运行前就已经存在代理类的字节码文件,代理类和目标类的关系在运行前就确定的是静态代理。在程序运行期间动态创建代理类及其实例的是动态代理。

代理模式中涉及到的角色:

ISubject:抽象主题角色,是一个接口/类。该接口是目标对象和它的代理共用的接口/类。

RealSubject:真实主题角色,是实现抽象主题接口的类。

Proxy:代理角色,可以通过聚合和继承的方式实现代理类,通常聚合方式更灵活。在聚合方式中,代理类内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象提供与真实对象相同的接口,以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

静态代理

/**方式一:聚合式静态代理
 * @author Goser    (mailto:goskalrie@163.com)
 */
//1.抽象主题接口
public interface Manager {
    void doSomething();
}
//2.真实主题类
public class Admin implements Manager {
    public void doSomething() {
        System.out.println("Admin do something.");
    }
}
//3.以聚合方式实现的代理主题
public class AdminPoly implements Manager{
    private Admin admin;
   
    public AdminPoly(Admin admin) {
        super();
        this.admin = admin;
    }
 
    public void doSomething() {
        System.out.println("Log:admin操作开始");
        admin.doSomething();
        System.out.println("Log:admin操作结束");
    }
}
//4.测试代码
        Admin admin = new Admin();
        Manager m = new AdminPoly(admin);
        m.doSomething();
//方式二:继承式静态代理
//与上面的方式仅代理类和测试代码不同
//1.代理类
public class AdminProxy extends Admin {
    @Override
    public void doSomething() {
        System.out.println("Log:admin操作开始");
        super.doSomething();
        System.out.println("Log:admin操作开始");
    }
}
//2.测试代码
        Manager proxy = new AdminProxy();
        proxy.doSomething();

以上方式是静态代理方式。

一个代理类的对象与一个委托类的对象关联,代理类对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法来提供特定的服务。也就是说,真正的业务功能还是由委托类来实现,但是在实现业务功能前后可以增加一些公共逻辑,用于增强业务功能。例如,在项目前期开发中我们没有加入缓存、日志等这些功能,后期若想加入,我们就可以使用代理来实现,而且不必对原有代码进行改动。

静态代理弊端:

1、如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
2、静态代理中,目标类与其代理类是一一对应的。但是,会存在这样的情况:有N个类型不同的目标类,但是代理类中的“预处理、后处理”都是相同的,仅仅是调用目标类不同。若采用静态代理,那么必然需要手动创建N个代理类,很麻烦。
基于以上弊端,引入动态代理。动态代理在运行期为各个目标类分别生成代理类,共享“预处理,后处理”功能,不需要事先知道目标对象的类型,这样可以大大减小程序规模。
我的理解是动态代理将对目标类的增强操作从与业务代码的强关联中抽取出来形成了一个类,可以复用与不同的目标对象。
jdk动态代理

在程序运行期间生成一个实现与目标类相同接口的代理类,其持有目标类的引用,客户端实际使用的是代理类,代理类在方法被调用的时候会调用InvocationHandler对的invoke方法对目标方法进行增强。invoke中也会使用反射调用目标方法

//目标接口
public interface HelloWorld {  
    void sayHello(String name);  
} 
//具体目标类 
public class HelloWorldImpl implements HelloWorld {  
    @Override  
    public void sayHello(String name) {  
        System.out.println("Hello " + name);  
    }  
}  
//对目标类的增强操作
public class CustomInvocationHandler implements InvocationHandler {  
    //被代理对象,即目标类
    private Object target;  
  
    public CustomInvocationHandler(Object target) {  
        this.target = target;  
    }  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        System.out.println("Before invocation");  
        Object retVal = method.invoke(target, args);//目标类方法执行,此方法前后的代码都是代理类对目标方法的增强
        System.out.println("After invocation");  
        return retVal;  
    }  
}
//客户端调用
public class ProxyTest {   
    public static void main(String[] args) throws Exception {    
        CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl());  
	//生成代理对象。因为生成的代理类实现HelloWorld接口,所以可以转型。需要将目标类的类加载器,实现的接口class,以及handler作为参数传递
        HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(  
                ProxyTest.class.getClassLoader(),  
                new Class[]{HelloWorld.class},  
                handler);  
        proxy.sayHello("Mikan");  
    }  
}

输出:
Before invocation  
Hello Mikan  
After invocation 

原理分析

1、通过实现 InvocationHandler 接口创建自己的调用处理器;
2、通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
3、通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

代理类的生成:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
	// 生成class
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
	    //反射,使用InvocationHandler.class作为参数获取代理类的构造方法。以供后续调用构造方法来获得代理类的实例。
	    //此处 private static final Class<?>[] constructorParams = { InvocationHandler.class };
    	    //此处说明代理类中有一个public 代理类(InvocationHandler handler)的构造函数签名
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //以我们实现的InvocationHandler对象作为参数反射调用构造函数生成代理类实例
            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);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

重点看Class的生成方法getProxyClass0

private static Class<?> getProxyClass0(ClassLoader loader,  
                                       Class<?>... interfaces) {  
    // 代理的接口数量不能超过65535(没有这种变态吧)  
    if (interfaces.length > 65535) {  
        throw new IllegalArgumentException("interface limit exceeded");  
    }  
    // JDK对代理进行了缓存,如果已经存在相应的代理类,则直接返回,否则才会通过ProxyClassFactory来创建代理  
    return proxyClassCache.get(loader, interfaces);  
}  

其中代理缓存是使用WeakCache实现的:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

具体的缓存逻辑这里暂不关心,只需要关心ProxyClassFactory是如何生成代理类的,ProxyClassFactory是Proxy的一个静态内部类,实现了BiFunction接口的apply方法:

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
	    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);  
            for (Class<?> intf : interfaces) {  
                /* 
                 * Verify that the class loader resolves the name of this 
                 * interface to the same Class object. 
                 */  
        //确保接口的类对象与类加载器加载的类对象相同,且由同一个加载器加载。《深入理解java虚拟机》提到,类加载器虽然只用于实现类的	加载动作,但在java程序起的作用远不止于类的加载。对于任何一个类,都需要由加载它的的类加载器和这个类本身一同确立其在java虚拟	机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间,通俗点,比较两个类是否相等,只有在这两个类是由同一个类加载器加载的	前提下才有意义,否则,即使这两个类来源于同一个class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不	相等。interfaceClass = Class.forName(intf.getName(), false, loader);验证类是否相等,实现原理如上所述。  
  
                Class<?> interfaceClass = null;  
                try {  
                    interfaceClass = Class.forName(intf.getName(), false, loader);  
                } catch (ClassNotFoundException e) {  
                }  
                if (interfaceClass != intf) {  
                    throw new IllegalArgumentException(  
                        intf + " is not visible from class loader");  
                }  
                /* 
                 * Verify that the Class object actually represents an 
                 * interface. 验证类对象表示的是接口 
                 */  
                if (!interfaceClass.isInterface()) {  
                    throw new IllegalArgumentException(  
                        interfaceClass.getName() + " is not an interface");  
                }  
                /* 
                 * Verify that this interface is not a duplicate. 验证接口未重复 
                 */  
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {  
                    throw new IllegalArgumentException(  
                        "repeated interface: " + interfaceClass.getName());  
                }  
            }  

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
	     * 对于非公共接口,代理类的包名与接口相同
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                //公共接口的包名,默认为com.sun.proxy。 
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
	    //默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增 
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            //真正生成代理类的字节码的地方
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
		//根据字节码生成Class对象
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

ProxyGenerator是sun.misc包中的类,它没有开源,但是可以反编译来一探究竟:

public static byte[] generateProxyClass(final String var0, Class[] var1) {  
    ProxyGenerator var2 = new ProxyGenerator(var0, var1);  
    final byte[] var3 = var2.generateClassFile(); //生成字节码文件的真正方法
    // 这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘,我们可以通过把相应的class文件保存到本地,再反编译来看看具体的实现,这样更直观  
    if(saveGeneratedFiles) {  
        AccessController.doPrivileged(new PrivilegedAction() {  
            public Void run() {  
                try {  
                    FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");  
                    var1.write(var3);  
                    var1.close();  
                    return null;  
                } catch (IOException var2) {  
                    throw new InternalError("I/O exception saving generated file: " + var2);  
                }  
            }  
        });  
    }  
    return var3;  
}

最终generateClassFile才是真正生成代理类字节码文件的方法

private byte[] generateClassFile() {
        /addProxyMethod系列方法就是将接口的方法和Object的hashCode,equals,toString方法添加到代理方法容器(proxyMethods),
         其中方法签名作为key,proxyMethod作为value*/
        /*hashCodeMethod方法位于静态代码块中通过Object对象获得,hashCodeMethod=Object.class.getMethod("hashCode",new Class[0]),
         相当于从Object中继承过来了这三个方法equalsMethod,toStringMethod*/    
        this.addProxyMethod(hashCodeMethod, Object.class);   -->
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        int var1;
        int var3;
         //获得所有接口中的所有方法,并将方法添加到代理方法中
        for(var1 = 0; var1 < this.interfaces.length; ++var1) {
            Method[] var2 = this.interfaces[var1].getMethods();           
            for(var3 = 0; var3 < var2.length; ++var3) {
                this.addProxyMethod(var2[var3], this.interfaces[var1]);
            }
        }
          
        Iterator var7 = this.proxyMethods.values().iterator();
        List var8;
        while(var7.hasNext()) {
            var8 = (List)var7.next();
            checkReturnTypes(var8);    //验证具有相同方法签名的的方法的返回值类型是否一致,因为不可能有两个方法名相同,参数相同,而返回值却不同的方法
        };
    //接下来就是写代理类文件的步骤了
        Iterator var11
        try {
             //生成代理类的构造函数
            this.methods.add(this.generateConstructor());
            var7 = this.proxyMethods.values().iterator();
            while(var7.hasNext()) {
                var8 = (List)var7.next();
                var11 = var8.iterator();
                while(var11.hasNext()) {
                    ProxyGenerator.ProxyMethod var4 = (ProxyGenerator.ProxyMethod)var11.next();
                    /将代理字段声明为Method,10为ACC_PRIVATE和ACC_STATAIC的与运算,表示该字段的修饰符为private static
                     所以代理类的字段都是private static Method XXX*/
                    this.fields.add(new ProxyGenerator.FieldInfo(var4.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                    //生成代理类的代理方法
                    this.methods.add(var4.generateMethod());
                }
            }
            //为代理类生成静态代码块,对一些字段进行初始化
            this.methods.add(this.generateStaticInitializer());
        } catch (IOException var6) {
            throw new InternalError("unexpected I/O Exception");
        }
       
        if(this.methods.size() > '\uffff') {  //代理方法超过65535将抛出异常
            throw new IllegalArgumentException("method limit exceeded");
        } else if(this.fields.size() > '\uffff') {   //代理类的字段超过65535将抛出异常
            throw new IllegalArgumentException("field limit exceeded");
        } else {
         //这里开始就是一些代理类文件的过程,此过程略过
            this.cp.getClass(dotToSlash(this.className));
            this.cp.getClass("java/lang/reflect/Proxy");
            for(var1 = 0; var1 < this.interfaces.length; ++var1) {
                this.cp.getClass(dotToSlash(this.interfaces[var1].getName()));
            }
            this.cp.setReadOnly();
            ByteArrayOutputStream var9 = new ByteArrayOutputStream();
            DataOutputStream var10 = new DataOutputStream(var9);
            try {
                var10.writeInt(-889275714);
                var10.writeShort(0);
                var10.writeShort(49);
                this.cp.write(var10);
                var10.writeShort(49);
                var10.writeShort(this.cp.getClass(dotToSlash(this.className)));
                var10.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
                var10.writeShort(this.interfaces.length);
                for(var3 = 0; var3 < this.interfaces.length; ++var3) {
                    var10.writeShort(this.cp.getClass(dotToSlash(this.interfaces[var3].getName())));
                }
                var10.writeShort(this.fields.size());
                var11 = this.fields.iterator();
                while(var11.hasNext()) {
                    ProxyGenerator.FieldInfo var12 = (ProxyGenerator.FieldInfo)var11.next();
                    var12.write(var10);
                }
                var10.writeShort(this.methods.size());
                var11 = this.methods.iterator();
                while(var11.hasNext()) {
                    ProxyGenerator.MethodInfo var13 = (ProxyGenerator.MethodInfo)var11.next();
                    var13.write(var10);
                }
                var10.writeShort(0);
                return var9.toByteArray();
            } catch (IOException var5) {
                throw new InternalError("unexpected I/O Exception");
            }
        }
    }

saveGeneratedFiles这个属性的值从哪里来呢:

private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue(); 

GetBooleanAction实际上是调用Boolean.getBoolean(propName)来获得的,而Boolean.getBoolean(propName)调用了System.getProperty(name),所以我们可以设置sun.misc.ProxyGenerator.saveGeneratedFiles这个系统属性为true来把生成的class保存到本地文件来查看。

这里要注意,当把这个属性设置为true时,生成的class文件及其所在的路径都需要提前创建,否则会抛出FileNotFoundException异常。即我们要在运行当前main方法的路径下创建com/sun/proxy目录,并创建一个$Proxy0.class文件,才能够正常运行并保存class文件内容。

反编译$Proxy0.class文件:

package com.sun.proxy;  
  
import com.mikan.proxy.HelloWorld;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
import java.lang.reflect.UndeclaredThrowableException;  
  
public final class $Proxy0 extends Proxy implements HelloWorld {  
  private static Method m1;  
  private static Method m3;  
  private static Method m0;  
  private static Method m2;  
  
  //在上面生成代理类实例的时候用反射调用了该构造函数生成代理对象。在代理类的父类Proxy中有一个InvocationHandler成员变量
  public $Proxy0(InvocationHandler paramInvocationHandler) {  
    super(paramInvocationHandler);  
  }  
  
  public final boolean equals(Object paramObject) {  
    try {  
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  
    }  
    catch (Error|RuntimeException localError) {  
      throw localError;  
    }  
    catch (Throwable localThrowable) {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final void sayHello(String paramString) {  
    try {  
      this.h.invoke(this, m3, new Object[] { paramString });  
      return;  
    }  
    catch (Error|RuntimeException localError) {  
      throw localError;  
    }  
    catch (Throwable localThrowable) {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final int hashCode() {  
    try {  
      return ((Integer)this.h.invoke(this, m0, null)).intValue();  
    }  
    catch (Error|RuntimeException localError) {  
      throw localError;  
    }  
    catch (Throwable localThrowable) {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final String toString() {  
    try {  
      return (String)this.h.invoke(this, m2, null);  
    }  
    catch (Error|RuntimeException localError) {  
      throw localError;  
    }  
    catch (Throwable localThrowable) {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  static {  
    try {  
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });  
      m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });  
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);  
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);  
      return;  
    }  
    catch (NoSuchMethodException localNoSuchMethodException) {  
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  
    }  
    catch (ClassNotFoundException localClassNotFoundException) {  
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  
    }  
  }  
}
可以看到,动态生成的代理类有如下特性:
继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
1、提供了一个使用InvocationHandler作为参数的构造方法。
2、生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。
3、重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。

4、代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。

再总结下jdk动态大代理的过程:

在运行期间动态生成一个代理类字节码文件,该字节码继承了Proxy类且实现了目标类实现的所有接口,并为每个接口方法生成了对应的Method对象。在生成该代理类对象时,将我们实现的InvocationHandler对象即h,传递给代理类。
在调用被代理方法时,代理类通过this.h.invoke(this, method, new Object[] { paramString });在invoke中通过反射调用目标对象的method方法,对目标方法增强。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        System.out.println("Before invocation");  
        Object retVal = method.invoke(target, args);  
        System.out.println("After invocation");  
        return retVal;  
    }  

生成动态代理方法newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)中,传递各个参数的目的。interfaces,使生成的字节码实现这些接口;loader,用同一个类加载去加载生成的字节码文件;h,传递给Proxy的成员变量InvocationHandler h。

参考:

https://www.jianshu.com/p/3616c70cb37b 自己写动态代理,对理解动态代理超棒

https://blog.csdn.net/mhmyqn/article/details/48474815

https://www.cnblogs.com/MOBIN/p/5597215.htm

cglib动态代理

cglib是一个强大的、高性能的代码生成包底层主要是通过asm技术(Java字节码生成框架),在程序运行期间,动态生成代理类的字节码,代理类继承目标类,并覆写父类中不是final的方法,在覆写逻辑中加入增强逻辑且调用父类(目标类)方法。

对此图总结一下:

  • 最底层的是字节码Bytecode
  • 位于字节码之上的是ASM,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉
  • 位于ASM之上的是CGLIB、Groovy、BeanShell,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码----只要你能生成Java字节码,JVM并不关心字节码的来源,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码
  • 位于CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP这些框架了,这一层大家都比较熟悉
  • 最上层的是Applications,即具体应用
JDK代理要求被代理的类必须实现接口,有很强的局限性。而CGLIB动态代理则没有此类强制性要求。

先看一个例子:

//定义业务逻辑
public class UserServiceImpl {  
    public void add() {  
        System.out.println("This is add service");  
    }  
    public void delete(int id) {  
        System.out.println("This is delete service:delete " + id );  
    }  
}

//实现MethodInterceptor接口,定义方法的拦截器
public class MyMethodInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
        System.out.println("Before:" + method);  
        Object object = methodProxy.invokeSuper(obj, arg);
        System.out.println("After:" + method); 
        return object;
    }
}
//利用Enhancer类生成代理类
Enhancer enhancer = new Enhancer();  
enhancer.setSuperclass(UserServiceImpl.class);//设置被代理类
enhancer.setCallback(new MyMethodInterceptor());//增强
UserServiceImpl userService = (UserServiceImpl)enhancer.create();//生成代理类
userService.add();//调用

执行结果:
Before: add
This is add service
After: add

反编译之后的代理类add方法实现如下:

import net.sf.cglib.core.Signature;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;

public class UserService$$EnhancerByCGLIB$$394dddeb extends UserService implements Factory
{
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;//Enhance中设置的MethodInterceptor
    private static final Method CGLIB$add$0$Method;//会传递到intercept方法中的method参数
    private static final MethodProxy CGLIB$add$0$Proxy;//会传递到intercept方法中的methodProxy参数
    private static final Object[] CGLIB$emptyArgs;

    
    static void CGLIB$STATICHOOK2() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        final Class<?> forName = Class.forName("UserService$$EnhancerByCGLIB$$394dddeb");
        final Class<?> forName3;
        CGLIB$add$0$Method = ReflectUtils.findMethods(new String[] { "add", "()V" }, (forName3 = Class.forName("UserService")).getDeclaredMethods())[0];
        CGLIB$add$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "add", "CGLIB$add$0");
    }
    
    final void CGLIB$add$0() {
        super.add();
    }
    
    public final void add() {
        MethodInterceptor cglib$CALLBACK_2;
        MethodInterceptor cglib$CALLBACK_0;
        if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
            CGLIB$BIND_CALLBACKS(this);
            cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
        }
	//MethodInterceptor不为null,即存在切入,就调用MethodInterceptor的intercept方法
        if (cglib$CALLBACK_0 != null) {
            cglib$CALLBACK_2.intercept((Object)this, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Method, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$emptyArgs, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Proxy);
            return;
        }
        super.add();
    }
    //省略其他方法……

    static {
        CGLIB$STATICHOOK2();
    }
}
通过cglib生成的字节码相比jdk实现来说显得更加复杂。
1、代理类 UserService$$EnhancerByCGLIB$$394dddeb继承了委托类 UserSevice,且委托类的final方法不能被代理;
2、代理类为每个方法生成一个代表目标方法的Method对象,和代表代理方法的MethodProxy对象。
3、 代理类为每个委托方法都生成两个方法,以add方法为例,一个是重写的add方法;一个是CGLIB$add$0方法,该方法直接调用委托类的add方法
4、当执行 代理对象的add方法时,会先判断是否 设置callbac ,即是否存在实现了MethodInterceptor接口的对象 cglib$CALLBACK_0,如果存在,则调用MethodInterceptor对象的 intercept方法。 intercept 方法中,除了会调用委托方法,还会进行一些增强操作。在Spring AOP中,典型的应用场景就是在某些敏感方法执行前后进行操作日志记录。

intercept方法:

public class MyInterceptor implements MethodInterceptor {
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("method before");
        Object r = methodProxy.invokeSuper(o,objects);
        //methodProxy.invoke(o,objects);//会导致循环调用堆栈溢出
        //method.invoke(o,objects);//同会循环调用导致堆栈溢出
        System.out.println("method after");
        return r;
    }
}
Object o:代理类对象
Method:目标方法,即目标类UserServiceImpl.class的add方法。
objects:方法参数
MethodProxy:代理方法的MethodProxy对象

4、每个被代理的方法都对应一个MethodProxy对象, methodProxy.invokeSuper 方法最终调用委托类的add方法,实现如下:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();//初始化fastInfo           
 MethodProxy.FastClassInfo e = this.fastClassInfo;
            return e.f2.invoke(e.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }
单看 invokeSuper 方法的实现,似乎看不出委托类add方法调用,继续往下分析,我们知道,在JDK动态代理中方法的调用是通过反射来完成的。但是在CGLIB中,方法的调用并不是通过反射来完成的,而是直接对方法进行调用,在MethodProxy实现中,通过FastClassInfo维护了委托类和代理类的FastClass。 FastClass 对Class对象进行特别的处理,比如会用数组保存method的引用,每次调用方法的时候都是通过一个index下标来保持对方法的引用。

private static class FastClassInfo {
        FastClass f1;
        FastClass f2;
        int i1;
        int i2;

        private FastClassInfo() {
        }
    }
以add方法的methodProxy为例,f1 是委托类的FastClass,f2指向代理类的FastClass,i1和i2分别是方法add和CGLIB$add$0在对象中索引位置。

FastClass实现机制

FastClass其实就是对Class对象进行特殊处理,提出下标概念index,通过索引保存方法的引用信息,将原先的反射调用,转化为方法的直接调用,从而体现所谓的fast,下面通过一个例子了解一下FastClass的实现机制。
1、定义原类

class Test {
    public void f(){
        System.out.println("f method");
    }
    
    public void g(){
        System.out.println("g method");
    }
}

2、定义Fast类

class FastTest {
    public int getIndex(String signature){
        switch(signature.hashCode()){
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }

    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;
    }
}

在FastTest中有两个方法,getIndex中对Test类的每个方法根据hash建立索引,invoke根据指定的索引,直接调用目标方法,避免了反射调用。所以当调用methodProxy.invokeSuper方法时,实际上是调用代理类的CGLIB$add$0方法,CGLIB$add$0直接调用了委托类的add方法。

再看invokeSuper方法

前面也提及了invoke和invokeSuper方法稍不注意就会出问题的问题,在这里我们从代码的层面去追踪一下,产生问题的原因。

我们看一下代理类方法invokeSuper的执行流程

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        this.init(); //初始化fastInfo
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}

invokeSuper在这里主要的作用就是初始化fastClassInfo。

init方法

private void init() {
    if (this.fastClassInfo == null) {
        Object var1 = this.initLock;
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                MethodProxy.CreateInfo ci = this.createInfo;
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(this.sig1);
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
}

上面的方法主要是加载methodProxy.FastClassInfo。ci是之前就初始化好的,其中c1指的就是被代理的类UserServiceImpl,c2则是com.eumji.proxy.cglib.UserServiceImpl$$EnhancerByCGLIB$$efe38465这个代理类。

然后生成对应的f1和f2以及方法的下标i1和i2,i1和i2对应的就是在最前面所说的add方法CGLIB$add$0方法,后面代码可以看出。

f1对应UserServiceImpl$$FastClassByCGLIB$$2e560a7d代理类,f2对应InfoDemo$$EnhancerByCGLIB$$efe38465$$FastClassByCGLIB$$38345933代理类。这些都可以在生成的代理class中去查看。

invoke和invokeSuper区别

public Object invoke(Object obj, Object[] args) throws Throwable {
  this.init();
  MethodProxy.FastClassInfo fci = this.fastClassInfo;
  return fci.f1.invoke(fci.i1, obj, args);
}

我们可以看到invoke使用的是f1.invoke方法,而invokeSuper则是使用f2.invoke方法。

首先看一下f1对应的invoke方法逻辑

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        InfoDemo var10000 = (InfoDemo)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                var10000.add((String)var3[0]);
                return null;
                ....
}

调用代理对象的welcome方法。

所以这也就能解释为什么我们之前会发生循环调用invoke的方法了,因为我们传入的var2是UserServiceImpl的代理对象,看最前面的代理类代码就可以看出,会再次调用intercept,从而又会调用invoke方法,造成死循环。(只要最终调的是代理对象的方法都会成死循环)

再看一下f2中对应invoke的实现

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        efe38465 var10000 = (efe38465)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
             ....
                var10000.CGLIB$finalize$1();
                return null;
            case 16:
                var10000.CGLIB$add$0((String)var3[0]);
                return null;
           ....
    }

调用代理类中的CGLIB$add$0方法,CGLIB$add$0方法最终调用父类,也就是目标类的add方法,成功

综上,实际methodProxy.invokeSuper()就是调用父类的add方法;而methodProxy.invoke()调用代理类的add(),当然进入死循环了。

而直接调用method.invoke()与methodProxy.invoke()实际最终都会进入一直调用代理类的add()方法进入死循环。(个人判断)

cglib动态代理中的一心核心类与方法:

1.1 CGLIB代理相关的类

  • net.sf.cglib.proxy.Enhancer    主要的增强类。
  • net.sf.cglib.proxy.MethodInterceptor    主要的方法拦截类,它是Callback接口的子接口,需要用户实现。
  • net.sf.cglib.proxy.MethodProxy    JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。

net.sf.cglib.proxy.Callback接口在CGLIB包中是一个重要的接口,所有被net.sf.cglib.proxy.Enhancer类调用的回调(callback)接口都要继承这个接口。

net.sf.cglib.proxy.MethodInterceptor能够满足任何的拦截(interception )需要。对有些情况下可能过度。为了简化和提高性能,CGLIB包提供了一些专门的回调(callback)类型:

  • net.sf.cglib.proxy.FixedValue 为提高性能,FixedValue回调对强制某一特别方法返回固定值是有用的。
  • net.sf.cglib.proxy.NoOp NoOp回调把对方法调用直接委派到这个方法在父类中的实现。
  • net.sf.cglib.proxy.LazyLoader 当实际的对象需要延迟装载时,可以使用LazyLoader回调。一旦实际对象被装载,它将被每一个调用代理对象的方法使用。
  • net.sf.cglib.proxy.Dispatcher Dispathcer回调和LazyLoader回调有相同的特点,不同的是,当代理方法被调用时,装载对象的方法也总要被调用。
  • net.sf.cglib.proxy.ProxyRefDispatcher ProxyRefDispatcher回调和Dispatcher一样,不同的是,它可以把代理对象作为装载对象方法的一个参数传递。

1.2 使用MethodInterceptor接口实现方法回调

当对代理中所有方法的调用时,都会转向MethodInterceptor类型的拦截(intercept)方法,在拦截方法中再调用底层对象相应的方法。下面我们举个例子,假设你想对目标对象的所有方法调用进行权限的检查,如果没有经过授权,就抛出一个运行时的异常。

net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用 来实现拦截(intercept)方法的调用。

MethodInterceptor接口只定义了一个方法:

public Object intercept(Object object, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable;

参数Object object是被代理对象,不会出现死循环的问题。

参数java.lang.reflect.Method method是java.lang.reflect.Method类型的被拦截方法。

参数Object[] args是被被拦截方法的参数。

参数MethodProxy proxy是CGLIB提供的MethodProxy 类型的被拦截方法。

注意:

1、若原方法的参数存在基本类型,则对于第三个参数Object[] args会被转化成类的类型。如原方法的存在一个参数为int,则在intercept方法中,对应的会存在一个Integer类型的参数。

2、若原方法为final方法,则MethodInterceptor接口无法拦截该方法。

1.3 使用CGLIB代理最核心类Enhancer生成代理对象

net.sf.cglib.proxy.Enhancer中有几个常用的方法:

  • void setSuperclass(java.lang.Class superclass) 设置产生的代理对象的父类。
  • void setCallback(Callback callback) 设置CallBack接口的实例。
  • void setCallbacks(Callback[] callbacks) 设置多个CallBack接口的实例。
  • void setCallbackFilter(CallbackFilter filter) 设置方法回调过滤器。
  • Object create() 使用默认无参数的构造函数创建目标对象。
  • Object create(Class[], Object[]) 使用有参数的构造函数创建目标对象。参数Class[] 定义了参数的类型,第二个Object[]是参数的值。

注意:在参数中,基本类型应被转化成类的类型。

基本代码:

public Object createProxy(Class targetClass) {

    Enhancer enhancer = new Enhancer();

    enhancer.setSuperclass(targetClass);

    enhancer.setCallback(new MethodInterceptorImpl ());

    return enhancer.create();

}

createProxy方法返回值是targetClass的一个实例的代理。

2 回调过滤器CallbackFilter

net.sf.cglib.proxy.CallbackFilter有选择的对一些方法使用回调。

CallbackFilter可以实现不同的方法使用不同的回调方法。所以CallbackFilter称为"回调选择器"更合适一些。

CallbackFilter中的accept方法,根据不同的method返回不同的值i,这个值是在callbacks中callback对象的序号,就是调用了callbacks[i]。

 

import java.lang.reflect.Method;

 

import net.sf.cglib.proxy.Callback;

import net.sf.cglib.proxy.CallbackFilter;

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

import net.sf.cglib.proxy.NoOp;

 

public class CallbackFilterDemo {

    public static void main(String[] args) {

        // 回调实例数组

        Callback[] callbacks = new Callback[] { new MethodInterceptorImpl(), NoOp.INSTANCE };

 

        // 使用enhancer,设置相关参数。

        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(User.class);

        enhancer.setCallbacks(callbacks);

        enhancer.setCallbackFilter(new CallbackFilterImpl());

 

        // 产生代理对象

        User proxyUser = (User) enhancer.create();

 

        proxyUser.pay(); // 买

        proxyUser.eat(); // 吃

    }

/**

     * 回调过滤器类。

     */

    private static class CallbackFilterImpl implements CallbackFilter {

 

        @Override

        public int accept(Method method) {

            String methodName = method.getName();

            if ("eat".equals(methodName)) {

                return 1; // eat()方法使用callbacks[1]对象拦截。

            } else if ("pay".equals(methodName)) {

                return 0; // pay()方法使用callbacks[0]对象拦截。

            }

            return 0;

        }

    }

 

    /**

     * 自定义回调类。

     */

    private static class MethodInterceptorImpl implements MethodInterceptor {

 

        @Override

        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throwsThrowable {

            System.out.println("Before invoke " + method);

            Object result = proxy.invokeSuper(obj, args); // 原方法调用。

            System.out.println("After invoke" + method);

            return result;

        }

    }

}

 

class User {

    public void pay() {

        System.out.println("买东西");

    }

 

    public void eat() {

        System.out.println("吃东西");

    }

}

 

输出结果:

Before invoke public void sjq.cglib.filter.User.pay()

pay()

After invokepublic void sjq.cglib.filter.User.pay()

eat()

延迟加载对象:先返回一个代理对象,等到获取属性值时才初始化真实对象返回。挺有趣

一、作用:
说到延迟加载,应该经常接触到,尤其是使用Hibernate的时候,本篇将通过一个实例分析延迟加载的实现方式。
LazyLoader接口继承了Callback,因此也算是CGLib中的一种Callback类型。

另一种延迟加载接口Dispatcher。

Dispatcher接口同样继承于Callback,也是一种回调类型。

但是Dispatcher和LazyLoader的区别在于:LazyLoader只在第一次访问延迟加载属性时触发代理类回调方法,而Dispatcher在每次访问延迟加载属性时都会触发代理类回调方法。

二、示例:
首先定义一个实体类LoaderBean,该Bean内有一个需要延迟加载的属性PropertyBean。


[java]  view plain  copy
  1. <span style="font-size:14px;">package com.zghw.cglib;  
  2.   
  3. import net.sf.cglib.proxy.Enhancer;  
  4.   
  5. public class LazyBean {  
  6.     private String name;  
  7.     private int age;  
  8.     private PropertyBean propertyBean;  
  9.     private PropertyBean propertyBeanDispatcher;  
  10.   
  11.     public LazyBean(String name, int age) {  
  12.         System.out.println("lazy bean init");  
  13.         this.name = name;  
  14.         this.age = age;  
  15.         this.propertyBean = createPropertyBean();  
  16.         this.propertyBeanDispatcher = createPropertyBeanDispatcher();  
  17.     }  
  18.   
  19.       
  20.   
  21.     /** 
  22.      * 只第一次懒加载 
  23.      * @return 
  24.      */  
  25.     private PropertyBean createPropertyBean() {  
  26.         /** 
  27.          * 使用cglib进行懒加载 对需要延迟加载的对象添加代理,在获取该对象属性时先通过代理类回调方法进行对象初始化。 
  28.          * 在不需要加载该对象时,只要不去获取该对象内属性,该对象就不会被初始化了(在CGLib的实现中只要去访问该对象内属性的getter方法, 
  29.          * 就会自动触发代理类回调)。 
  30.          */  
  31.         Enhancer enhancer = new Enhancer();  
  32.         enhancer.setSuperclass(PropertyBean.class);  
  33.         PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class,  
  34.                 new ConcreteClassLazyLoader());  
  35.         return pb;  
  36.     }  
  37.     /** 
  38.      * 每次都懒加载 
  39.      * @return 
  40.      */  
  41.     private PropertyBean createPropertyBeanDispatcher() {  
  42.         Enhancer enhancer = new Enhancer();  
  43.         enhancer.setSuperclass(PropertyBean.class);  
  44.         PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class,  
  45.                 new ConcreteClassDispatcher());  
  46.         return pb;  
  47.     }  
  48.     public String getName() {  
  49.         return name;  
  50.     }  
  51.   
  52.     public void setName(String name) {  
  53.         this.name = name;  
  54.     }  
  55.   
  56.     public int getAge() {  
  57.         return age;  
  58.     }  
  59.   
  60.     public void setAge(int age) {  
  61.         this.age = age;  
  62.     }  
  63.   
  64.     public PropertyBean getPropertyBean() {  
  65.         return propertyBean;  
  66.     }  
  67.   
  68.     public void setPropertyBean(PropertyBean propertyBean) {  
  69.         this.propertyBean = propertyBean;  
  70.     }  
  71.   
  72.     public PropertyBean getPropertyBeanDispatcher() {  
  73.         return propertyBeanDispatcher;  
  74.     }  
  75.   
  76.     public void setPropertyBeanDispatcher(PropertyBean propertyBeanDispatcher) {  
  77.         this.propertyBeanDispatcher = propertyBeanDispatcher;  
  78.     }  
  79.   
  80.     @Override  
  81.     public String toString() {  
  82.         return "LazyBean [name=" + name + ", age=" + age + ", propertyBean="  
  83.                 + propertyBean + "]";  
  84.     }  
  85. }  
  86. </span>  

[java]  view plain  copy
  1. <span style="font-size:14px;">package com.zghw.cglib;  
  2.   
  3. public class PropertyBean {  
  4.     private String key;  
  5.     private Object value;  
  6.     public String getKey() {  
  7.         return key;  
  8.     }  
  9.     public void setKey(String key) {  
  10.         this.key = key;  
  11.     }  
  12.     public Object getValue() {  
  13.         return value;  
  14.     }  
  15.     public void setValue(Object value) {  
  16.         this.value = value;  
  17.     }  
  18.     @Override  
  19.     public String toString() {  
  20.         return "PropertyBean [key=" + key + ", value=" + value + "]" +getClass();  
  21.     }  
  22.       
  23. }  
  24. </span>  

[java]  view plain  copy
  1. <span style="font-size:14px;">package com.zghw.cglib;  
  2.   
  3. import net.sf.cglib.proxy.LazyLoader;  
  4.   
  5. public class ConcreteClassLazyLoader implements LazyLoader {  
  6.     /** 
  7.      * 对需要延迟加载的对象添加代理,在获取该对象属性时先通过代理类回调方法进行对象初始化。 
  8.      * 在不需要加载该对象时,只要不去获取该对象内属性,该对象就不会被初始化了(在CGLib的实现中只要去访问该对象内属性的getter方法, 
  9.      * 就会自动触发代理类回调)。 
  10.      */  
  11.     @Override  
  12.     public Object loadObject() throws Exception {  
  13.         System.out.println("before lazyLoader...");  
  14.         PropertyBean propertyBean = new PropertyBean();  
  15.         propertyBean.setKey("zghw");  
  16.         propertyBean.setValue(new TargetObject());  
  17.         System.out.println("after lazyLoader...");  
  18.         return propertyBean;  
  19.     }  
  20.   
  21. }  
  22. </span>  

[java]  view plain  copy
  1. <span style="font-size:14px;">package com.zghw.cglib;  
  2.   
  3. import net.sf.cglib.proxy.Dispatcher;  
  4.   
  5. public class ConcreteClassDispatcher implements Dispatcher{  
  6.   
  7.     @Override  
  8.     public Object loadObject() throws Exception {  
  9.         System.out.println("before Dispatcher...");  
  10.         PropertyBean propertyBean = new PropertyBean();  
  11.         propertyBean.setKey("xxx");  
  12.         propertyBean.setValue(new TargetObject());  
  13.         System.out.println("after Dispatcher...");  
  14.         return propertyBean;  
  15.     }  
  16.   
  17. }  

参考:

http://www.cnblogs.com/shijiaqi1066/p/3429691.html

https://www.jianshu.com/p/13aa63e1ac95

https://juejin.im/post/5a8f750af265da4e983f2369

https://blog.csdn.net/zghwaicsdn/article/details/50957474

https://blog.csdn.net/jiankunking/article/details/52143504

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值