目录
反射概述
Reflection
五种class对象获取方式:
所有类型的class对象:
类加载内存分析:
什么时候会发生类的初始化?
类加载器:
类加载器的作用:
双亲委派机制:
获取类的运行时结构:
动态创建对象执行:
性能对比分析:
获取泛型信息:
获取注解信息:
反射概述
java是静态类型语言类型,因为有了反射机制,所以变成了一门准动态语言,同时反射还是框架设计的灵魂(前提:必须得到class字节码)。
动态语言:表示一种在运行时可以改变结构的语言,列如新的函数对象,甚至代码可以被引进,已有的代码可以被删除,或是增加,简单一点就是:可以在运行时期可以通过某些条件改变自己结构,如:Object-C,C#,JavaScript,PHP,python.
静态语言:相对于动态语言表示在运行时期不可以改变自身结构,如:java,C,C++.
补充:java不是动态语言但可以称为"准动态语言",也有一定的动态性,可以通过java的反射机制来达到类似动态语言的特征。
反射就是把一个类的信息映射成多个对象
Reflection
javaReflection(反射)是java称为准动态语言的关键,在java中可以通过反射机制的API获取类中的任何信息,并且操作类中的属性或者方法.同时jvm(虚拟机)在加载完一个类之后在堆内存的方法区中就生产了一个对应的class对象(一个类只有一个class对象),这个class对象包含了完整的内部结构信息,我们可以通过这个对象看到类的结构信息,这个对象类似于一面镜子,映射着类的结构信息,所以形象的称之为“反射“;
正常创建对象方式:引入包名-->通过new实例化对象-->取得实例化对象
通过反射对象方式:实例化对象-->getClass方法-->得到完成的包名
反射的优点:可以实现动态的创建对象和编译,体现出代码的灵活性
反射缺点:对性能有影响使用反射基本上是一种解释性操作我们告诉jvm我们希望做什么,并且满足我们的要求,这类操作总是慢于直接执行相同操作.
五种class对象获取方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62package com.text; public class test28 { public static void main(String[] args) throws ClassNotFoundException { //获得对象 Person person=new Student(); System.out.println("这个对象的名字是:"+person.name); //第一种:通过对象的方式获得Class对象 Class<? extends Person> aClass = person.getClass();//这个getClass是Object类中的方法 System.out.println(aClass.hashCode()); //第二种:通过路径获得class对象(推荐) Class<?> aClass1 = Class.forName("com.text.Student");//这里会抛出ClassNotFoundException(找不到类)异常 System.out.println(aClass.hashCode());//这里输出会发现第一个class对象的地址和第二个对象的地址是一样的证明了每个类只有一个class对象不管创建多少给对象 //第三种方式:通过类名.class获得(这个的前提是要引入包) Class<Person> personClass = Person.class; System.out.println(personClass.hashCode()); //第四种方式:基本类型的包装类有一个TYPE属性可以通过TYPE属性获得class对象 Class<Integer> type = Integer.TYPE; System.out.println(type); //第五种方式:通过子类class对象获得父类class对象 TeaCher teacher=new TeaCher(); Class<? extends TeaCher> aClass2 = teacher.getClass(); Class<?> superclass = aClass2.getSuperclass();//获取父类class对象 System.out.println(superclass.hashCode()); } } //人类 class Person{ public String name; public Person() {} public Person(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + ''' + '}'; } } //学生类 class Student extends Person{ public Student(){ this.name="学生"; } } //老师类 class TeaCher extends Person{ public TeaCher() { this.name="老师"; } }
注意:
- 一个类在内存中只有一个class对象
- 一个类被加载后类的整个结构被封装在class对象里
- Class本身也是一个类
- Class对象只能由系统创建
- 一个class对象对应的是一个被加载到jvm(虚拟机)的class文件
- 每个类的实例都会记得自己是由那个class的实例生成
- 通过class可以得到一个类中所以被加载的结构
- Class类是Reflection(反射)的根源,任何想要动态加载运行的类,必须先获得对应的class对象.
所有类型的class对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44package com.text; import java.lang.annotation.ElementType; import java.util.Calendar; public class test29 { @SuppressWarnings("all") public static void main(String[] args) { Class c1 = Object.class;//类class Class c2 = Calendar.class; //接口class Class c3 = Override.class; //注解class Class c4 = ElementType.FIELD.getClass();//枚举class Class c5 = Integer.class;//基本数据类型 Class c6 = void.class;//void的class Class c7 = Class.class;//Class类的class Class c8 = int[].class;//一维数组class Class c9 = int[][].class;//多维数组class System.out.println(c1); System.out.println(c2); System.out.println(c3); System.out.println(c4); System.out.println(c5); System.out.println(c6); System.out.println(c7); System.out.println(c8); System.out.println(c9); //只要和类型维度一样,就是一个class对象 int[] a = new int[1024]; Class c10 = a.getClass(); System.out.println(c10.hashCode()); int[] b = new int[4201]; Class c11 = b.getClass(); System.out.println(c11.hashCode()); //与上面一个地址一样 int[][] c=new int[10][10]; Class c12 = c.getClass(); System.out.println(c12.hashCode()); } }
类加载内存分析:
java存储数据分为堆,栈,方法区(一个特殊的堆)三个简单的概念:
当程序主动去调用某个类时恰好这个类没有加载到jvm内存中则系统会通过以下三个步骤进行加载:
类的加载(Load)-->类的链接(Link)-->类的初始化(Initilaize)
类的加载:将类的class文件字节码加载到内存中,并将这些静态数据转化为方法区运行时的数据结构然后,生产一个java.lang.Class对象.
类的链接:将java类的二进制代码合并到jvm运行状态之中的过程
- 验证:确保加载的类信息符合jvm规范,没有安全方面的信息
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都存放在方法区中进行分配.
- 解析:虚拟机常量池的符号引用(常量名)替换为直接引用(地址)的过程
类的初始化:当加载和链接完毕则进入初始化,就jvm进行调用
- 执行类构造器<Clinit>()的方法过程.类构造器<Clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的.(构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有初始化则先构造父类的初始化
- 虚拟机会保证一个类的<Clinit>()方法在多线程环境中被正确的加锁和同步.
什么时候会发生类的初始化?
类的主动引用(一定会发生类的初始化):
- 当虚拟机启动,先初始化main方法所在的类
- new一个对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类如果其父类没有被初始化,则先初始化父类
注意:反射也会产生主动引用.
类的被动引用(不会发送类的初始化):
- 当访问一个静态域时只有真正声明这个域的类才会被初始化.如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用不会触发此类的初始化.
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中)
类加载器:
类缓存:标准的javaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间不过jvm垃圾回收制(GC守护线程)可以回收这些class对象,类缓存主要还是为了提高效率..
类加载器的作用:
类加载器是用来把class文件装入内存的JVM定义了如下类型的类加载器:
- 引导类加载器:用C++编写的,是JVM自带的类加载器负责Java平台核心库,用来装载核心内库,该加载器无法直接获取
- 扩展类加载器:负责jre/lib/exe目录下的jre包或-D java.ext.dirs指定目录下的jar包装入工作库
- 系统类加载器:负责java -classpath或-D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器
小知识:我们平常用的JDK自带的类是rt.jar架包下的.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37package com.text; import java.util.Properties; public class test31 { public static void main(String[] args) throws ClassNotFoundException { //获取系统类加载器 ClassLoader c1 = ClassLoader.getSystemClassLoader(); System.out.println("系统类加载器-->"+c1); //获取系统加载器的父类加载器-->扩展类加载器 ClassLoader c2 = ClassLoader.getSystemClassLoader();//需要先获得系统类加载器 ClassLoader c3 = c2.getParent();//获取扩展类加载器 System.out.println("扩展类加载器-->"+c3); //获取扩展类加载器的父类加载器-->根加载器或者叫做引导类加载器(c/c++) ClassLoader c4 = ClassLoader.getSystemClassLoader();//获取扩展类加载器 ClassLoader c5 = c4.getParent();//获取扩展类加载器 ClassLoader c6 = c5.getParent();//获取根加载器 System.out.println("根加载器或者叫做引导类加载器-->"+c6);//输出null 因为java的根加载器是c/c++编写的所以java无法获取到 //测试当前类是那个加载器加载的 Class<?> aClass = Class.forName("com.text.test31"); ClassLoader classLoader = aClass.getClassLoader();//获取加载器 System.out.println("当前类的加载器-->"+classLoader); //测试系统类是那个加载器加载的 Class<?> aClass1 = Class.forName("java.lang.Object"); ClassLoader classLoader1 = aClass.getClassLoader();//获取加载器 System.out.println("JDK自带的类的加载器-->"+classLoader1); //获取系统类加载器加载的路径 String property = System.getProperty("java.lang.path"); System.out.println(property); } }
双亲委派机制:
java中运行于双亲委派机制,是为了防止如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被引导类加载器加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是引导类加载器),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
获取类的运行时结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146package com.text; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; public class test32 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //获取Class类 Class<?> c1 = Class.forName("com.text.Animal"); //获取类名 System.out.println("--------------------获取类名--------------------"); System.out.println("类名" + c1.getSimpleName()); System.out.println("包名+类名:" + c1.getName());//包名+类名 //获取类的属性 System.out.println("--------------------获取类的属性--------------------"); Field[] fields = c1.getFields();//只能获取公共的字段表示属性 Field[] declaredFields = c1.getDeclaredFields();//获取所以的字段属性(包括私有的等等) for (Field declaredField : declaredFields) { System.out.println(declaredField); } //获取指定属性的值 System.out.println("--------------------获取指定属性的值--------------------"); Field name = c1.getDeclaredField("name"); System.out.println(name); //获取方法 System.out.println("--------------------获取类的方法--------------------"); Method[] declaredMethods = c1.getDeclaredMethods();//获得本类全部方法(无关修饰符) Method[] methods = c1.getMethods(); //获得本类及父类的全部public方法 for (Method method : declaredMethods) { System.out.println(method); } //获取指定方法 System.out.println("--------------------获取指定方法--------------------"); Method getNames = c1.getMethod("getName", null); System.out.println(getNames); //获取Class全部的公有构造方法 System.out.println("---------------------获取所有公共的构造函数---------------------"); Constructor<?>[] constructors = c1.getConstructors(); for (Constructor<?> constructor : constructors) { System.out.println(constructor); } //获取Class全部的构造方法 System.out.println("---------------------获取所有全部的构造函数(包括:公共的,默认的,受保护的,私有的)---------------------"); Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors(); for (Constructor<?> declaredConstructor : declaredConstructors) { System.out.println(declaredConstructor); } //获取公共构造方法v System.out.println("---------------------获取公共无参构造方法---------------------"); Constructor<?> constructor = c1.getConstructor(null);//这里必须是一个参数类型的class,默认为null System.out.println(constructor); Object o = constructor.newInstance();//调用构造方法 // System.out.println("o:"+o.toString()); // User user=(User)o; // user.user2(); //获取私有构造放 System.out.println("---------------------获取私有构造方法---------------------"); Constructor<?> declaredConstructor = c1.getDeclaredConstructor(int.class); System.out.println(constructor); declaredConstructor.setAccessible(true); Object test = declaredConstructor.newInstance(123); System.out.println(test); //获取指定构造方法 System.out.println("---------------------获取指定构造方法---------------------"); Constructor<?> declaredConstructor1 = c1.getDeclaredConstructor(int.class, String.class); System.out.println(declaredConstructor1); } } class Animal { private int age; private String name; public Animal() { } //这个是测试 private Animal(int a){ } private void test() { System.out.println("私有方法"); } public Animal(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Animal{" + "age=" + age + ", name='" + name + ''' + '}'; } } class Dog extends Animal { private String name; public Dog() { this.setName("小狗"); this.setAge((int) (Math.random() * 7)); } } class Cat extends Animal { public Cat() { this.setName("猫"); this.setAge((int) (Math.random() * 7)); } }
动态创建对象执行:
有了class 对象之后就可以通过反射进行再次创建一个对象,来访问对象里的方法属性等等,但有一些方法或者属性设置了private则需要取消安全检测调用setAccessible(true)方法来关闭安全检测就可以强行写入了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39package com.text; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; public class test34 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { //1.创建class对象 Class<?> c1 = Class.forName("com.text.personnel"); //2.获取构造方法 Constructor<?> declaredConstructor = c1.getDeclaredConstructor(); //3.通过构造方法new类(这里知道是什么类就可以强行转换了) personnel o = (personnel)(declaredConstructor.newInstance()); //4.这里获取字段(也可以获取方法全看需要) Field id = c1.getDeclaredField("id"); //5.因为是私有的属性所以关闭安全检测 id.setAccessible(true); //6.设置属性,参数是(设置属性的对象,属性值) id.set(o,"1234"); //7.查看调用方法查看是否设置成功 System.out.println(o.getId()); } } //账号类 class personnel{ private String id; private String password; public String getId() { return id; } public String getPassword() { return password; } }
性能对比分析:
使用发射调用信息会耗费较大的资源,但是如果调用setAccessible(true)关闭安全检测则消耗的资源会大大降低,使用代码来进行循环10亿次的调用对象名字响应速度对比:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75package com.text; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; //性能测试 public class test35 { public static void test1(){ GamePlayer test=new GamePlayer(); //获取循环前系统时间单位为毫秒 long l = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { test.getName(); } //获取循环后系统时间 long l1 = System.currentTimeMillis(); System.out.println("普通方法循环循环10亿次所需时间:"+(l1-l)+"ms"); } public static void test2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //1.创建反射对象 Class<?> c1 = Class.forName("com.text.GamePlayer"); //2.获取构造对象 Constructor<?> declaredConstructor = c1.getDeclaredConstructor(); //3.创建对象 GamePlayer o = (GamePlayer)(declaredConstructor.newInstance()); //4.获取方法 Method getName = c1.getDeclaredMethod("getName"); //5.调用方法 long l = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(o); } long l1 = System.currentTimeMillis(); System.out.println("反射循环循环10亿次所需时间:"+(l1-l)+"ms"); } //反射关闭安全检测所需要的时间 public static void test3() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //1.创建反射对象 Class<?> c1 = Class.forName("com.text.GamePlayer"); //2.获取构造对象 Constructor<?> declaredConstructor = c1.getDeclaredConstructor(); //3.创建对象 GamePlayer o = (GamePlayer)(declaredConstructor.newInstance()); //4.获取方法 Method getName = c1.getDeclaredMethod("getName"); //5.关闭安全检测 getName.setAccessible(true); //6.调用方法 long l = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(o); } long l1 = System.currentTimeMillis(); System.out.println("反射关闭安全检测循环循环10亿次所需时间:"+(l1-l)+"ms"); } public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { test1(); test2(); test3(); } } //玩家类(这里并没有实际作用只是用来测试) class GamePlayer{ String name; public String getName() { return name; } }
结果:
1
2
3
4普通方法循环循环10亿次所需时间:5ms 反射循环循环10亿次所需时间:3166ms 反射关闭安全检测循环循环10亿次所需时间:1671ms
可以得出结论如果需要反射实现大量的操作,则可以关闭安全检测,进行效率的提升,但是相对应的安全性会降低.
获取泛型信息:
java采用泛型擦除机制来引入泛型,java中泛型仅仅是给javac(编译时期)来使用的,确保数据的安全性,和强制转换问题,但是一旦编译完成所有和泛型有关的类型全部擦除,为了通过反射去操作这些类型java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来表达不能被归一到Class类中的类型但是又和原始类型其名的类型。
ParameterizedType:表示一种参数化类型,比如Collection<String>
GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable:是各种类型变量的公共父接口
WildcardType:表示一种通配符类型表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56package com.text; import java.lang.reflect.*; import java.util.Map; //反射获取泛型信息 public class test36 { public void test1(Map<String,Integer> a){ System.out.println("test1"); } public Map<Character,Integer> test2(){ System.out.println("test2"); return null; } public static void main(String[] args) throws NoSuchMethodException { System.out.println("------------------测试参数类型返回值泛型------------------"); //创建反射的方法 Method test1 = test36.class.getDeclaredMethod("test1", Map.class); //获得同用参数类型 Type[] type1 = test1.getGenericParameterTypes(); for (Type type : type1) { System.out.println(type); //判断是否属于参数化类型 if (type instanceof ParameterizedType){ //如果属于则转为参数化类型 ParameterizedType type2 = (ParameterizedType) type; //然后获取实际类型 Type[] actualTypeArguments = type2.getActualTypeArguments(); //遍历实际类型 for (Type actualTypeArgument : actualTypeArguments) { System.out.println("实际类型:"+actualTypeArgument); } } } System.out.println("------------------测试返回值泛型信息------------------"); //获取反射方法 Method test2 = test36.class.getDeclaredMethod("test2"); //获得返回类型 Type genericReturnType = test2.getGenericReturnType(); System.out.println("信息:"+genericReturnType); if (genericReturnType instanceof ParameterizedType){ //强转获取实际参数类型 Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments(); //遍历输出参数类型 for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument); } } } }
获取注解信息:
ORM是对象映射关系,进行java中的类与数据库中的表相互映射
java中的表:
1
2
3
4
5public class{ int id; String name; int age; }
可以对应数据库中的表如:
相对的:
- 类和表结构对应
- 属性和字段对应
- 对象和记录对应
使用反射和注解完成映射关系:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91package com.text; import java.lang.annotation.*; //反射操作注解 public class test38 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { //创建Class对象 Class<?> c1 = Class.forName("com.text.Student2"); //通过Class对象获得类注解 Annotation[] annotations = c1.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); } //获得指定注解值 Table annotation = c1.getAnnotation(Table.class); System.out.println(annotation.value()); //获得类中的指定注解 java.lang.reflect.Field id = c1.getDeclaredField("id");//指定字段 Field annotation1 = id.getAnnotation(Field.class);//指定注解 System.out.println(annotation1.listName()); System.out.println(annotation1.type()); System.out.println(annotation1.length()); } } @Table("staff_table") class Student2 { @Field(listName = "id", type = "int", length = 10) private int id; @Field(listName = "name", type = "String", length = 3) private String name; @Field(listName = "age", type = "varchar", length = 3) private int age; public Student2() { } public Student2(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } //修饰表 @Target(ElementType.TYPE) //表示作用于类 @Retention(RetentionPolicy.RUNTIME) //表示它会被加载进入到JVM中 @interface Table { String value();//如果只有一个可以用value来命名 } //修饰字段 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface Field { String listName(); String type(); int length(); }
最后
以上就是紧张羽毛最近收集整理的关于反射(狂神说笔记)反射概述类加载内存分析:什么时候会发生类的初始化?类加载器: 获取类的运行时结构:动态创建对象执行:性能对比分析:获取泛型信息:获取注解信息:的全部内容,更多相关反射(狂神说笔记)反射概述类加载内存分析:什么时候会发生类内容请搜索靠谱客的其他文章。
发表评论 取消回复