Java反射底层原理以及应用

写在前面: Java反射, 这个东西百度就会出来相关概念:

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

字每一个都认识, 连起来的意思也不是那么的深奥, 但总给人感觉前面蒙着一层面纱看不清, 似懂非懂, 懂又不懂的感觉. 笔者就尝试从自己的理解对Java反射进行一些解释.

0. 类加载过程

要说反射就得从Java的类加载过程说起.
在这里插入图片描述
    字丑见谅, Java类加载大体分为这么5个步骤, 有机会详细分析这五个步骤, 我们这里主要说的是第一个阶段–加载, 这个加载和前面的类加载是不一样的概念, 加载是类加载的一个阶段.加载是类加载子系统将类的Class对象加载到JVM的过程, 主要工作是:

  1. 通过全限定名来获取定义此类的二进制字节流.
  2. 将二进制字节流的静态存储结构转化为方法区的运行时数据结构.
  3. 在内存中生成这个类的java.lang.Class对象, 作为方法区这个类的数据访问入口.

    以上方法区之类的名称就不解释分析, 那属于另外一部分的内容. 重点看第三点的在内存中生成这个类的java.lang.Class对象. 反射的几乎所有东西都是围绕这个东西在做操作, 所以在讲反射之前我们有必要先对这个东西进行了解.在Java代码中很好获取这种类型的对象实例.

public class TestClass {
	public TestClass(){};
}

public class Test{
	public static void Main(String ... args) {
		// 1. 通过对象类型.class
		Class clazz1 = TestClass.class;
		// 2. 通过forName 这个名称熟悉吗, 就是JDBC连接其中加载驱动那一步
		Class class2 = Class.forName("TestClass的权限定类型名");
		// 3. 通过对象实例的getClass()方法, 这个方法是继承自Object类的方法
		Class class3 = new TestClass().getClass();
		// 4. 还有一种通过类加载器获取, 笔者就知道这4种
		// 另外拓展其实java 8个基础数据类型和void的关键字也有Class类型.
		// void.class int.class ...读者可以自行尝试打印看看.
	}
}

    万物皆对象, 这是面向对象的语言经常说的. Class是类的类, 这么说好绕, 是不是, 也可以把类的Class对象实例看做蓝图或者建筑图纸, 每当我们要去建房的时候我们就去拿图纸(图纸就是Class对象实例)根据图纸描述一步一步的建设房子(房子就是我们通过new 等方式创建的对象), 不管我们建多少套房子, 只要是房子类型一样的, 就是拿的同一张图纸, 就如上面代码中的TestClass类我们不管创建了多少个这个类型的对象实例, 使用都是TestClass.class这张图纸. 上面这一段主要解释的是无论某个对象在Jvm中被创建了多少实例, 都只有一个Class对象与之对应.
    做个小结, 加载就是类加载子系统把类的Class加载到虚拟机内存, 并生成类的Class对象实例, 而Class对象实例则保存了要加载的类的类信息. 需要提示下在加载阶段完成后类本身还是不可用的, 类加载还要经历后面几个关键步骤.

2. 反射

    反射的概念上面其实已经讲了. 现在知道了Class对象是什么了, 以及每一个类都有个Class对象实例, 而类的创建的(上面的建房子)都是从Class对象实例获取相应的信息创建的. new TestClass()这种方式创建对象实例实际上Jvm内部自动去从TestClass的Class对象实例中获取了相应的信息进行创建, 而反射浅显理解就是把上面new的自动的过程变成我们程序员手动操作的过程, 手动去创建类的Class对象实例, 手动去获取构造函数, 手动去创建实例, 你可以这样理解反射, 但事实上还是有细微的差别. 写个例子初体验一下反射:

public class TestClass {  
    private String param;
    public TestClass(String param) { this.param = param; }
    public TestClass() { }
    public void test(){
        System.out.println("hello reflect... " +  param);
    }
    private void test(String testParam){
        System.out.println("test:hello reflect... " +  testParam);
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        // 1. 类的加载的加载阶段, 创建类的Class对象实例
        // 当然这里不止加载阶段, 还有其它阶段进行的.
        Class<TestClass> testClazz = TestClass.class;
        // 2. 从Class对象实例中获取TestClass的构造函数
        // 参数String.class表示获取只有一个参数且为String类型的构造函数
        // 不传参数默认为无参构造函数
        Constructor<TestClass> testClazzDeclaredConstructor = 
        			testClazz.getDeclaredConstructor(String.class);
        // 3. 通过调用构造函数的newInstance方法创建实例
        // newInstance的参数就是构造函数需要的参数
        TestClass testClass = 
        			testClazzDeclaredConstructor.newInstance("param 111");
        // 4. 调用实例的方法输出 hello reflect... param 111
        testClass.test();
        // 作为对比正常调用
        new TestClass("param 111").test();
    }
}

    上面的代码案例就是反射, 可以看到相比正常的new TestClass("param 111").test()多了好几步更加繁琐了, 很多人就产生了疑问. 法拉第发明圆盘发电机的时候, 贵妇问他有什么用, 他说刚出生的婴儿有什么用呢? 况且反射之于Java可是壮汉而不是婴儿.
    与反射相关的API大都在java.lang.reflect下, 比较核心的类有Method, Field, Constructor,Array还有代理用到Proxy等.在想需不需要介绍下相关API的基本操作.
介绍反射相关的操作:

  1. 反射-方法
    // 还是上面的TestClass类, 现在通过反射分别调用两个test方法
    public class Main{
    	public static void main(String ... args) {
    		// 为了简便这里就不通过反射创建对象了, 直接通过new创建
            TestClass testClass = new TestClass("param 111");
            // getMethod的方法可以通过方法名称获取到方法.
            // 方法签名getMethod(String name, Class<?>... parameterTypes)
            // 后面的Class类型的可变长参数是指的需要调用的方法参数列表
            // 必须严格按照顺序, java方法参数是方法签名的一部分,不同顺序的参数是不同方法
            Method testNoargs = testClass.getClass().getMethod("test");
       		// 通过Method.invoke调用
       		// 要调用实例方法肯定需要对象实例     
            testNoargs.invoke(testClass);
            // getDeclaredMethod使用方式和getMethod一样, 参数含义也想通
            // 只是getDeclaredMethod可以获取private方法
            Method testArgString = 
            		testClass.getClass().getDeclaredMethod("test", String.class);
            // 设置方法的访问权限可以通过设置为true, 从而可以调用私有方法
            testArgString.setAccessible(true);
            // 按照常规正常的操作我们是不可以调用私有方法的, 通过反射可以
            // 这也是反射的应用之一
            testArgString.invoke(testClass, "param 222");
    	}
    }
    
  2. 反射-字段
// 还是使用上面的TestClass对象
public class Main {
	public static void main(String ... args) {
		TestClass testClass = new TestClass("param 111");
        Class<? extends TestClass> testClassClass = testClass.getClass();
        // 1. 通过getField获取字段, 和上面一样只能获取public
        Field publicField1 = testClassClass.getField("publicField1");
        // 这里get传入的是对象实例
        System.out.println(publicField1.get(testClass));
		// 2. 通过getDeclareField获取字段, 可以获取私有字段
        Field field1 = testClassClass.getDeclaredField("field1");
        // 设置访问权限, 把private变成public
        field1.setAccessible(true);
        System.out.println(field1.get(testClass));
        // 还可以修改它的值
        field1.set(testClass, "field22");
        System.out.println(field1.get(testClass));
		// 3.这是静态字段的示例
        Field staticField = testClassClass.getDeclaredField("staticField");
        staticField.setAccessible(true);
        // 这一段这几个testClass传null也可以, 想想为什么
        // 静态字段和非静态字段的区别
        System.out.println(staticField.get(testClass));
        staticField.set(testClass, "staticField2");
        System.out.println(staticField.get(testClass));
		// 4. 这是常量, 常量也是可以修改. 常量由final修饰
        Field staticFinalField = testClassClass.getDeclaredField("staticFinalField");
        staticFinalField.setAccessible(true);
        // 这句代码我也不明白, 测试发现打开下面的这句代码, 就没办法修改常量了.
//        System.out.println(staticFinalField.get(null));
		// 把常量改为可修改. 
        Field modifiers = staticFinalField.getClass().getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.set(staticFinalField, staticFinalField.getModifiers() & ~Modifier.FINAL);
        staticFinalField.set(null, "waxxd22");
        System.out.println(staticFinalField.get(TestClass.class));
	}
}

注意4的例子修改常量在高版本上可能有些问题, 高版本的jdk需要用别的方式修改常量, 高版本大概12以上吧, 并且这个东西了解即可, 一般还是不要去修改常量的好.另外在修改基本类型的常量比如int的时候应该会不起效果. 具体原因public static final int a = 100;定义这么一个变量, 在别的地方使用a的时候它可能直接把a替换为100, 所以你改了a, 别的地方依旧是100.
3. 反射-数组

public class Main {
	public static void main(String ... args) {
		// 1. 反射创建数组, 之前说过基础数据类型也有Class对象
        Object intArr = Array.newInstance(int.class, 3);
        // 2. 反射赋值
        Array.set(intArr, 1, 2);
        // 3. 反射取值
        System.out.println(Array.get(intArr, 1));
        // 4. 获取数组的Class类型
        System.out.println(int[].class);
        // 如何通过forName获取int[]的Class呢
        System.out.println(Class.forName("[I"));
        // boolean[] 是[Z long[] 是[J  对象类型[]是[L-> String[] -> [Ljava.lang.String]
	}
}

关于数组那里的Class.forName为什么那么怪异的说明, 你认为不怪异那就算了. 我们声明的时候如字符串数组new java.lang.String[]但是经过编译器编译会变成Ljava.lang.String所以Class.forName里面的参数要写成那样的形式. 但是并不能通过这种方式生成一个数组, 这只表示把Ljava.lang.String这么一个Class对象加载到内存中去了, 想想生成一个数组是需要传入长度的, 我们又不知道Ljava.lang.String这个类型的构造函数长什么样.

3.0 反射应用

反射能做什么, 在工作中也有人问过我这样的问题, 反射能做什么. 上面的例子已经回答了这个问题, 当我告诉别人能调用方法, 能读取字段修改字段, 能创建对象的时候, 他们的头脑中的大都大大的疑惑, 这些我不反射也可以做并且更简单. 所以这里我试图解释一下反射到底能做什么

  1. 肉眼可见的一个作用, 读懂源码, 很多框架都大量应用到反射, 比如spring, 随处可见的反射.
  2. 偶尔一些特殊需求做一些骚操作. 就像上面举例的访问私有方法私有字段, 甚至修改常量. 操作一些别人不让你操作的东西. 这里有一个示例:
    // java有个类Unsafe类, 听名字都很唬人, 不安全类
    // 这个类是Java的黑魔法, 之前官方说在JDK8以后要遗弃它, 可是现在到了最新的15,16都还存在
    // 很多优秀的框架或者功能都基于它完成, 比如NIO, Netty, CAS
    // 有人说这个类一半天使一半魔鬼
    // 众所周知Java自动内存管理没有指针
    // 那么Unsafe就可以解放你的天性, 随意申请内存, 指针都给你. 但这也是危险的
    /*
    	这个类正常的方式肯定是没办法创建的, 因为构造函数是私有的.
    	然后有人说不是有个getUnsafe()方法吗, 你去调用就知道会报错.
    	它会检查调用者的类加载器不是Bootstrap就抛异常, 而我们自己编写的类肯定不是Bootstrap
    	这样做也是因为安全性限制我们调用
    	所以非要调用就只能通过反射生成这个类实例或者反射获取theUnsafe字段
    */
    public final class Unsafe {
        private static native void registerNatives();
        static {
            registerNatives();
        }
        private Unsafe() {}
        private static final Unsafe theUnsafe = new Unsafe();
        public static Unsafe getUnsafe() {
            Class var0 = Reflection.getCallerClass();
            if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
                throw new SecurityException("Unsafe");
            } else {
                return theUnsafe;
            }
        }
    	// 删除了其它的代码
    }
    /*
    	还有其它骚操作我在工作中实际用到的一个, Gson工具在反序列化时候默认把所有数字都
    	反序列化成double还弄些科学计数法之类的.改源码的话是个很简单的判断就搞定了
    	但是源码不好改啊,所以就通过反射修改相关属性. 替换成自己的实现类
    */
    
  3. 确确实实大量需要用到反射的地方, 写框架. 框架之所以为框架, 就因为它只搭了一个架子, 很多具体的功能就需要框架的使用者去自己做. 这个时候写框架的人员是不知道框架使用者的类叫什么但是他又要创建对象并调用你的方法.
    // 定义接口
    public interface UserFunction { void userWork();}
    // 未来的流弊框架
    public class FrameWaxxd {
    	public FrameWaxxd(){};
    	
    	public void frameDo() {
    		// 1. do frame workd
    		// code
    		// 2. List<UserFunction> userFunctions = 获取所有实现UserFunction接口的类;
    		// 3. 遍历创建对象调用userWork();
    		// 4. do fram work
    	}
    }
    
    上面的示例代码就很好的说明了反射的作用, 首先作为框架开发人员, 并不能知道使用者实现了多少个UserFunction接口, 也不知道使用者UserFunction接口实现类的类名, 所以框架开发者没有办法在框架开发阶段通过new 构造函数()的方式去调用, 这些类都是需要运行时确定的, 所以这里几乎是必须通过反射去调用, 结合实际的工作实例, 我们在Springboot开发中那些Controller或者Service我们并没有进行创建, 但是我们知道对象肯定是被创建了, 就是通过反射的方式进行创建.
  4. Java的注解大概率会用到反射, 注解上面的值需要反射获取. 这里就不举例了.
  5. 获取泛型也需要用到反射, Java的泛型实际不那么泛型, 是一种伪泛型, 只是在编译阶段有用, 可以用来检查代码, List<Integer>和List<String>是一样的类型, 因为在JVM里都是List, 而反射可以用来获取里面泛型类型. 有的时候也挺有用的.
  6. 还记得前文吗, JDK动态代理的目标方法调用实际就是基于反射

最后

这次好像没有最后了, 最后说一个int.class == Integer.class 结果是true还是false, 这个本质就是在问基本类型和包装类型的Class对象是不是同一个, 其实很简单, 明显不是同一个, 如果你理解了上文说讲的Class到底是个什么东西, Class是类的元数据, 保存了类的相关属性信息, 面向对象把事物抽象为类, 而在Java中类的抽象就是Class, Integer对象有各种各样的方法, 而int就只是一个数据类型, 由此它们肯定不是以同一个Class对象为蓝本创建的. 其它基础类型包装类型也类似. 那么另外一个问题int.class == Integer.TYPE吗? true. Integer.TYPE就是保存的int.class.

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
面试题包括以下十九部分:Java 基础、容器、多线程、反射、对象拷贝、Java Web 模块、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、Mybatis、RabbitMQ、Kafka、Zookeeper、MySql、Redis、JVM 。 目录: 一、Java 基础 1.JDK 和 JRE 有什么区别? 2.== 和 equals 的区别是什么? 3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗? 4.final 在 java 中有什么作用? 5.java 中的 Math.round(-1.5) 等于多少? 6.String 属于基础的数据类型吗? 7.java 中操作字符串都有哪些类?它们之间有什么区别? 8.String str="i"与 String str=new String(“i”)一样吗? 9.如何将字符串反转? 10.String 类的常用方法都有那些? 11.抽象类必须要有抽象方法吗? 12.普通类和抽象类有哪些区别? 13.抽象类能使用 final 修饰吗? 14.接口和抽象类有什么区别? 15.java 中 IO 流分为几种? 16.BIO、NIO、AIO 有什么区别? 17.Files的常用方法都有哪些? 二、容器 18.java 容器都有哪些? 19.Collection 和 Collections 有什么区别? 20.List、Set、Map 之间的区别是什么? 21.HashMap 和 Hashtable 有什么区别? 22.如何决定使用 HashMap 还是 TreeMap? 23.说一下 HashMap 的实现原理? 24.说一下 HashSet 的实现原理? 25.ArrayList 和 LinkedList 的区别是什么? 26.如何实现数组和 List 之间的转换? 27.ArrayList 和 Vector 的区别是什么? 28.Array 和 ArrayList 有何区别? 29.在 Queue 中 poll()和 remove()有什么区别? 30.哪些集合类是线程安全的? 31.迭代器 Iterator 是什么? 32.Iterator 怎么使用?有什么特点? 33.Iterator 和 ListIterator 有什么区别? 34.怎么确保一个集合不能被修改? 三、多线程 35.并行和并发有什么区别? 36.线程和进程的区别? 37.守护线程是什么? 38.创建线程有哪几种方式? 39.说一下 runnable 和 callable 有什么区别? 40.线程有哪些状态? 41.sleep() 和 wait() 有什么区别? 42.notify()和 notifyAll()有什么区别? 43.线程的 run()和 start()有什么区别? 44.创建线程池有哪几种方式? 45.线程池都有哪些状态? 46.线程池中 submit()和 execute()方法有什么区别? 47.在 java 程序中怎么保证多线程的运行安全? 48.多线程锁的升级原理是什么? 49.什么是死锁? 50.怎么防止死锁? 51.ThreadLocal 是什么?有哪些使用场景? 52.说一下 synchronized 底层实现原理? 53.synchronized 和 volatile 的区别是什么? 54.synchronized 和 Lock 有什么区别? 55.synchronized 和 ReentrantLock 区别是什么? 56.说一下 atomic 的原理? 四、反射 57.什么是反射? 58.什么是 java 序列化?什么情况下需要序列化? 59.动态代理是什么?有哪些应用? 60.怎么实现动态代理? 五、对象拷贝 61.为什么要使用克隆? 62.如何实现对象克隆? 63.深拷贝和浅拷贝区别是什么? 六、Java Web 64.jsp 和 servlet 有什么区别? 65.jsp 有哪些内置对象?作用分别是什么? 66.说一下 jsp 的 4 种作用域? 67.session 和 cookie 有什么区别? 68.说一下 session 的工作原理? 69.如果客户端禁止 cookie 能实现 session 还能用吗? 70.spring mvc 和 struts 的区别是什么? 71.如何避免 sql 注入? 72.什么是 XSS 攻击,如何避免? 73.什么是 CSRF 攻击,如何避免? 七、异常 74.throw 和 throws 的区别? 75.final、finally、finalize 有什么区别? 76.try-catch-finally 中哪个部分
一、Java 基础 1 1. JDK 和 JRE 有什么区别? 1 2. == 和 equals 的区别是什么? 1 3. 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗? 3 4. final 在 java 中有什么作用? 4 5. java 中的 Math.round(-1.5) 等于多少? 4 6. String 属于基础的数据类型吗? 4 7. java 中操作字符串都有哪些类?它们之间有什么区别? 4 8. String str="i"与 String str=new String("i")一样吗? 5 9. 如何将字符串反转? 5 10. String 类的常用方法都有那些? 5 11. 抽象类必须要有抽象方法吗? 6 12. 普通类和抽象类有哪些区别? 6 13. 抽象类能使用 final 修饰吗? 6 14. 接口和抽象类有什么区别? 7 15. java 中 IO 流分为几种? 7 16. BIO、NIO、AIO 有什么区别? 7 17. Files的常用方法都有哪些? 8 二、容器 8 18. java 容器都有哪些? 8 19. Collection 和 Collections 有什么区别? 9 20. List、Set、Map 之间的区别是什么? 9 21. HashMap 和 Hashtable 有什么区别? 10 22. 如何决定使用 HashMap 还是 TreeMap? 10 23. 说一下 HashMap 的实现原理? 10 24. 说一下 HashSet 的实现原理? 11 25. ArrayList 和 LinkedList 的区别是什么? 11 26. 如何实现数组和 List 之间的转换? 11 27. ArrayList 和 Vector 的区别是什么? 11 28. Array 和 ArrayList 有何区别? 12 29. 在 Queue 中 poll()和 remove()有什么区别? 12 30. 哪些集合类是线程安全的? 12 31. 迭代器 Iterator 是什么? 12 32. Iterator 怎么使用?有什么特点? 12 33. Iterator 和 ListIterator 有什么区别? 13 三、多线程 13 35. 并行和并发有什么区别? 13 36. 线程和进程的区别? 14 37. 守护线程是什么? 14 38. 创建线程有哪几种方式? 14 39. 说一下 runnable 和 callable 有什么区别? 15 40. 线程有哪些状态? 15 41. sleep() 和 wait() 有什么区别? 16 42. notify()和 notifyAll()有什么区别? 16 43. 线程的 run()和 start()有什么区别? 16 44. 创建线程池有哪几种方式? 17 45. 线程池都有哪些状态? 18 46. 线程池中 submit()和 execute()方法有什么区别? 18 49. 什么是死锁? 19 50. 怎么防止死锁? 19 51. ThreadLocal 是什么?有哪些使用场景? 20 52.说一下 synchronized 底层实现原理? 20 53. synchronized 和 volatile 的区别是什么? 21 54. synchronized 和 Lock 有什么区别? 21 55. synchronized 和 ReentrantLock 区别是什么? 22 56. 说一下 atomic 的原理? 22 四、反射 23 57. 什么是反射? 23 58. 什么是 java 序列化?什么情况下需要序列化? 23 59. 动态代理是什么?有哪些应用? 23 60. 怎么实现动态代理? 24 五、对象拷贝 24 61. 为什么要使用克隆? 24 62. 如何实现对象克隆? 24 63. 深拷贝和浅拷贝区别是什么? 28 六、Java Web 28 64. jsp 和 servlet 有什么区别? 28 65. jsp 有哪些内置对象?作用分别是什么? 29 66. 说一下 jsp 的 4 种作用域? 29 67. session 和 cookie 有什么区别? 30 68. 说一下 session 的工作原理? 31 69. 如果客户端禁止 cookie 能实现 session 还能用吗? 31 70. spring mvc 和 struts 的区别是什么? 31 71. 如何避免 sql 注入? 33 72. 什么是 XSS 攻击,如何避免? 33 73. 什么是 CSRF 攻击,如何避免? 33 七、异常 35 74. throw 和 throws 的区别? 35 75. final、finally、finalize 有什么区别? 35 76. try-catch-finally 中哪个部分可以省略? 35 77. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗? 36 78. 常见的异常类有哪些? 38 八、网络 39 79. http 响应码 301 和 302 代表的是什么?有什么区别? 39 80. forward 和 redirect 的区别? 39 81. 简述 tcp 和 udp的区别? 40 82. tcp 为什么要三次握手,两次不行吗?为什么? 40 84. OSI 的七层模型都有哪些? 42 85. get 和 post 请求有哪些区别? 42 86. 如何实现跨域? 43 87.说一下 JSONP 实现原理? 49 九、设计模式 49 88. 说一下你熟悉的设计模式? 49 89. 简单工厂和抽象工厂有什么区别? 49 十、Spring / Spring MVC 52 90. 为什么要使用 spring? 52 91. 解释一下什么是 aop? 53 92. 解释一下什么是 ioc? 54 93. spring 有哪些主要模块? 56 94. spring 常用的注入方式有哪些? 57 95. spring 中的 bean 是线程安全的吗? 57 96. spring 支持几种 bean 的作用域? 58 97. spring 自动装配 bean 有哪些方式? 59 98. spring 事务实现方式有哪些? 59 99. 说一下 spring 的事务隔离? 59 100. 说一下 spring mvc 运行流程? 60 101. spring mvc 有哪些组件? 61 102. @RequestMapping 的作用是什么? 62 103. @Autowired 的作用是什么? 62

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值