junit

  • 概述

    • 是第三方公司编写的一个用于帮助程序员测试代码的框架;
  • 特点

    • 可以直接运行带 @Test注解的方法;(相当于main方法的功能)
    • 可以自动生成测试报告;
    • 可以批量运行多个方法,且部分方法出异常不会影响其他的测试;
  • 使用步骤

image-20230601093239118

入门代码

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
/*
练习junit入门
*/
public class MyTest {
// 1: 导jar包
// 2: 编写被测试的方法
// 3: 在想测的方法上面添加固定的注解

@Test
public void a(){
System.out.println("a方法执行了...");
}
@Test
public void b(){
System.out.println("b方法执行了...");
int i=1/0;
}
public void c(){
System.out.println("c方法执行了...");
}
@Test
public void d(){
System.out.println("d方法执行了...");
}
}

断言

  • 概述

    • 可以对某件事情的结果进行预测,如果将来真实的结果和预测的结果一致,那么断言就成功了,否则断言就失败了,因此断言的结果必定是boolean值;
  • 作用

    • 可以检测程序中存在的逻辑问题;
  • 使用方式

    • 直接利用Assret类的静态方法assertEquals(提示信息,预计结果,真实结果)即可;

参考代码

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
/*
练习junit断言

断言:
可以对某件事情的结果进行预测,如果将来真实的结果和预测的结果一致,那么断言就成功了,否则断言就失败了,因此断言的结果必定是boolean值;

作用:
可以检测程序中存在的逻辑问题;

*/
public class MyTest3 {

@Test
public void a(){
// 直接调用 getIndex方法,传递参数,得到结果
int[] arr = {2,5,8};
int index = getIndex(arr, 5);
// 我们预测(期望)得到一个 1,实际得到的是 index,可以利用断言,让junit框架帮我们比较预测的结果和真实的结果是否一致
Assert.assertEquals("嚣张啊,还得继续努力呀",1,index);

}
// 被测试的业务方法,模拟根据元素,找数组中该元素出现的索引位置
public int getIndex(int[] arr,int key){
if(null==arr){
throw new RuntimeException("数组不能为null");
}
for (int i = 0; i < arr.length; i++) {
if(arr[i]==key){
return i;
}
}
return -1;
}
}

反射

  • 概述

    • 是一种可以在代码运行期间动态获取类的”组成部分”,并单独让这些”组成部分”执行起来的技术;
  • 作用

    • 配合多态的技术,可以处理很多通用的流程,是编写框架和工具非常常用的技术;
  • 相关类

    1. Class 表示所有类的字节码文件对象的类型
    2. Construct 表示所有类中单独”抽取”出来的构造方法对象;
    3. Field 表示所有类中单独抽取出来的成员变量对象;
    4. Method 表示所有类中单独抽取出来的方法对象;
  • 反射的通用步骤

    • 获取类的字节码文件对象;
    • 利用字节码文件对象,反射得到构造方法对象;
    • 利用构造方法对象执行后得到一个普通对象;(等同于以前new的对象)
    • 利用字节码文件对象,反射得到成员变量对象和方法对象;
    • 让反射得到的成员变量或方法对象执行起来;

获取字节码文件对象的三种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
反射入门-- 获取类的字节码文件对象
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1: 方式1,利用Class类的静态方法获取
Class c1 = Class.forName("com.itheima.demo02_fanShe.Phone");
// 2: 方式2, 直接使用类名.class即可
Class c2 = Phone.class;
// 3: 方式3.通过对象,调用方法获取
Class c3 = new Phone().getClass();

System.out.println(c1==c2);
System.out.println(c3==c2);
}
}

反射构造器

  • 目的

    • 为了通过构造器创建出普通的对象;
  • 相关方法

image-20230601111942096

参考代码

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 Test2 {
public static void main(String[] args) throws Exception {
// 1: 方式1,利用Class类的静态方法获取
Class c1 = Class.forName("com.itheima.demo02_fanShe.Phone");
// 2: 利用 c1获取里面所有的public的构造方法
Constructor[] arr = c1.getConstructors();
for (Constructor constructor : arr) {
System.out.println(constructor);
}
System.out.println("=================");
// 获取所有构造方法,包含私有的
Constructor[] arr2 = c1.getDeclaredConstructors();
for (Constructor constructor : arr2) {
System.out.println(constructor);
}
System.out.println("------------------------------");
// 精确反射具体的某个构造方法,带name的私有构造方法,带两个参数的构造方法
Constructor constructor = c1.getConstructor(String.class, double.class);
System.out.println(constructor);
Constructor constructor2 = c1.getDeclaredConstructor(String.class);
System.out.println(constructor2);

}
}
  • 让构造器执行

image-20230601113458780

  • 注意事项
    • 如果是暴力反射的私有内容,执行前必须忽略权限检查,否则执行失败!

反射成员变量

  • 目的

    • 绕过多态,给指定的对象中的成员变量存值或取值;
  • 相关方法

image-20230601121047407

参考代码

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 MyField {
public static void main(String[] args) throws Exception {
// 1: 获取类的字节码文件对象
Class c = Phone.class;
// 2: 获取所有的成员变量
Field[] arr = c.getDeclaredFields();
for (Field field : arr) {
System.out.println(field);
}
System.out.println("------------------------");
// 精确反射
Field name = c.getDeclaredField("name");
// 由于成员变量是随着对象的创建而存在,所以要想操作成员变量,必须提前创建出一个包含该成员变量的那个类的对象(Phone对象)
// 如果一个类中有空参数的public权限的构造方法,可以直接利用字节码文件对象,一步到位直接创建普通对象
Object o = c.newInstance();// 此时的o就是以多态的形式存在的手机对象
// 面向name,操作 o
// 忽略权限检查
name.setAccessible(true);
name.set(o,"盗个");
System.out.println(o);

Field price = c.getField("price");
price.set(o,8.8);
System.out.println(o);
Object o1 = name.get(o);
System.out.println(o1);

}
}
  • 注意事项
    • 如果是暴力反射的私有内容,执行前必须忽略权限检查,否则执行失败!
    • 成员变量是随着对象的创建而存在,所以要想操作成员变量,必须提前创建出一个包含该成员变量的那个类的对象;

反射成员方法[重点]

  • 目的

    • 希望让类中的某个方法,单独运行起来,可以绕过多态的限制和泛型的限制;
  • 相关方法

image-20230601142042469

参考代码

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
/*
练习反射成员方法
*/
public class TestMethod {
public static void main(String[] args) throws Exception {
// 1: 获取字节码文件对象
Class c = Phone.class;
// 2: 暴力反射,只能获取本类中所有的方法 包含私有
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("-----------------------");
//3: 非暴力反射,能获取本类中所有的public权限和父类public权限的方法
Method[] methods1 = c.getMethods();
for (Method method : methods1) {
System.out.println(method);
}
System.out.println("======================================================");
// 4: 精确反射 toString方法
Method toString = c.getMethod("toString");
System.out.println(toString);
// 5: 精确反射带参数的方法
Method setNames = c.getDeclaredMethod("setName", String.class);
System.out.println(setNames);

}
}

让反射得到的方法执行

image-20230601143348771

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestMethod2 {
public static void main(String[] args) throws Exception {
// 1: 获取字节码文件对象
Class c = Phone.class;
// 2: 精确反射 toString方法
Method toString = c.getMethod("toString");
// 3: 准备一个用于执行方法的对象
Object o = c.newInstance();
// 4: 面向 toString 让它执行起来
Object res = toString.invoke(o);
// 此时的res代表的就是 toString方法返回的结果
System.out.println(res);

// 5: 精确反射带参数的方法
Method setNames = c.getDeclaredMethod("setName", String.class);
// 6: 忽略权限检查
setNames.setAccessible(true);
Object res2 = setNames.invoke(o, "菠萝");
System.out.println(res2);

System.out.println(o);

}
}

反射的应用场景

  1. 反射可以绕过泛型的限制;
  2. 反射可以绕过多态的限制;
  3. 反射可以结合IO流技术,完成对象文件内容的操作;
  4. 反射可以动态的让多个类中的方法执行起来;
  5. …….

示例代码1-绕过泛型和多态

image-20230601145821657

示例代码2-模拟idea自动生成gettersetter方法

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
/*
传递一个类路径,自动生成idea中的空参数,全参数,getter和setter方法
大体思路:
1:先将指定的java文件中所有内容读取到内存中,使用StringBuilder对象保存;
2:利用反射技术读取字节码文件中所有的成员变量名称和数据类型保存到map集合中,名做键,类型做值;
3:利用字符串拼接功能,根据map中的成员变量和对应的数据类型,拼接出对应的空参数和全参数构造方法,使用StringBuilder对象保存;
4:利用字符串拼接功能,根据map中的成员变量和对应的数据类型,拼接出对应的getter和setter方法,使用StringBuilder对象保存;
5:将生成的构造方法,getter和setter方法插入到原始java文件对应的StringBuilder对象的正确位置;
6:利用IO流技术将最终的StringBuilder对象重新写入java文件;
*/
public class MyIdeaInsert {
public static void main(String[] args) throws Exception {
// 1:先将指定的java文件中所有内容读取到内存中,使用StringBuilder对象保存;
String jpath = "com.itheima.demo02_fanShe.Phone";
// 1.1:获取项目路径
String ppath = MyIdeaInsert.class.getResource("/").getPath().replace("/out/production", "");
// 1.2: 拼接java文件的硬盘绝对路径
File f = new File(ppath + "/src/" + jpath.replace(".", "/") + ".java");
// 1.3: 将指定的java文件中所有内容读取到内存中,使用StringBuilder对象保存;
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new FileReader(f));
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\r\n");
}
br.close();
//System.out.println(sb);
// -----------------------读文件内容结束--------------------------------

//2:利用反射技术读取字节码文件中所有的成员变量名称和数据类型保存到map集合中,名做键,类型做值;
Class aClass = Class.forName(jpath);
Field[] fields = aClass.getDeclaredFields();
Map<String, String> map = new HashMap();
for (Field field : fields) {
map.put(field.getName(), field.getType().getName());
}
//System.out.println(map);
// -----------------------读成员变量存map结束--------------------------------

//3:利用字符串拼接功能,根据map中的成员变量和对应的数据类型,拼接出对应的空参数和全参数构造方法,使用StringBuilder对象保存;
String kong = "public " + aClass.getSimpleName() + "(){}\r\n";
StringBuilder quan = new StringBuilder("public " + aClass.getSimpleName() + "(");
map.forEach((k, v) -> {
// 将map中的成员变量拼接成带参数构造方法的形参列表
quan.append(v).append(" ").append(k).append(",");
});
// 删除参数列表中多余的逗号
quan.deleteCharAt(quan.length() - 1);
quan.append("){\r\n");
map.forEach((k, v) -> {
// 拼接构造方法的方法体
quan.append("this." + k + "=" + k + ";\r\n");
});
quan.append("}\r\n");
//System.out.println(kong);
//System.out.println(quan);
//----------------------拼接构造方法结束--------------------------------

//4:利用字符串拼接功能,根据map中的成员变量和对应的数据类型,拼接出对应的getter和setter方法,使用StringBuilder对象保存;
StringBuilder setAll = new StringBuilder();
map.forEach((name, type) -> {
StringBuilder set = new StringBuilder("public void set");
// 拼接set方法
String s = name.substring(0, 1).toUpperCase();
set.append(s).append(name.substring(1)).append("(").append(type)
.append(" ").append(name).append("){\r\n").append("this.")
.append(name).append("=").append(name).append(";\r\n}\r\n");
setAll.append(set);
});
//System.out.println(setAll);
//----------------------拼接set方法结束--------------------------------
StringBuilder getAll = new StringBuilder();
map.forEach((name, type) -> {
StringBuilder get = new StringBuilder("public "+type+" get");
// 拼接set方法
String s = name.substring(0, 1).toUpperCase();//截取首字母变大写
get.append(s).append(name.substring(1)).append("(){\r\n").append("return ").append(name).append(";\r\n}\r\n");
getAll.append(get);
});
//System.out.println(getAll);
//----------------------拼接get方法结束--------------------------------

//5:将生成的构造方法,getter和setter方法插入到原始java文件对应的StringBuilder对象的正确位置;
sb.insert(sb.lastIndexOf("}")-1,kong);
sb.insert(sb.lastIndexOf("}")-1,quan);
sb.insert(sb.lastIndexOf("}")-1,setAll);
sb.insert(sb.lastIndexOf("}")-1,getAll);
//System.out.println(sb);
//----------------------拼接所有内容结束--------------------------------
//6:利用IO流技术将最终的StringBuilder对象重新写入java文件;
FileWriter fout = new FileWriter(f);
BufferedWriter bf = new BufferedWriter(fout);
bf.write(sb.toString());
bf.close();
System.out.println("已经生成完毕");
}
}

示例代码3-通用框架-将对象的所有成员变量写入文件

image-20230601153737056

注解

  • 概述

    • 就是一个自定义的标记,将来我们可以通过反射的技术,获取这些标记,并进行相应的操作即可;
  • 作用

    • 可以给类或变量或方法添加注解,以便于将来对带有标记的内容进行解析;
    • 也可以通过注解传递一些数据值,供方法或类或变量使用;
  • 语法格式

    • 定义注解使用 @Interface表示;
    • 定义属性时,使用 权限修饰符 数据类型 变量名() default 默认值;的形式表示;

image-20240422223151841

元注解

  • 概述

    • 注解注解的注解就是元注解;元注解是java提前写好的,可以对我们自定义的注解进行限制;
  • 常用的元注解

image-20230601163110317

  • 具体作用

image-20230601163135629

image-20230601163145208

注解解析

  • 概述
    • 就是通过反射的技术,可以获取某个组件上面的注解对象及注解对象中保存的数据;
  • 相关方法

image-20230601170003637

  • 注意事项

    • 以上方法可以通过字节码文件(Class)对象,属性对象(Field),方法对象(Method)均可以调用;
    • 自定义的注解必须使用元注解指定生命周期为 Runtime,否则代码运行的时候,自定义的注解会消失;
  • 参考代码

image-20240422223543404

  • 应用场景介绍
    • 模拟Junit的@Test功能

image-20230601173011298

类加载器

  • 概述

    • 专门负责将java的类加载的内存中并形成字节码文件对象的东西;
  • 作用

    • 帮我们创建字节码文件对象的,目的是为了避免同一个类被多次加载!
  • 分类

一共有3种类加载器:

  1. 应用类加载器: AppClassLoader作用主要是加载程序员自定义的类及第三方jar包的类;
  2. 平台类加载器: PlatformClassLoader 主要作用是加载java中已经提前准备好的API及扩展的类;
  3. 核心类加载器: 无法在java中直接获取使用(null);
  • 获取方式
    • 可以通过字节码文件对象的 getClassLoader方法即可获取类加载器;

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
测试类加载器
*/
public class Test {
public static void main(String[] args) {
// 1: 获取当前类的字节码文件对象
ClassLoader classLoader = Test.class.getClassLoader();
System.out.println(classLoader);
ClassLoader parent = classLoader.getParent();
System.out.println(parent);
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
}
}

动态代理

  • 概述

    • 在代码运行期间,动态的给某个类(目标类)生成一个代理类(可以对目标类进行增强的类)的技术,就是动态代理技术;
  • 作用

    • 可以在不改变已有类的功能的基础上对原有功能进行增强;
  • 相关API

    • jdk提供的Proxy类可以帮我们生成代理对象;
  • 使用前提

    • 目标类必须实现接口;

流程分析

image-20230601203417396

01.动态代理的代码执行流程分析示意图