0%

单元测试

JUnit 是一个 Java 编程语言的单元测试工具。JUnit 是一个非常重要的测试工具

JUnit 在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色

使用步骤

  1. 将 junit 的 jar 包导入到工程中 junit-4.9.jar
  2. 编写测试方法该测试方法必须是公共的无参数无返回值的非静态方法
  3. 在测试方法上使用 @Test 注解标注该方法是一个测试方法
  4. 选中测试方法右键通过 junit 运行该方法

代码示例

1
2
3
4
5
6
7
8
9
10
public class JunitDemo1 {
@Test
public void add() {
System.out.println(2 / 0);
int a = 10;
int b = 20;
int sum = a + b;
System.out.println(sum);
}
}

相关注解

  • @Test:表示测试该方法
  • @Before:在测试的方法前运行
  • @After:在测试的方法后运行

日志

程序中的日志可以用来记录程序在运行的时候点点滴滴,并可以进行永久存储

日志与输出语句的区别

输出语句 日志技术
取消日志 需要修改代码,灵活性比较差 不需要修改代码,灵活性比较好
输出位置 只能是控制台 可以将日志信息写入到文件或者数据库中
多线程 和业务代码处于一个线程中 多线程方式记录日志,不影响业务代码的性能

日志体系结构

01-日志的体系结构.png

Log4J

Log4j 是 Apache 的一个开源项目;通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件等位置,我们也可以控制每一条日志的输出格式,通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程

最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

入门案例

  1. 导入 log4j 的相关 jar 包
  2. 编写 log4j 配置文件
  3. 在代码中获取日志的对象
  4. 按照级别设置记录日志信息

1、properties 文件

注意:配置文件的文件名必须是 log4j.properties,放在 src 目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// log4j的配置文件,名字为log4j.properties, 放在src根目录下
log4j.rootLogger=debug,my,fileAppender

### direct log messages to my ###
log4j.appender.my=org.apache.log4j.ConsoleAppender
log4j.appender.my.ImmediateFlush = true
log4j.appender.my.Target=System.out
log4j.appender.my.layout=org.apache.log4j.PatternLayout
log4j.appender.my.layout.ConversionPattern=%d %t %5p %c{1}:%L - %m%n

# fileAppender演示
log4j.appender.fileAppender=org.apache.log4j.FileAppender
log4j.appender.fileAppender.ImmediateFlush = true
log4j.appender.fileAppender.Append=true
log4j.appender.fileAppender.File=D:/log4j-log.log
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=%d %5p %c{1}:%L - %m%n

2、测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Log4JTest01 {
//方式1:使用log4j的api来获取日志的对象
//弊端:如果以后我们更换日志的实现类,那么下面的代码就需要跟着改
//不推荐使用
//private static final Logger LOGGER = Logger.getLogger(Log4JTest01.class);

//方式2:使用slf4j里面的api来获取日志的对象
//好处:如果以后我们更换日志的实现类,那么下面的代码不需要跟着修改
//推荐使用(在代码中获取日志的对象)
private static final Logger LOGGER = LoggerFactory.getLogger(Log4JTest01.class);

public static void main(String[] args) {
//1.导入jar包
//2.编写配置文件
//3.在代码中获取日志的对象
//4.按照日志级别设置日志信息
LOGGER.debug("debug级别的日志");
LOGGER.info("info级别的日志");
LOGGER.warn("warn级别的日志");
LOGGER.error("error级别的日志");
}
}

配置文件详解

三个核心

  • Loggers (记录器) :日志的级别
    • Loggers 组件在此系统中常见的五个级别:DEBUG、INFO、WARN、ERROR 和 FATAL
    • DEBUG < INFO < WARN < ERROR < FATAL
    • Log4j 有一个规则:只输出级别不低于设定级别的日志信息
  • Appenders (输出源):日志要输出的地方
    • 把日志输出到不同的地方,如控制台(Console)、文件(Files)等
    • org.apache.log4j.ConsoleAppender(控制台)
    • org.apache.log4j.FileAppender(文件)
  • Layouts (布局):日志输出的格式
    • org.apache.log4j.PatternLayout(可以灵活地指定布局模式,这个比较常用)
    • org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
    • org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// log4j的配置文件,名字为log4j.properties, 放在src根目录下
log4j.rootLogger=debug,my,fileAppender

### direct log messages to my ###
log4j.appender.my=org.apache.log4j.ConsoleAppender
log4j.appender.my.ImmediateFlush = true
log4j.appender.my.Target=System.out
log4j.appender.my.layout=org.apache.log4j.PatternLayout
log4j.appender.my.layout.ConversionPattern=%d %t %5p %c{1}:%L - %m%n

# fileAppender演示
log4j.appender.fileAppender=org.apache.log4j.FileAppender
log4j.appender.fileAppender.ImmediateFlush = true
log4j.appender.fileAppender.Append=true
log4j.appender.fileAppender.File=D:/log4j-log.log
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=%d %5p %c{1}:%L - %m%n

1、配置根 Logger

1
2
3
4
log4j.rootLogger=日志级别,appenderName1,appenderName2,…

# 日志级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定义的级别
# appenderName1:就是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开

2、ConsoleAppender 常用的选项

1
2
3
log4j.appender.my=org.apache.log4j.ConsoleAppender
log4j.appender.my.ImmediateFlush = true
log4j.appender.my.Target=System.out
  • 第一句表示往控制台输出
  • 第二句表示所有消息都会被立即输出,设为 false 则不输出,默认值是 true
  • 第三句默认值是 System.out
    • 比如 System.err 打印出来是红色的

3、FileAppender 常用的选项

1
2
3
4
log4j.appender.fileAppender=org.apache.log4j.FileAppender
log4j.appender.fileAppender.ImmediateFlush = true
log4j.appender.fileAppender.Append=true
log4j.appender.fileAppender.File=D:/log4j-log.log
  • 第一句:往文件中输出
  • 第二句:表示所有消息都会被立即输出。设为 false 则不输出,默认值是 true
  • 第三句:true 表示将消息添加到指定文件中,原来的消息不覆盖,默认值是 true
  • 第四句:指定消息输出到某个文件中

4、PatternLayout 常用的选项

  • org.apache.log4j.PatternLayout(可以灵活地指定布局模式,最常用)
  • org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
  • org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)

这里我们只讲第一种:PatternLayout 的常用选项

02-日志中PatternLayout的常用选项.png

在项目中的应用

  1. 导入相关的依赖
  2. 将资料中的 properties 配置文件复制到 src 目录下
  3. 在代码中获取日志的对象
  4. 按照级别设置记录日志信息

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@WebServlet(urlPatterns = "/servlet/loginservlet")
public class LoginServlet implements HttpServlet{

//获取日志的对象
private static final Logger LOGGER = LoggerFactory.getLogger(LoginServlet.class);

@Override
public void service(HttpRequest httpRequest, HttpResponse httpResponse) {
//处理
System.out.println("LoginServlet处理了登录请求");

LOGGER.info("现在已经处理了登录请求,准备给浏览器响应");

//响应
httpResponse.setContentTpye("text/html;charset=UTF-8");
httpResponse.write("登录成功");
}
}

注意:Logger 对象是 slf4j (org.slf4jh) 中的,不要导错包了

反射的概述

反射机制

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

01-用子类创建对象与常规创建对象的比较.png

总结下来

  • 利用反射可以无视修饰符获取类里面所有的属性和方法
  • 先获取配置文件中的信息,动态获取(如从配置文件中的读取)信息并创建对象和调用方法

获取 Class 对象

调用一个类中的方法(如上图所示)

  1. 创建这个类的对象
  2. 用对象调用方法

反射去调用一个类中的方法(如上图所示)

  1. 反射方式:创建对象
  2. 反射方式:调用方法
    02-利用反射调用类中的方法.png

那么现在的问题就是如何获取这个 Class 对象
03-获取Class对象的三种方式.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
30
31
32
33
34
35
36
37
38
39
40
public class Student {
private String name;
private int age;

public Student() {
}

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

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;
}

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

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

三种获取 Class 对象方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ReflectDemo1 {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class类中的静态方法forName("全类名")
//全类名:包名 + 类名
Class clazz = Class.forName("com.itheima.myreflect2.Student");
System.out.println(clazz);

//2.通过class属性来获取
Class clazz2 = Student.class;
System.out.println(clazz2);

//3.利用对象的getClass方法来获取class对象
//getClass方法是定义在Object类中.
Student s = new Student();
Class clazz3 = s.getClass();
System.out.println(clazz3);

//class对象是唯一的
System.out.println(clazz == clazz2);
System.out.println(clazz2 == clazz3);
}
}

反射获取 Class 类的对象

Class 类的对象包括:成员变量对象、构造器对象及成员方法对象

04-Class对象中的3类对象.png

数据准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Student {
private String name;
private int age;

//私有的有参构造方法
private Student(String name) {
System.out.println("name的值为:" + name);
System.out.println("private...Student...有参构造方法");
}

//公共的无参构造方法
public Student() {
System.out.println("public...Student...无参构造方法");
}

//公共的有参构造方法
public Student(String name, int age) {
System.out.println("name的值为:" + name + "age的值为:" + age);
System.out.println("public...Student...有参构造方法");
}
}

获取 Constructor 对象

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
public class ReflectDemo1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//method1();
//method2();
//method3();
//method4();
}

private static void method4() throws ClassNotFoundException, NoSuchMethodException {
//返回单个构造方法对象
//1.获取Class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//获取单个构造方法对象
Constructor constructor = clazz.getDeclaredConstructor(String.class);
System.out.println(constructor);
}

private static void method3() throws ClassNotFoundException, NoSuchMethodException {
//返回单个公共构造方法对象
//1.获取Class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//小括号中,一定要跟构造方法的形参保持一致
//获取单个公共构造方法对象
Constructor constructor1 = clazz.getConstructor();
System.out.println(constructor1);
//这里获取有参构造方法
Constructor constructor2 = clazz.getConstructor(String.class, int.class);
System.out.println(constructor2);

//因为Student类中,没有只有一个int的构造,所以这里会报错.
Constructor constructor3 = clazz.getConstructor(int.class);
System.out.println(constructor3);
}

private static void method2() throws ClassNotFoundException {
//返回所有构造方法对象的数组
//1.获取Class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//2.获取所有构造方法对象的数组
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}

private static void method1() throws ClassNotFoundException {
//返回所有公共构造方法对象的数组
//1.获取Class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//2.获取所有公共构造方法对象的数组
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}
}

Constructor 创建对象

那么我们获取到的 Class 对象中的构造方法,不就是为了使用它来创建对象嘛,方法如下:

  • T newInstance(Object…initargs):根据指定的构造方法创建对象(T 表示返回值类型,此处为创建对来的对象)
  • setAccessible(boolean flag):设置为 true,表示取消访问检查(通过获取私有的构造方法来创建对象,如果用反射强行获取并使用,需要临时取消访问检查)

也就是说,如果构造方法是 public 的,直接使用 newInstance 即可创建对象;如果构造方法是 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
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
// Student类同上一个示例,这里就不在重复提供了
public class ReflectDemo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//T newInstance(Object... initargs):根据指定的构造方法创建对象
//method1();
//method2();
//method3();
//method4();

}
private static void method1() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");

//2.获取构造方法对象
Constructor constructor = clazz.getConstructor(String.class, int.class);

//3.利用newInstance创建Student的对象
Student student = (Student) constructor.newInstance("zhangsan", 23);

System.out.println(student);
}

private static void method2() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");

//2.获取构造方法对象
Constructor constructor = clazz.getConstructor();

//3.利用空参来创建Student的对象
Student student = (Student) constructor.newInstance();

System.out.println(student);
}

private static void method3() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//method2的简写格式
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");

//2.在Class类中,有一个newInstance方法,可以利用空参直接创建一个对象
Student student = (Student) clazz.newInstance();//这个方法现在已经过时了,了解一下

System.out.println(student);
}

private static void method4() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//获取一个私有的构造方法并创建对象
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");

//2.获取一个私有化的构造方法.
Constructor constructor = clazz.getDeclaredConstructor(String.class);

//被private修饰的成员,不能直接使用的
//如果用反射强行获取并使用,需要临时取消访问检查
constructor.setAccessible(true);

//3.直接创建对象
Student student = (Student) constructor.newInstance("zhangsan");

System.out.println(student);
}

}

反射获取成员变量

  1. 获取 Class 对象
  2. 获取 Field 对象
  3. 赋值或者取值

数据准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Student {  

public String name;
public int age;
public String gender;
private int money = 300;

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

获取成员变量

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
public class ReflectDemo1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
// method1();
//method2();
//method3();
//method4();
}

private static void method1() throws ClassNotFoundException {
//返回所有公共成员变量对象的数组

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

//2.获取Field对象.
Field[] fields = clazz.getFields();

//3.遍历
for (Field field : fields) {
System.out.println(field);
}
}

private static void method2() throws ClassNotFoundException {
//返回所有成员变量对象的数组
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect4.Student");

//2.获取所有的Field对象
Field[] fields = clazz.getDeclaredFields();

//3.遍历
for (Field field : fields) {
System.out.println(field);
}
}

private static void method3() throws ClassNotFoundException, NoSuchFieldException {
//返回单个公共成员变量对象
//想要获取的成员变量必须是真实存在的
//且必须是public修饰的.
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect4.Student");

//2.获取name这个成员变量
//Field field = clazz.getField("name");
//Field field = clazz.getField("name1");
Field field = clazz.getField("money");

//3.打印一下
System.out.println(field);
}

private static void method4() throws ClassNotFoundException, NoSuchFieldException {
//返回单个成员变量对象
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect4.Student");

//2.获取money成员变量
Field field = clazz.getDeclaredField("money");

//3.打印一下
System.out.println(field);
}
}

Field 对象取值或赋值

既然获取到 Field 对象了,那么就赋值或取值吧

赋值:void set(Object obj, Object value),给指定对象的成员变量赋值
取值:Object get(Object obj),返回指定对象的 Field 值

为什么中间需要有一个 Object 类呢?比如遇到如下情形,set 方法怎么知道需要将值赋给谁呢?
05-为成员变量赋值.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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class ReflectDemo2 {  
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
//返回由该 Field表示的字段在指定对象上的值。
//method1();
//method2(); }

private static void method1() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
//给obj对象的成员变量赋值为value
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect4.Student");

//2.获取name这个Field对象
Field field = clazz.getField("name");

//3.利用set方法进行赋值.
//3.1先创建一个Student对象
Student student = (Student) clazz.newInstance();
//3.2有了对象才可以给指定对象进行赋值
field.set(student,"zhangsan");

System.out.println(student);
}

private static void method2() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect4.Student");

//2.获取成员变量Field的对象
Field field = clazz.getDeclaredField("money");

//3.取消一下访问检查
field.setAccessible(true);

//4.调用get方法来获取值
//4.1创建一个对象
Student student = (Student) clazz.newInstance();
//4.2获取指定对象的money的值
Object o = field.get(student);

//5.打印一下
System.out.println(o);
}
}

反射获取成员方法

  1. 获取 Class 对象
  2. 获取 Method 对象
  3. 运行方法
  • Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
  • Method[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
  • 返回单个公共成员方法对象
  • 返回单个成员方法对象

数据准备

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 Student {  

//私有的,无参无返回值
private void show() {
System.out.println("私有的show方法,无参无返回值");
}

//公共的,无参无返回值
public void function1() {
System.out.println("function1方法,无参无返回值");
}

//公共的,有参无返回值
public void function2(String name) {
System.out.println("function2方法,有参无返回值,参数为" + name);
}

//公共的,无参有返回值
public String function3() {
System.out.println("function3方法,无参有返回值");
return "aaa";
}

//公共的,有参有返回值
public String function4(String name) {
System.out.println("function4方法,有参有返回值,参数为" + name);
return "aaa";
}
}

获取成员方法

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
public class ReflectDemo1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//method1();
//method2();
//method3();
//method4();
//method5();
}

private static void method1() throws ClassNotFoundException {
//返回所有公共成员方法对象的数组,包括继承的
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect5.Student");
//2.获取成员方法对象
Method[] methods = clazz.getMethods();
//3.遍历
for (Method method : methods) {
System.out.println(method);
}
}

private static void method2() throws ClassNotFoundException {

//返回所有成员方法对象的数组,不包括继承的
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect5.Student");

//2.获取Method对象
Method[] methods = clazz.getDeclaredMethods();
//3.遍历一下数组
for (Method method : methods) {
System.out.println(method);
}
}

private static void method3() throws ClassNotFoundException, NoSuchMethodException {
//返回单个公共成员方法对象
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect5.Student");
//2.获取成员方法function1
Method method1 = clazz.getMethod("function1");
//3.打印一下
System.out.println(method1);
}

private static void method4() throws ClassNotFoundException, NoSuchMethodException {
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect5.Student");
//2.获取一个有形参的方法function2
Method method = clazz.getMethod("function2", String.class);
//3.打印一下
System.out.println(method);
}

private static void method5() throws ClassNotFoundException, NoSuchMethodException {
//返回单个成员方法对象
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect5.Student");
//2.获取一个成员方法show
Method method = clazz.getDeclaredMethod("show");
//3.打印一下
System.out.println(method);
}
}

运行成员方法

  • Object invoke(Object obj, Object… args):运行方法
    • 参数 1:用 obj 对象调用该方法
    • 参数 2:调用方法的传递的参数(如果没有就不写)
    • 返回值:方法的返回值(如果没有就不写)

比如说,我们现在想通过反射来获取 Class 中的 function4 方法,并运行它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ReflectDemo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//Object invoke(Object obj, Object... args):运行方法
//参数一:用obj对象调用该方法
//参数二:调用方法的传递的参数(如果没有就不写)
//返回值:方法的返回值(如果没有就不写)

//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect5.Student");
//2.获取里面的Method对象 function4
Method method = clazz.getMethod("function4", String.class);
//3.运行function4方法就可以了
//3.1创建一个Student对象,当做方法的调用者
Student student = (Student) clazz.newInstance();
//3.2运行方法
Object result = method.invoke(student, "zhangsan");
//4.打印一下返回值
System.out.println(result);
}
}

coffeelize

这两天收到阿里云域名续费消息,同时也收到了网站 SSL 证书到期的邮件,是的,域名买了一年,到今天刚好一年。

博客在中途更换过一次域名,也就是现在的域名:coffeelize.top。之所以叫 coffeelize,不仅仅是因为 #咖啡日常 #,更多的其实是想强调 lizelize 在英语单词中通常为动词的后缀,很多情况下 名词 + lize 后就变成了动词,一是想表达:就像喝了一杯咖啡一样,满满的驱动力;二是期望:自己能够像 “coffeee” 那样,能够带给周围人更多的积极向上的 “动力”。

01-博客运行时间.png

02-域名续费消息.png

03-证书到期.png

去年的这个时候,还在复习着数值分析的期末考试呢😂,搭建这个博客花了我很长时间,记得那个 SSL 总是无法正常连接,导致无法往仓库中推送文件,也算是苦中作乐吧,哈哈

有了自己的个人博客之后,开始关注更多相关领域的个人博客,学着有模有样的发布笔记、装扮博客、交换友链,开始更加注重消息的来源和质量。自己也喜欢看别人的博客和笔记,有种窥探别人日记本闯入他人领地的感觉,遇到和自己 “技术栈” 差不多的站长,会主动联系,加个好友、交换一下友链或者相互鼓励一下,这种感觉很棒,可能也是我不断更新下去的主要原因吧。

博客在这一年里也有一点点的收获,下图为必应搜索引擎的数据报告(因为之前更换了一次域名的原因,这里只显示从 6 月份开始到现在的数据)

05-必应控制台.png

其实我也不在乎这个数据报告,更在乎的是我的🤝朋友们及笔记呀
06-朋友们以及笔记.png

什么是 Git

Git 是一个分布式版本控制工具,主要用于管理开发过程中的源代码文件(Java 类、xml 文件、html 页面等),在软件开发过程中被广泛使用

Git 能做什么

  • 代码回溯:Git 在管理文件过程中会记录日志,方便回退到历史版本
  • 版本切换:Git 存在分支的概念,一个项目可以有多个分支(版本),可以任意切换
  • 多人协作:Git 支持多人协作,即一个团队共同开发一个项目,每个团队成员负责一部分代码,通过 Git 就可以管理和协调
  • 远程备份:Git 通过仓库管理文件,在 Git 中存在远程仓库,如果本地文件丢失还可以从远程仓库获取

我们可以借助互联网上提供的一些代码托管服务来实现,其中比较常用的有 GitHub、码云、GitLab 等,这里以码云为例进行讲解

Git 全局设置

当安装 Git 后首先要做的事情是设置用户名称和 email 地址。这是非常重要的,因为每次 Git 提交都会使用该用户信息。在 Git 命令行中执行下面命令:

  • 设置用户信息
1
2
git config --global user.name "coffeelize"
git config --global user.email "coffeelize@qq.com"

注意:上面设置的 user.name 和 user.email 并不是我们在注册码云账号时使用的用户名和邮箱,此处可以任意设置

  • 查看配置信息
1
git config --list

01-配置用户名和邮箱.png

获取 Git 仓库

要使用 Git 对我们的代码进行管理,首先需要获得 Git 仓库,获取 Git 仓库通常有两种方式

  • 在本地初始化 Git 仓库(不常用)
  • 从远程仓库克隆(常用)

在本地初始化 Git 仓库

  1. 在任意目录下创建一个空目录(例如 repo1)作为我们的本地 Git 仓库
  2. 进入这个目录中,点击右键打开 Git bash 窗口
  3. 执行命令 git init

初始化之后,目录中会多一个 .git 隐藏文件夹,命令行后会多出一个 master 分支
02-master主分支.png

注意:本地仓库不是从远程仓库克隆下来的,且本地仓库中若已经存放了一些文件,此时再从远程仓库拉去文件的时候可能会报如下所示的错误:fatal: refusing to merge unrelated histories(原因是本地仓库中含有本地仓库中文件的历史记录,远程仓库中包含远程仓库中文件的记录信息,这两者间完全没有联系,此时就需要将两者建立起联系)

016-本地仓库首次连接远程仓库报错.png

此时,可以通过如下命令解决

1
git pull origin master --allow-unrelated-histories

从远程仓库克隆

可以通过 Git 提供的命令从远程仓库进行克隆,将远程仓库克隆到本地

1
git clone 远程仓库地址

注:第一次克隆远程仓库时,可能需要登录 Git Credential Manage,即登录 Gitee 的账号,登录之后即可正常操作。同时登录之后,也会将一些验证信息保存到电脑当中,之后再次使用就不用再手动登录了

03-登录Gitee验证.png

工作区、暂存区、版本库

  • 版本库:前面看到的 .git 隐藏文件夹 就是版本库,版本库中存储了很多配置信息、日志信息和文件版本信息等
  • 工作区:包含.git 文件夹的目录就是工作区,也称为工作目录,主要用于存放开发的代码
  • 暂存区:.git 文件夹中有很多文件,其中有一个 index 文件 就是暂存区。暂存区是一个临时保存修改文件的地方

Git 工作区中文件的状态

Git 工作区中的文件存在两种状态:

  • untracked:未跟踪(未被纳入版本控制)
  • tracked:已跟踪(被纳入版本控制)
    • Unmodified 未修改状态
    • Modified 已修改状态
    • Staged 已暂存状态

本地仓库常用操作

  • git status:查看文件状态
  • git add:将文件的修改加入暂存区
  • git reset:将暂存区的文件取消暂存或者是切换到指定版本
  • git commit:将暂存区的文件修改提交到版本库
  • git log:查看日志

git status

可以通过 git status 来查看当前状态
04-status查看状态.png

git add

1
git add fileName

git reset

每次 Git 提交都会产生新的版本号,通过版本号就可以回到历史版本

1
2
git reset --hard 版本号
git reset --hard 76b00c3fd44c92359d70e02cb4ff35c3acf90b40

git commit

1
2
git commit -m msg 文件名
git commit -m "提交一个文件" User.java

红色字体:未跟踪,也就是还没有纳入 git 的版本管理
绿色字体:文件已经放到了缓存区

git log

git log 命令的作用是查看提交日志;通过 git log 命令查看日志,可以发现每次提交都会产生一个版本号,提交时设置的 message、提交人、邮箱、提交时间等信息都会记录到日志中

05-log查看日志.png

远程仓库常用命令

  • git remote:查看远程仓库
  • git remote add:添加远程仓库
  • git clone:从远程仓库克隆
  • git pull:从远程仓库拉取
  • git push:推送到远程仓库

git remote

origin 表示远程仓库的简称

1
2
git remote
git remote -v

06-查看远程仓库简称.png

说明当前本地的仓库已经和远程的仓库之间建立好了连接了。如果输入以上两个命令没有返回值的话,说明这仅仅只是一个本地仓库

git remote add

1
git remote add 简称 远程仓库地址

注意

  • 一个本地仓库可以关联多个远程仓库
  • 这个简称我们习惯命名为 origin

比如我这边初始化了一个本地仓库,然后还有一个远程仓库,想要将这个本地仓库和远程仓库联系起来

1
git remote add origin https://gitee.com/coffeelize/repo1.git

此时再输入命令 git remote -v,即可查看是否已经关联成功

git clone

Git 克隆的是该 Git 仓库服务器上的几乎所有数据(包括日志信息、历史记录等)

1
git clone 远程仓库地址

git push

将本地仓库内容推送到远程仓库

1
2
git push 远程仓库简称 分支名称

先需要将文件提交到本地仓库 (add & commit),再推送到远程仓库

切换远程仓库

如果当前本地仓库需要链接到另一个远程仓库呢,怎么处理?
比如说本地仓库当前绑定的是 repo1 仓库,想要将本地仓库绑定到另外一个远程仓库 hellogit。当前如果已经连接到了一个远程仓库,是无法直接通过添加远程仓库 URL 来覆盖掉原来的 URL,如下图所示:

017-无法直接覆盖远程连接的URL.png

方式一:直接修改远程仓库地址,更换远程仓库地址

1
2
3
git remote set-url origin URL
git remote set-url orgin https://gitee.com/coffeelize/hellogit.git

方式二:先先删除当前连接的远程仓库地址,然后在添加

1
2
git remote rm origin
git remote add origin url

分支操作

分支是 Git 使用过程中非常重要的概念。使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。

本地仓库和远程仓库中都有分支,同一个仓库可以有多个分支,各个分支相互独立,互不干扰。通过 git init 命令创建本地仓库时默认会创建一个 master 分支。

查看分支

  • git branch:列出所有本地分支
  • git branch -r:列出所有远程分支
  • git branch -a:列出所有本地分支和远程分支

创建分支

1
2
git branch 分支名称
git branch test

07-创建分支.png

切换分支

1
2
git checkout 分支名称
git checkout test

08-切换分支.png

推送至远程仓库分支

1
2
3
git push 远程仓库简称 分支名称
//将test分支推送到远程仓库
git push origin test

合并分支

1
2
git merge 分支名称
git merge test

分支合并时需注意合并的方向,是将命令中的分支合并到当前所在的分支

09-合并分支.png

在合并分支这种大操作下,会进入 vim 模式要求我们写日志,按照 vim 的操作即可

如果在合并当中遇到文件冲突,比如说主分支对 A 文件进行了修改,测试分支也对 A 文件进行了修改,然后在主分支中合并分支时,报如下错误:Automatic merge failed; fix conflicts and then commit the result.

10-合并分支报错.png

此时冲突的文件会自动加入如下内容。
011-合并后的冲突文件.png

假如我们是想要保留这两行,那么可以把这些自动生成的符号删除,然后还需要将这个有冲突的文件再 add 和 commit 一下,此时,仍然会有报错:
fatal: cannot do a partial commit during a merge.(不能在合并的时候只提交一部分)
012-合并解决冲突文件后再次报错.png

这个时候我们需要在 commit 后面添加一个 -i 参数,此时这个冲突被我们手动解决了,然后就可以正常 push 了

1
2
git commit -m "modify by me" testBranch.txt -i
git push origin master

标签操作

Git 中的标签,指的是某个分支某个特定时间点的状态。通过标签,可以很方便的切换到标记时的状态。比较有代表性的是人们会使用这个功能来标记发布结点(v1.0 、v1.2 等)。

查看标签

1
git tag

创建标签

1
2
git tag 标签名
git tag v0.1

删除本地标签

1
2
git tag -d 标签名
git tag -d v0.1

删除远程标签

1
2
git push origin :refs/tags/标签名
git push origin :refs/tags/v0.1

013-删除远程标签.png

将标签推送至远程仓库

1
2
git push 远程仓库简称 标签名
git push origin v0.1

014-将标签推送至远程仓库.png

检出标签

检出标签时需要新建一个分支来指向某个标签。会自动将某个 tag 中的内容检出到一个新的分支下面

1
2
git checkout -b 新建的一个分支名 标签名
git checkout -b aNewBranch v0.2

015-检出标签.png

Maven 主要功能

Maven 是专门用于管理和构建 Java 项目的工具,它的主要功能有:

  • 提供了一套标准化的项目结构
  • 提供了一套标准化的构建流程(编译,测试,打包,发布…)
  • 提供了一套依赖管理机制

标准化的项目结构

不同 IDE 之间,项目结构不一样,不通用
01-不同IDE之间项目结构不一致.png

所有 IDE 使用 Maven 构建的项目结构完全一样,所有 IDE 创建的 Maven 项目都可以通用

Maven 的项目结构
02-Maven项目结构.png

标准化的构建流程

源代码⇒编译⇒测试⇒打包⇒发布,Maven 提供了一套简单的命令来完成项目构建

提供了一套依赖管理机制

比如 JDBC,需要使用的 MySQL 的驱动包,依赖管理其实就是管理你项目所依赖的第三方资源(Jar 包、依赖),原先我们是如何操作的呢:

  1. 下载 jar 包
  2. 将 jar 包复制到 lib 文件夹里
  3. 右键 jar 包,作为库

那 Maven 是如何管理依赖的呢

  1. Maven 使用标准的 坐标 配置来管理各种依赖
  2. 只需要简单的配置就可以完成依赖管理
    03-通过坐标管理依赖.png

Maven 简介

Apache Maven 是一个项目管理和构建工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建、报告和文档

仓库分类

  • 本地仓库
  • 中央仓库:有 Maven 团队维护的全球唯一的仓库(免费的开源的 jar 包)
  • 远程仓库(私服):一般有公司团队搭建的私有仓库(可以存放一下自己的公司的和一些可能具有版权的 jar 包)

查找流程:首先会查找本地仓库,如果本地仓库没有,则去中央仓库查找是否有,有的话就会 自动下载 到本地仓库

Maven 的安装和配置

  1. 解压即可安装
  2. 配置环境变量

下图中的 Maven 文件夹呢就是包含 bin 文件夹的文件夹
04-配置Maven环境路径1.png

将 bin 目录添加到 Path 目录中
05-配置Maven环境路径2.png

  1. 配置本地仓库:修改 conf/setting.xml 中的 <localRepository> 为其指定一个目录

06-指定本地仓库路径.png

注意:为了保守起见,在 Intellij 中也对 Maven 的本地路径配置一下:)
12-指定本地仓库路径2.png

  1. 配置阿里云私服:修改 conf/setting.xml 中的 <mirrors> 标签,为其添加如下子标签
1
2
3
4
5
6
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>

07-配置阿里云远程仓库.png

Maven 的基本使用

Maven 的常用命令

  • compile:编译
  • clean:清理,删除前面编译产生的 target 目录
  • test:测试,执行 test 文件夹下的代码
  • package:打包
  • install:安装

在含有 pom.xml 文件的目录下,进入 PowerShell

1
2
3
4
5
mvn compile
mvn clean
mvn package
mvn tast
mvn install

Maven 的生命周期

Maven 对项目构建的生命周期划分为三套

  • clean:清理工作
  • default:核心工作,例如编译,测试,打包,安装等
  • site:产生报告,发布站点等

08-Maven的生命周期.png

比如说执行 install,就会自动执行 compile,但是不会自动执行 clean(因为这是两套不同的生命周期)

依赖管理

使用坐标导入 jar 包

  1. 在 pom.xml 中编写 <dependencies> 标签
  2. <dependencies> 标签中使用 <dependency> 来引入坐标
  3. 定义坐标的 groupId,artifactId,version
  4. 点击刷新按钮,是坐标生效(或者对 IDE 进行配置,每次变更自动生效)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 导入 mysql 驱动jar包-->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>

比如去找 MySQL 的 Maven 配置信息(浏览器搜索 mysql maven),比如从以下网页中找到了:MySQL Connector Java

还为我们提供了 maven 需要使用的信息
09-从官网获得Maven的配置信息.png

快速导入坐标信息到 pom.xml

如果本地仓库就有相应的 jar 包,那么直接可以通过搜索 jar 包的名字来导入(即可自动导入模板)

  1. 在 pom.xml 文件中,Alt+Insert
  2. 选择依赖项模板,自动为我们添加模板

10-快速插入依赖项模板.png

依赖范围

通过设置坐标依赖范围(scope),可以设置对应 jar 包的作用范围:编译环境、测试环境、运行环境

编译环境:在主工程 java 文件夹中可以使用
测试环境:在测试文件夹 test 中可以使用
运行环境:

依赖范围的取值有以下六种,默认值是 compile(其实范围也是最大的)

分模块开发与设计

将原始模块按照功能拆分为若干个子模块,方便模块间的相互调用,接口共享

01-分模块开发思想.png

Intellij 中同时导入多个模块方式如下:
02-Intellij同时导入多个模块.png

1、项目准备:之前做好的 SSM 整合的项目 maven_02_ssm 进行讲解
2、新建一个模块:maven_03_pojo

1)新建 com.itheima.domain 包
2)将 maven_02_ssm 中的 domain 下的 Book 实体类剪切至 maven_03_pojo 下的 domain 包下
3)此时 maven_02_ssm 将无法运行,因为缺少了 Book 实体类
4)现在要做到是:如何在 maven_02_ssm 中访问 / 加载 maven_03_pojo 下的 Book 实体类呢?

maven_03_pojo 模块中 pom.xml 的坐标如下

1
2
3
<groupId>com.itheima</groupId>  
<artifactId>maven_03_pojo</artifactId>
<version>1.0-SNAPSHOT</version>

那么我们在 maven_02_ssm 中引入上面的坐标

1
2
3
4
5
6
<!--依赖domain运行-->  
<dependency>
<groupId>com.itheima</groupId>
<artifactId>maven_03_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

此时就将 maven_03_pojo 模块引入进来了,此时 maven_02_ssm 也没有报错了

总结:将一个模块中的一部分功能抽取出来单独做一个模块,然后在原来的使用方去引用这个抽取出来的模块,这样就做成了两个模块

但是此时 02 模块会有问题?为什么呢
因为通过坐标导入,会将相应的资源下载到本地仓库,02 模块引入了 03 模块,但是本地仓库里面却找不到 03 的资源。因此,我们还需要将 03 模块 install 到本地仓库中

03-导入坐标之后需要安装到本地仓库.png

安装完之后,本地仓库中就可以找到模块 03 的资源了。此时我们在 compile 一下 02 模块,若能够编译成功,说明没有问题了

依赖管理

如果一个模块 A 依赖了模块 B,而 B 模块依赖了其他的东西,那么这个 A 模块可以直接使用这些东西

直接依赖:在当前项目中通过依赖配置建立的依赖关系
简介依赖:被依赖的资源如果依赖其他资源,当前项目简介依赖其他资源

依赖冲突

  • 路径优先:当依赖中出现相同的资源时,层级越深,优先级越低,层级越浅,优先级越高
  • 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序靠后的
  • 特殊优先:当同级配置类相同资源的不同版本,后配置的覆盖先配置的

04-依赖层级关系.png

可以通过此处查看项目中的依赖关系
05-Intellij中查看依赖层级关系.png

可选依赖

比如说 02 模块引用了 04 模块,04 模块中引用了几个坐标。现在的需求是,我不想让 02 模块能够加载或引用 04 模块中的坐标,怎么处理呢?

那么在 04 模块中,对如下这个坐标进行处理

1
2
3
4
5
6
7
<dependency>
<groupId>com.itheima</groupId>
<artifactId>maven_03_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
<!--可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依赖传递性-->
<optional>true</optional>
</dependency>

其实这个需要就是想要某个坐标没有传递性

排除依赖

比如引用了 maven_04_dao 坐标,但是排除这个坐标下的另外两个坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>com.itheima</groupId>
<artifactId>maven_04_dao</artifactId>
<version>1.0-SNAPSHOT</version>
<!--排除依赖是隐藏当前资源对应的依赖关系-->
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>

那么可选和排除有什么区别呢:

  • 用的可选,别人引用了我的坐标,但是不知道我背后引用了哪些坐标;对外隐藏当前所依赖的资源
  • 用的排除,别人是知道我背后引用了那些坐标的;主动断开依赖的资源,被排除的资源无需指定版本

聚合

06-聚合的引入.png

比如:上面的三个模块都是依赖 pojo 模块的,假如我们更新了 pojo 模块,上面三个模块会及时更新吗?如果 pojo 因为更新出现了问题,上面三个模块能够及时发现吗?

聚合:将多个模块组织成一个整理,同时进行项目构建的过程称为聚合(其实有点像事务的概念)
聚合工程:通常是一个不具有业务功能的 “空” 工程(仅有一个 pom 文件)
作用:使用聚合工程可以将多个模块编组,通过对聚合工程进行构建,实现对所包含的模块进行同步构建;当工程中某个模块发生更新时,必须保障工程中与已更新模块关联的模块同步更新,此时可以使用聚合工程来解决批量模块同步构建的问题

1)创建新 Maven 模块
2)聚合工程的特点:在 pom.xml 中,将打包方式设置为 pom

1
<packaging>pom</packaging>

3)设置管理模块的模块名称

1
2
3
4
5
6
7
8
<!--设置管理模块名称
..表示当前文件pom.xml文件的上一级文件
-->
<modules>
<module>../maven_02_ssm</module>
<module>../maven_03_pojo</module>
<module>../maven_04_dao</module>
</modules>

07-聚合-引入子模块.png

08-聚合后各层级的关系.png

4)进行同步编译 compile

09-聚合后进行同步编译.png

继承

概念:继承描述的是两个工程间的关系,与 Java 中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承

作用:简化配置;减少版本冲突

1)继承关系在子类中描述

1
2
3
4
5
6
7
<!--配置当前工程继承自parent工程-->  
<parent>
<groupId>com.itheima</groupId>
<artifactId>maven_01_parent</artifactId>
<version>1.0-RELEASE</version>
<relativePath>../maven_01_parent/pom.xml</relativePath>
</parent>

此时,就可以继承父工程中依赖的坐标了

2)父工程中的坐标都必须要被所有子工程继承吗?不一定
可以在父工程 pom 文件中通过 dependencyManagement 来指定这是一个可选的坐标

1
2
3
4
5
6
7
8
9
10
11
<!--定义依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

如果子工程中想要引用的话就在 pom 中加上相应的坐标,但是注意不要加版本号,因为他会自动继承父类中坐标的版本号;而对于其他子工程就不会自动继承引用这个坐标

1
2
3
4
5
<dependency>  
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>

3)子工程继承父工程中的坐标,只要父工程中坐标的版本号一改,所有子工程中对应的版本号都会改

聚合与继承的区别

  • 作用
    • 聚合用于快速构建项目
    • 继承用于快速配置
  • 相同点
    • 聚合与继承的 pom.xml 文件打包方式均为 pom,可以将两种关系制作到同一个 pom 文件中
    • 聚合与继承均属于设计型模块,并无实际的模块内容
  • 不同点
    • 聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
    • 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

属性

10-属性问题的引入.png

1)定义属性

1
2
3
4
<!--定义属性,标签名可以自定义-->  
<properties>
<spring.version>5.2.10.RELEASE</spring.version>
</properties>

2)在定义坐标时,可以直接使用变量

1
2
3
4
5
6
7
8
9
10
11
<dependency>  
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

3)这样就好了呀,以后可以直接从这儿就可以看到各种坐标的版本

1
2
3
4
5
6
7
<!--定义属性-->  
<properties>
<spring.version>5.2.10.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<mybatis-spring.version>1.3.0</mybatis-spring.version>
<!--<jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db</jdbc.url>-->
</properties>

版本管理

比如如下为某一工程 pom.xml 中的坐标,其中的 version 有什么用呢?

1
2
3
4
<groupId>com.itheima</groupId>  
<artifactId>maven_02_ssm</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
  • 工程版本
    • SNAPSHOT(快照版本)
      • 项目开发过程中临时输出的版本,称为快照版本
      • 快照版本会随着开发的进展不断更新
    • RELESE(发布版本)
      • 项目开发到进入阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构建文件时稳定的,即便进行功能的后续开发,也不会改变当前发布版本的内容,这种版本称为发布版本
  • 发布版本
    • alpha 版
    • beta 版
    • 纯数字版

多环境配置

011-多环境配置.png

maven 提供配置多种环境的设定,帮助开发者使用过程中快速切换环境

1)在父工程 pom.xml 中

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
<!--配置多环境-->
<profiles>
<!--开发环境-->
<profile>
<id>env_dep</id>
<properties>
<jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
</properties>
<!--设定是否为默认启动环境-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!--生产环境-->
<profile>
<id>env_pro</id>
<properties>
<jdbc.url>jdbc:mysql://127.2.2.2:3306/ssm_db</jdbc.url>
</properties>
</profile>
<!--测试环境-->
<profile>
<id>env_test</id>
<properties>
<jdbc.url>jdbc:mysql://127.3.3.3:3306/ssm_db</jdbc.url>
</properties>
</profile>
</profiles>

2)对工程进行 install,然后可以查看项目构建好的 war 包,双击 war 包,进入 WEB-INF 文件夹 ⇒ classes 文件夹 ⇒ jdbc.properties 文件,查看配置是否生效

如果要更换环境,可以将设置默认启动环境的那几行代码切换一下位置,比如切换到测试环境中,那么 install 后,默认就是测试环境的配置了

或者可以不用挪动那几行代码,直接使用 Maven 指令来表明我们将使用 env_dep 环境来进行 install,如下图所示
012-Maven指令处理多环境.png

013-Maven指令处理多环境-2.png

跳过测试

跳过测试:跳过所有测试
014-跳过测试.png
跳过测试:指定跳过某些内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>false</skipTests>
<!--排除掉不参与测试的内容-->
<excludes>
<exclude>**/BookServiceTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

Rest 简介

Rest (Representation State Transfer, 表现形式状态转换),即访问网络资源的格式

传统风格资源描述形式书写如下

1
2
http://localhost/user/getById?id=1
http://localhost/user/saveUser

REST 风格描述形式如下

1
2
http://localhost/user/1
http://localhost/user

特点

  • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
  • 书写简化
  • 按照 REST 风格访问资源时使用行为动作区分对资源进行了何种操作
  • 根据 REST 风格对资源进行访问称为 RESTful

Rest风格增删改查.png

注意事项

  • 上述行为是约定方式,约定不是规范,可以打破,所以称 REST 风格,而不是 REST 规范
  • 描述模块的名称通常使用复数,也就是加 s 的格式描述,表示此类资源,而非单个资源,例如:users、books 等

Rest 入门案例

1、原先的风格

1
2
3
4
5
6
@RequestMapping("/save")  
@ResponseBody
public String save(){
System.out.println("user save...");
return "{'module':'user save'}";
}

2、REST 风格

1
2
3
4
5
6
7
//设置当前请求方法为POST,表示REST风格中的添加操作  
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String save(){
System.out.println("user save...");
return "{'module':'user save'}";
}

删除

1
2
3
4
5
6
7
8
//设置当前请求方法为DELETE,表示REST风格中的删除操作  
//@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id){
System.out.println("user delete..." + id);
return "{'module':'user delete'}";
}

注意 Postman 中的请求路径:

1
http://localhost//users/1

@PathVariable 表示后面的变量来自路径,但是来自路径中的哪儿呢?
而通过 value = "/users/{id}" 中就指明了路径参数(路径变量)

修改

1
2
3
4
5
6
7
//设置当前请求方法为PUT,表示REST风格中的修改操作  
@RequestMapping(value = "/users",method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody User user){
System.out.println("user update..."+user);
return "{'module':'user update'}";
}

根据 id 查询

1
2
3
4
5
6
7
8
//设置当前请求方法为GET,表示REST风格中的查询操作  
//@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
@RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("user getById..."+id);
return "{'module':'user getById'}";
}

查询所有

1
2
3
4
5
6
7
//设置当前请求方法为GET,表示REST风格中的查询操作  
@RequestMapping(value = "/users",method = RequestMethod.GET)
@ResponseBody
public String getAll(){
System.out.println("user getAll...");
return "{'module':'user getAll'}";
}

总结

  1. 设定 http 请求动作:@RequestMapping 的 mathod 属性设置请求动作
  2. 设定请求参数(路径变量)
  3. @PathVariable 形参注解,用于绑定路径参数与处理器方法形参间的关系,要求路径参数名和形参名一一对应

@RequestBody:用于接收 json 数据
@RequestParam:接受 URL 地址传参或表单传参
@PathVariable:用于接收路径参数,使用 {参数名称} 描述路径参数

RESTful 快速开发

简化书写

1
2
3
@RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
@RequestMapping(value = "/users",method = RequestMethod.GET)
@RequestMapping(value = "/users",method = RequestMethod.PUT)

问题 1:可以看到,以上的这几个中 value = "/users" 都是重复要写的内容,能不能更简化呢?

1
2
3
4
5
@Controller
@RequestMapping("/books")
public class BookController {
//各种处理器方法
}

问题 2:每一个处理器方法前面都带着一个 @ResponseBody 注解,能不能更简化些呢?

@ResponseBody 写到类的前面

1
2
3
4
5
6
@Controller
@ResponseBody
@RequestMapping("/books")
public class BookController {
//各种处理器方法
}

Spring ⇒ 既然每次都得写 @Controller@ResponseBody,那就合二为一吧,使用 @RestController 即可

1
2
3
4
5
@RestController
@RequestMapping("/books")
public class BookController {
//各种处理器方法
}

问题 3:每个处理器方法中都有

1
2
3
4
method = RequestMethod.POST
method = RequestMethod.DELETE
method = RequestMethod.PUT
//...

那么能不能简化书写呢?

1
2
3
4
5
6
7
//@RequestMapping( method = RequestMethod.POST)  
//使用@PostMapping简化Post请求方法对应的映射配置
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}

也就是说,使用注解 @PostMapping 来实现前面 mathod 属性中的功能

那么对于含有路径参数的呢?比如 delete ⇒ @DeleteMapping("/{id}")

1
2
3
4
5
6
//@RequestMapping(value = "/{id}" ,method = RequestMethod.DELETE)  
@DeleteMapping("/{id}") //使用@DeleteMapping简化DELETE请求方法对应的映射配置
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
1
2
3
4
5
@PostMapping
@DeleteMapping("/{id}")
@PutMapping
@GetMapping
@GetMapping("/{id}")

页面数据展示

非本案例重点,这里省略操作

总结

基于 RESTful 页面数据交互总结

  • 先做后台功能,开发接口并调通接口
  • 再做页面异步调用,确认功能可以正常访问
  • 最后完成页面数据展示

参考资料

1、常用快捷键

操作 快捷键
插入环境 Ctrl+E
插入行内公式 Ctrl+Shift+M
注释 / 取消注释 Ctrl+T
查看 PDF F7
预览行内数学公式 Alt +P
从 PDF 跳转至对应的 Tex 在 PDF 中按 Ctrl 单击

2、设置中文界面

01-Texstudio设置中文界面.png

3、设置编译器与编码

为了正常的输出中文,我们需要把编译器改成 XeLaTeX ,utf-8 编码(默认)

4、显示代码行号

02-Texstudio显示行号.png

5、括号匹配高亮

TexStudio 默认的括号匹配背景色好像是黄色的,有些看不清,不如设置个更亮一点的颜色吧

03-括号匹配高亮1.png

04-括号匹配高亮2.png

bean 基础配置

01-Bean的配置信息一览图.png

bean 别名配置

1、设置别名

问 1:那么不同的人编写代码,可能给 bean 不同的命名,如何解决命名问题呢?
答 1:可以通过给 bean 起别名的方式

1
2
3
<bean id="bookService" name="service,service2,bookEbi" class="com.itheima.service.impl.BookServiceImpl">  
<property name="bookDao" ref="bookDao"/>
</bean>

name 属性值可以起多个别名,别名间以逗号、分号或空格来分割,比如如上代码的 name="service,service2,bookEbi"

2、在到运行类中修改一下获取 bean 的 id 就可以了

1
2
BookService bookService = (BookService) ctx.getBean("service");  
bookService.save();

除此之外,在 DI 配置属性的时候,也支持 name 属性,但是我们还是推荐通过 id 来进行引用
02-可通过ID或者Name属性来进行引用.png

bean 作用范围

Bean 的单例和多例

问 1:Spring 默认给我们创建的 bean 是 单例 的;那如果我想要造一个非单例的 bean 怎么办?
03-创建的Bean默认为单例的.png

答 1:通过配置的形式,在 bean 标签中再插入一个 scope 属性(默认为 singleton)

1
<bean id="bookDao" name="dao" scope="prototype" class="com.itheima.dao.impl.BookDaoImpl"/>

问 2:那么为什么 bean 默认为单例呢?
答 2:单例的 bean 如果能够复用的话,那么下次需要使用直接去容器中拿就好了,而不是每用一次就造一个对象

问 3:那么那些 bean 适合造单例呢?
答 3:适合交给容器进行管理的 bean,包括表现层对象、业务层对象、数据层对象以及工具对象等

问 4:那么哪些 bean 不适合交给容器进行管理呢?
答 4:封装实体的域对象(如记录有成员变量的)

bean 实例化

bean 本质上就是对象,创建 bean 使用构造方法完成

构造方法(常用)

1、提供可访问的构造方法

构造方法不写也行,Spring 会为我们处理;如果我们手动写上的话,注意一定是无参的构造方法。即如果我们创建了含参的构造方法,但是没有提供无参的构造方法,将抛出异常 BeanCreationException

1
2
3
4
5
6
7
8
9
10
public class BookDaoImpl implements BookDao {  
//无参的构造方法
public BookDaoImpl() {
System.out.println("book dao constructor is running");
}

public void save() {
System.out.println("book dao save ...");
}
}

2、配置 bean

1
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

静态工厂

通过工厂的方式造对象(而不是使用 new)也是一种解耦的方式,那么通过工厂造出来的对象如何交给 Spring 进行管理呢?

1、静态工厂创建对象

1
2
3
4
5
6
7
//静态工厂创建对象  
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}

2、配置

1
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>

3、运行

1
2
OrderDao orderDao = OrderDaoFactory.getOrderDao();  
orderDao.save();

也就是说:

  1. 需要在配置文件中的 class 属性中指明工厂的路径
  2. 通过 factory-method 属性指明工厂中造对象的方法

实例工厂(了解)

1、实例工厂创建对象

1
2
3
4
5
6
//实例工厂创建对象  
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}

2、配置

1
2
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
  1. 先造工厂的 bean,对应着第一行代码
  2. factory-bean 指的是这个工厂的实例,也就是第一行代码中的 userFactory
  3. factory-method 指明工厂中造对象的方法

3、运行

1
2
3
4
5
//创建实例工厂对象;  
UserDaoFactory userDaoFactory = new UserDaoFactory();
//通过实例工厂对象创建对象
UserDao userDao = userDaoFactory.getUserDao();
userDao.save();

bean 生命周期

通过配置文件

1、在配置文件中绑定类的初始化和销毁前方法

1
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>

2、在类(BookDaoImpl)中创建对应的方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BookDaoImpl implements BookDao {  
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}

}

需要注意的是:当容器关闭时才会执行销毁前方法

bean 的生命周期

  • 初始化容器
    • 创建对象(内存分配)
    • 执行构造方法
    • 执行属性注入(set 操作)
    • 执行 bean 初始化方法
  • 使用 bean
    • 执行业务操作
  • 关闭 / 销毁容器
    • 执行 bean 销毁方法

附注

遇到 Spring 报错信息的解决办法

  1. 找到最后一条报错信息,Caused By:哒哒哒
  2. 如果最下面的能解决就 ok,不能解决就继续看上一条报错信息
  3. 因为第一条信息是包含全部的报错信息(很长看不到重点)

Element 是什么

将页面变得美美的呀😀

Element 是饿了么公司前端开发团队提供的一套基于 Vue 的网站组件库,用于快速构建网页
组件:组件网页的部件,例如:超链接、按钮、图片、表格等等

官网:Element - 网站快速成型工具

Element 快速入门

  1. 引入 Element 的 css、js 文件和 Vue.js
  2. 创建 Vue 核心对象
  3. 官网复制 Element 组件代码

1、将整个 element-ui 文件夹拷贝至 webapp 文件夹下

因为包含了一整套组件,element-ui 包含了大量文件(差不多 8M 了)

2、新建 HTML 页面,并引入 css、js 文件

1
2
3
4
5
6
7
8
9
<script src="js/vue.js"></script>
<!--通过下载好的element-ui文件-->
<script src="element-ui/lib/index.js"></script>
<link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css">
<!--或者-->
<!--
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
-->

3、创建 Vue 核心对象 (搭建框架)

将代码写到 div 标签中即可;若需要引入 CSS 样式,可将其复制到 </head> 之上

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
<!DOCTYPE html>  
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>


</head>
<body>

<div id="app">

</div>

<script src="js/vue.js"></script>
<script src="element-ui/lib/index.js"></script>
<link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css">

<script>
new Vue({
el:"app",

})
</script>

</body>
</html>

4、去官网上找组件的代码

比如去官网上找着这个按钮,看起来还不错,复制它的代码到 div 标签中即可

1
<el-button type="danger">危险按钮</el-button>

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<div id="app">
<el-button type="danger">危险按钮</el-button>
</div>

<script src="js/vue.js"></script>
<script src="element-ui/lib/index.js"></script>
<link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css">

<script>
new Vue({
el:"app"
})
</script>

</body>
</html>

Element 布局

Element 中有两种布局方式:Layout 布局和 Container 布局容器

Layout 布局

通过基础的 24 分栏,迅速简便地创建布局

官方案例:基础布局 | Element,直接复制 CSS 和 HTML 标签到我们的页面当中即可

01-从实例代码中复制HTML和CSS实现布局的引入.png

Container 容器布局

用于布局的容器组件,方便快速搭建页面的基本结构

官网示例:Container 布局容器 | Element

同样也是复制 css 和 html。需要注意到是,如果布局中包含数据(比如含有表格等),那么我们只要将 data 复制到我们创建的 vue 对象中即可

官方给出代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export default {
data() {
const item = {
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
};
return {
tableData: Array(20).fill(item)
}
}
};
</script>

将 以上代码中的 data 复制到我们创建的 HTML 页面中的 vue 对象中去;同理,如果实例代码中有 methods 等属性的话,也一并复制到新建的 Vue 对象中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>  
new Vue({
el:"app",
data() {
const item = {
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
};
return {
tableData: Array(20).fill(item)
}
}
})
</script>

Element 组件

参考官方示例:组件 | Element

参考资料

  1. Mermaid 官方文档:mermaid - Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.
  2. Github:Mermaid 中文说明文档
  3. Mermaid 在线编辑 Github:mermaid-live-editor
  4. VScode 插件:Markdown Preview Mermaid Support
  5. VScode 插件:Mermaid Markdown Syntax Highlighting

推荐阅读

  1. 个人博客:Mermaid 流程图 - Sherwood 的博客

流程图快速入门

案例 1:常规流程图

1
2
3
4
5
6
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]
1
2
3
4
5
6
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]
  • 第一行:TD(Top down)自上而下;
    除此之外,还有上 (Top) 下 (Bottom) 左 (Left) 右 (Right),可以两两组合来确定流程图的方向

  • 第二行:中括号表示图形中的矩形,圆括号为圆角矩形,两竖线之间的表示箭头上的文字
    除此之外,还有各种箭头和图形样式,这里不再列举,详细请查看官方文档和 Mermaid 流程图 - Sherwood 的博客

案例 2:带有子图的流程图

子图的基本语法为

1
2
3
subgraph title
graph definition
end

完整案例如下

1
2
3
4
5
6
7
8
9
10
11
flowchart RL
b1-->|"username = &quot;zhangsan&quot;"|servlet1
servlet1-->|data|b1
    subgraph Browsers
    b1
    b2
    end
    subgraph servers
    servlet1
    servlet2
    end
1
2
3
4
5
6
7
8
9
10
11
flowchart RL
b1-->|"username = &quot;zhangsan&quot;"|servlet1
servlet1-->|data|b1
    subgraph Browsers
    b1
    b2
    end
    subgraph servers
    servlet1
    servlet2
    end

特别注意:两竖线中的内容(也就是箭头上的文字)不能包含双引号,在 Mermaid 中算是特殊符号,请查阅 Mermaid 流程图 - Sherwood 的博客,需要额外进行处理。&quot; 来表示双引号。所以,在箭头上还是尽量少用双引号吧😀

以下方式均会导致绘图出错

1
2
3
b1-->|1.username = '"zhangsan"'|servlet1
b1-->'|1.username = "zhangsan"|'servlet1
b1-->|'1.username = "zhangsan"'|servlet1

正确的处理方式

1
b1-->|"username = &quot;zhangsan&quot;"|servlet1

基础表达形式

直线结构

1
A[Christmas] -->|Get money| B(Go shopping)

![[Pasted image 20240327083121.png]]

多分支结构

1
2
3
4
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]

![[Pasted image 20240327083210.png]]

多合一结构

1
2
3
A --> C
B --> C
D --> C

![[Pasted image 20240327083404.png]]

需求

比如说一个任务流程图,需要同时确认了 A 和 B,才能执行 C,那么流程图该怎么表达

参考案例 1:

1
2
3
4
5
6
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]

参考案例 2:

1
2
3
4
graph TD
A[First node] --> C
B --> C
D --> C

根据案例 2,如何给 A,B 添加样式,以区别显示呢:

1
2
3
4
5
6
7
8
graph TD
style A fill:#9f9,stroke:#333,stroke-width:2px;
style B fill:red;
style D fill:cyan,stroke:dark gray;
style C fill:yellow;
A --> C
B --> C
D --> C