diff --git a/README.md b/README.md index 99bcf13d9fb5cf9e31978d3ef381091d505d1ad3..d5a560eccb76a87096750d93b7745f9b3284491c 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,5 @@ ## 介绍 -日常开发笔记。 +日常开发笔记 + --xing diff --git a/Coding Style.md b/coding-style/Coding Style.md similarity index 100% rename from Coding Style.md rename to coding-style/Coding Style.md diff --git a/CSS.md b/css/CSS.md similarity index 98% rename from CSS.md rename to css/CSS.md index 069626129bfd7e949288c707b40244207b293dd3..270872449d36d2055fba580d97de8729c204a864 100644 --- a/CSS.md +++ b/css/CSS.md @@ -1,5 +1,11 @@ # CSS 笔记 +## 参考文档 + +- [CSS W3C](https://www.w3school.com.cn/css/index.asp) +- [CSS Layout](https://zh.learnlayout.com/) +- [calc 函数](https://developer.mozilla.org/zh-CN/docs/Web/CSS/calc()) + ## css 选择器 ### 简单选择器 diff --git a/Flex.md b/css/Flex.md similarity index 97% rename from Flex.md rename to css/Flex.md index 5d44425ba009c029f12361db31e5155851ce2574..c6f469212b326993d53e112905ecc09ab7cb1446 100644 --- a/Flex.md +++ b/css/Flex.md @@ -1,6 +1,9 @@ - # Flex 笔记 +## 参考文档 + +- [Flex 阮一峰](http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html) + ## 简介 **Flexible Box**,弹性布局。 diff --git a/Less.md b/css/Less.md similarity index 98% rename from Less.md rename to css/Less.md index 3141fde0782cf8cffd86a855319991f2a0cb0f65..a1f69523fffde4b0b21e054d6acb593b6a9538f2 100644 --- a/Less.md +++ b/css/Less.md @@ -1,5 +1,9 @@ # Less 笔记 +## 参考文档 + +- [Less](https://less.bootcss.com) + ## 变量 示例: diff --git a/git/Git.md b/git/Git.md new file mode 100644 index 0000000000000000000000000000000000000000..c77621fbf16820e72cb6a1e3547f6dfd7b69a3f5 --- /dev/null +++ b/git/Git.md @@ -0,0 +1,5 @@ +# Git 笔记 + +## 参考文档 + +- [Pro Git](https://git-scm.com/book/en/v2) diff --git a/html/HTML.md b/html/HTML.md new file mode 100644 index 0000000000000000000000000000000000000000..629a2b34a699f8a4aa60a0c45d873108b5dbe48e --- /dev/null +++ b/html/HTML.md @@ -0,0 +1,5 @@ +# HTML 笔记 + +## 参考文档 + +- [网道 HTML](https://wangdoc.com/html/) diff --git a/java/Java.md b/java/Java.md new file mode 100644 index 0000000000000000000000000000000000000000..49c28adcc1c746680ed521369e338ba9ced29d58 --- /dev/null +++ b/java/Java.md @@ -0,0 +1,1011 @@ +# Java 笔记 + +## 参考文档 + +- [廖雪峰 Java](https://www.liaoxuefeng.com/wiki/1252599548343744) + +## 基本数据类型 + +- Java 的数据类型一共有 2 种,一种是基本数据类型,另一种是引用数据类型。 +- Java 有 8 大基本数据类型,如果不给初始值,Java 将赋值为 `0`、`null`、 `false`。 +- Java 中采用 Unicode 编码,它的汉字和字母都占用 2 个字节。 + +### 整数型 + +- `byte` +- `short` +- `int` +- `long` + +### 浮点型 + +- `float` +- `double` + +### 字符型 + +- `char` + +### 布尔类型 + +- `boolean` + +## 输入输出 + +### 占位符 + +- `%d`,格式化输出整数。 +- `%s`,格式化字符串。 +- `%f`,格式化输出浮点数。 +- `%e`,格式化输出科学计数法表示的浮点数。 +- `%x`,格式化输出十六进制整数。 + +## 面向对象 + +### 向上转型 + +- 子类可以调用覆写父类的方法,如果没有覆写,也可以调用父类的方法。 +- 不可以调用子类独有的方法。 + +### this + +this 始终指向当前实例,在类中 + +### 静态字段和静态方法 + +- 所有实例共享一个静态字段。 + + ```md + ┌──────────────────┐ + ming ──>│Person instance │ + ├──────────────────┤ + │name = "Xiao Ming"│ + │age = 12 │ + │number ───────────┼──┐ ┌─────────────┐ + └──────────────────┘ │ │Person class │ + │ ├─────────────┤ + ├───>│number = 99 │ + ┌──────────────────┐ │ └─────────────┘ + hong ──>│Person instance │ │ + ├──────────────────┤ │ + │name = "Xiao Hong"│ │ + │age = 15 │ │ + │number ───────────┼──┘ + └──────────────────┘ + ``` + +### 访问修饰符 + +- `private`,当前 class 内。 +- `default`,即包访问权限,当前包内。 +- `protected`,当前包内 + 子类。 +- `public`,任何位置。 + +## Java 核心类 + +### String 类 + +String 类是使用 char[] 实现的。 + +字符串内容是不可变的,所有的对象都是不可变的,当一个对象变化的时候,实际上是直接将它的指针指向了一个新的对象。 + +从String的不变性设计可以看出,如果传入的对象有可能改变,我们需要复制而不是直接引用。 + +字符串的比较: + +- `==` 比较的是内存单元的存储内容,`equals` 比较的是对象的实际内容,也就是内存单元存储的指针指向的位置的内容,因为 String 是引用类型,所以使用 `equals` 进行比较内容。 +- 示例 + + ```java + String s1 = "hello"; + String s2 = "hello"; + System.out.println(s1 == s2) + // true + ``` + + Q:这里明明是声明了两个变量 s1 s2,原理上 s1 s2 这两个变量存储的是两个不同的指针,分别指向两个不同的地址,这两个地址内存储的都是 hello,但是为什么这里打印出来是 true。 + + A:因为 Java 在编译期会把所有的字符串当做一个对象放进常量池,所以它们的引用在这里就是一样的了。 + +### StringBuilder 类 + +- 避免重复创建新字符串 +- 使用 StringBuilder 类,可以只开辟一块空间,操作完以后使用 toString() 将内容赋值给 s。 + +### Enum 类 + +- `enum` 类型的每个常量在 `JVM` 中只有一个唯一实例,所以可以直接用 `==` 比较: +- 定义的 `enum` 类型继承自 `java.lang.Enum`,无法被继承; +- 无法通过 `new` 操作符创建 `enum` 的实例 +- 定义的每个实例都是引用类型的唯一实例; + +### BigDecimal 类 + +- `scale()`,显示小数部分。 +- `setScale()`,设置精度。 +- `RoundingMode.HALF_UP`,四舍五入。 +- `RoundingMode.DOWN`,直接截取。 + +**注意:** *比较大小* 使用 `compareTo()`,不要使用 `equals()`。因为 BigDecimal 是由两部分组成的,一部分是整数部分,一部分是小数部分。 + +## 日志/异常处理 + +### 捕获异常 + +- 多 `catch` 语句只会执行一条,遇到一个进去以后,就直接返回了。 +- 子类写在前面。 +- `catch (IOException | NumberFormatException e)`,异常之间使用 `|` 进行并列选择。 + +### NullPointerException + +- 尽量初始化不要为 `null`。 + +### 断言 assert + +- 格式: `assert x >= 0 : "error message";` +- 断言失败时会抛出 `AssertionError`,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段。 + +### 日志 + +日志就是 `Logging`,它的目的是为了取代 `System.out.println()`。 + +输出日志,而不是用 `System.out.println()`,有以下几个好处: + +- 可以设置输出样式,避免自己每次都写 `"ERROR: " + var`; +- 可以设置输出级别,禁止某些级别输出。例如,只输出错误日志; +- 可以被重定向到文件,这样可以在程序运行结束后查看日志; +- 可以按包名控制日志级别,只输出某些包打的日志; +- 等等 + +### JDK Logging + +- Java 标准库内置了日志包 `java.util.logging`。 +- 日志级别(从严重到普通): + - `SEVERE` + - `WARNING` + - `INFO`,默认级别 + - `CONFIG` + - `FINE` + - `FINER` + - `FINEST` + +- + +### SLF4J 和 Logback + +- `{}`,使用占位符。 + +## 注解 Annotation + +### 三类注解 + +- 编译器使用的 + - `@Override` + - `Suppress Warnings`,告诉编译器忽略此处的代码产生的警告 + - 不会编译进 .class 文件 + +- 第二类,是由工具处理 .class 文件使用的注解, + - 比如有些工具会在加载 class 的时候,对 class 做动态修改,实现一些特殊的功能。这类注解会被编译进入 .class 文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。 +- 自定义 + +### 自定义注解 + +#### Java 使用 `@interface` 语法来定义注解。 + +- 格式: + + ```java + public @interface Report { + int type() default 0; + String level() default "info"; + String value() default ""; + } + ``` + + 注解的参数类似无参数方法,可以用 `default` 设定一个默认值(强烈推荐)。 + + 最常用的参数应当命名为 `value`。 + +#### 元注解 + +- `@target`,描述注解的适用范围(被修饰的注解可以用在什么地方) + - 参数 + - 类或接口:`ElementType.TYPE` + - 字段:`ElementType.FIELD` + - 方法:`ElementType.METHOD` + - 构造方法:`ElementType.CONSTRUCTOR` + - 方法参数:`ElementType.PARAMETER` + - 示例: + + 例如,定义注解 `@Report` 可用在方法上,我们必须添加一个 `@Target(ElementType.METHOD)`: + + ```java + @Target(ElementType.METHOD) + public @interface Report { + int type() default 0; + String level() default "info"; + String value() default ""; + } + ``` + + 定义注解 `@Report` 可用在方法或字段上,可以把@Target注解参数变为数组 `{ ElementType.METHOD, ElementType.FIELD }`: + + ```java + @Target({ + ElementType.METHOD, + ElementType.FIELD + }) + public @interface Report { + ... + } + ``` + + 实际上 `@Target` 定义的 `value` 是 `ElementType[]` 数组,只有一个元素时,可以省略数组的写法。 +- `Retention`,描述注解保留的时间范围(声明周期) + - 3种策略 + - 编译期:`RetentionPolicy.SOURCE` + - class 文件:`RetentionPolicy.CLASS` + - 运行期:`RetentionPolicy.RUNTIME` +- `@Inherited`,使得被它修饰的注解拥有继承性(如果某个类使用了被 `@Inherited` 修饰的注解,则其子类自动拥有该注解) + - 注意:使用 `@Inherited` 定义子类是否可继承父类定义的 `Annotation`。@Inherited仅针对@Target(ElementType.TYPE)类型的 `annotation` 有效,并且仅针对class的继承,对interface的继承无效: + +## 泛型 + +### 使用泛型 + +- 例如 `ArrayList` 使用泛型的时候,如果不加定义的话,默认传入的是 Object。 + +### 编写泛型 + +- 编写泛型类时,**注意** 不能用于静态方法。 + +### 擦拭法 - 实现方式 + +- `` 不能是基本类型,例如 `int`,因为实际类型是 `Object`,`Object` 类型无法持有基本类型。 +- 无法取得带泛型的 `Class`。 +- 无法判断带泛型的类型。 +- 不能实例化 `T` 类型。 + +### extends 通配符 + +- `Pair` 使得方法接收所有泛型类型为 `Number` 或 `Number` 子类的 `Pair` 类型。 +- 上面的写法可以称为上界通配符 `(Upper Bounds Wildcards)` + +### super 通配符 + +- `Pair` +- `Number` 的父类都可以。 + +### PECS 原则 + +- `Producer Extends Consumer Super` + +### 无限制通配符 ? + +- `` + +## 集合 + +`Collection`,`java.util` 包提供的,是除 Map 以外的所有集合的根接口。 + +- `Java` 集合使用统一的 `Iterator` 遍历 + +### List + +- 在末尾添加一个元素:`boolean add(E e)` +- 在指定索引添加一个元素:`boolean add(int index, E e)` +- 删除指定索引的元素:`E remove(int index)` +- 删除某个元素:`boolean remove(Object e)` +- 获取指定索引的元素:`E get(int index)` +- 获取链表大小(包含元素的个数):`int size()` + +### Map + +- 作为 `key` 的对象必须正确覆写 `equals()` 方法,相等的两个 `key` 实例调用 `equals()` 必须返回 `true`; + +- 作为 key 的对象还必须正确覆写 `hashCode()` 方法,且 `hashCode()` 方法要严格遵循以下规范: + +- 如果两个对象相等,则两个对象的 `hashCode()` 必须相等; +- 如果两个对象不相等,则两个对象的 `hashCode()` 尽量不要相等。 + +### Enum Map + +如果 `key` 是 `enum` 类型,使用 `Java` 集合库提供的 `EnumMap`,它在内部以一个非常紧凑的数组存储 `value`,并且根据 `enum` 类型的 `key` 直接定位到内部数组的索引,并不需要计算 `hashCode()`,**不但效率最高,而且没有额外的空间浪费**。 + +### Properties + +- Properties 文件是 `k-v`,即 `String - String`,格式的。 +- Java 默认配置文件以 `.properties` 为扩展名。 +- 每行以 `key = value` 表示,以 `#` 开头的是注释。 + +用 Properties 读取配置文件示例: + +```java +String f = "setting.properties"; +Properties props = new Properties(); +props.load(new java.io.FileInputStream(f)); + +String filepath = props.getProperty("last_open_file"); +String interval = props.getProperty("auto_save_interval",s 120"); +``` + +可见,用 `Properties` 读取配置文件,一共有三步: + +- 创建 `Properties` 实例; +- 调用 `load()` 读取文件; +- 调用 `getProperty()` 获取配置。 + +通过 `setProperty()` 修改了 `Properties` 实例,可以把配置写入文件,以便下次启动时获得最新配置。写入配置文件使用 `store()` 方法: + +```java +Properties props = new Properties(); +props.setProperty("url", "http://www.liaoxuefeng.com"); +props.setProperty("language", "Java"); +props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释"); +``` + +### Set + +常用方法: + +- 将元素添加进 `Set`:`boolean add(E e)` +- 将元素从 `Set` 删除:`boolean remove(Object e)` +- 判断是否包含元素: `boolean contains(Object e)` + +### Queue + +Queue 实际上是实现了一个先进先出(FIFO:First In First Out)的有序表。它只支持: + +- 从头部取出元素 +- 从尾部添加元素 + +常用方法: + +- `int size()`:获取队列长度; +- `boolean add(E)/boolean offer(E)`:添加元素到队尾; +- `E remove()/E poll()`:获取队首元素并从队列中删除; +- `E element()/E peek()`:获取队首元素但并不从队列中删除。 + +## 日期与时间 + +### 本地化 + +- 在计算机中,通常使用 `Locale` 表示一个国家或地区的日期、时间、数字、货币等格式。`Locale` 由 **语言_国家** 的字母缩写构成,例如,`zh_CN` 表示中文 + 中国,`en_US` 表示英文 + 美国。语言使用小写,国家使用大写。 +- `System.currentTimeMillis()`,当前时间毫秒数,从 1970 年开始。 + +### API + +- 一套定义在 `java.util` 这个包里面,主要包括 `Date`、`Calendar` 和 `TimeZone` 这几个类; +- 一套新的 `API` 是在 Java 8 引入的,定义在 `java.time` 这个包里面,主要包括 `LocalDateTime`、`ZonedDateTime`、`ZoneId` 等。 + +### 旧 API + +#### (一)Date 类 + +`java.util.Date` 是用于表示一个日期和时间的对象,注意与 `java.sql.Date` 区分,后者用在数据库中。如果观察 `Date` 的源码,可以发现它实际上存储了一个 `long` 类型的以毫秒表示的时间戳: + +- 不能转换时区,`Date` 总是以当前计算机系统的默认时区为基础进行输出。 + +```java +public class Main { + public static void main(String[] args) { + // 获取当前时间: + Date date = new Date(); + System.out.println(date.getYear() + 1900); // 必须加上1900 + System.out.println(date.getMonth() + 1); // 0~11,必须加上1 + System.out.println(date.getDate()); // 1~31,不能加1 + // 转换为String: + System.out.println(date.toString()); + // 转换为GMT时区: + System.out.println(date.toGMTString()); + // 转换为本地时区: + System.out.println(date.toLocaleString()); + } +} +``` + +**注意:** + +- `getYear()`,返回的年份必须加上 1900。 +- `getMonth()`,返回的月份是 0 ~ 11,分别表示 1 ~ 12 月,所以要加 1。 +- `getDate()`,返回的日期范围是 1 ~ 31,又不能加 1。 + +`SimpleDateFormat` 可以对 `Date` 进行格式化转换。它用预定义的字符串表示格式化: + +- `yyyy`:年 +- `MM`:月 +- `dd`: 日 +- `HH`: 小时 +- `mm`: 分钟 +- `ss`: 秒 + +示例: + +```java +public class Main { + public static void main(String[] args) { + // 获取当前时间: + Date date = new Date(); + var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + System.out.println(sdf.format(date)); + } +} +``` + +#### (二)Calendar 类 + +`Calendar` 可以用于获取并设置年、月、日、时、分、秒,它和 `Date` 比,主要**多了一个可以做简单的日期和时间运算的功能**。 + +```java +public class Main { + public static void main(String[] args) { + // 获取当前时间: + Calendar c = Calendar.getInstance(); + int y = c.get(Calendar.YEAR); + int m = 1 + c.get(Calendar.MONTH); + int d = c.get(Calendar.DAY_OF_MONTH); + int w = c.get(Calendar.DAY_OF_WEEK); + int hh = c.get(Calendar.HOUR_OF_DAY); + int mm = c.get(Calendar.MINUTE); + int ss = c.get(Calendar.SECOND); + int ms = c.get(Calendar.MILLISECOND); + System.out.println(y + "-" + m + "-" + d + " " + w + " " + hh + ":" + mm + ":" + ss + "." + ms); + } +} +``` + +**注意:** + +- 返回的月份是 0 ~ 11,要加 1。 +- 返回的星期要特别注意,1 ~ 7 分别表示周日,周一,……,周六。 + +`Calendar` 只有一种方式获取,即 `Calendar.getInstance()`,而且一获取到就是当前时间。如果我们想给它设置成特定的一个日期和时间,就必须先清除所有字段: + +```java +public class Main { + public static void main(String[] args) { + // 当前时间: + Calendar c = Calendar.getInstance(); + // 清除所有: + c.clear(); + // 设置2019年: + c.set(Calendar.YEAR, 2019); + // 设置9月:注意8表示9月: + c.set(Calendar.MONTH, 8); + // 设置2日: + c.set(Calendar.DATE, 2); + // 设置时间: + c.set(Calendar.HOUR_OF_DAY, 21); + c.set(Calendar.MINUTE, 22); + c.set(Calendar.SECOND, 23); + System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getTime())); + // 2019-09-02 21:22:23 + } +} +``` + +**注意:** + +- 利用 `Calendar.getTime()` 可以将一个 `Calendar` 对象转换成 `Date` 对象,然后就可以用 `SimpleDateFormat` 进行格式化了。 + +#### (三)TimeZone 类 + +`Calendar` 和 `Date` 相比,它提供了时区转换的功能。时区用 `TimeZone` 对象表示: + +```java +public class Main { + public static void main(String[] args) { + TimeZone tzDefault = TimeZone.getDefault(); // 当前时区 + TimeZone tzGMT9 = TimeZone.getTimeZone("GMT+09:00"); // GMT+9:00时区 + TimeZone tzNY = TimeZone.getTimeZone("America/New_York"); // 纽约时区 + System.out.println(tzDefault.getID()); // Asia/Shanghai + System.out.println(tzGMT9.getID()); // GMT+09:00 + System.out.println(tzNY.getID()); // America/New_York + } +} +``` + +- 时区的唯一标识是以字符串表示的 ID,我们获取指定 `TimeZone` 对象也是以这个 ID 为参数获取。 +- `GMT+09:00`、`Asia/Shanghai` 都是有效的时区 ID。 +- 要列出系统支持的所有 ID,请使用 `TimeZone.getAvailableIDs()`。 + +有了时区,我们就可以对指定时间进行转换。例如,下面的例子演示了如何将北京时间 `2019-11-20 8:15:00` 转换为纽约时间: + +```java +public class Main { + public static void main(String[] args) { + // 当前时间: + Calendar c = Calendar.getInstance(); + // 清除所有: + c.clear(); + // 设置为北京时区: + c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); + // 设置年月日时分秒: + c.set(2019, 6 /* 11月 */, 20, 8, 15, 0); + // 加5天并减去2小时: + c.add(Calendar.DAY_OF_MONTH, 5); + c.add(Calendar.HOUR_OF_DAY, -2); + // 显示时间: + var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getTimeZone("America/New_York")); + System.out.println(sdf.format(c.getTime())); + // 2019-07-24 18:15:00 + } +} +``` + +可见,利用 `Calendar` 进行时区转换的步骤是: + +- 清除所有字段; +- 设定指定时区; +- 设定日期和时间; +- 创建 `SimpleDateFormat` 并设定目标时区; +- 格式化获取的Date对象(注意Date对象无时区信息,时区信息存储在 `SimpleDateFormat` 中)。 + +因此,本质上时区转换只能通过 `SimpleDateFormat` 在显示的时候完成。 + +### 新 API + +从 Java 8 开始,`java.time` 包提供了新的日期和时间 API,主要涉及的类型有: + +- 本地日期和时间:`LocalDateTime`,`LocalDate`,`LocalTime`; +- 带时区的日期和时间:`ZonedDateTime`; +- 时刻:`Instant`; +- 时区:`ZoneId`,`ZoneOffset`; +- 时间间隔:`Duration`。 +- 以及一套新的用于取代 `SimpleDateFormat` 的格式化类型 `DateTimeFormatter`。 + +和旧的 API 相比,新 API 严格区分了时刻、本地日期、本地时间和带时区的日期时间,并且,对日期和时间进行运算更加方便。 + +此外,新 API 修正了旧 API 不合理的常量设计: + +- `Month` 的范围用 1 ~ 12 表示 1 月到 12 月; +- `Week` 的范围用 1 ~ 7 表示周一到周日。 + +最后,新 API 的类型几乎全部是不变类型(和 `String` 类似),可以放心使用不必担心被修改。 + +#### (一)LocalDateTime 类 + +- now 获取时间 +- toLocalDate 转换格式 +- of 指定时间 +- withXxx() 调整时间 +- 使用 DateTimeFormatter 进行格式的转换 + - 示例 + + ```java + // 自定义格式化: + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); + System.out.println(dtf.format(LocalDateTime.now())); + + // 用自定义格式解析: + LocalDateTime dt2 = LocalDateTime.parse("2019/11/30 15:16:17", dtf); + System.out.println(dt2); + ``` + +#### (二)ZonedDateTime 类 + +可以简单地把 `ZonedDateTime` 理解成 `LocalDateTime` 加 `ZoneId`。`ZoneId` 是 `java.time` 引入的新的时区类,注意和旧的 `java.util.TimeZone` 区别。 + +#### (三)DateTimeFormatter 类 + +#### (四)Instant 类 + +## IO + +- flush(),强制刷新 +- Class 对象的 `getResourceAsStream()` 可以从 `classpath` 中读取指定资源; + +### 序列化 + +序列化是指把一个 `Java` 对象变成二进制内容,本质上就是一个 `byte[]` 数组。 + +为什么要把 Java 对象序列化呢?因为序列化后可以把 `byte[]` 保存到文件中,或者把 `byte[]` 通过网络传输到远程,这样,就相当于把 Java 对象存储到文件或者通过网络传输出去了。 + +有序列化,就有反序列化,即把一个二进制内容(也就是 `byte[]` 数组)变回 Java 对象。有了反序列化,保存到文件中的 `byte[]` 数组又可以“变回”Java 对象,或者从网络上读取 `byte[]` 并把它“变回” Java 对象。 + +## 反射 + +通过 `Class` 实例获取 `class` 信息的方法称为反射(`Reflection`)。 + +### Class 类 + +- class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。 + +- 获取一个class的Class实例?有三个方法: + - 直接通过一个class的静态变量class获取: + - `Class cls = String.class;` + - 通过该实例变量提供的getClass()方法获取: + + ```java + String s = "Hello"; + Class cls = s.getClass(); + ``` + + - 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取: + - `Class cls = Class.forName("java.lang.String");` + +### 访问字段 + +通过 `Class` 实例获取字段信息。 + +- `Field getField(name)`:根据字段名获取某个 `public` 的 `field` (包括父类) +- `Field getDeclaredField(name)`:根据字段名获取当前类的某个 `field` (不包括父类) +- `Field[] getFields()`:获取所有 `public` 的 `field` (包括父类) +- `Field[] getDeclaredFields()`:获取当前类的所有 `field` (不包括父类) + +## 多线程 + +### 多进程和多线程 + +多进程缺点: + +- 创建进程比创建线程开销大,尤其是在 Windows 系统上; +- 进程间通信比线程间通信要慢,因为线程间通信就是读写同一个变量,速度很快。 + +多线程优点: + +- 多进程稳定性比多线程高 + - 多进程的情况下,一个进程崩溃不会影响其他进程。 + - 多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。 + +### 创建多线程 + +两个方法: + +1. 继承 Tread 类,然后覆写 run 方法,最后使用 start 方法执行。 +2. 实现 Runable 接口,然后使用 start 方法进行执行。 + +建议使用第二种方法,因为类的继承为单继承,第 2 种可以避免不能继承其他的类。 + +示例: + +```java +// 第一种,继承 Tread 类 +public class Main { + public static void main(String[] args) { + Thread t = new MyThread(); + t.start(); // 启动新线程 + } +} + +class MyThread extends Thread { + @Override + public void run() { + System.out.println("start new thread!"); + } +} +``` + +```java +// 第二种,实现 Runable 接口 +public class Main { + public static void main(String[] args) { + Thread t = new Thread(new MyRunnable()); + t.start(); // 启动新线程 + } +} + +class MyRunnable implements Runnable { + @Override + public void run() { + System.out.println("start new thread!"); + } +} +``` + +### 线程优先级 + +- `Thread.setPriority(int n)` ,1 ~ 10, 默认值 5,1 为最低 +- 优先级高的只意味着更频繁的 CPU 调度,而不是说优先级高的就要比优先级低的早执行。 + +### 线程状态 + +在 Java 程序中,一个线程对象只能调用一次 `start()` 方法启动新线程,并在新线程中执行 `run()` 方法。 + +一旦 `run()` 方法执行完毕,线程就结束了。因此,Java 线程的状态有以下几种: + +- `New` + - 新创建的线程,尚未执行; +- `Runnable` + - 运行中的线程,正在执行 `run()` 方法的 Java 代码; +- `Blocked` + - 运行中的线程,因为某些操作被阻塞而挂起; +- `Waiting` + - 运行中的线程,因为某些操作在等待中; +- `Timed Waiting` + - 运行中的线程,因为执行 `sleep()` 方法正在计时等待; +- `Terminated` + - 线程已终止,因为 `run()` 方法执行完毕。 +- 即 + + ```bash + ┌─────────────┐ + │ New │ + └─────────────┘ + │ + ▼ + ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ + ┌─────────────┐ ┌─────────────┐ + ││ Runnable │ │ Blocked ││ + └─────────────┘ └─────────────┘ + │┌─────────────┐ ┌─────────────┐│ + | Waiting │ │Timed Waiting│ + │└─────────────┘ └─────────────┘│ + ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + │ + ▼ + ┌─────────────┐ + │ Terminated │ + └─────────────┘ + ``` + +线程终止的原因有: + +- 线程正常终止:`run()` 方法执行到 `return` 语句返回; +- 线程意外终止:`run()` 方法因为未捕获的异常导致线程终止; +- 对某个线程的 `Thread` 实例调用 `stop()` 方法强制终止(强烈不推荐使用)。 +- `join()` 可以使得当前线程的方法优先执行。 + +### 中断线程 + +- 使用 `interrupt()` 中断线程,使用 `isInterrupted()` 检测是否中断; + +`volatile` 关键字的目的是告诉虚拟机: + +- 每次访问变量时,总是获取主内存的最新值; +- 每次修改变量后,立刻回写到主内存。 + +### 守护线程 + +`Daemon Thread`,守护线程是指为其他线程服务的线程。 + +在 JVM 中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。因此, JVM 退出时,不必关心守护线程是否已结束。 + +创建方式: + +- 在 Daemon 线程中产生的新线程也是 Daemon 的。 +- 在调用 `start()` 方法前,调用 `setDaemon(true)` 把该线程标记为守护线程。 +- 示例: + + ```java + Thread t = new MyThread(); + t.setDaemon(true); + t.start(); + ``` + +守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失。 + +### 线程同步 + +加锁与解锁之间的代码块--临界区 + +#### 加锁 + +`synchronized` 锁是可重入锁; + +我们来概括一下如何使用 `synchronized`: + +- 找出修改共享变量的线程代码块; +- 选择一个共享实例作为锁; +- 使用 `synchronized(lockObject) { ... }`。 + +不需要 `synchronized` 的操作 +JVM 规范定义了几种原子操作: + +- 基本类型(`long` 和 `double` 除外)赋值,例如:`int n = m;` +- 引用类型赋值,例如:`List list = anotherList`。 + +### 死锁 + +如何避免死锁呢? + +- 线程获取锁的顺序要一致。 + +### wait 和 notify + +- `wait()` 方法必须在当前获取的锁对象上调用,这里获取的是 `this` 锁,因此调用 `this.wait()`。 +- `wait()` 方法调用时,会释放线程获得的锁,`wait()` 方法返回后,线程又会重新试图获得锁。 +- 如何让等待的线程被重新唤醒,然后从 `wait()` 方法返回? + - 答案是在相同的锁对象上调用 `notify()` 方法。 + +### 线程池 + +`ExecutorService` 接口表示线程池 + +线程池的初始化 + +`ExecutorService` 只是接口,Java 标准库提供的几个常用实现类有: + +- `FixedThreadPool`:线程数固定的线程池; +- `CachedThreadPool`:线程数根据任务动态调整的线程池; +- `SingleThreadExecutor`:仅单线程执行的线程池。 + +创建这些线程池的方法都被封装到 `Executors` 这个类中。 + +线程池的使用 + +- 使用 `submit()` 方法提交任务,提交的任务是已经实现了 Runnable 接口的类。 + +线程池的关闭 + +- 使用 `shutdown()`方法关闭线程池 + - 它会等待正在执行的任务先完成,然后再关闭。 +- `shutdownNow()` 会立刻停止正在执行的任务 +- `awaitTermination()` 则会等待指定的时间让线程池关闭。 + +示例: + +```java +ExecutorService es = Executors.newFixedThreadPool(5); + for (int i = 0; i < 7; i++) { + es.submit(new Task("第" + i + "个任务")); + } +es.shutdown(); +``` + +```java +class Task implements Runnable { + private String name; + + public Task(String name) { + this.name = name; + } + + @Override + public void run() { + System.out.println("启动了" + name); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + return; + } + System.out.println("结束" + name); + } +} +``` + +### ThreadLocal + +ThreadLocal 实例通常总是以**静态字段**初始化: + +格式:`static ThreadLocal threadLocalUser = new ThreadLocal<>();` + +特别注意ThreadLocal一定要在finally中清除: + +```java +try { + threadLocalUser.set(user); + ... +} finally { + threadLocalUser.remove(); +} +``` + +## Maven 基础 + +### 结构 + +项目结构: + +```bash +a-maven-project +├── pom.xml +├── src +│ ├── main +│ │ ├── java +│ │ └── resources +│ └── test +│ ├── java +│ └── resources +└── target +``` + +- 项目的根目录 `a-maven-project` 是项目名 +- 项目描述文件 `pom.xml` +- 存放 `Java` 源码的目录是 `src/main/java` +- 存放资源文件的目录是 `src/main/resources` +- 存放测试源码的目录是 `src/test/java` +- 存放测试资源的目录是 `src/test/resources` +- 所有编译、打包生成的文件都放在 `target` 目录里。 + +这些就是一个 Maven 项目的标准目录结构。 + +pom.xml 文件格式: + +```xml + + 4.0.0 + com.itranswarp.learnjava + hello + 1.0 + jar + + ... + + + + commons-logging + commons-logging + 1.2 + + + +``` + +- `groupId` 类似于 Java 的包名,通常是公司或组织名称 +- `artifactId` 类似于 Java 的类名,通常是项目名称 +- 再加上 `version` + +一个 Maven 工程就是由 `groupId`,`artifactId` 和 `version` 作为唯一标识。我们在引用其他第三方库的时候,也是通过这 3 个变量确定。 + +### 依赖关系 + +- `compile`,编译时需要用到该 jar 包(默认) +- `test`,编译Test时需要用到该 jar 包 +- `runtime`,编译时不需要,但运行时需要用到 +- `provided`,编译时需要用到,但运行时由 JDK 或某个服务器提供 + +### 阿里云镜像 + +在用户主目录下进入.m2目录,创建一个settings.xml配置文件,内容如下: + +```xml + + + + aliyun + aliyun + central + + https://maven.aliyun.com/repository/central + + + +``` + +### 常用命令 + +在实际开发过程中,经常使用的命令有: + +- `mvn clean` + - 清理所有生成的 class 和 jar; +- `mvn clean compile` + - 先清理,再执行到 compile; +- `mvn clean test` + - 先清理,再执行到 test,因为执行 test 前必须执行 compile,所以这里不必指定 compile; +- `mvn clean package` + - 先清理,再执行到package。 + +### Maven 插件 + +## 网络编程 + +### HTTP 编程 + +## XML 和 JSON + +### JSON + +JSON 作为数据传输的格式,有几个显著的优点: + +- JSON 只允许使用 UTF-8 编码,不存在编码问题; +- JSON 只允许使用双引号作为 key,特殊字符用 \ 转义,格式简单; +- 浏览器内置 JSON 支持,如果把数据用 JSON 发送给浏览器,可以用 JavaScript 直接处理。 + +因此,JSON适合表示层次结构,因为它格式简单,仅支持以下几种数据类型: + +- 键值对:`{"key": value}` +- 数组:`[1, 2, 3]` +- 字符串:`"abc"` +- 数值(整数和浮点数):`12.34` +- 布尔值:`true` 或 `false` +- 空值:`null` + +## 函数式编程 + +### 函数式接口 + +单方法接口被称为 `FunctionalInterface`。 + +### 方法引用 diff --git a/ES.md b/js/ES.md similarity index 99% rename from ES.md rename to js/ES.md index 9054b83e69cb5aa02b544eff2538f65d74f8d43b..b50cca8fdb2d10c9336d6f76e79b898982063260 100644 --- a/ES.md +++ b/js/ES.md @@ -1,5 +1,9 @@ # ES 笔记 +## 参考文档 + +- [网道 JS](https://wangdoc.com/javascript/) + ## let 和 const 命令 ### let 命令 diff --git a/JS.md b/js/JS.md similarity index 99% rename from JS.md rename to js/JS.md index ba31c080595794d97fbe03525607c32cbc3a7617..8ee14e0ba5856e53ca7cdba1de2a27b329011240 100644 --- a/JS.md +++ b/js/JS.md @@ -1,5 +1,9 @@ # JS 笔记 +## 参考文档 + +- [网道 ES6](https://wangdoc.com/es6/) + ## 基本语法 ### 语句 diff --git "a/linux/Linux \345\221\275\344\273\244\350\241\214.md" "b/linux/Linux \345\221\275\344\273\244\350\241\214.md" new file mode 100644 index 0000000000000000000000000000000000000000..61eb3d48bba07428f115a00b3e289d48858becce --- /dev/null +++ "b/linux/Linux \345\221\275\344\273\244\350\241\214.md" @@ -0,0 +1,40 @@ +# Linux 命令行 + +## 参考文档 + +- [Linux 命令行 Book](http://billie66.github.io/TLCL/book/index.html) + +## systemctl + +系统服务控制,systemd control,它提供了一组子命令来管理单个的 unit,其命令格式为: + +`systemctl [command] [unit]` + +command 主要有: + +- `start`:立刻启动后面接的 unit。 +- `stop`:立刻关闭后面接的 unit。 +- `restart`:立刻关闭后启动后面接的 unit,亦即执行 stop 再 start 的意思。 +- `reload`:不关闭 unit 的情况下,重新载入配置文件,让设置生效。 +- `enable`:设置下次开机时,后面接的 unit 会被启动。 +- `disable`:设置下次开机时,后面接的 unit 不会被启动。 +- `status`:目前后面接的这个 unit 的状态,会列出有没有正在执行、开机时是否启动等信息。 +- `is-active`:目前有没有正在运行中。 +- `is-enable`:开机时有没有默认要启用这个 unit。 +- `kill`:不要被 kill 这个名字吓着了,它其实是向运行 unit 的进程发送信号。 +- `show`:列出 unit 的配置。 +- `mask`:注销 unit,注销后你就无法启动这个 unit 了。 +- `unmask`:取消对 unit 的注销。 + +## curl + +`CommandLine URL` 或 `CommandLine Uniform Resource Locator`,顾名思义,`curl` 命令是在命令行方式下工作,利用 `URL` 的语法进行数据的传输或者文件的传输。 + +- -X 选项,指定请求方式,包括 `GET`、`PUT`、`POST`、`DELETE` 四种方式。 + + ```bash + curl -XGET www.baidu.com + curl -XPOST www.baidu.com + curl -XDELETE www.baidu.com + curl -XPUT www.baidu.com + ``` diff --git a/markdown/Markdown.md b/markdown/Markdown.md new file mode 100644 index 0000000000000000000000000000000000000000..feeec6a7db2139b0fdf72d48d2d452b29b448ed4 --- /dev/null +++ b/markdown/Markdown.md @@ -0,0 +1,5 @@ +# Markdown 笔记 + +## 参考文档 + +- [Markdown]() diff --git a/MongoDB.md b/mongodb/MongoDB.md similarity index 94% rename from MongoDB.md rename to mongodb/MongoDB.md index 44d64b6fbc36dc29aabe7378c4a765879952fbb0..1406829e67b71119babb75ba3e75681f09bb9940 100644 --- a/MongoDB.md +++ b/mongodb/MongoDB.md @@ -1,5 +1,9 @@ # MongoDB +## 参考文档 + +- [MongoDB](https://docs.mongodb.com/manual/) + ## BSON ### 基本类型 diff --git "a/\351\233\266\346\230\237.md" "b/other/\351\233\266\346\230\237.md" similarity index 100% rename from "\351\233\266\346\230\237.md" rename to "other/\351\233\266\346\230\237.md" diff --git a/Redis.md b/redis/Redis.md similarity index 93% rename from Redis.md rename to redis/Redis.md index 6b9f1452a2737cad6e2f1bf17f235abd7c255f9d..ec0f7fbdda2c66e203b83c3d540da9151b92c644 100644 --- a/Redis.md +++ b/redis/Redis.md @@ -1,6 +1,8 @@ # Redis 笔记 -Redis [中文文档地址](https://www.redis.com.cn/) +## 参考文档 + +- [Redis 中文](https://www.redis.com.cn/) ## 简介 @@ -144,6 +146,16 @@ Redis 主要由有两个程序组成: ## Redis 分布式锁 +### 算法最低保证 + +算法只需具备3个特性就可以实现一个最低保障的分布式锁。 + +- 安全属性: 独享(相互排斥)。在任一时刻,只有一个客户端持有锁。 +- 活性 A:无死锁。即便持有锁的客户端崩溃或者网络分裂等,锁依然可以被拿到。 +- 活性 B:容错。只要大部分 Redis 节点活着,客户端就可以获取和释放锁。 + +**概括:** 独享、无死锁、容错。 + ## 配置文件 Redis 的根目录中有一个配置文件(redis.conf)。 diff --git a/spring-boot/Spring Boot.md b/spring-boot/Spring Boot.md new file mode 100644 index 0000000000000000000000000000000000000000..50a04dcaa8ded258731f77d5c856cc9383210d98 --- /dev/null +++ b/spring-boot/Spring Boot.md @@ -0,0 +1,5 @@ +# Spring Boot 开发笔记 + +## 参考文档 + +- [廖雪峰 Spring Boot 开发](https://www.liaoxuefeng.com/wiki/1252599548343744/1255945497738400) diff --git a/spring/Spring.md b/spring/Spring.md new file mode 100644 index 0000000000000000000000000000000000000000..b55edf2025fb6ca91afcf421d0e0f79535666b3b --- /dev/null +++ b/spring/Spring.md @@ -0,0 +1,267 @@ +# Spring 开发 + +## 参考文档 + +- [廖雪峰 Spring 开发](https://www.liaoxuefeng.com/wiki/1252599548343744/1266263217140032) + +## IoC 容器 + +IoC 全称 `Inversion of Control`,直译为控制反转。 + +### 原理 + +实际上是通过反射实现的,由框架实现创建 Bean。 + +### 装配 Bean + +示例: + +```xml + + + + + + + + + +``` + +上述配置文件,其中与 XML Schema 相关的部分格式是固定的,我们只关注两个 ``的配置: + +- 每个 `` 都有一个 id 标识,相当于 Bean 的唯一 ID; +- 在 `userServiceBean` 中,通过 `` 注入了另一个 Bean; +- Bean 的顺序不重要,Spring 根据依赖关系会自动正确初始化。 + +把上述XML配置文件用 Java 代码写出来,就像这样: + +```java +UserService userService = new UserService(); +MailService mailService = new MailService(); +userService.setMailService(mailService); +``` + +只不过 Spring 容器是通过读取 XML 文件后使用反射完成的。 + +### 使用注解配置 + +- `@Component` 注解就相当于定义了一个 Bean,它有一个可选的名称,默认是小写开头的类名。 +- `@Autowired` 就相当于把指定类型的 Bean 注入到指定的字段中。 +- `@ComponentScan`,它告诉容器,自动搜索当前类所在的包以及子包,把所有标注为 `@Component` 的 Bean 自动创建出来,并根据 `@Autowired` 进行装配。 +- 示例: + + ```java + @Component + public class UserService { + @Autowired + MailService mailService; + + ... + } + ``` + +### 定制 Bean + +有两种 Bean: + +- `singleton`,单例 + - 容器初始化时创建 Bean,容器关闭前销毁 Bean。 + - 在容器运行期间,调用 `getBean(Class)` 获取到的 Bean 总是同一个实例。 +- `prototype`,原型 + - 每次调用 `getBean(Class)`,容器都返回一个新的实例。 + - 声明一个 `Prototype` 的 Bean 时,需要添加一个额外的 `@Scope` 注解。 + - 示例: + + ```java + @Component + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype") + public class MailSession { + ... + } + ``` + +#### 创建第三方 Bean + +如果一个 Bean 不在我们自己的 package 管理之内,例如 ZoneId,如何创建它? + +答案是我们自己在 `@Configuration` 类中编写一个 Java 方法创建并返回它,注意给方法标记一个 `@Bean` 注解: + +```java +@Configuration +@ComponentScan +public class AppConfig { + // 创建一个Bean: + @Bean + ZoneId createZoneId() { + return ZoneId.of("Z"); + } +} +``` + +Spring 对标记为 `@Bean` 的方法只调用一次,因此返回的 Bean 仍然是单例。 + +### 使用 FactoryBean + +- Spring 也提供了工厂模式,允许定义一个工厂,然后由工厂创建真正的 Bean。 +- 用工厂模式创建 Bean 需要实现 `FactoryBean` 接口。 + +### 使用 Resource + +- Spring 提供了一个 `org.springframework.core.io.Resource` +- **注意**不是 `javax.annotation.Resource` +- 它可以像 String、int 一样使用 `@Value`注入 + + ```java + @Component + public class AppService { + @Value("classpath:/logo.txt") + private Resource resource; + + private String logo; + + @PostConstruct + public void init() throws IOException { + try (var reader = new BufferedReader( + new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) { + this.logo = reader.lines().collect(Collectors.joining("\n")); + } + } + } + ``` + +- 注入 Resource 最常用的方式是通过 classpath, +- 即类似 `classpath:/logo.txt` 表示在 classpath 中搜索 logo.txt 文件 +- 然后,我们直接调用 `Resource.getInputStream()` 就可以获取到输入流,避免了自己搜索文件的代码。 + +也可以直接指定文件的路径,例如: + +```java +@Value("file:/path/to/logo.txt") +private Resource resource; +``` + +但使用 classpath 是最简单的方式。 + +上述工程结构如下: + +```bash +spring-ioc-resource +├── pom.xml +└── src + └── main + ├── java + │ └── com + │ └── itranswarp + │ └── learnjava + │ ├── AppConfig.java + │ └── AppService.java + └── resources + └── logo.txt +``` + +使用Maven的标准目录结构,所有资源文件放入 `src/main/resources` 即可。 + +### 注入配置 + +先使用 `@PropertySource` 读取配置文件,然后通过 `@Value` 以 `${key:defaultValue}` 的形式注入,可以极大地简化读取配置的麻烦。 + +- `@PropertySource` 来自动读取配置文件 +- Spring 容器看到 `@PropertySource("app.properties")` 注解后,自动读取这个配置文件,然后使用 `@Value` 正常注入 + +```java +@Configuration +@ComponentScan +@PropertySource("app.properties") // 表示读取classpath的app.properties +public class AppConfig { + @Value("${app.zone:Z}") + String zoneId; + + @Bean + ZoneId createZoneId() { + return ZoneId.of(zoneId); + } +} +``` + +注入格式: + +- `${app.zone}` 表示读取 key 为 `app.zone` 的 value,如果 key 不存在,启动将报错; +- `${app.zone:Z}` 表示读取 key 为 `app.zone` 的 value,但如果 key 不存在,就使用默认值 Z。 + +`#` 与 `$` 的区别 + +- `#{}` 这种注入语法,它和 `${key}` 不同的是,`#{}` 表示从 JavaBean 读取属性。 +- `#{smtpConfig.host}` 的意思是,从名称为 `smtpConfig` 的 Bean 读取 host 属性,即调用 `getHost()` 方法。一个 Class 名为 `SmtpConfig` 的 Bean,它在 Spring 容器中的默认名称就是 smtpConfig,除非用 `@Qualifier` 指定了名称。 + +### 使用条件装配 + +```java +@Configuration +@ComponentScan +public class AppConfig { + @Bean + @Profile("!test") + ZoneId createZoneId() { + return ZoneId.systemDefault(); + } + + @Bean + @Profile("test") + ZoneId createZoneIdForTest() { + return ZoneId.of("America/New_York"); + } +} +``` + +- 如果当前的 Profile 设置为 test,则 Spring 容器会调用 `createZoneIdForTest()` 创建 ZoneId,否则,调用 `createZoneId()` 创建 ZoneId。注意到 `@Profile("!test")` 表示非 test 环境。 +- 在运行程序时,加上 JVM 参数 `-Dspring.profiles.active=test` 就可以指定以 test 环境启动。 + +实际上,Spring 允许指定多个 Profile,例如: + +- `-Dspring.profiles.active=test,master` +- 可以表示 test 环境,并使用 master 分支代码。 +- 要满足多个 Profile 条件,可以这样写: + + ```java + @Bean + @Profile({ "test", "master" }) // 同时满足test和master + ZoneId createZoneId() { + ... + } + ``` + +## AOP + +- `Aspect Oriented Programming`,即面向切面编程。 + +## 使用 Interceptor + +- 使用 `Interceptor` 的好处是 `Interceptor` 本身是 Spring 管理的 Bean,因此注入任意 Bean 都非常简单。此外,可以应用多个 `Interceptor`,并通过简单的 `@Order` 指定顺序。 +- Interceptor 仅针对 Controller 拦截。 +- 编写一个 Interceptor 首先必须实现 `HandlerInterceptor` 接口。 +- 可以选择实现 `preHandle()`、`postHandle()` 和 `afterCompletion()` 方法。 + - `preHandle()` 是 `Controller` 方法调用前执行 + - 在 `postHandle()` 中,因为捕获了 `Controller` 方法返回的 `ModelAndView`,所以可以继续往 `ModelAndView` 里添加一些通用数据,很多页面需要的全局数据如 `Copyright` 信息等都可以放到这里,无需在每个 `Controller` 方法中重复添加。 + + - `postHandle()` 是 `Controller` 方法正常返回后执行 + - `afterCompletion()` 无论 `Controller` 方法是否抛异常都会执行 + - 参数 ex 就是 `Controller` 方法抛出的异常(未抛出异常是 null)。 +- 最后,要让拦截器生效,在 `WebMvcConfigurer` 中注册所有的 `Interceptor`。 + + ```java + @Bean + WebMvcConfigurer createWebMvcConfigurer(@Autowired HandlerInterceptor[] interceptors) { + return new WebMvcConfigurer() { + public void addInterceptors(InterceptorRegistry registry) { + for (var interceptor : interceptors) { + registry.addInterceptor(interceptor); + } + } + ... + }; + } + ``` diff --git a/Vue.md b/vue/Vue.md similarity index 98% rename from Vue.md rename to vue/Vue.md index c02e37d3d9de5bbf7fa08a3dc0284988dfcaf0c5..3e844696845ec5162fbac1d397668c3560ef9fa2 100644 --- a/Vue.md +++ b/vue/Vue.md @@ -1,5 +1,10 @@ # Vue 笔记 +## 参考文档 + +- [Vue v2](https://cn.vuejs.org/v2/guide/) +- [Vue Router](https://router.vuejs.org/zh/guide/) + ## 注意 - 不要在选项 `property` 或回调上使用箭头函数, diff --git a/web-develop/Web Develop.md b/web-develop/Web Develop.md new file mode 100644 index 0000000000000000000000000000000000000000..d9d509c1fa92eb736dd5e349a027e734183d880c --- /dev/null +++ b/web-develop/Web Develop.md @@ -0,0 +1,82 @@ +# Web 开发笔记 + +## 参考文档 + +- [廖雪峰 Web 开发](https://www.liaoxuefeng.com/wiki/1252599548343744/1255945497738400) + +## Servlet 入门 + +### 结构 + +浏览器发出的 HTTP 请求总是由 `Web Server` 先接收,然后,根据 Servlet 配置的映射,不同的路径转发到不同的 Servlet(这里一般指的是实现了Servlet 接口的类): + +```bash + ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ + + │ /hello ┌───────────────┐│ + ┌──────────>│ HelloServlet │ + │ │ └───────────────┘│ +┌───────┐ ┌──────────┐ │ /signin ┌───────────────┐ +│Browser│───>│Dispatcher│─┼──────────>│ SignInServlet ││ +└───────┘ └──────────┘ │ └───────────────┘ + │ │ / ┌───────────────┐│ + └──────────>│ IndexServlet │ + │ └───────────────┘│ + Web Server + └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ +``` + +这种根据路径转发的功能我们一般称为 Dispatch。映射到 / 的 IndexServlet 比较特殊,它实际上会接收所有未匹配的路径,相当于 /*。 + +### Redirect 重定向 + +```java +@WebServlet(urlPatterns = "/hi") +public class RedirectServlet extends HttpServlet { +protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // 构造重定向的路径: + String name = req.getParameter("name"); + String redirectToUrl = "/hello" + (name == null ? "" : "?name=" + name); + // 发送重定向响应: + resp.sendRedirect(redirectToUrl); + } +} +``` + +如果浏览器发送GET /hi请求,RedirectServlet 将处理此请求。由于 RedirectServlet 在内部又发送了重定向响应,因此,浏览器会收到如下响应: + +```java +HTTP/1.1 302 Found +Location: /hello +``` + +当浏览器收到 302 响应后,它会立刻根据 Location 的指示发送一个新的 GET /hello 请求,这个过程就是重定向: + +### Forward 内部转发 + +Forward 是指内部转发。当一个 Servlet 处理请求的时候,它可以决定自己不继续处理,而是转发给另一个 Servlet 处理。 + +### MVC 架构 + +```bash + HTTP Request ┌─────────────────┐ +──────────────────>│DispatcherServlet│ + └─────────────────┘ + │ + ┌────────────┼────────────┐ + ▼ ▼ ▼ + ┌───────────┐┌───────────┐┌───────────┐ + │Controller1││Controller2││Controller3│ + └───────────┘└───────────┘└───────────┘ + │ │ │ + └────────────┼────────────┘ + ▼ + HTTP Response ┌────────────────────┐ +<────────────────│render(ModelAndView)│ + └────────────────────┘ +``` + +- M (model), V (view), C (controller) +- 一个请求进来以后,先到达 Dispatch,经过分发(按照每个方法上的注解地址(`@GetMapping('/url')`)或者路径转发),到达指定的 controller,然后进行逻辑操作。 +- 操作完毕,controller 接口返回一个 Model and View 模型,这个模型包含了返回的 Model,还有前端的 path(View)。 +- 将 Model and View 封装进 response 进行返回。