Spring常用到的几种设计模式--代理模式

一、代理模式

生活中的列子:中介、黄牛、媒婆、快递、经纪人…
特点:

  • 执行人、被代理人
  • 对于代理人来说这件事必须要做,但是自己不想做或者没时间做。所以要找代理人去干这件事。
  • 需要获取到被代理人的个人资料

二、JDK代理模式–案例

下面我们采用JDK动态代理写个征婚的例子:
在这里插入图片描述
Person:

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.proxy;

/**
 * @author yangjikang
 * @date 2019/8/2 16:26
 * @modified By yangjikang
 * @since
 */
public interface Person {

    void findLove();

    String getName();

    String getSex();

    Integer getAge();

    boolean equals(Object obj);
}

Jerry:

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.proxy;

/**
 * 杰瑞
 *
 * @author yangjikang
 * @date 2019/8/2 16:27
 * @modified By yangjikang
 * @since
 */
public class Jerry implements Person {

    private String name = "Jerry";
    private String sex = "男";
    private Integer age = 22;

    public void findLove() {
        System.out.println("个人资料:\n");
        System.out.println("姓名:"+this.name);
        System.out.println("性别:"+this.sex);
        System.out.println("年龄:"+this.age);
        System.out.println("=========================");
        System.out.println("择偶要求:\n");
        System.out.println("门当户对...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Jerry) {
            Jerry jerry = (Jerry) obj;
            return this.name == jerry.name;
        }
        else {
            return false;
        }
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public boolean equals(String name) {
        return name.equals(this.name);
    }
}

XiaoLi:

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.proxy;

/**
 * 杰瑞
 *
 * @author yangjikang
 * @date 2019/8/2 16:27
 * @modified By yangjikang
 * @since
 */
public class XiaoLi implements Person {

    private String name = "小莉";
    private String sex = "女";
    private Integer age = 26;

    public void findLove() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }


    @Override
    public boolean equals(Object obj) {
        if (obj instanceof XiaoLi) {
            XiaoLi xiaoLi = (XiaoLi) obj;
            return this.name == xiaoLi.name;
        }
        else {
            return false;
        }
    }
}

XiaoMei

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.proxy;

/**
 * 杰瑞
 *
 * @author yangjikang
 * @date 2019/8/2 16:27
 * @modified By yangjikang
 * @since
 */
public class XiaoMei implements Person {

    private String name = "小美";
    private String sex = "女";
    private Integer age = 22;

    public void findLove() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public boolean equals(String name) {
        return name.equals(this.name);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof XiaoMei) {
            XiaoMei xiaoMei = (XiaoMei) obj;
            return this.name == xiaoMei.name;
        }
        else {
            return false;
        }
    }
}

MeiPo

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * 媒婆
 *
 * @author yangjikang
 * @date 2019/8/2 16:32
 * @modified By yangjikang
 * @since
 */
public class MeiPo implements InvocationHandler {

    private static List<Person> persons = new ArrayList<Person>();
    /**
     * 获取被代理人的个人信息
     */
    public Object getInstance(Person person){
        System.out.println(person.getName()+"找媒婆发布信息...");
        persons.add(person);
        Class aClass = person.getClass();
        return Proxy.newProxyInstance(aClass.getClassLoader(),aClass.getInterfaces(),this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        for (Person person1 : persons) {
            System.out.println("开始匹配...");
            person1.findLove();
            for (Person person2 : persons) {
                //只要是异性并且年龄一样,就给他们匹配成功!
                if (person1.getAge().equals(person2.getAge()) && !(person1.getName().equals(person2.getName()))) {
                    System.out.println("恭喜:" + person1.getName() + "和" + person2.getName() + "匹配成功!");
                    persons.remove(person1);
                    persons.remove(person2);
                    return null;
                }
            }
        }
        return null;
    }
}

StartFindLove

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.proxy;

import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.lang.reflect.Proxy;

/**
 * @author yangjikang
 * @date 2019/8/2 16:31
 * @modified By yangjikang
 * @since
 */
public class StartFindLove {

    public static void main(String[] args) throws Exception{
        Person jerry = (Person) new MeiPo().getInstance(new Jerry());
        System.out.println(jerry.getClass());
        Person xiaoli = (Person) new MeiPo().getInstance(new XiaoLi());
        Person xiaomei = (Person) new MeiPo().getInstance(new XiaoMei());
        jerry.findLove();

        //原理:
        //1.拿到被代理对象所实现的接口的引用,然后获取它的接口(拿到Person对象)
        //2.JDK代理重新生成一个类,同时实现我们给的被代理对象实现的接口(实现Person所有接口)
        //3.把被代理对象的引用也拿到(Jerry、XiaoLi、XiaoMei)
        //4.重新动态生成一个class字节码($Proxy0.class)
        //5.然后编译($Proxy0.java)


        //获取字节码内容
        byte[] data;
        data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
        FileOutputStream os = new FileOutputStream("F:/$Proxy0.class");
        os.write(data);
        os.close();
    }
}

效果图:
在这里插入图片描述
我们来看一下这个JDK动态生成的 $Proxy0.class 文件

先把这个类输出到本地,然后用反编译工具转成 java 文件查看(在线反编译工具)

import com.jikang.proxy.Person;
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 Person {

   private static Method m1;
   private static Method m6;
   private static Method m3;
   private static Method m5;
   private static Method m2;
   private static Method m4;
   private static Method m0;


   public $Proxy0(InvocationHandler var1) throws  {
      super(var1);
   }

   public final boolean equals(Object var1) throws  {
      try {
         return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
      } catch (RuntimeException | Error var3) {
         throw var3;
      } catch (Throwable var4) {
         throw new UndeclaredThrowableException(var4);
      }
   }

   public final String getSex() throws  {
      try {
         return (String)super.h.invoke(this, m6, (Object[])null);
      } catch (RuntimeException | Error var2) {
         throw var2;
      } catch (Throwable var3) {
         throw new UndeclaredThrowableException(var3);
      }
   }

   public final String getName() throws  {
      try {
         return (String)super.h.invoke(this, m3, (Object[])null);
      } catch (RuntimeException | Error var2) {
         throw var2;
      } catch (Throwable var3) {
         throw new UndeclaredThrowableException(var3);
      }
   }

   public final void findLove() throws  {
      try {
         super.h.invoke(this, m5, (Object[])null);
      } catch (RuntimeException | Error var2) {
         throw var2;
      } catch (Throwable var3) {
         throw new UndeclaredThrowableException(var3);
      }
   }

   public final String toString() throws  {
      try {
         return (String)super.h.invoke(this, m2, (Object[])null);
      } catch (RuntimeException | Error var2) {
         throw var2;
      } catch (Throwable var3) {
         throw new UndeclaredThrowableException(var3);
      }
   }

   public final Integer getAge() throws  {
      try {
         return (Integer)super.h.invoke(this, m4, (Object[])null);
      } catch (RuntimeException | Error var2) {
         throw var2;
      } catch (Throwable var3) {
         throw new UndeclaredThrowableException(var3);
      }
   }

   public final int hashCode() throws  {
      try {
         return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
      } catch (RuntimeException | Error var2) {
         throw var2;
      } catch (Throwable var3) {
         throw new UndeclaredThrowableException(var3);
      }
   }

   static {
      try {
         m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
         m6 = Class.forName("com.jikang.proxy.Person").getMethod("getSex", new Class[0]);
         m3 = Class.forName("com.jikang.proxy.Person").getMethod("getName", new Class[0]);
         m5 = Class.forName("com.jikang.proxy.Person").getMethod("findLove", new Class[0]);
         m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
         m4 = Class.forName("com.jikang.proxy.Person").getMethod("getAge", new Class[0]);
         m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      } catch (NoSuchMethodException var2) {
         throw new NoSuchMethodError(var2.getMessage());
      } catch (ClassNotFoundException var3) {
         throw new NoClassDefFoundError(var3.getMessage());
      }
   }
}

代码分析阶段:

        Person jerry = (Person) new MeiPo().getInstance(new Jerry());
        System.out.println(jerry.getClass());
        Person xiaoli = (Person) new MeiPo().getInstance(new XiaoLi());
        Person xiaomei = (Person) new MeiPo().getInstance(new XiaoMei());
        jerry.findLove();

这里打印jerry对象引用就是 class com.sun.proxy.$Proxy0
我们先看 getInstance()

//所有征婚者信息档案集合
private static List<Person> persons = new ArrayList<Person>();
   /**
     * 获取被代理人的个人信息
     */
    public Object getInstance(Person person){
        System.out.println(person.getName()+"找媒婆发布信息...");
        //添加到档案集合
        persons.add(person);
        Class aClass = person.getClass();
        //返回指定接口的代理类的实例将方法调用分派到指定的调用处理程序。
        return Proxy.newProxyInstance(aClass.getClassLoader(),aClass.getInterfaces(),this);
    }

下面我们再看 findLove()

   public final void findLove() throws  {
      try {
         super.h.invoke(this, m5, (Object[])null);
      } catch (RuntimeException | Error var2) {
         throw var2;
      } catch (Throwable var3) {
         throw new UndeclaredThrowableException(var3);
      }
   }

super 是 Proxy 。我们进去看看 h 是什么

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

是一个 InvocationHandler 类。其实就是我们的Meipo,因为我们

public class MeiPo implements InvocationHandler

那么它的 invoke () 其实就是我们的

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        for (Person person1 : persons) {
            System.out.println("开始匹配...");
            person1.findLove();
            for (Person person2 : persons) {
                //只要是异性并且年龄一样,就给他们匹配成功!
                if (person1.getAge().equals(person2.getAge()) && !(person1.getName().equals(person2.getName()))) {
                    System.out.println("恭喜:" + person1.getName() + "和" + person2.getName() + "匹配成功!");
                    persons.remove(person1);
                    persons.remove(person2);
                    return null;
                }
            }
        }
        return null;
    }

invoke () 有三个变量 this,m5,(Object[])null
this就是当前对象 $Proxy0
m5是什么?

m5 = Class.forName("com.jikang.proxy.Person").getMethod("findLove", new Class[0]);

其实就是当前对象的 findLove()

三、手写一个JDK代理模式

需要的几个类文件
在这里插入图片描述
MyClassLoader

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.custom;

import java.io.*;

/**
 * 代码生成、编译、重新动态load到JVM
 *
 * @author yangjikang
 * @date 2019/8/6 17:14
 * @modified By yangjikang
 * @since
 */
public class MyClassLoader extends ClassLoader {
    private File baseDir;

    public MyClassLoader() {
        String path = MyClassLoader.class.getResource("").getPath();
        this.baseDir = new File(path);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        if (baseDir != null) {
            File classFile = new File(baseDir, name.replaceAll("\\.", "/") + ".class");
            if (classFile.exists()) {
                FileInputStream in = null;
                ByteArrayOutputStream outputStream = null;
                    try{
                        in = new FileInputStream(classFile);
                        outputStream = new ByteArrayOutputStream();
                        byte[] bytes = new byte[1024];
                        int len;
                        while ((len = in.read(bytes))!=-1){
                            outputStream.write(bytes,0,len);
                        }
                        return defineClass(className,outputStream.toByteArray(),0,outputStream.size());
                    }catch (Exception e){
                        e.printStackTrace();
                    }finally {
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        try {
                            outputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        classFile.delete();
                    }
            }
        }
        return null;
    }
}

MyInvocationHandler

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.custom;

import java.lang.reflect.Method;

/**
 * @author yangjikang
 * @date 2019/8/6 17:11
 * @modified By yangjikang
 * @since
 */
public interface MyInvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

MyJerry

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.custom;

/**
 * @author yangjikang
 * @date 2019/8/6 18:05
 * @modified By yangjikang
 * @since
 */
public class MyJerry implements MyPerson {

    private String name = "Jerry";
    private String sex = "男";
    private Integer age = 22;

    @Override
    public void findLove() {
        System.out.println("个人资料:\n");
        System.out.println("姓名:"+this.name);
        System.out.println("性别:"+this.sex);
        System.out.println("年龄:"+this.age);
        System.out.println("=========================");
        System.out.println("择偶要求:\n");
        System.out.println("门当户对...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

MyMeiPo

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.custom;

import com.jikang.proxy.Person;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yangjikang
 * @date 2019/8/6 17:15
 * @modified By yangjikang
 * @since
 */
public class MyMeiPo implements MyInvocationHandler {
    private MyPerson person = null;
    /**
     * 获取被代理人的个人信息
     */
    public Object getInstance(MyPerson person) {
        this.person = person;
        Class aClass = person.getClass();
        System.out.println("被代理对象的class: " + aClass.getName());
        return MyProxy.newProxyInstance(new MyClassLoader(), aClass.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("媒婆帮你找对象!靠谱媒婆");
        person.findLove();
        return null;
    }


}

MyPerson

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.custom;

/**
 * @author yangjikang
 * @date 2019/8/6 18:04
 * @modified By yangjikang
 * @since
 */
public interface MyPerson {
    void findLove();
}

MyProxy

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.custom;

import javax.swing.*;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 生成代理对象的代码
 *
 * @author yangjikang
 * @date 2019/8/6 17:13
 * @modified By yangjikang
 * @since
 */
public class MyProxy {
    private static String ln = "\r\n";

    public static Object newProxyInstance(MyClassLoader loader,
                                          Class<?>[] interfaces,
                                          MyInvocationHandler h)
            throws IllegalArgumentException {
        try {
            //1.生成源代码
            String proxySrc = generateSrc(interfaces[0]);

            //2.将生成的源代码输出到磁盘,保存为.java文件
            String path = MyProxy.class.getResource("").getPath();
            File file = new File(path + "$Proxy0.java");
            FileWriter fw = new FileWriter(file);
            fw.write(proxySrc);
            fw.flush();
            fw.close();

            //3.编译源代码,并且生成.class文件
            JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null, null, null);
            Iterable iterable = standardFileManager.getJavaFileObjects(file);

            JavaCompiler.CompilationTask task = systemJavaCompiler.getTask(null, standardFileManager, null, null, null, iterable);

            task.call();
            standardFileManager.close();
            
            //4.将class里面的内容动态加载到JVM
            Class proxyClass = loader.findClass("$Proxy0");
            Constructor constructors = proxyClass.getConstructor(MyInvocationHandler.class);

            file.delete();
            
            //5.返回被代理后的代理对象
            return constructors.newInstance(h);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?> interfaces) {
        StringBuffer src = new StringBuffer();
        src.append("package com.jikang.custom;" + ln);
        src.append("import java.lang.reflect.Method;" + ln);
        src.append("public final class $Proxy0 implements " + interfaces.getName() + " {" + ln);
        src.append("MyInvocationHandler h;" + ln);
        src.append("public $Proxy0(MyInvocationHandler h) {" + ln);
        src.append("this.h = h;" + ln);
        src.append("}" + ln);
        for (Method method : interfaces.getMethods()) {
            src.append("@Override"+ln);
            src.append("public " + method.getReturnType().getName() + " " + method.getName() + "(){" + ln);

            src.append("try{" + ln);
            src.append("Method m = " + interfaces.getName() + ".class.getMethod(\"" + method.getName() + "\",new Class[]{});" + ln);
            src.append("this.h.invoke(this,m,null);" + ln);
            src.append("}catch(Throwable e){e.printStackTrace();}" + ln);
            src.append("}" + ln);
        }

        src.append("}");

        return src.toString();
    }
}

MyStartFindLove

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.custom;

import com.jikang.proxy.*;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;

/**
 * @author yangjikang
 * @date 2019/8/2 16:31
 * @modified By yangjikang
 * @since
 */
public class MyStartFindLove {

    public static void main(String[] args) throws Exception{
        MyPerson myPerson = (MyPerson) new MyMeiPo().getInstance(new MyJerry());
        System.out.println(myPerson.getClass());
        myPerson.findLove();
    }
}

四、cglib 代理模式

在这里插入图片描述
MeiPo

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author yangjikang
 * @date 2019/8/8 9:32
 * @modified By yangjikang
 * @since
 */
public class MeiPo implements MethodInterceptor {

    public Object getInstance(Object object) throws Exception{
        Enhancer enhancer = new Enhancer();
        //告诉cglib需要继承哪一个类
        enhancer.setSuperclass(object.getClass());
        //设置回调,触发intercept()方法
        enhancer.setCallback(this);
        //step1.生成源代码
        //step2.编译成class文件
        //step3.加载到JVM,并返回代理对象
        return enhancer.create();
    }

    /**
     *  它会创建一个类来继承你的类。类似于JDK接口实现类。(一个通过继承拿到对象信息。一个通过实现拿到对象信息。)
     *  同样是做了字节码重组一件事,对于使用api用户来说是无感知的。
     *
     * @param obj
     * @param method
     * @param objects
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("媒婆开始给你找对象");
        System.out.println("你的择偶要求是???");
        System.out.println(obj.getClass());
        //这个 obj 引用,是我们cglib new 出来的
        //cglib new出来的对象。是被代理对象的子类
        //在new 子类之前,实际上默认先调用了我们的super()方法
        //子类重写了父类的所有方法
        //所以就可以间接的操作父类的属性和方法。
        methodProxy.invokeSuper(obj, objects);
        System.out.println("匹配成功...");
        return null;
    }
}

TestEnter

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.cglib;


/**
 * @author yangjikang
 * @date 2019/8/8 9:51
 * @modified By yangjikang
 * @since
 */
public class TestEnter {

    public static void main(String[] args) {
        try {
            XiaoMing xiaoMing = (XiaoMing)new MeiPo().getInstance(new XiaoMing());
            xiaoMing.demand();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

XiaoMing

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.cglib;

/**
 * @author yangjikang
 * @date 2019/8/8 9:32
 * @modified By yangjikang
 * @since
 */
public class XiaoMing {

    public void demand(){
        System.out.println("白富美,大长腿");
    }
}

在这里插入图片描述

五、cglib动态代理和JDK动态代理

cglib和jdk 我们都介绍过了,代码也都看过了。所以让你们选择的时候你们应该怎么选择呢?

就代码量而言,cglib是不是要简洁很多啊?

而且CGLIB相比于JDK动态代理更加强大,JDK动态代理有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。

cglib会自动创建一个子类而继承你的父类。从而达到接口实现获取被代理对象信息的目的!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值