Java基础加强

写在前面的话:

基础加强包含了:

反射,动态代理,类加载器,xml,注解,日志,单元测试等知识点

其中最难的是反射和动态代理,其他知识点都非常简单

1. 反射

1.1 反射的概述:

专业的解释(了解一下):

​ 是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;

​ 对于任意一个对象,都能够调用它的任意属性和方法;

​ 这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

通俗的理解:(掌握)

  • 利用反射创建的对象可以无视修饰符调用类里面的内容

  • 可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中。

    读取到什么类,就创建什么类的对象

    读取到什么方法,就调用什么方法

    此时当需求变更的时候不需要修改代码,只要修改配置文件即可。

1.2 学习反射到底学什么?

反射都是从class字节码文件中获取的内容。

  • 如何获取class字节码文件的对象
  • 利用反射如何获取构造方法(创建对象)
  • 利用反射如何获取成员变量(赋值,获取值)
  • 利用反射如何获取成员方法(运行)

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

  • Class这个类里面的静态方法forName(“全类名”)(最常用)
  • 通过class属性获取
  • 通过对象获取字节码文件对象

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//1.Class这个类里面的静态方法forName
//Class.forName("类的全类名"): 全类名 = 包名 + 类名
Class clazz1 = Class.forName("com.itheima.reflectdemo.Student");
//源代码阶段获取 --- 先把Student加载到内存中,再获取字节码文件的对象
//clazz 就表示Student这个类的字节码文件对象。
//就是当Student.class这个文件加载到内存之后,产生的字节码文件对象


//2.通过class属性获取
//类名.class
Class clazz2 = Student.class;

//因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的
System.out.println(clazz1 == clazz2);//true


//3.通过Student对象获取字节码文件对象
Student s = new Student();
Class clazz3 = s.getClass();
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz2 == clazz3);//true

1.4 字节码文件和字节码文件对象

java文件:就是我们自己编写的java代码。

字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)

字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。

​ 这个对象里面至少包含了:构造方法,成员变量,成员方法。

而我们的反射获取的是什么?字节码文件对象,这个对象在内存中是唯一的。

1.5 获取构造方法

规则:

​ get表示获取

​ Declared表示私有

​ 最后的s表示所有,复数形式

​ 如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

方法名 说明
Constructor<?>[] getConstructors() 获得所有的构造(只能public修饰)
Constructor<?>[] getDeclaredConstructors() 获得所有的构造(包含private修饰)
Constructor getConstructor(Class<?>… parameterTypes) 获取指定构造(只能public修饰)
Constructor 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
36
37
38
39
40
41
public class ReflectDemo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获得整体(class字节码文件对象)
Class clazz = Class.forName("com.itheima.reflectdemo.Student");


//2.获取构造方法对象
//获取所有构造方法(public)
Constructor[] constructors1 = clazz.getConstructors();
for (Constructor constructor : constructors1) {
System.out.println(constructor);
}

System.out.println("=======================");

//获取所有构造(带私有的)
Constructor[] constructors2 = clazz.getDeclaredConstructors();

for (Constructor constructor : constructors2) {
System.out.println(constructor);
}
System.out.println("=======================");

//获取指定的空参构造
Constructor con1 = clazz.getConstructor();
System.out.println(con1);

Constructor con2 = clazz.getConstructor(String.class,int.class);
System.out.println(con2);

System.out.println("=======================");
//获取指定的构造(所有构造都可以获取到,包括public包括private)
Constructor con3 = clazz.getDeclaredConstructor();
System.out.println(con3);
//了解 System.out.println(con3 == con1);
//每一次获取构造方法对象的时候,都会新new一个。

Constructor con4 = clazz.getDeclaredConstructor(String.class);
System.out.println(con4);
}
}

1.6 获取构造方法并创建对象

涉及到的方法:newInstance

代码示例:

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
//首先要有一个javabean类
public class Student {
private String name;

private int age;


public Student() {

}

public Student(String name) {
this.name = name;
}

private Student(String name, int age) {
this.name = name;
this.age = age;
}


/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

/**
* 获取
* @return age
*/
public int getAge() {
return age;
}

/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}

public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}



//测试类中的代码:
//需求1:
//获取空参,并创建对象

//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
//2.获取空参的构造方法
Constructor con = clazz.getConstructor();
//3.利用空参构造方法创建对象
Student stu = (Student) con.newInstance();
System.out.println(stu);


System.out.println("=============================================");


//测试类中的代码:
//需求2:
//获取带参构造,并创建对象
//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
//2.获取有参构造方法
Constructor con = clazz.getDeclaredConstructor(String.class, int.class);
//3.临时修改构造方法的访问权限(暴力反射)
con.setAccessible(true);
//4.直接创建对象
Student stu = (Student) con.newInstance("zhangsan", 23);
System.out.println(stu);

1.7 获取成员变量

规则:

​ get表示获取

​ Declared表示私有

​ 最后的s表示所有,复数形式

​ 如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

方法名:

方法名 说明
Field[] getFields() 返回所有成员变量对象的数组(只能拿public的)
Field[] getDeclaredFields() 返回所有成员变量对象的数组,存在就能拿到
Field getField(String name) 返回单个成员变量对象(只能拿public的)
Field getDeclaredField(String name) 返回单个成员变量对象,存在就能拿到

代码示例:

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
public class ReflectDemo4 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//获取成员变量对象

//1.获取class对象
Class clazz = Class.forName("com.itheima.reflectdemo.Student");

//2.获取成员变量的对象(Field对象)只能获取public修饰的
Field[] fields1 = clazz.getFields();
for (Field field : fields1) {
System.out.println(field);
}

System.out.println("===============================");

//获取成员变量的对象(public + private)
Field[] fields2 = clazz.getDeclaredFields();
for (Field field : fields2) {
System.out.println(field);
}

System.out.println("===============================");
//获得单个成员变量对象
//如果获取的属性是不存在的,那么会报异常
//Field field3 = clazz.getField("aaa");
//System.out.println(field3);//NoSuchFieldException

Field field4 = clazz.getField("gender");
System.out.println(field4);

System.out.println("===============================");
//获取单个成员变量(私有)
Field field5 = clazz.getDeclaredField("name");
System.out.println(field5);

}
}



public class Student {
private String name;

private int age;

public String gender;

public String address;


public Student() {
}

public Student(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}


public Student(String name, int age, String gender, String address) {
this.name = name;
this.age = age;
this.gender = gender;
this.address = address;
}

/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

/**
* 获取
* @return age
*/
public int getAge() {
return age;
}

/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}

/**
* 获取
* @return gender
*/
public String getGender() {
return gender;
}

/**
* 设置
* @param gender
*/
public void setGender(String gender) {
this.gender = gender;
}

/**
* 获取
* @return address
*/
public String getAddress() {
return address;
}

/**
* 设置
* @param address
*/
public void setAddress(String address) {
this.address = address;
}

public String toString() {
return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}";
}
}

1.8 获取成员变量并获取值和修改值

方法 说明
void set(Object obj, Object value) 赋值
Object get(Object obj) 获取值

代码示例:

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
public class ReflectDemo5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Student s = new Student("zhangsan",23,"广州");
Student ss = new Student("lisi",24,"北京");

//需求:
//利用反射获取成员变量并获取值和修改值

//1.获取class对象
Class clazz = Class.forName("com.itheima.reflectdemo.Student");

//2.获取name成员变量
//field就表示name这个属性的对象
Field field = clazz.getDeclaredField("name");
//临时修饰他的访问权限
field.setAccessible(true);

//3.设置(修改)name的值
//参数一:表示要修改哪个对象的name?
//参数二:表示要修改为多少?
field.set(s,"wangwu");

//3.获取name的值
//表示我要获取这个对象的name的值
String result = (String)field.get(s);

//4.打印结果
System.out.println(result);

System.out.println(s);
System.out.println(ss);

}
}


public class Student {
private String name;
private int age;
public String gender;
public String address;


public Student() {
}

public Student(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}


public Student(String name, int age, String gender, String address) {
this.name = name;
this.age = age;
this.gender = gender;
this.address = address;
}

/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

/**
* 获取
* @return age
*/
public int getAge() {
return age;
}

/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}

/**
* 获取
* @return gender
*/
public String getGender() {
return gender;
}

/**
* 设置
* @param gender
*/
public void setGender(String gender) {
this.gender = gender;
}

/**
* 获取
* @return address
*/
public String getAddress() {
return address;
}

/**
* 设置
* @param address
*/
public void setAddress(String address) {
this.address = address;
}

public String toString() {
return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}";
}
}

1.9 获取成员方法

规则:

​ get表示获取

​ Declared表示私有

​ 最后的s表示所有,复数形式

​ 如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

方法名 说明
Method[] getMethods() 返回所有成员方法对象的数组(只能拿public的)
Method[] getDeclaredMethods() 返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象(只能拿public的)
Method getDeclaredMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象,存在就能拿到

代码示例:

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 ReflectDemo6 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获取class对象
Class<?> clazz = Class.forName("com.itheima.reflectdemo.Student");


//2.获取方法
//getMethods可以获取父类中public修饰的方法
Method[] methods1 = clazz.getMethods();
for (Method method : methods1) {
System.out.println(method);
}

System.out.println("===========================");
//获取所有的方法(包含私有)
//但是只能获取自己类中的方法
Method[] methods2 = clazz.getDeclaredMethods();
for (Method method : methods2) {
System.out.println(method);
}

System.out.println("===========================");
//获取指定的方法(空参)
Method method3 = clazz.getMethod("sleep");
System.out.println(method3);

Method method4 = clazz.getMethod("eat",String.class);
System.out.println(method4);

//获取指定的私有方法
Method method5 = clazz.getDeclaredMethod("playGame");
System.out.println(method5);
}
}

1.10 获取成员方法并运行

方法

Object invoke(Object obj, Object… args) :运行方法

参数一:用obj对象调用该方法

参数二:调用方法的传递的参数(如果没有就不写)

返回值:方法的返回值(如果没有就不写)

代码示例:

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
package com.itheima.a02reflectdemo1;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectDemo6 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//1.获取字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");

//2.获取一个对象
//需要用这个对象去调用方法
Student s = new Student();

//3.获取一个指定的方法
//参数一:方法名
//参数二:参数列表,如果没有可以不写
Method eatMethod = clazz.getMethod("eat",String.class);

//运行
//参数一:表示方法的调用对象
//参数二:方法在运行时需要的实际参数
//注意点:如果方法有返回值,那么需要接收invoke的结果
//如果方法没有返回值,则不需要接收
String result = (String) eatMethod.invoke(s, "重庆小面");
System.out.println(result);

}
}



public class Student {
private String name;
private int age;
public String gender;
public String address;


public Student() {

}

public Student(String name) {
this.name = name;
}

private Student(String name, int age) {
this.name = name;
this.age = age;
}

/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

/**
* 获取
* @return age
*/
public int getAge() {
return age;
}

/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}

public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}

private void study(){
System.out.println("学生在学习");
}

private void sleep(){
System.out.println("学生在睡觉");
}

public String eat(String something){
System.out.println("学生在吃" + something);
return "学生已经吃完了,非常happy";
}
}

面试题:

​ 你觉得反射好不好?好,有两个方向

​ 第一个方向:无视修饰符访问类中的内容。但是这种操作在开发中一般不用,都是框架底层来用的。

​ 第二个方向:反射可以跟配置文件结合起来使用,动态的创建对象,动态的调用方法。

1.11 练习泛型擦除(掌握概念,了解代码)

理解:(掌握)

​ 集合中的泛型只在java文件中存在,当编译成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
package com.itheima.reflectdemo;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class ReflectDemo8 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//1.创建集合对象
ArrayList<Integer> list = new ArrayList<>();
list.add(123);
// list.add("aaa");

//2.利用反射运行add方法去添加字符串
//因为反射使用的是class字节码文件

//获取class对象
Class clazz = list.getClass();

//获取add方法对象
Method method = clazz.getMethod("add", Object.class);

//运行方法
method.invoke(list,"aaa");

//打印集合
System.out.println(list);
}
}

1.12 练习:修改字符串的内容(掌握概念,了解代码)

在这个练习中,我需要你掌握的是字符串不能修改的真正原因。

字符串,在底层是一个byte类型的字节数组,名字叫做value

1
private final byte[] value;

真正不能被修改的原因:final和private

final修饰value表示value记录的地址值不能修改。

private修饰value而且没有对外提供getvalue和setvalue的方法。所以,在外界不能获取或修改value记录的地址值。

如果要强行修改可以用反射:

代码示例:(了解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
String s = "abc";
String ss = "abc";
// private final byte[] value= {97,98,99};
// 没有对外提供getvalue和setvalue的方法,不能修改value记录的地址值
// 如果我们利用反射获取了value的地址值。
// 也是可以修改的,final修饰的value
// 真正不可变的value数组的地址值,里面的内容利用反射还是可以修改的,比较危险

//1.获取class对象
Class clazz = s.getClass();

//2.获取value成员变量(private)
Field field = clazz.getDeclaredField("value");
//但是这种操作非常危险
//JDK高版本已经屏蔽了这种操作,低版本还是可以的
//临时修改权限
field.setAccessible(true);

//3.获取value记录的地址值
byte[] bytes = (byte[]) field.get(s);
bytes[0] = 100;

System.out.println(s);//dbc
System.out.println(ss);//dbc

1.13 练习,反射和配置文件结合动态获取的练习(重点)

需求: 利用反射根据文件中的不同类名和方法名,创建不同的对象并调用方法。

分析:

①通过Properties加载配置文件

②得到类名和方法名

③通过类名反射得到Class对象

④通过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
public class ReflectDemo9 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1.读取配置文件的信息
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("day14-code\\prop.properties");
prop.load(fis);
fis.close();
System.out.println(prop);

String classname = prop.get("classname") + "";
String methodname = prop.get("methodname") + "";

//2.获取字节码文件对象
Class clazz = Class.forName(classname);

//3.要先创建这个类的对象
Constructor con = clazz.getDeclaredConstructor();
con.setAccessible(true);
Object o = con.newInstance();
System.out.println(o);

//4.获取方法的对象
Method method = clazz.getDeclaredMethod(methodname);
method.setAccessible(true);

//5.运行方法
method.invoke(o);


}
}

配置文件中的信息:
classname=com.itheima.a02reflectdemo1.Student
methodname=sleep

1.14 利用发射保存对象中的信息(重点)

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 MyReflectDemo {
public static void main(String[] args) throws IllegalAccessException, IOException {
/*
对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去
*/
Student s = new Student("小A",23,'女',167.5,"睡觉");
Teacher t = new Teacher("播妞",10000);
saveObject(s);
}

//把对象里面所有的成员变量名和值保存到本地文件中
public static void saveObject(Object obj) throws IllegalAccessException, IOException {
//1.获取字节码文件的对象
Class clazz = obj.getClass();
//2. 创建IO流
BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\\a.txt"));
//3. 获取所有的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//获取成员变量的名字
String name = field.getName();
//获取成员变量的值
Object value = field.get(obj);
//写出数据
bw.write(name + "=" + value);
bw.newLine();
}

bw.close();

}
}
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
public class Student {
private String name;
private int age;
private char gender;
private double height;
private String hobby;

public Student() {
}

public Student(String name, int age, char gender, double height, String hobby) {
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.hobby = hobby;
}

/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

/**
* 获取
* @return age
*/
public int getAge() {
return age;
}

/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}

/**
* 获取
* @return gender
*/
public char getGender() {
return gender;
}

/**
* 设置
* @param gender
*/
public void setGender(char gender) {
this.gender = gender;
}

/**
* 获取
* @return height
*/
public double getHeight() {
return height;
}

/**
* 设置
* @param height
*/
public void setHeight(double height) {
this.height = height;
}

/**
* 获取
* @return hobby
*/
public String getHobby() {
return hobby;
}

/**
* 设置
* @param hobby
*/
public void setHobby(String hobby) {
this.hobby = hobby;
}

public String toString() {
return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}";
}
}
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
public class Teacher {
private String name;
private double salary;

public Teacher() {
}

public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}

/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

/**
* 获取
* @return salary
*/
public double getSalary() {
return salary;
}

/**
* 设置
* @param salary
*/
public void setSalary(double salary) {
this.salary = salary;
}

public String toString() {
return "Teacher{name = " + name + ", salary = " + salary + "}";
}
}

2. 动态代理

2.1 好处:

​ 无侵入式的给方法增强功能

2.2 动态代理三要素:

1,真正干活的对象

2,代理对象

3,利用代理调用方法

切记一点:代理可以增强或者拦截的方法都在接口中,接口需要写在newProxyInstance的第二个参数里。

2.3 代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void main(String[] args) {
/*
需求:
外面的人想要大明星唱一首歌
1. 获取代理的对象
代理对象 = ProxyUtil.createProxy(大明星的对象);
2. 再调用代理的唱歌方法
代理对象.唱歌的方法("只因你太美");
*/
//1. 获取代理的对象
BigStar bigStar = new BigStar("鸡哥");
Star proxy = ProxyUtil.createProxy(bigStar);

//2. 调用唱歌的方法
String result = proxy.sing("只因你太美");
System.out.println(result);
}
}
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
/*
*
* 类的作用:
* 创建一个代理
*
* */
public class ProxyUtil {
/*
*
* 方法的作用:
* 给一个明星的对象,创建一个代理
*
* 形参:
* 被代理的明星对象
*
* 返回值:
* 给明星创建的代理
*
*
*
* 需求:
* 外面的人想要大明星唱一首歌
* 1. 获取代理的对象
* 代理对象 = ProxyUtil.createProxy(大明星的对象);
* 2. 再调用代理的唱歌方法
* 代理对象.唱歌的方法("只因你太美");
* */
public static Star createProxy(BigStar bigStar){
/* java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数一:用于指定用哪个类加载器,去加载生成的代理类
参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
参数三:用来指定生成的代理对象要干什么事情*/
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),//参数一:用于指定用哪个类加载器,去加载生成的代理类
new Class[]{Star.class},//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
//参数三:用来指定生成的代理对象要干什么事情
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* 参数一:代理的对象
* 参数二:要运行的方法 sing
* 参数三:调用sing方法时,传递的实参
* */
if("sing".equals(method.getName())){
System.out.println("准备话筒,收钱");
}else if("dance".equals(method.getName())){
System.out.println("准备场地,收钱");
}
//去找大明星开始唱歌或者跳舞
//代码的表现形式:调用大明星里面唱歌或者跳舞的方法
return method.invoke(bigStar,args);
}
}
);
return star;
}
}
1
2
3
4
5
6
7
public interface Star {
//我们可以把所有想要被代理的方法定义在接口当中
//唱歌
public abstract String sing(String name);
//跳舞
public abstract void dance();
}
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
public class BigStar implements Star {
private String name;


public BigStar() {
}

public BigStar(String name) {
this.name = name;
}

//唱歌
@Override
public String sing(String name){
System.out.println(this.name + "正在唱" + name);
return "谢谢";
}

//跳舞
@Override
public void dance(){
System.out.println(this.name + "正在跳舞");
}

/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

public String toString() {
return "BigStar{name = " + name + "}";
}
}

2.4 额外扩展

动态代理,还可以拦截方法

比如:

​ 在这个故事中,经济人作为代理,如果别人让邀请大明星去唱歌,打篮球,经纪人就增强功能。

​ 但是如果别人让大明星去扫厕所,经纪人就要拦截,不会去调用大明星的方法。

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
/*
* 类的作用:
* 创建一个代理
* */
public class ProxyUtil {
public static Star createProxy(BigStar bigStar){
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),
new Class[]{Star.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("cleanWC".equals(method.getName())){
System.out.println("拦截,不调用大明星的方法");
return null;
}
//如果是其他方法,正常执行
return method.invoke(bigStar,args);
}
}
);
return star;
}
}

2.5 动态代理的练习

​ 对add方法进行增强,对remove方法进行拦截,对其他方法不拦截也不增强

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
public class MyProxyDemo1 {
public static void main(String[] args) {
//动态代码可以增强也可以拦截
//1.创建真正干活的人
ArrayList<String> list = new ArrayList<>();

//2.创建代理对象
//参数一:类加载器。当前类名.class.getClassLoader()
// 找到是谁,把当前的类,加载到内存中了,我再麻烦他帮我干一件事情,把后面的代理类,也加载到内存

//参数二:是一个数组,在数组里面写接口的字节码文件对象。
// 如果写了List,那么表示代理,可以代理List接口里面所有的方法,对这些方法可以增强或者拦截
// 但是,一定要写ArrayList真实实现的接口
// 假设在第二个参数中,写了MyInter接口,那么是错误的。
// 因为ArrayList并没有实现这个接口,那么就无法对这个接口里面的方法,进行增强或拦截
//参数三:用来创建代理对象的匿名内部类
List proxyList = (List) Proxy.newProxyInstance(
//参数一:类加载器
MyProxyDemo1.class.getClassLoader(),
//参数二:是一个数组,表示代理对象能代理的方法范围
new Class[]{List.class},
//参数三:本质就是代理对象
new InvocationHandler() {
@Override
//invoke方法参数的意义
//参数一:表示代理对象,一般不用(了解)
//参数二:就是方法名,我们可以对方法名进行判断,是增强还是拦截
//参数三:就是下面第三步调用方法时,传递的参数。
//举例1:
//list.add("XX好帅");
//此时参数二就是add这个方法名
//此时参数三 args[0] 就是 XX好帅
//举例2:
//list.set(1, "aaa");
//此时参数二就是set这个方法名
//此时参数三 args[0] 就是 1 args[1]"aaa"
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//对add方法做一个增强,统计耗时时间
if (method.getName().equals("add")) {
long start = System.currentTimeMillis();
//调用集合的方法,真正的添加数据
method.invoke(list, args);
long end = System.currentTimeMillis();
System.out.println("耗时时间:" + (end - start));
//需要进行返回,返回值要跟真正增强或者拦截的方法保持一致
return true;
}else if(method.getName().equals("remove") && args[0] instanceof Integer){
System.out.println("拦截了按照索引删除的方法");
return null;
}else if(method.getName().equals("remove")){
System.out.println("拦截了按照对象删除的方法");
return false;
}else{
//如果当前调用的是其他方法,我们既不增强,也不拦截
method.invoke(list,args);
return null;
}
}
}
);

//3.调用方法
//如果调用者是list,就好比绕过了第二步的代码,直接添加元素
//如果调用者是代理对象,此时代理才能帮我们增强或者拦截

//每次调用方法的时候,都不会直接操作集合
//而是先调用代理里面的invoke,在invoke方法中进行判断,可以增强或者拦截
proxyList.add("aaa");
proxyList.add("bbb");
proxyList.add("ccc");
proxyList.add("ddd");

proxyList.remove(0);
proxyList.remove("aaa");


//打印集合
System.out.println(list);
}
}

3.日志

3.1 作用:

​ 跟输出语句一样,可以把程序在运行过程中的详细信息都打印在控制台上。

​ 利用log日志还可以把这些详细信息保存到文件和数据库中。

3.2 使用步骤:

​ 不是java的,也不是自己写的,是第三方提供的代码,所以我们要导入jar包。

  • 把第三方的代码导入到当前的项目当中

    新建lib文件夹,把jar粘贴到lib文件夹当中,全选后右键点击选择add as a ….

    检测导入成功:导入成功后jar包可以展开。在项目重构界面可以看到导入的内容

  • 把配置文件粘贴到src文件夹下

  • 在代码中获取日志对象

  • 调用方法打印日志

3.3 日志级别

1
TRACE, DEBUG, INFO, WARN, ERROR

还有两个特殊的:

​ ALL:输出所有日志

​ OFF:关闭所有日志

日志级别从小到大的关系:

​ TRACE < DEBUG < INFO < WARN < ERROR

3.4 配置文件

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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
</encoder>
</appender>

<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>C:/code/itheima-data.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>C:/code/itheima-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>

<!--

level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
</root>
</configuration>

4.类加载器

4.1类加载器

  • 作用

    负责将.class文件(存储的物理文件)加载在到内存中

    01_类加载器.png

4.2类加载的完整过程

  • 类加载时机

    简单理解:字节码文件什么时候会被加载到内存中?

    有以下的几种情况:

    • 创建类的实例(对象)
    • 调用类的类方法
    • 访问类或者接口的类变量,或者为该类变量赋值
    • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    • 初始化某个类的子类
    • 直接使用java.exe命令来运行某个主类

    总结而言:用到了就加载,不用不加载

  • 类加载过程

    1. 加载

      • 通过包名 + 类名,获取这个类,准备用流进行传输
      • 在这个类加载到内存中
      • 加载完毕创建一个class对象

      02_类加载过程加载.png

    2. 链接

      • 验证

        确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全

        (文件中的信息是否符合虚拟机规范有没有安全隐患)

      03_类加载过程验证.png

      • 准备

        负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值

        (初始化静态变量)

      04_类加载过程准备.png

      • 解析

        将类的二进制数据流中的符号引用替换为直接引用

        (本类中如果用到了其他类,此时就需要找到对应的类)

      05_类加载过程解析.png

    3. 初始化

      根据程序员通过程序制定的主观计划去初始化类变量和其他资源

      (静态变量赋值以及初始化其他资源)

      06_类加载过程初始化.png

  • 小结

    • 当一个类被使用的时候,才会加载到内存
    • 类加载的过程: 加载、验证、准备、解析、初始化

4.3类加载的分类【理解】

  • 分类

    • Bootstrap class loader:虚拟机的内置类加载器,通常表示为null ,并且没有父null
    • Platform class loader:平台类加载器,负责加载JDK中一些特殊的模块
    • System class loader:系统类加载器,负责加载用户类路径上所指定的类库
  • 类加载器的继承关系

    • System的父加载器为Platform
    • Platform的父加载器为Bootstrap
  • 代码演示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class ClassLoaderDemo1 {
    public static void main(String[] args) {
    //获取系统类加载器
    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

    //获取系统类加载器的父加载器 --- 平台类加载器
    ClassLoader classLoader1 = systemClassLoader.getParent();

    //获取平台类加载器的父加载器 --- 启动类加载器
    ClassLoader classLoader2 = classLoader1.getParent();

    System.out.println("系统类加载器" + systemClassLoader);
    System.out.println("平台类加载器" + classLoader1);
    System.out.println("启动类加载器" + classLoader2);

    }
    }

4.4双亲委派模型【理解】

  • 介绍

    如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

    07_双亲委派模型.png

4.5ClassLoader 中的两个方法【应用】

  • 方法介绍

    方法名 说明
    public static ClassLoader getSystemClassLoader() 获取系统类加载器
    public InputStream getResourceAsStream(String name) 加载某一个资源文件
  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class ClassLoaderDemo2 {
    public static void main(String[] args) throws IOException {
    //static ClassLoader getSystemClassLoader() 获取系统类加载器
    //InputStream getResourceAsStream(String name) 加载某一个资源文件

    //获取系统类加载器
    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

    //利用加载器去加载一个指定的文件
    //参数:文件的路径(放在src的根目录下,默认去那里加载)
    //返回值:字节流。
    InputStream is = systemClassLoader.getResourceAsStream("prop.properties");

    Properties prop = new Properties();
    prop.load(is);

    System.out.println(prop);

    is.close();
    }
    }

5.xml

5.1概述【理解】

  • 万维网联盟(W3C)

    万维网联盟(W3C)创建于1994年,又称W3C理事会。1994年10月在麻省理工学院计算机科学实验室成立。
    建立者: Tim Berners-Lee (蒂姆·伯纳斯·李)。
    是Web技术领域最具权威和影响力的国际中立性技术标准机构。
    到目前为止,W3C已发布了200多项影响深远的Web技术标准及实施指南,

    • 如广为业界采用的超文本标记语言HTML(标准通用标记语言下的一个应用)、

    • 可扩展标记语言XML(标准通用标记语言下的一个子集)

    • 以及帮助残障人士有效获得Web信息的无障碍指南(WCAG)等

      01_w3c概述.png

  • xml概述

    XML的全称为(EXtensible Markup Language),是一种可扩展的标记语言
    标记语言: 通过标签来描述数据的一门语言(标签有时我们也将其称之为元素)
    可扩展:标签的名字是可以自定义的,XML文件是由很多标签组成的,而标签名是可以自定义的

  • 作用

    • 用于进行存储数据和传输数据
    • 作为软件的配置文件
  • 作为配置文件的优势

    • 可读性好
    • 可维护性高

5.2标签的规则【应用】

  • 标签由一对尖括号和合法标识符组成

    1
    <student>
  • 标签必须成对出现

    1
    2
    <student> </student>
    前边的是开始标签,后边的是结束标签
  • 特殊的标签可以不成对,但是必须有结束标记

    1
    <address/>
  • 标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来

    1
    <student id="1"> </student>
  • 标签需要正确的嵌套

    1
    2
    这是正确的: <student id="1"> <name>张三</name> </student>
    这是错误的: <student id="1"><name>张三</student></name>

5.3语法规则【应用】

  • 语法规则

    • XML文件的后缀名为:xml

    • 文档声明必须是第一行第一列

      version:该属性是必须存在的
      encoding:该属性不是必须的

      ​ 打开当前xml文件的时候应该是使用什么字符编码表(一般取值都是UTF-8)

      standalone: 该属性不是必须的,描述XML文件是否依赖其他的xml文件,取值为yes/no

    • 必须存在一个根标签,有且只能有一个

    • XML文件中可以定义注释信息

    • XML文件中可以存在以下特殊字符

      1
      2
      3
      4
      5
      &lt; < 小于
      &gt; > 大于
      &amp; & 和号
      &apos; ' 单引号
      &quot; " 引号
    • XML文件中可以存在CDATA区

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="UTF-8" ?>
    <!--注释的内容-->
    <!--本xml文件用来描述多个学生信息-->
    <students>

    <!--第一个学生信息-->
    <student id="1">
    <name>张三</name>
    <age>23</age>
    <info>学生&lt; &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;的信息</info>
    <message> <![CDATA[内容 <<<<<< >>>>>> ]]]></message>
    </student>

    <!--第二个学生信息-->
    <student id="2">
    <name>李四</name>
    <age>24</age>
    </student>

    </students>

5.4xml解析【应用】

  • 概述

    xml解析就是从xml中获取到数据

  • 常见的解析思想

    DOM(Document Object Model)文档对象模型:就是把文档的各个组成部分看做成对应的对象。
    会把xml文件全部加载到内存,在内存中形成一个树形结构,再获取对应的值

    02_dom解析概述.png

  • 常见的解析工具

    • JAXP: SUN公司提供的一套XML的解析的API
    • JDOM: 开源组织提供了一套XML的解析的API-jdom
    • DOM4J: 开源组织提供了一套XML的解析的API-dom4j,全称:Dom For Java
    • pull: 主要应用在Android手机端解析XML
  • 解析的准备工作

    1. 我们可以通过网站:https://dom4j.github.io/ 去下载dom4j

      今天的资料中已经提供,我们不用再单独下载了,直接使用即可

    2. 将提供好的dom4j-1.6.1.zip解压,找到里面的dom4j-1.6.1.jar

    3. 在idea中当前模块下新建一个libs文件夹,将jar包复制到文件夹中

    4. 选中jar包 -> 右键 -> 选择add as library即可

  • 需求

    • 解析提供好的xml文件
    • 将解析到的数据封装到学生对象中
    • 并将学生对象存储到ArrayList集合中
    • 遍历集合
  • 代码实现

    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
    <?xml version="1.0" encoding="UTF-8" ?>
    <!--注释的内容-->
    <!--本xml文件用来描述多个学生信息-->
    <students>

    <!--第一个学生信息-->
    <student id="1">
    <name>张三</name>
    <age>23</age>
    </student>

    <!--第二个学生信息-->
    <student id="2">
    <name>李四</name>
    <age>24</age>
    </student>

    </students>

    // 上边是已经准备好的student.xml文件
    public class Student {
    private String id;
    private String name;
    private int age;

    public Student() {
    }

    public Student(String id, String name, int age) {
    this.id = id;
    this.name = name;
    this.age = age;
    }

    public String getId() {
    return id;
    }

    public void setId(String 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;
    }

    @Override
    public String toString() {
    return "Student{" +
    "id='" + id + '\'' +
    ", name='" + name + '\'' +
    ", age=" + age +
    '}';
    }
    }

    /**
    * 利用dom4j解析xml文件
    */
    public class XmlParse {
    public static void main(String[] args) throws DocumentException {
    //1.获取一个解析器对象
    SAXReader saxReader = new SAXReader();
    //2.利用解析器把xml文件加载到内存中,并返回一个文档对象
    Document document = saxReader.read(new File("myxml\\xml\\student.xml"));
    //3.获取到根标签
    Element rootElement = document.getRootElement();
    //4.通过根标签来获取student标签
    //elements():可以获取调用者所有的子标签.会把这些子标签放到一个集合中返回.
    //elements("标签名"):可以获取调用者所有的指定的子标签,会把这些子标签放到一个集合中并返回
    //List list = rootElement.elements();
    List<Element> studentElements = rootElement.elements("student");
    //System.out.println(list.size());

    //用来装学生对象
    ArrayList<Student> list = new ArrayList<>();

    //5.遍历集合,得到每一个student标签
    for (Element element : studentElements) {
    //element依次表示每一个student标签

    //获取id这个属性
    Attribute attribute = element.attribute("id");
    //获取id的属性值
    String id = attribute.getValue();

    //获取name标签
    //element("标签名"):获取调用者指定的子标签
    Element nameElement = element.element("name");
    //获取这个标签的标签体内容
    String name = nameElement.getText();

    //获取age标签
    Element ageElement = element.element("age");
    //获取age标签的标签体内容
    String age = ageElement.getText();

    // System.out.println(id);
    // System.out.println(name);
    // System.out.println(age);

    Student s = new Student(id,name,Integer.parseInt(age));
    list.add(s);
    }
    //遍历操作
    for (Student student : list) {
    System.out.println(student);
    }
    }
    }

5.5DTD约束【理解】

  • 什么是约束

    用来限定xml文件中可使用的标签以及属性

  • 约束的分类

    • DTD
    • schema
  • 编写DTD约束

    • 步骤

      1. 创建一个文件,这个文件的后缀名为.dtd

      2. 看xml文件中使用了哪些元素

        可以定义元素

      3. 判断元素是简单元素还是复杂元素

        简单元素:没有子元素。
        复杂元素:有子元素的元素;

    • 代码实现

      1
      2
      3
      4
      <!ELEMENT persons (person)>
      <!ELEMENT person (name,age)>
      <!ELEMENT name (#PCDATA)>
      <!ELEMENT age (#PCDATA)>
    1
      
  • 引入DTD约束

    • 引入DTD约束的三种方法

      • 引入本地dtd

      • 在xml文件内部引入

      • 引入网络dtd

    • 代码实现

      • 引入本地DTD约束

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        // 这是persondtd.dtd文件中的内容,已经提前写好
        <!ELEMENT persons (person)>
        <!ELEMENT person (name,age)>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT age (#PCDATA)>

        // 在person1.xml文件中引入persondtd.dtd约束
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE persons SYSTEM 'persondtd.dtd'>

        <persons>
        <person>
        <name>张三</name>
        <age>23</age>
        </person>

        </persons>
      • 在xml文件内部引入

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE persons [
        <!ELEMENT persons (person)>
        <!ELEMENT person (name,age)>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT age (#PCDATA)>
        ]>

        <persons>
        <person>
        <name>张三</name>
        <age>23</age>
        </person>

        </persons>
      • 引入网络dtd

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE persons PUBLIC "dtd文件的名称" "dtd文档的URL">

        <persons>
        <person>
        <name>张三</name>
        <age>23</age>
        </person>

        </persons>
  • DTD语法

    • 定义元素

      定义一个元素的格式为:
      简单元素:

      ​ EMPTY: 表示标签体为空

      ​ ANY: 表示标签体可以为空也可以不为空

      ​ PCDATA: 表示该元素的内容部分为字符串

      复杂元素:
      ​ 直接写子元素名称. 多个子元素可以使用”,”或者”|”隔开;
      ​ “,”表示定义子元素的顺序 ; “|”: 表示子元素只能出现任意一个
      ​ “?”零次或一次, “+”一次或多次, “*”零次或多次;如果不写则表示出现一次

      03_DTD语法定义元素.png

    • 定义属性

      格式

      定义一个属性的格式为:
      属性的类型:
      ​ CDATA类型:普通的字符串

      属性的约束:

      ​ // #REQUIRED: 必须的
      ​ // #IMPLIED: 属性不是必需的
      ​ // #FIXED value:属性值是固定的

    • 代码实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <!ELEMENT persons (person+)>
      <!ELEMENT person (name,age)>
      <!ELEMENT name (#PCDATA)>
      <!ELEMENT age (#PCDATA)>
      <!ATTLIST person id CDATA #REQUIRED>

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE persons SYSTEM 'persondtd.dtd'>

      <persons>
      <person id="001">
      <name>张三</name>
      <age>23</age>
      </person>

      <person id = "002">
      <name>张三</name>
      <age>23</age>
      </person>

      </persons>
      ​```

5.6schema约束【理解】

  • schema和dtd的区别

    1. schema约束文件也是一个xml文件,符合xml的语法,这个文件的后缀名.xsd
    2. 一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
    3. dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
    4. schema 语法更加的复杂

    04_schema约束介绍.png

  • 编写schema约束

    • 步骤

      1,创建一个文件,这个文件的后缀名为.xsd。
      2,定义文档声明
      3,schema文件的根标签为:
      4,在中定义属性:
      ​ xmlns=http://www.w3.org/2001/XMLSchema
      5,在中定义属性 :
      ​ targetNamespace =唯一的url地址,指定当前这个schema文件的名称空间。
      6,在中定义属性 :
      ​ elementFormDefault=”qualified“,表示当前schema文件是一个质量良好的文件。
      7,通过element定义元素
      8,判断当前元素是简单元素还是复杂元素

      05_schema约束编写.png

    • 代码实现

      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
      <?xml version="1.0" encoding="UTF-8" ?>
      <schema
      xmlns="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://www.itheima.cn/javase"
      elementFormDefault="qualified"
      >

      <!--定义persons复杂元素-->
      <element name="persons">
      <complexType>
      <sequence>
      <!--定义person复杂元素-->
      <element name = "person">
      <complexType>
      <sequence>
      <!--定义name和age简单元素-->
      <element name = "name" type = "string"></element>
      <element name = "age" type = "string"></element>
      </sequence>

      </complexType>
      </element>
      </sequence>
      </complexType>

      </element>

      </schema>

  • 引入schema约束

    • 步骤

      1,在根标签上定义属性xmlns=”http://www.w3.org/2001/XMLSchema-instance
      2,通过xmlns引入约束文件的名称空间
      3,给某一个xmlns属性添加一个标识,用于区分不同的名称空间
      ​ 格式为: xmlns:标识=“名称空间地址” ,标识可以是任意的,但是一般取值都是xsi
      4,通过xsi:schemaLocation指定名称空间所对应的约束文件路径
      ​ 格式为:xsi:schemaLocation = “名称空间url 文件路径“

    • 代码实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <?xml version="1.0" encoding="UTF-8" ?>

      <persons
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://www.itheima.cn/javase"
      xsi:schemaLocation="http://www.itheima.cn/javase person.xsd"
      >
      <person>
      <name>张三</name>
      <age>23</age>
      </person>

      </persons>
      ​```
  • schema约束定义属性

    • 代码示例

      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
      <?xml version="1.0" encoding="UTF-8" ?>
      <schema
      xmlns="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://www.itheima.cn/javase"
      elementFormDefault="qualified"
      >

      <!--定义persons复杂元素-->
      <element name="persons">
      <complexType>
      <sequence>
      <!--定义person复杂元素-->
      <element name = "person">
      <complexType>
      <sequence>
      <!--定义name和age简单元素-->
      <element name = "name" type = "string"></element>
      <element name = "age" type = "string"></element>
      </sequence>

      <!--定义属性,required( 必须的)/optional( 可选的)-->
      <attribute name="id" type="string" use="required"></attribute>
      </complexType>

      </element>
      </sequence>
      </complexType>
      </element>

      </schema>

      <?xml version="1.0" encoding="UTF-8" ?>
      <persons
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://www.itheima.cn/javase"
      xsi:schemaLocation="http://www.itheima.cn/javase person.xsd"
      >
      <person id="001">
      <name>张三</name>
      <age>23</age>
      </person>

      </persons>
      ​```

6. 单元测试Junit

6.1 什么是单元测试?(掌握)

对部分代码进行测试。

6.2 Junit的特点?(掌握)

  • 是一个第三方的工具。(把别人写的代码导入项目中)(专业叫法:导jar包)

  • 如果运行结果显示绿色,表示运行结果是正确的。

    如果运行结果显示红色,表示运行结果是错误的。

6.3 基本用法:(掌握)

1,一定要先写一个方法。

2,在这个方法的上面写@Test

3,鼠标点一下@Test 按alt + 回车,点击Junit4

​ 此时就可以自动导包。

​ 如果自动导包失败(连接外网,或者自己手动导包)

​ 如果导包成功在左下角就会出现Junit4的相关jar包

手动导包(掌握)

1,在当前模块下,右键新建一个文件夹(lib)

2,把今天资料里面的两个jar包,拷贝到lib文件夹里面

3,选中两个jar右键点击add as a lib….

4,到代码中,找到@Test,按alt + 回车,再来导入。

运行测试代码(掌握)

  • 只能直接运行无参无返回值的非静态方法
  • 想要运行谁,就右键点击哪个方法。如果想要运行一个类里面所有的测试方法,选择类名,有点点击即可。

Junit正确的打开方式(正确的使用方式)(掌握)

注意点:并不是直接在要测试的方法上面直接加@Test

原因:因为要测试的方法有可能是有参数的,有返回值,或者是静态的。

正确的使用方式:(掌握)

1,新建测试类

2,新建测试方法(要测试的方法名 + Test) methodTest

3,在这个方法中直接调用要测试的方法

4,在测试方法的上面写@Test

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//真正用来测试的类
//测试用例(测试类)
public class JunitTest {

//在这个类里面再写无参无返回值的非静态方法
//在方法中调用想要测试的方法

@Test
public void method2Test(){
//调用要测试的方法
JunitDemo1 jd = new JunitDemo1();
jd.method2(10);
}
}

实际开发中单元测试的使用方式(掌握)

需求:测试File中的delete方法,写的是否正确(掌握)

开发中的测试原则:

不污染原数据。

代码示例:

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
public class JunitDemo3 {
//在实际开发中,真正完整的单元测试该怎么写?
//前提:
//以后在工作的时候,测试代码不能污染原数据。(修改,篡改)
//1.利用Before去对数据做一个初始化的动作
//2.利用Test真正的去测试方法
//3.利用After去还原数据

//需求:测试File类中的delete方法是否书写正确???
@Before
public void beforemethod() throws IOException {
//先备份
File src = new File("C:\\Users\\moon\\Desktop\\a.txt");
File dest = new File("C:\\Users\\moon\\Desktop\\copy.txt");

FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest);
int b;
while((b = fis.read()) != -1){
fos.write(b);
}
fos.close();
fis.close();
}

//作为一个标准的测试人员,运行完单元测试之后,不能污染原数据
//需要达到下面两个要求:
//1.得到结果
//2.a.txt还在而且其他的备份文件消失
@Test
public void method(){
File file = new File("C:\\Users\\moon\\Desktop\\a.txt");
boolean delete = file.delete();

//检查a.txt是否存在
boolean exists = file.exists();

//只有同时满足了下面所有的断言,才表示delete方法编写正确
Assert.assertEquals("delete方法出错了",delete,true);
Assert.assertEquals("delete方法出错了",exists,false);
}


@After
public void aftermethod() throws IOException {
//还要对a.txt做一个还原
File src = new File("C:\\Users\\moon\\Desktop\\copy.txt");
File dest = new File("C:\\Users\\moon\\Desktop\\a.txt");

FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest);
int b;
while((b = fis.read()) != -1){
fos.write(b);
}
fos.close();
fis.close();

//备份数据要删除
src.delete();

}
}

作业:

​ 测试Properties类中的store方法是否书写正确?

开发心得:

1.Before 准备数据

2.Test 测试方法

3.After 还原

Before

1
2
3
4
准备数据
1.创建Properties的对象
2.put数据到集合当中
//只不过在下面的方法中,我们也需要用到Properties的对象,所以写完之后要挪到成员位置

Test

1
2
3
4
5
6
7
8
9
10
11
调用store方法,保存数据到本地文件

断言1
判断当前文件是否存在
断言2
文件的大小一定是大于0
断言3
再次读取文件中的数据,判断是否跟集合中一致

结论:
如果所有的断言都通过了,表示store方法是正确的

After

1
把本地文件给删除

扩展点:

在单元测试中,相对路径是相对当前模块而言的。

代码示例:

1
2
3
File file = new File("aweihaoshuai.txt");
file.createNewFile();
//此时是把aweihaoshuai.txt这个文件新建到模块中了。

7. 注解

7.1 注释和注解的区别?(掌握)

共同点:都可以对程序进行解释说明。

不同点:注释,是给程序员看的。只在Java中有效。在class文件中不存在注释的。

​ 当编译之后,会进行注释擦除。

​ 注解,是给虚拟机看的。当虚拟机看到注解之后,就知道要做什么事情了。

7.2 如何使用注解(掌握)

在以前看过注解@Override。

当子类重写父类方法的时候,在重写的方法上面写@Override。

当虚拟机看到@Override的时候,就知道下面的方法是重写的父类的。检查语法,如果语法正确编译正常,如果语法错误,就会报错。

7.3 Java中已经存在的注解(掌握)

@Override:表示方法的重写

@Deprecated:表示修饰的方法已过时

@SuppressWarnings(“all”):压制警告

除此之外,还需要掌握第三方框架中提供的注解:

比如:Junit

@Test 表示运行测试方法

@Before 表示在Test之前运行,进行数据的初始化

@After 表示在Test之后运行,进行数据的还原

7.4 自定义注解(了解)

自定义注解单独存在是没有什么意义的,一般会跟反射结合起来使用,会用发射去解析注解。

针对于注解,只要掌握会使用别人已经写好的注解即可。

关于注解的解析,一般是在框架的底层已经写好了。

7.5 特殊属性(掌握)

value:

​ 当注解中只有”一个属性”,并且属性名是”value”,使用注解时,可以省略value属性名

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//注解的定义
public @interface Anno2 {
public String value();

public int age() default 23;
}

//注解的使用
@Anno2("123")
public class AnnoDemo2 {

@Anno2("123")
public void method(){

}
}

7.6 元注解(了解)

可以写在注解上面的注解

@Target :指定注解能在哪里使用

@Retention :可以理解为保留时间(生命周期)

Target:

​ 作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。

​ 可使用的值定义在ElementType枚举类中,常用值如下

  • TYPE,类,接口
  • FIELD, 成员变量
  • METHOD, 成员方法
  • PARAMETER, 方法参数
  • CONSTRUCTOR, 构造方法
  • LOCAL_VARIABLE, 局部变量

Retention:

​ 作用:用来标识注解的生命周期(有效范围)

​ 可使用的值定义在RetentionPolicy枚举类中,常用值如下

  • SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
  • CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
  • RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段

注解的解析:

7.7 模拟JUnit自带的@Test注解(了解)

代码示例:

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
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}

public class MyTestMethod {

@MyTest
public void method1(){
System.out.println("method1");
}

public void method2(){
System.out.println("method2");
}

@MyTest
public void method3(){
System.out.println("method3");
}
}

public class MyTestDemo {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
//1,获取class对象
Class clazz = Class.forName("com.itheima.test2.MyTestMethod");

//获取对象
Object o = clazz.newInstance();

//2.获取所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//method依次表示类里面的每一个方法
method.setAccessible(true);
//判断当前方法有没有MyTest注解
if(method.isAnnotationPresent(MyTest.class)){
method.invoke(o);
}
}
}
}

7.8 注解小结:

掌握如何使用已经存在的注解即可。

@Override:表示方法的重写

@Deprecated:表示修饰的方法已过时

@SuppressWarnings(“all”):压制警告

@Test:表示要运行的方法

在以后的实际开发中,注解是使用框架已经提供好的注解。

自定义注解+解析注解(很难的,了解),一般会出现在框架的底层。当以后我们要自己写一个框架的时候,才会用到自定义注解+解析注解。