类加载器-反射笔记

【类加载器反射】

第一章 类加载器

1.1 类的加载时机

类(类的.class文件)的加载时机
    1. 创建类的实例。
    2. 类的静态变量,或者为静态变量赋值。
    3. 类的静态方法。
    4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
    5. 初始化某个类的子类。
    6. 直接使用java.exe命令来运行某个主类。

1.2 类的加载器

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
/*
类加载器:
1.作用:
负责把.class文件加载到内存的方法区中.
将class文件(硬盘)加载到内存生成Class对象。
2.组成:
(1)BootstrapClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载
引导类加载器BootstrapClassLoader:
用C++编写的,是JVM自带的类加载器,
负责Java平台核心库,用来加载核心类库。该加载器无法直接获取
比如System,String等。

(2)ExtClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载
在JDK中JRE的lib目录下ext目录

(3)AppClassLoader 系统类加载器/应用类加载器
Java语言编写的类加载器,负责加载我们定义的类和第三方jar包中的类。

3.获取类加载器
java.lang.Class类
成员方法:
public ClassLoader getClassLoader(): 返回该类的类加载器。

java.lang.ClassLoader类
成员方法:
public ClassLoader getParent(): 返回委托的父类加载器。

4.类加载器加载机制:
双亲委派机制: 谁用谁加载
比如:
public Person {
String s name;
DNSNameService
...
}

java Person
Person类是由AppClassLoader加载
Person类内部使用String,原则上来讲,应该由AppClassLoader加载
但是,先找父加载器ExtClassLoader,不负责
ExtClassLoader找父加载器BootstrapClassLoader,负责

Person类内部使用DNSNameService,原则上来讲,应该由AppClassLoader加载
但是,先找父加载器ExtClassLoader,负责
不管怎样? .class文件只能被加载一次
注意:
BootstrapClassLoader 根类/引导类/核心类加载器 String,System
ExtClassLoader 扩展类加载器 DNSNameService
AppClassLoader 系统类加载器/应用类加载器
它们之间不存在继承关系,最终的父类 java.lang.ClassLoader
*/
public class MyTest04 {
public static void main(String[] args) {
// 1: 获取 app 类加载器
ClassLoader app = ClassLoader.getSystemClassLoader();
ClassLoader app2 = MyTest04.class.getClassLoader();

System.out.println(app);
System.out.println(app2);
System.out.println(app2 == app);

System.out.println("---------------------");
// 2: 获取扩展类加载器
ClassLoader p1 = ClassLoader.getPlatformClassLoader();
ClassLoader p2 = app.getParent();
System.out.println(p1);
System.out.println(p2);
System.out.println(p2==p1);

System.out.println("---------------------");
// 3: 获取引导类加载器/根类加载器 由于这个类加载器不是用java代码编写的,索引只能得到 null
ClassLoader c1 = String.class.getClassLoader();
ClassLoader c2 = p1.getParent();
System.out.println(c1);
System.out.println(c2);
}
}
1
2
3
4
5
6
7
public class MyTest05 {
public static void main(String[] args) {
// 1: 利用类加载器,将 src下面的11.txt转成一个字节输入流 ,相对路径,默认相对于 源码路径;(src)
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("1.txt");
System.out.println(inputStream);
}
}

第二章 反射

2.1 Class类的介绍

1
2
3
4
5
6
7
注意:
1.java文件编译生成.class文件,java文件中的内容就是.class文件中的内容,
只不过.class文件是给JVM看的(称为字节码文件)
2.java是面向对象的编程语言,任何事物都可以定义类,并创建对象,
.class文件也是一种事物,也可以定义类,创建对象
3.jdk中有个类java.lang.Class,是专门用来描述.class文件的
4.该Class类也是有对象的,但是程序员无法自己创建,由jvm帮助创建,程序员可以获取到该Class类型的对象,从而完成相关的操作

1602583477137

2.2 反射的介绍

1
2
3
4
5
6
7
8
9
注意:
1.当第一次使用类的信息时,该类的.class文件,会被加载到内存中的方法区中
2.jvm同时为加载到内存方法区的.class文件,创建一个Class类型的对象,该对象被保存在堆内存中
相当于堆内存中的Class类型的对象,指向了方法区的.class文件
3.一个类的.class文件,只能被加载一次,所以对应的Class类型的对象,只有一个
4.任意类型(基本类型/引用类型)都有对应的Class类型的对象
什么叫做反射呢?
通过获取Class类型的对象,从而操作对应的.class文件
说白了: 通过Class类型的对象获取.class文件中的成员变量/成员方法/构造方法并执行

反射技术,指的是加载类的字节码到内存,并以编程的方法解刨出类中的各个成分(成员变量、方法、构造器等)

1602583518078

反射有啥用呢?

其实反射是用来写框架用的。平时我们用IDEA开发程序时,用对象调用方法,IDEA会有代码提示,idea会将这个对象能调用的方法都给你列举出来,供你选择,如果下图所示

null

问题是IDEA怎么知道这个对象有这些方法可以调用呢?

原因是对象能调用的方法全都来自于类,IDEA通过反射技术就可以获取到类中有哪些方法,并且把方法的名称以提示框的形式显示出来,所以你能看到这些提示了。

反射具体学什么?

因为反射获取的是类的信息,那么反射的第一步首先获取到类才行。

由于Java的设计原则是万物皆对象,获取到的类其实也是以对象的形式体现的,叫字节码对象,用Class类来表示

获取到字节码对象之后,再通过字节码对象就可以获取到类的组成成分了,这些组成成分其实也是对象,

其中每一个成员变量用Field类的对象来表示、
每一个成员方法用Method类的对象来表示,
每一个构造器用Constructor类的对象来表示。

如下图所示:

null

2.4 三种方式获取字节码文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
获取Class对象的方式
方式一:
java.lang.Object类,成员方法:
public Class getClass(): 获取Class类型的对象
方式二:
任意类型(基本/引用),隐藏的class属性,获取该类对应的Class对象
方式三:
java.lang.Class类,静态方法:
public static Class<?> forName(String className)
返回与带有给定字符串名的类或接口相关联的 Class 对象。
参数:
String className: 类/接口 的全名称(包名+类/接口名)

建议使用方式三:
参数是String,可以写在配置文件中
Class类的成员方法:
public String getSimpleName(): 获得简单类名,只是类名,没有包
public String getName(): 获取完整类名,包含包名+类名
*/
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
public class Test06 {
public static void main(String[] args) throws Exception {
// 1: 使用3种方式,分别获取字节码文件对象
Class a1 = Class.forName("com.zxq._06_获取字节码文件对象.A"); // 需要写全路径 带包
Class b1 = Class.forName("com.zxq._06_获取字节码文件对象.B"); // 需要写全路径 带包

Class a2 = A.class;
Class a3 = new A().getClass();
Class a4 = new A().getClass();
System.out.println(a1 == a2); // 上面的3种方式,4个形式获取的A的字节码文件都是同一份
System.out.println(a3 == a2);
System.out.println(a3 == a4);

System.out.println(a1 == b1); // A和B是不同的字节码文件对象

System.out.println("----------------");
Class b2 = B.class;
Class b3 = new B().getClass();
Class b4 = new B().getClass();
System.out.println(b1==b2);
System.out.println(b3==b2);
System.out.println(b3==b4);
}
}

public class A {
}
public class B {
}

2.5 反射获取构造方法

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
/*
反射获取构造方法
步骤:
1.获取Class类型的对象(推荐使用: Class.forName)
2.java.lang.Class类,成员方法: 作用获取构造方法对象的
public Constructor[] getConstructors():
获取所有的public修饰的所有构造方法
每个构造方法,被封装成了一个Constructor对象,存储到数组中,并返回数组

public Constructor[] getDeclaredConstructors():
获取所有的构造方法(包含public,默认,protected,private修饰的)
每个构造方法,被封装成了一个Constructor对象,存储到数组中,并返回数组

public Constructor getConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
参数是可变参数,调用此方法时,可以不写参数,获取的空参构造
可以写参数,给定的参数必须是Class对象
比如:
参数 String name,int age
调用此方法: String.class,int.class

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
根据参数类型获取构造方法对象,可以获得private修饰的构造方法。
*/
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
public class C {
private int a;
private double b;
private String c;

private C(String c) {
this.c = c;
}

public C(double b) {
this.b = b;
}

private C(int a) {
this.a = a;
}

public C() {
}

public C(int a, double b, String c) {
this.a = a;
this.b = b;
this.c = c;
}

@Override
public String toString() {
return "C{" +
"a=" + a +
", b=" + b +
", c='" + c + '\'' +
'}';
}
}
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
public class TestC {
public static void main(String[] args) throws Exception {
// 1: 获取 C 类的字节码文件对象
Class clazz = C.class;
// 2: 面向 clazz 对象,获取所有的 公共的构造方法
Constructor[] arr1 = clazz.getConstructors();
System.out.println(Arrays.toString(arr1));

// 3: 获取所有的任意权限的构造方法
Constructor[] arr2 = clazz.getDeclaredConstructors();
System.out.println(Arrays.toString(arr2));

// 4: 精准反射指定参数的构造方法 反射空参数构造方法
Constructor c1 = clazz.getConstructor();

System.out.println(c1);
// 5: 面向 c1 对象,让它执行,执行后,就可以得到一个 C类型的对象,相当于 new C();
Object o = c1.newInstance();
System.out.println(o);

// 6: 反射 3个参数的构造方法
Constructor c2 = clazz.getConstructor(int.class, double.class, String.class);
System.out.println(c2);

// 8: 让构造方法执行,,相当于 new C(int参数,double参数,String参数);
Object o1 = c2.newInstance(100, 200.1, "嘿嘿");
System.out.println(o1);

System.out.println("==================================");
// 9: 反射 带一个 int参数的构造方法
Constructor c3 = clazz.getDeclaredConstructor(int.class);
System.out.println(c3);
// 10: 暴力反射得到的私有的内容,执行前必须忽略权限检查
c3.setAccessible(true);
Object o2 = c3.newInstance(666);
System.out.println(o2);

System.out.println("--------------------------------");
// 如果一个类中,有 public 的 空参数的构造方法,那么可以直接使用 字节码文件对象,调用 newInstance() 得到一个 普通对象;(不需要手动反射构造方法)
Object o3 = clazz.newInstance();
System.out.println(o3);
}
}

2.10 反射获取成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
反射获取成员方法
步骤:
1.获取Class类型的对象(推荐使用: Class.forName)
2.java.lang.Class类,成员方法: 作用获取成员方法对象的
public Method[] getMethods()
获取所有的public修饰的成员方法,包括父类中。
每个成员方法被封装成为一个Method对象,存储到数组中,并返回
public Method getMethod(String methodName, Class... params)
根据方法名和参数类型获得一个方法对象,只能是获取public修饰的
参数:
String methodName: 方法的名字
Class... params: 方法参数类型对应的Class对象
可变参数: 传递数组,参数列表,不传参
Method getDeclaredMethod(String name, Class... params)
根据方法名和参数类型获得一个方法对象,可以获取private修饰的
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class D {
private void show1(){
System.out.println("私有的show1...");
}
private void show1(double a){
System.out.println("私有的show1...a="+a);
}
public int show2(int a){
System.out.println("公共的的show2...a="+a);
return 2*a;
}
public void show2(int a,int b){
System.out.println("公共的的show2...a="+a+",b="+b);
}
}
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
public class TestD {
public static void main(String[] args) throws Exception {
// 1: 获取字节码文件对象
Class cz = D.class;
// 2: 由于D类中,没有显式的写构造方法,可以直接利用字节码文件对象得到一个 D类型的普通对象
Object o = cz.newInstance();

// 3: 反射所有的 public的方法
Method[] arr = cz.getMethods();
System.out.println(Arrays.toString(arr));

// 4: 反射任意权限的成员方法
Method[] arr2 = cz.getDeclaredMethods();
System.out.println(Arrays.toString(arr2));

// 5: 精准反射 带一个 int参数的成员方法并执行
Method m1 = cz.getMethod("show2", int.class);
Object invoke = m1.invoke(o, 123);
System.out.println("方法返回的结果是:"+invoke);

Method m2 = cz.getMethod("show2", int.class, int.class);
Object invoke1 = m2.invoke(o, 2, 5);
System.out.println(invoke1); // null

// 6: 暴力反射私有的成员方法并执行
Method m3 = cz.getDeclaredMethod("show1", double.class);
m3.setAccessible(true);
Object invoke2 = m3.invoke(o, 2.2);
System.out.println(invoke2);
}
}

2.11反射获取成员属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class E {
private String name;
private int age;
private double score;

@Override
public String toString() {
return "E{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
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
public class TestE {
public static void main(String[] args) throws Exception {
// 1: 获取字节码文件对象
Class cz = E.class;
Object o = cz.newInstance();

// 2: 暴力反射所有的成员变量
Field[] arr = cz.getDeclaredFields();
System.out.println(Arrays.toString(arr));

// 3: 精准反射
Field name = cz.getDeclaredField("name");
// 4: 忽略权限检查
name.setAccessible(true);

// 5: 存值,取值
System.out.println(o);
name.set(o,"张三");
System.out.println(o);

Field age = cz.getDeclaredField("age");
Field score = cz.getDeclaredField("score");
age.setAccessible(true);
score.setAccessible(true);

age.set(o,18);
score.set(o,99.8);
System.out.println(o);

System.out.println(age.get(o));
System.out.println(score.get(o));
}
}

2.13 反射案例分析

1
2
3
4
5
6
# 类的全限定名(根据你自己的包名改)
className=com.example.UserService
# 要执行的方法名
methodName=sayHello
# 传给方法的参数
param=灵星智能商城
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
/*
自定义的框架,可以帮使用者,执行"配置文件中配置的类的指定方法"
要求使用这个框架的使用者,需要自己提前在我们指定的配置文件中,填写对应的执行信息即可
*/
// 1. 定义工具类 MyTools1
public class MyTools1 {
// 2. 私有构造方法:工具类禁止外部实例化(只能通过静态方法调用)
private MyTools1(){}

// 3. 静态核心方法 start():返回方法执行结果,抛出所有反射相关异常
public static Object start() throws Exception {
/*
1: 读配置文件
2: 根据 className 获取字节码文件对象
3: 根据 methodName 反射带String参数的成员方法
4: 执行方法,传递 param
*/

// 4. 从【类路径】下读取配置文件 my.conf,返回字节输入流
// 注意:配置文件必须放在 src/main/resources(Maven项目)或 src(普通项目)目录下
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream("my.conf");

// 5. 创建 Properties 对象:专门用来存储 .properties/.conf 格式的键值对配置
Properties p = new Properties();

// 6. 注释说明:如果包路径含中文,字节流读中文会乱码,需转成字符流并指定 UTF-8 编码
// 7. 字节流 → 字符流,指定 UTF-8 编码
InputStreamReader isr = new InputStreamReader(in,"utf-8");

// 8. 加载字符流到 Properties 对象中,此时 p 里就存了配置文件的所有键值对
p.load(isr);

// 9. 从配置中读取 key 为 "className" 的值(类的全限定名,如 com.example.UserService)
String className = p.getProperty("className");

// 10. 通过全限定名加载类,获取该类的【字节码对象 Class】(反射的核心入口)
Class c = Class.forName(className);

// 11. 从配置中读取 "methodName",并反射获取该类中【带一个 String 参数的 public 方法】
// getMethod(方法名, 参数类型列表):只能获取 public 修饰的方法
Method method = c.getMethod(p.getProperty("methodName"), String.class);

// 12. 执行方法并返回结果
// c.newInstance():通过无参构造方法创建该类的实例(JDK9后过时,推荐用 c.getDeclaredConstructor().newInstance())
// method.invoke(对象, 实参):调用方法,传入实例和参数值
return method.invoke(c.newInstance(),p.getProperty("param"));
}
}
1
2
3
4
5
6
public class A {
public String abc(String s){
System.out.println("A类的abc方法执行的时候,得到的参数是:"+s);
return s+s;
}
}
1
2
3
4
5
6
public class B {
public Integer bbc(String s){
System.out.println("B类的bbc方法执行的时候,得到的参数是:"+s);
return Integer.parseInt(s)*2;
}
}
1
2
3
4
5
6
7
public class Test01 {
public static void main(String[] args) throws Exception {
// 1: 填写好配置文件之后,就可以用工具,让工具按我们填写的信息,执行代码
Object start = MyTools1.start();
System.out.println("工具返回的结果是:"+start);
}
}
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
/*
使用反射的技术,可以绕过泛型检查
*/
public class Test02 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<Integer> list = new ArrayList<>();
list.add(123);
//list.add("abc");// 直接调用,会被泛型约束,无法直接调用
/*
利用反射,反射 list接口的 add方法,并执行即可
*/
Class c = list.getClass();
Method add = c.getMethod("add", Object.class);
add.invoke(list,"abc");
System.out.println(list);

// 面向方法对象,可以反向获取方法相关信息以及类相关信息
String name = add.getName();
Class returnType = add.getReturnType();
Class[] parameterTypes = add.getParameterTypes();

Class aClass = add.getDeclaringClass();
String simpleName = aClass.getSimpleName();
String name1 = aClass.getName();
System.out.println("反射的add是:"+simpleName+"类("+name1+")下的:"+name+",参数类型是:"+ Arrays.toString(parameterTypes)+",返回值类型是"+returnType);
}
}