git&shell

This commit is contained in:
法然
2023-02-14 18:34:43 +08:00
parent a19f675f78
commit a4a57a92d2
37 changed files with 1683 additions and 482 deletions

View File

@@ -2,6 +2,11 @@ Git的本地操作
> 参考文献
> * [git使用教程](https://github.com/geeeeeeeeek/git-recipes/wiki)
>
> 总共有三种类型的主要操作
> * 版本操作,用于版本的迭代和管理。时间管理
> * 分支操作,用于实现分布式管理。空间管理
> * 远程操作,用于远程仓库交互实现多人合作。时空共享
## 1 git安装

374
Git/2 版本操作.md Normal file
View File

@@ -0,0 +1,374 @@
# 4 版本操作
## 0 命令手册
### git git add 文件名。
* 作用:将文件由 工作区 添加到 暂存区,暂存文件
* git add --all 或者 git add -A 或者git add .(简写) 添加所有文件
* git add a.txt b.txt 同时添加两个文件
* git add *.js 添加当前目录下的所有js文件
### git commit
* 作用:将文件由 暂存区 添加到 仓库区
* git commit -m "提交说明"
### git checkout 文件名
* 作用:暂存区的内容恢复到工作区。
* git checkout 1.txt 将暂存区中1.txt文件恢复到工作区
### git status
* 作用:查看文件的状态
* 命令:git status
* 命令:git stauts -s 简化日志输出格式
### git log
* 作用:查看提交日志
* git log 只能查看当前head以及以前的日志
* git log --oneline 简洁的日志信息
* git reflog 查看所有的提交变更日志
### git reset
* 作用:版本回退,将代码恢复到已经提交的某一个版本中。
* git reset --hard 版本号 将代码回退到某个指定的版本(版本号只要有前几位即可)
* git reset --hard head~1将版本回退到上一次提交
## 1 git的工作流
### 提交工作流
* 工作区:即自己当前分支所修改的代码,git add xx 之前的!不包括 git add xx 和 git commit xxx 之后的。
* 暂存区:已经 git add xxx 进去,且未 git commit xxx 的。
* 本地分支:已经git commit -m xxx 提交到本地分支的。
![](../Git/image/2021-06-15-11-04-57.png)
### 回滚工作流
在上传代码到远程仓库的时候,不免会出现问题,任何过程都有可能要回滚代码:
git reset的作用是修改HEAD的位置,即将HEAD指向的位置改变为之前存在的某个版本。
```
git reset --hard HEAD^
```
即往前回退一个版本,回退完了后工作区就是上一个版本的代码了,并且是clean的。
```
git reset --soft HEAD^
```
往前回退一个版本,并且将这次错误的提交的代码改动,放在暂存区里。
```
git reset --mixed HEAD^(和不带参数是一样的)
```
往前回退一个版本,并且将这次错误的提交的代码改动,放在工作区里。
## 2 进阶操作:Git撤销&回滚操作
git reset 和 get revert
### 在工作区的代码
```
git checkout -- a.txt # 丢弃某个文件,或者
git checkout -- . # 丢弃全部
```
* 注意:git checkout – . 丢弃全部,也包括:新增的文件会被删除、删除的文件会恢复回来、修改的文件会回去。
* 这几个前提都说的是,回到暂存区之前的样子。对之前保存在暂存区里的代码不会有任何影响。对commit提交到本地分支的代码就更没影响了。当然,如果你之前压根都没有暂存或commit,那就是回到你上次pull下来的样子了。
### 代码git add到缓存区,并未commit提交
```
git reset HEAD . 或者
git reset HEAD a.txt
```
* 这个命令仅改变暂存区,并不改变工作区,这意味着在无任何其他操作的情况下,工作区中的实际文件同该命令运行之前无任何变化
### git commit到本地分支、但没有git push到远程
```
git log # 得到你需要回退一次提交的commit id
git reset --hard commit_id # 回到其中你想要的某个版
```
或者
```
git reset --hard HEAD^ # 回到最新的一次提交
```
或者
```
git reset --mixed HEAD^ # 此时代码保留,回到 git add 之前,默认就是--mixed
```
* --hard分支、暂存区、工作区都回退到指定版本
* --mixed默认,只改变分支和暂存区的内容,不会改变工作区的内容。
* --soft分支回退到指定版本,不会改变暂存区和工作区。
### git push把修改提交到远程仓库
1. 通过git reset是直接删除指定的commit
```
git log # 得到你需要回退一次提交的commit id
git reset --hard
git push origin HEAD --force # 强制提交一次,之前错误的提交就从远程仓库删除
```
2. 通过git revert是用一次新的commit来回滚之前的commit
```
git log # 得到你需要回退一次提交的commit id
git revert # 撤销指定的版本,撤销也会作为一次提交进行保存
```
3. git revert 和 git reset的区别
- git revert是用一次新的commit来回滚之前的commit,此次提交之前的commit都会被保留;
- git reset是回到某次提交,提交及之前的commit都会被保留,但是此commit id之后的修改都会被删除
### 场景
* 场景一:糟了,我刚把不想要的代码,commit到本地仓库中了,但是还没有做push操作!
* 场景二:彻底完了,刚线上更新的代码出现问题了,需要还原这次提交的代码!
### 针对场景一
在未进行git push前的所有操作,都是在“本地仓库”中执行的。我们暂且将“本地仓库”的代码还原操作叫做“撤销”!
* 情况一:文件被修改了,但未执行git add操作(working tree内撤销)
```
git checkout fileName
git checkout .
```
* 情况二:同时对多个文件执行了git add操作,但本次只想提交其中一部分文件
```
$ git add *
$ git status
# 取消暂存
$ git reset HEAD
```
* 情况三:文件执行了git add操作,但想撤销对其的修改(index内回滚)
````
# 取消暂存
git reset HEAD fileName
# 撤销修改
git checkout fileName
````
* 情况四:修改的文件已被git commit,但想再次修改不再产生新的Commit
```
# 修改最后一次提交
$ git add sample.txt
$ git commit --amend -m"说明"
```
* 情况五:已在本地进行了多次git commit操作,现在想**撤销到**其中某次Commit
```
git reset [--hard|soft|mixed|merge|keep] [commit|HEAD]
```
### 针对场景二
已进行git push,即已推送到“远程仓库”中。我们将已被提交到“远程仓库”的代码还原操作叫做“回滚”!注意:对远程仓库做回滚操作是有风险的,需提前做好备份和通知其他团队成员!
如果你每次更新线上,都会打tag,那恭喜你,你可以很快的处理上述场景二的情况
```
git checkout
```
如果你回到当前HEAD指向
```
git checkout
```
* 情况一:撤销指定文件到指定版本
```
# 查看指定文件的历史版本
git log
# 回滚到指定commitID
git checkout
```
* 情况二:删除最后一次远程提交
* 方式一:使用revert
* 方式二:使用reset
```
git revert HEAD
git push origin master
```
```
git reset --hard HEAD^
git push origin master -f
```
二者区别:
revert是放弃指定提交的修改,但是会生成一次新的提交,需要填写提交注释,以前的历史记录都在;
reset是指将HEAD指针指到指定提交,历史记录中不会出现放弃的提交记录。
* 情况三:回滚某次提交
```
# 找到要回滚的commitID
git log
git revert commitID
```
删除某次提交
```
git log --oneline -n5
```
Git撤销&回滚操作(git reset 和 get revert)2
```
git rebase -i "commit id"^
```
注意:需要注意最后的^号,意思是commit id的前一次提交
```
git rebase -i "5b3ba7a"^
```
Git撤销&回滚操作(git reset 和 get revert)3
在编辑框中删除相关commit,如pick 5b3ba7a test2,然后保存退出(如果遇到冲突需要先解决冲突)!
```
git push origin master -f
```
通过上述操作,如果你想对历史多个commit进行处理或者,可以选择git rebase -i,只需删除对应的记录就好。rebase还可对 commit 消息进行编辑,以及合并多个commit
## 3 进阶操作:查看历史git log
### 基本使用
git log能够交互式显示历史信息包括以下内容
* 版本id
* 作者
* 日期
* 提交说明
```log
commit 994d5e4308b984041149676d62c604cfe34ae19b
Author: 法然 <yinkanglong.ykl@alipay.com>
Date: Wed Jan 4 18:34:05 2023 +0800
修复sofa-common-configs版本低的问题
commit d3b0fc52e55702f01559f5407810e7f26a7eb0a2 (tag: ER62577606_empub_20221222, origin/EI62577605_20221107, EI62655147_20230104)
Merge: 9c8235f5e 4a0b61c71
Author: 梵茜 <yaomengjie.ymj@digital-engine.com>
Date: Wed Dec 21 11:20:12 2022 +0800
PullRequest: 1820 traceplugin-bugfix
Merge branch 'EI62577605_20221107_traceplugin_bugfix of git@gitlab.alipay-inc.com:jiuzhou-middleware/dsrconsole.git into EI62577605_20221107
https://code.alipay.com/jiuzhou-middleware/dsrconsole/pull_requests/1820
Signed-off-by: 省善 <wanghongjia.whj@antgroup.com>
```
### 显示格式
```sh
-p:按补丁显示每个更新间的差异,比下一条- -stat命令信息更全
--stat:显示每次更新的修改文件的统计信息,每个提交都列出了修改过的文件,以及其中添加和移除的行数,并在最后列出所有增减行数小计
--graph:显示ASCII图形表示的分支合并历史
—pretty=:使用其他格式显示历史提交信息,可选项有:oneline,short,medium,full,fuller,email,raw
```
### 筛选参数
1. 按数量
* -n:显示前n条log
2. 按日期
1. --after= --before=
2. 比如git log --after="2014-7-1”,显示2014年7月1号之后的commit(包含7月1号)后边的日期还可以用相对时间表示,比如"1 week ago"和”yesterday",比如git log --after="yesterday"
3. 而且:可以使用正则表达式,比如git log --author="John\|Mary”,搜索Marry和John贡献的commit
4. 而且:这个--author不仅包含名还包含email, 所以你可以用这个搜索email
3. 按作者
1. --author=
2. 比如git log --author=“John",显示John贡献的commit
4. 按描述
1. --grep=
2. 比如:git log --grep="JRA-224"
3. 而且:可以传入-i用来忽略大小写
4. 注意:如果想同时使用--grep和--author,必须在附加一个--all-match参数
5. 按文件
1. - -(空格)或[没有]
2. 有时你可能只对某个文件的修改感兴趣, 你只想查看跟某个文件相关的历史信息, 你只需要插入你感兴趣文件的路径[对,是路径,所以经常是不太好用]就可以了
3. 比如:git log -- foo.py bar.py ,只返回和foo.py或bar.py相关的commit
4. 这里的--是告诉Git后面的参数是文件路径而不是branch的名字. 如果后面的文件路径不会和某个branch产生混淆, 你可以省略- -,比如git log foo.py
5. 另外,后边的路径还支持正则,比如:git log *install.md 是,指定项目路径下的所有以install.md结尾的文件的提交历史
6. 另外,文件名应该放到参数的最后位置,通常在前面加上--并用空格隔开表示是文件
7. 另外,git log file/ 查看file文件夹下所有文件的提交记录
6. 按分支
1. --
2. --branchName branchName为任意一个分支名字,查看某个分支上的提交记录
3. 需要放到参数中的最后位置处
4. 如果分支名与文件名相同,系统会提示错 误,可通过--选项来指定给定的参数是分支名还是文件名
5. 比如:在当前分支中有一个名为v1的文件,同时还存在一个名为v1的分支
6. git log v1 -- 此时的v1代表的是分支名字(--后边是空的)
7. git log -- v1 此时的v1代表的是名为v1的文件
8. git log v1 -- v1 代表v1分支下的v1文件
7. 按内容
1. -S"<string>"、-G"<string>"
2. 有时你想搜索和新增或删除某行代码相关的commit. 可以使用这条命令
3. 假设你想知道Hello, World!这句话是什么时候加入到项目里去的,可以用:git log -S"Hello,World!"
4. 另外:如果你想使用正则表达式去匹配而不是字符串, 那么你可以使用-G代替-S.
5. 这是一个非常有用的debug工具, 使用他你可以定位所有跟某行代码相关的commit. 甚至可以查看某行是什么时候被copy的, 什么时候移到另外一个文件中去的
6. 注:-S后没有"=",与查询内容之间也没有空格符
8. 按范围
1. git log <since>..<until>
2. 这个命令可以查看某个范围的commit
3. 这个命令非常有用当你使用branch做为range参数的时候. 能很方便的显示2个branch之间的不同
4. 比如:git log master..feature,master..feature这个range包含了在feature有而在master没有的所有commit,同样,如果是feature..master包含所有master有但是feature没有的commit
5. 另外,如果是三个点,表示或的意思:git log master...test 查询master或test分支中的提交记录
9. 过滤掉merge commit
1. --no-merges
2. 默认情况下git log会输出merge commit. 你可以通过--no-merges标记来过滤掉merge commit,git log --no-merges
3. 另外,如果你只对merge commit感兴趣可以使用—merges,git log --merges
10. 按标签tag
1. git log v1.0
2. 直接这样是查询标签之前的commit
3. 加两个点git log v1.0.. 查询从v1.0以后的提交历史记录(不包含v1.0)
11. 按commit
1. git log commit :查询commit之前的记录,包含commit
2. git log commit1 commit2:查询commit1与commit2之间的记录,包括commit1和commit2
3. git log commit1..commit2:同上,但是不包括commit1
4. 其中,commit可以是提交哈希值的简写模式,也可以使用HEAD代替
5. HEAD代表最后一次提交,HEAD^为最后一个提交的父提交,等同于HEAD~1
6. HEAD~2代表倒数第二次提交
```

View File

@@ -1,5 +1,5 @@
## 8 分支管理
## 3 分支管理
这里主要体现的git的功能的分离,这才是真正的git吧。每一个分支都是一个单独的可以分离的工作单位。每个用户可以建立不同的分支进行工作,最终提交到同一个开发分支上。一个用户可以建立不同的分支实现不同的功能,最终提交到同一个用户分支上。分支的灵活性,可以让我们很容易的实现不同分工的分割。
@@ -35,6 +35,12 @@ git branch -d dev
```
用来删除一个已经无效的分支指正,但是在这个分支指针上左右的操作都会保留。
**分支合并方式**
```
git merge --no-ff -m "merge messge" dev --no-ff
```
不适用fastfoward方式进行合并,会产生一个新的版本节点。加上-m参数,是因为这种方式合并过程中会自己提交合并后的版本。
### **解决冲突**
- 当合并过程中出现冲突,会进入长途解决工作区,手动解决后提交,就会退出。
@@ -47,13 +53,7 @@ git log --graph
可以查看分支合并图。
### **分支合并方式**
```
git merge --no-ff -m "merge messge" dev --no-ff
```
不适用fastfoward方式进行合并,会产生一个新的版本节点。加上-m参数,是因为这种方式合并过程中会自己提交合并后的版本。
### **分支管理**
## 2 **分支管理**
- 团队合作的分支管理组图就是你们要的方式
@@ -107,4 +107,37 @@ git checkout dev
git merge vulcan.py
git branch -D feature-vulcan 不合并,强制删除
```
```
## 3 进阶操作:cherry-pick
同步一个提交的命令:git cherry-pick -x 提交id
同步多个提交的命令:git cherry-pick -x 提交id1 提交id2 提交id3 ...
* -x 可加可不加,一般建议加,因为我们这次的提交会产生新在 commit ID,加了 -x 他会在提交信息的末尾追加一行 (cherry picked from commit ...),方便以后查到这个提交是如何产生的。
* 同步多个提交时要注意,提交较早的 commit ID 要放在前面,比如 commitIDA 比 commitIDB 提交早,那么同步的时候就要这样:git cherry-pick -x commitIDA commitIDB
如下有两个分支master和dev,从C2开始两个分支开始有不同的commits。
![](image/2023-01-31-15-26-24.png)
需要把dev的C6合并到master,只需在master分支上执行git cherry-pick C6,就会把dev分支的C6应用到master分支上并产生一个新的commitC6'。
![](image/2023-01-31-15-26-30.png)
如果要合并多个commits,比如C7和C8两个commits,可以执行git cherry-pick C7 C8,则git提交记录就会变成下面这样。
![](image/2023-01-31-15-26-52.png)
需要注意的是,新的commit SHA-1 校验和会和原来的commit SHA-1 校验和不一样。
## 4 进阶操作:git merge和git rebase
> 用merge准没错

View File

@@ -1,220 +0,0 @@
# 4 版本回退
## 1 git的工作流
### 提交工作流
* 工作区:即自己当前分支所修改的代码,git add xx 之前的!不包括 git add xx 和 git commit xxx 之后的。
* 暂存区:已经 git add xxx 进去,且未 git commit xxx 的。
* 本地分支:已经git commit -m xxx 提交到本地分支的。
![](../Git/image/2021-06-15-11-04-57.png)
### 回滚工作流
在上传代码到远程仓库的时候,不免会出现问题,任何过程都有可能要回滚代码:
git reset的作用是修改HEAD的位置,即将HEAD指向的位置改变为之前存在的某个版本。
```
git reset --hard HEAD^
```
即往前回退一个版本,回退完了后工作区就是上一个版本的代码了,并且是clean的。
```
git reset --soft HEAD^
```
往前回退一个版本,并且将这次错误的提交的代码改动,放在暂存区里。
```
git reset --mixed HEAD^(和不带参数是一样的)
```
往前回退一个版本,并且将这次错误的提交的代码改动,放在工作区里。
## 2 Git撤销&回滚操作(git reset 和 get revert)
### 在工作区的代码
```
git checkout -- a.txt # 丢弃某个文件,或者
git checkout -- . # 丢弃全部
```
* 注意:git checkout – . 丢弃全部,也包括:新增的文件会被删除、删除的文件会恢复回来、修改的文件会回去。这几个前提都说的是,回到暂存区之前的样子。对之前保存在暂存区里的代码不会有任何影响。对commit提交到本地分支的代码就更没影响了。当然,如果你之前压根都没有暂存或commit,那就是回到你上次pull下来的样子了。
### 代码git add到缓存区,并未commit提交
```
git reset HEAD . 或者
git reset HEAD a.txt
```
* 这个命令仅改变暂存区,并不改变工作区,这意味着在无任何其他操作的情况下,工作区中的实际文件同该命令运行之前无任何变化
### git commit到本地分支、但没有git push到远程
```
git log # 得到你需要回退一次提交的commit id
git reset --hard # 回到其中你想要的某个版
```
或者
```
git reset --hard HEAD^ # 回到最新的一次提交
```
或者
```
git reset HEAD^ # 此时代码保留,回到 git add 之前
```
### git push把修改提交到远程仓库
1. 通过git reset是直接删除指定的commit
```
git log # 得到你需要回退一次提交的commit id
git reset --hard
git push origin HEAD --force # 强制提交一次,之前错误的提交就从远程仓库删除
```
2. 通过git revert是用一次新的commit来回滚之前的commit
```
git log # 得到你需要回退一次提交的commit id
git revert # 撤销指定的版本,撤销也会作为一次提交进行保存
```
3. git revert 和 git reset的区别
- git revert是用一次新的commit来回滚之前的commit,此次提交之前的commit都会被保留;
- git reset是回到某次提交,提交及之前的commit都会被保留,但是此commit id之后的修改都会被删除
## 3 场景
### 场景一:
糟了,我刚把不想要的代码,commit到本地仓库中了,但是还没有做push操作!
### 场景二:
彻底完了,刚线上更新的代码出现问题了,需要还原这次提交的代码!
### 场景三:
刚才我发现之前的某次提交太愚蠢了,现在想要干掉它!
## 4 操作
### 针对场景一
在未进行git push前的所有操作,都是在“本地仓库”中执行的。我们暂且将“本地仓库”的代码还原操作叫做“撤销”!
* 情况一:文件被修改了,但未执行git add操作(working tree内撤销)
```
git checkout fileName
git checkout .
```
* 情况二:同时对多个文件执行了git add操作,但本次只想提交其中一部分文件
```
$ git add *
$ git status
# 取消暂存
$ git reset HEAD
```
* 情况三:文件执行了git add操作,但想撤销对其的修改(index内回滚)
````
# 取消暂存
git reset HEAD fileName
# 撤销修改
git checkout fileName
````
* 情况四:修改的文件已被git commit,但想再次修改不再产生新的Commit
```
# 修改最后一次提交
$ git add sample.txt
$ git commit --amend -m"说明"
```
* 情况五:已在本地进行了多次git commit操作,现在想撤销到其中某次Commit
```
git reset [--hard|soft|mixed|merge|keep] [commit|HEAD]
```
### 针对场景二
已进行git push,即已推送到“远程仓库”中。我们将已被提交到“远程仓库”的代码还原操作叫做“回滚”!注意:对远程仓库做回滚操作是有风险的,需提前做好备份和通知其他团队成员!
如果你每次更新线上,都会打tag,那恭喜你,你可以很快的处理上述场景二的情况
```
git checkout
```
如果你回到当前HEAD指向
```
git checkout
```
* 情况一:撤销指定文件到指定版本
```
# 查看指定文件的历史版本
git log
# 回滚到指定commitID
git checkout
```
* 情况二:删除最后一次远程提交
* 方式一:使用revert
* 方式二:使用reset
```
git revert HEAD
git push origin master
```
```
git reset --hard HEAD^
git push origin master -f
```
二者区别:
revert是放弃指定提交的修改,但是会生成一次新的提交,需要填写提交注释,以前的历史记录都在;
reset是指将HEAD指针指到指定提交,历史记录中不会出现放弃的提交记录。
* 情况三:回滚某次提交
```
# 找到要回滚的commitID
git log
git revert commitID
```
删除某次提交
```
git log --oneline -n5
```
Git撤销&回滚操作(git reset 和 get revert)2
```
git rebase -i "commit id"^
```
注意:需要注意最后的^号,意思是commit id的前一次提交
```
git rebase -i "5b3ba7a"^
```
Git撤销&回滚操作(git reset 和 get revert)3
在编辑框中删除相关commit,如pick 5b3ba7a test2,然后保存退出(如果遇到冲突需要先解决冲突)!
```
git push origin master -f
```
通过上述操作,如果你想对历史多个commit进行处理或者,可以选择git rebase -i,只需删除对应的记录就好。rebase还可对 commit 消息进行编辑,以及合并多个commit

View File

@@ -27,8 +27,7 @@ git remote add origin git@github.com:michaelliao/learngit.git
git push -u origin master
```
将本地的主干分支master推送到远程的origin分支上。-u参数实现了两个分支的关联,将远程的git仓库的master和本地的master进行了分支管理,当再次执行时,只需要使用git
push origin master命令。
将本地的主干分支master推送到远程的origin分支上。-u参数实现了两个分支的关联,将远程的git仓库的master和本地的master进行了分支管理,当再次执行时,只需要使用git push origin master命令。
### **从远程仓库克隆**

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

442
Java三方库/gson.md Normal file
View File

@@ -0,0 +1,442 @@
两种类型之间转换。
Java对象和json格式之间转换。
* 序列化:转换成json格式
* json字符串
* json字节码
* 反序列化:转换成java格式
* java对象
* jsonobject如果没有指定java对象
## 1 Gson介绍
GSON是Google提供的用来在Java对象和JSON数据之间进行映射的Java类库。可以将一个Json字符转成一个Java对象,或者将一个Java转化为Json字符串
### 在使用Gson时需要先引入Gson依赖
```
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
```
## 2 Gson使用
### 1. 简单对象 序列化/反序列化
序列化:
```
/**
* 简单对象转Json
*
* @param obj
* @return
*/
public static String simpleObjToJson(Object obj) {
if (Objects.isNull(obj)) return "";
try {
Gson gson = new Gson();
return gson.toJson(obj);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
```
测试:
@Test
void GsonUtilTest() {
// 简单对象转json
User user = new User(1, "张三", 18);
String json = GsonUtil.simpleObjToJson(user);
System.out.println(json);
}
结果:
{"id":1,"name":"张三","age":18}
如果对象中存在空值:
先看一下user对象中的数据类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public User(String name) {
this.name = name;
}
}
测试:
@Test
void GsonUtilTest() {
// 简单对象转json
User user = new User("张三");
String json = GsonUtil.simpleObjToJson(user);
System.out.println(json);
}
结果:
{"id":0,"name":"张三"}
可以看出基本类型有默认值,包装类不解析
反序列化:
/**
* 简单Json转对象
*
* @param json
* @param cls
* @param <T>
* @return
*/
public static <T> T simpleJsonToObj(String json, Class<T> cls) {
Gson gson = new Gson();
if (Objects.isNull(json)) return null;
T obj = gson.fromJson(json, cls);
if (Objects.isNull(obj)) {
return null;
} else {
return obj;
}
}
测试:
@Test
void test2() {
String json = "{\"id\":1,\"name\":\"张三\",\"age\":18}";
User user = GsonUtil.simpleJsonToObj(json, User.class);
System.out.println(user);
}
结果:
User(id=1, name=张三, age=18)
2. 复杂对象 序列化/反序列化(对象中嵌套对象)
同简单对象一样
序列化:
复杂对象:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private Integer age;
private Job job;
private List<String> nickName;
public User(int id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
}
@Data
public class Job {
private String jobName;
private String company;
}
/**
* 复杂对象转Json
*
* @param obj
* @return
*/
public static String complexObjToJson(Object obj) {
if (Objects.isNull(obj)) return "";
try {
Gson gson = new Gson();
return gson.toJson(obj);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
测试:
@Test
void test3() {
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(18);
Job job = new Job();
job.setJobName("Java开发");
job.setCompany("某知名大厂");
user.setJob(job);
List<String> list = Arrays.asList("张三", "法外狂徒", "传奇人物");
user.setNickName(list);
String json = GsonUtil.complexObjToJson(user);
System.out.println(json);
}
结果:
{
"id":1,
"name":"张三",
"age":18,
"job":{
"jobName":"Java开发",
"company":"某知名大厂"
},
"nickName":[
"张三",
"法外狂徒",
"传奇人物"
]
}
反序列化:
/**
* 复杂Json转对象
*
* @param json
* @param cls
* @param <T>
* @return
*/
public static <T> T complexJsonToObj(String json, Class<T> cls) {
Gson gson = new Gson();
if (Objects.isNull(json)) return null;
T obj = gson.fromJson(json, cls);
if (Objects.isNull(obj)) {
return null;
} else {
return obj;
}
}
测试:
@Test
void test4() {
String json = "{\"id\":1,\"name\":\"张三\",\"age\":18,\"job\":{\"jobName\":\"Java开发\",\"company\":\"某知名大厂\"},\"nickName\":[\"张三\",\"法外狂徒\",\"传奇人物\"]}";
User user = GsonUtil.complexJsonToObj(json, User.class);
System.out.println(user);
}
结果:
User(id=1, name=张三, age=18, job=Job(jobName=Java开发, company=某知名大厂), nickName=[张三, 法外狂徒, 传奇人物])
3. 数组 序列化/反序列化
这里数据序列化和上面一样,
反序列化又=有一点区别String[] nameArray = gson.fromJson(namesJson, String[].class);
工作中不常用,就不再详细介绍
4. Map和List 序列化反序列化
Map和List是工作中比较常用的,而且这两个操作比较相似:
List序列化和反序列化 序列化:
/**
* list To Json
*
* @param list
* @return
*/
public static String listToJson(List list) {
if (Objects.isNull(list)) return "";
try {
Gson gson = new Gson();
return gson.toJson(list);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
测试:
@Test
void test6() {
List<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
String json = GsonUtil.listToJson(list);
System.out.println(json);
}
结果:
java ["zhangsan","lisi","wangwu"]
反序列化: 这里反序列化时需要提供Type,通过Gson提供的TypeToken<T>.getType()方法可以定义当前List的Type
/**
* json to list
*
* @param json
* @param cls
* @param <T>
* @return
*/
public static <T> T jsonToList(String json,Class<T> cls) {
if (Objects.isNull(json)) return null;
try {
Gson gson = new Gson();
// 需要注意这里的type
Type type = new TypeToken<ArrayList<T>>(){}.getType();
return gson.fromJson(json, type);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
测试:
@Test
void test7() {
String json = "[{\"id\":1,\"name\":\"zhangsan\",\"age\":18},{\"id\":2,\"name\":\"sili\",\"age\":28}]";
System.out.println(GsonUtil.jsonToList(json, User.class));
}
结果:
json [{id=1.0, name=zhangsan, age=18.0}, {id=2.0, name=sili, age=28.0}]
Map 序列化和反序列化
和List一样
Gson进阶用法
1. 指定序列化和反序列化 字段名称
这个用法是常用的,尤其在解析第三方接口返回数据时,可以指定字段名称解析
实体类加@SerializedName注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
@SerializedName("login_name")
private String name;
private Integer age;
private Job job;
private List<String> nickName;
public User(int id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
}
反序列化测试:
@Test
void test8() {
String json = "{\"id\":1,\"login_name\":\"张三\",\"age\":18}";
User user = GsonUtil.complexJsonToObj(json, User.class);
System.out.println(user);
}
结果:
User(id=1, name=张三, age=18, job=null, nickName=null)
Gson 解析时会将 login_name 解析出的数据封装到 name 属性中
序列化测试:
@Test
void test9() {
User user = new User(1, "张三", 18);
System.out.println(GsonUtil.complexObjToJson(user));
}
结果:
{"id":1,"login_name":"张三","age":18}
实际工作中用到场景:
我们工作中经常会需要调用第三方提供的接口,第三方接口数据有些字段命名不符合我们习惯,不如字段用下滑线而我们习惯驼峰命名,这是便可以用@SerializedName注解,这个注解还有一个属性alternate,@SerializedName(value = "login_name", alternate = "name"),此时这个注解意思是如果Json中是login_name就用login_name的值,如果是name就用name值。
2. 忽略解析某个值
有两种方式可以在解析是忽略某个值
@Expose注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Expose()
private int id; // 参与序列化/反序列化
@SerializedName("login_name")
@Expose(serialize = false,deserialize = false)
private String name; // 不参与序列化,也不参与反序列化
@Expose(serialize = false, deserialize = true)
private Integer age; // 只参与反序列化
@Expose(serialize = true, deserialize = false)
private Job job; // 只参与序列化
private List<String> nickName;
public User(int id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
}
在使用这个注解时,就不能使用之前gson对象了,必须使用下面方式构建gson对象,该对象会排除没有注解的字段。
public static String exposeObjToJson(Object obj) {
if (Objects.isNull(obj)) return "";
try {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.excludeFieldsWithoutExposeAnnotation();
Gson gson = gsonBuilder.create();
return gson.toJson(obj);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static <T> T exposeJsonToObj(String json, Class<T> cls) {
if (Objects.isNull(json)) return null;
try {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.excludeFieldsWithoutExposeAnnotation();
Gson gson = gsonBuilder.create();
return gson.fromJson(json, cls);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
transient关键字
使用这个关键字,可以直接让变量不参与序列化/反序列化
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Expose()
private int id; // 参与序列化/反序列化
@SerializedName(value = "login_name",alternate = "name")
@Expose(serialize = false,deserialize = false)
private String name; // 不参与序列化,也不参与反序列化
@Expose(serialize = false)
private Integer age; // 只参与反序列化
@Expose(deserialize = false)
private Job job; // 只参与序列化
private transient List<String> nickName; // transient 关键字
public User(int id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
}
使用该关键字时,直接用普通new的gson对象即可。

508
Java三方库/micrometer.md Normal file
View File

@@ -0,0 +1,508 @@
## 概述
> https://blog.csdn.net/weixin_45994575/article/details/117733062
做度量指标收集。
Micrometer提供了与供应商无关的接口,包括 timers(计时器), gauges(量规), counters(计数器), distribution summaries(分布式摘要), long task timers(长任务定时器)。它具有维度数据模型,当与维度监视系统结合使用时,可以高效地访问特定的命名度量,并能够跨维度深入研究。
支持的监控系统:AppOptics , Azure Monitor , Netflix Atlas , CloudWatch , Datadog , Dynatrace , Elastic , Ganglia , Graphite , Humio , Influx/Telegraf , JMX , KairosDB , New Relic , Prometheus , SignalFx , Google Stackdriver , StatsD , Wavefront
### 主要功能
1. 提供了Dimentsion维度的数据。提供了度量指标类,通过micrometer-core实现
2. 系统指标的收集,在micrometer-core中都继承自MeterBinder.class。这些类需要绑定到MeterRegtry上才可以进行指标收集,在micrometer-spring-legacy 中MeterRegistryPostProcessor来绑定这些类。
3. 支持向监控系统发送指标。接入不同监控系统,如Influxdb、Graphite等。可以通过“micrometer-registry-xx”包来指定使用哪个监控系统。
## 教程
### 引入
```java
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
```
## 概念
### Registry
Meter是收集关于你的应用的一系列指标的接口。Meter是由MeterRegistry创建的。每个支持的监控系统都必须实现MeterRegistry。
Micrometer中包含一个SimpleMeterRegistry,它在内存中维护每个meter的最新值,并且不将数据导出到任何地方。如果你还没有一个首选的监测系统,你可以先用SimpleMeterRegistry:
```
MeterRegistry registry = new SimpleMeterRegistry();
```
如果你用Spring的话,SimpleMeterRegistry是自动注入的
Micrometer还提供一个CompositeMeterRegistry用于将多个registries结合在一起使用,允许同时向多个监视系统发布指标。
```java
CompositeMeterRegistry composite = new CompositeMeterRegistry();
Counter compositeCounter = composite.counter("counter");
compositeCounter.increment();
SimpleMeterRegistry simple = new SimpleMeterRegistry();
composite.add(simple);
compositeCounter.increment();
```
### Meter
Micrometer提供一系列原生的Meter,包括Timer , Counter , Gauge , DistributionSummary , LongTaskTimer , FunctionCounter , FunctionTimer , TimeGauge。不同的meter类型导致有不同的时间序列指标值。例如,单个指标值用Gauge表示,计时事件的次数和总时间用Timer表示。
每一项指标都有一个唯一标识的名字和维度。“维度”和“标签”是一个意思,Micrometer中有一个Tag接口,仅仅因为它更简短。一般来说,应该尽可能地使用名称作为轴心。
(PS:指标的名字很好理解,维度怎么理解呢?如果把name想象成横坐标的话,那么dimension就是纵坐标。Tag是一个key/value对,代表指标的一个维度值)
### 2.3. Naming meters(指标命名)
Micrometer使用了一种命名约定,用.分隔小写单词字符。不同的监控系统有不同的命名约定。每个Micrometer的实现都要负责将Micrometer这种以.分隔的小写字符命名转换成对应监控系统推荐的命名。你可以提供一个自己的NamingConvention来覆盖默认的命名转换:
```java
registry.config().namingConvention(myCustomNamingConvention);
```
有了命名约定以后,下面这个timer在不同的监控系统中看起来就是这样的:
```
registry.timer("http.server.requests");
```
在Prometheus中,它是http_server_requests_duration_seconds
在Atlas中,它对应的是httpServerRequests
在InfluxDB中,对应的是http_server_requests
(PS:每项指标都有一个名字,不同的监控系统的命名规则(风格)都不太一样,因此可能同一个指标在不同的监控系统中有不同的名字。简单地来说,比如内存使用率这个指标可能在Prometheus中用MemoryUsage表示,在InfluxDB中用mem_usage表示,因此每个监控系统都要提供一个命名转换器,当看到mem.usage的时候InfluxDB应该知道说的是内存使用率,对应的指标名称是mem_usage。这就好比,中文“你好”翻译成英文是“hello”,翻译成日文是“こんにちは” )
2.3.1. Tag naming
假设,我们想要统计HTTP请求数和数据库调用次数,那么可以这样写:
1 registry.counter("database.calls", "db", "users"); // 数据库调用次数
2 registry.counter("http.requests", "uri", "/api/users"); // HTTP请求数
1
2
2.3.2. Common tags
Common tags可以被定义在registry级别,并且会被添加到每个监控系统的报告中
预定义的Tags有host , instance , region , stack等
1 registry.config().commonTags("stack", "prod", "region", "us-east-1");
2 registry.config().commonTags(Arrays.asList(Tag.of("stack", "prod"), Tag.of("region", "us-east-1"))); // 二者等价
1
2
2.3.4. Tag values
Tag values must be non-null
2.4. Meter filters
每个registry都可以配置指标过滤器,它有3个方法:
Deny (or accept) meters from being registered
Transform meter IDs
Configure distribution statistics for some meter types.
实现MeterFilter就可以加到registry中
1 registry.config()
2 .meterFilter(MeterFilter.ignoreTags("too.much.information"))
3 .meterFilter(MeterFilter.denyNameStartsWith("jvm"));
1
2
3
过滤器按顺序应用,所有的过滤器形成一个过滤器链(chain)
2.4.1. Deny/accept meters
接受或拒绝指标
1 new MeterFilter() {
2 @Override
3 public MeterFilterReply accept(Meter.Id id) {
4 if(id.getName().contains("test")) {
5 return MeterFilterReply.DENY;
6 }
7 return MeterFilterReply.NEUTRAL;
8 }
9 }
1
2
3
4
5
6
7
8
9
MeterFilter还提供了许多方便的静态方法用于接受或拒绝指标
img
2.4.2. Transforming metrics
一个转换过滤器可能是这样的:
1 new MeterFilter() {
2 @Override
3 public Meter.Id map(Meter.Id id) {
4 if(id.getName().startsWith("test")) {
5 return id.withName("extra." + id.getName()).withTag("extra.tag", "value");
6 }
7 return id;
8 }
9 }
1
2
3
4
5
6
7
8
9
2.5. Counters(计数器)
Counter接口允许以固定的数值递增,该数值必须为正数。
1 MeterRegistry registry = new SimpleMeterRegistry();
2
3 // 写法一
4 Counter counter = registry.counter("counter");
5
6 // 写法二
7 Counter counter = Counter
8 .builder("counter")
9 .baseUnit("beans") // optional
10 .description("a description of what this counter does") // optional
11 .tags("region", "test") // optional
12 .register(registry);
1
2
3
4
5
6
7
8
9
10
11
12
2.5.1. Function-tracking counters
跟踪单调递增函数的计数器
1 Cache cache = ...; // suppose we have a Guava cache with stats recording on
2 registry.more().counter("evictions", tags, cache, c -> c.stats().evictionCount()); // evictionCount()是一个单调递增函数,用于记录缓存被剔除的次数
1
2
2.6. Gauges
gauge是获取当前值的句柄。典型的例子是,获取集合、map、或运行中的线程数等。
MeterRegistry接口包含了用于构建gauges的方法,用于观察数字值、函数、集合和map。
1 List<String> list = registry.gauge("listGauge", Collections.emptyList(), new ArrayList<>(), List::size); //监视非数值对象
2 List<String> list2 = registry.gaugeCollectionSize("listSize2", Tags.empty(), new ArrayList<>()); //监视集合大小
3 Map<String, Integer> map = registry.gaugeMapSize("mapGauge", Tags.empty(), new HashMap<>());
1
2
3
还可以手动加减Gauge
1 AtomicInteger n = registry.gauge("numberGauge", new AtomicInteger(0));
2 n.set(1);
3 n.set(2);
1
2
3
2.7. Timers(计时器)
Timer用于测量短时间延迟和此类事件的频率。所有Timer实现至少将总时间和事件次数报告为单独的时间序列。
例如,可以考虑用一个图表来显示一个典型的web服务器的请求延迟情况。服务器可以快速响应许多请求,因此定时器每秒将更新很多次。
1 // 方式一
2 public interface Timer extends Meter {
3 ...
4 void record(long amount, TimeUnit unit);
5 void record(Duration duration);
6 double totalTime(TimeUnit unit);
7 }
8
9 // 方式二
10 Timer timer = Timer
11 .builder("my.timer")
12 .description("a description of what this timer does") // optional
13 .tags("region", "test") // optional
14 .register(registry);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
查看源代码,一目了然,不一一赘述
img
2.8. Long task timers
长任务计时器用于跟踪所有正在运行的长时间运行任务的总持续时间和此类任务的数量。
Timer记录的是次数,Long Task Timer记录的是任务时长和任务数
1 // 方式一
2 @Timed(value = "aws.scrape", longTask = true)
3 @Scheduled(fixedDelay = 360000)
4 void scrapeResources() {
5 // find instances, volumes, auto-scaling groups, etc...
6 }
7
8 // 方式二
9 LongTaskTimer scrapeTimer = registry.more().longTaskTimer("scrape");
10 void scrapeResources() {
11 scrapeTimer.record(() => {
12 // find instances, volumes, auto-scaling groups, etc...
13 });
14 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2.9. Distribution summaries(分布汇总)
distribution summary用于跟踪分布式的事件。它在结构上类似于计时器,但是记录的值不代表时间单位。例如,记录http服务器上的请求的响应大小。
1 DistributionSummary summary = registry.summary("response.size");
1
2.10. Histograms and percentiles(直方图和百分比)
Timers 和 distribution summaries 支持收集数据来观察它们的百分比。查看百分比有两种主要方法:
Percentile histograms(百分比直方图): Micrometer将值累积到底层直方图,并将一组预先确定的buckets发送到监控系统。监控系统的查询语言负责从这个直方图中计算百分比。目前,只有Prometheus , Atlas , Wavefront支持基于直方图的百分位数近似值,并且通过histogram_quantile , :percentile , hs()依次表示。
Client-side percentiles(客户端百分比):Micrometer为每个meter ID(一组name和tag)计算百分位数近似值,并将百分位数值发送到监控系统。
下面是用直方图构建Timer的一个例子:
1 Timer.builder("my.timer")
2 .publishPercentiles(0.5, 0.95) // median and 95th percentile
3 .publishPercentileHistogram()
4 .sla(Duration.ofMillis(100))
5 .minimumExpectedValue(Duration.ofMillis(1))
6 .maximumExpectedValue(Duration.ofSeconds(10))
1
2
3
4
5
6
\3. Micrometer Prometheus
Prometheus是一个内存中的维度时间序列数据库,具有简单的内置UI、定制查询语言和数学操作。Prometheus的设计是基于pull模型进行操作,根据服务发现定期从应用程序实例中抓取指标。
3.1. 安装
1 <dependency>
2 <groupId>io.micrometer</groupId>
3 <artifactId>micrometer-registry-prometheus</artifactId>
4 <version>${micrometer.version}</version>
5 </dependency>
1
2
3
4
5
3.2. 配置
Prometheus希望通过抓取或轮询单个应用程序实例来获得指标。除了创建Prometheus registry之外,还需要向Prometheus的scraper公开一个HTTP端点。在Spring环境中,一个Prometheus actuator endpoint是在Spring Boot Actuator存在的情况下自动配置的。
下面的示例使用JDK的com.sun.net.httpserver.HttpServer来公布scrape端点:
1 PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
2
3 try {
4 HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
5 server.createContext("/prometheus", httpExchange -> {
6 String response = prometheusRegistry.scrape();
7 httpExchange.sendResponseHeaders(200, response.getBytes().length);
8 try (OutputStream os = httpExchange.getResponseBody()) {
9 os.write(response.getBytes());
10 }
11 });
12
13 new Thread(server::start).start();
14 } catch (IOException e) {
15 throw new RuntimeException(e);
16 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
3.3. 图表
Grafana Dashboard
img
\4. Spring Boot 2.0
Spring Boot Actuator提供依赖管理并自动配置Micrometer
Spring Boot 自动配置一个组合的MeterRegistry,并添加一个registry到这个组合MeterRegistry中。
你可以注册任意数量的MeterRegistryCustomizer来进一步配置registry
1 @Bean
2 MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
3 return registry -> registry.config().commonTags("region", "us-east-1");
4 }
1
2
3
4
你可以在组件中注入MeterRegistry,并注册指标:
1 @Component
2 public class SampleBean {
3
4 private final Counter counter;
5
6 public SampleBean(MeterRegistry registry) {
7 this.counter = registry.counter("received.messages");
8 }
9
10 public void handleMessage(String message) {
11 this.counter.increment();
12 // handle message implementation
13 }
14
15 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Spring Boot为Prometheus提供/actuator/prometheus端点
下面是一个简单的例子,scrape_config添加到prometheus.yml中:
1 scrape_configs:
2 - job_name: 'spring'
3 metrics_path: '/actuator/prometheus'
4 static_configs:
5 - targets: ['HOST:PORT']
1
2
3
4
5
\5. JVM、Cache、OkHttpClient
Micrometer提供了几个用于监视JVM、Cache等的binder。例如:
1 new ClassLoaderMetrics().bindTo(registry);
2 new JvmMemoryMetrics().bindTo(registry);
3 new JvmGcMetrics().bindTo(registry);
4 new ProcessorMetrics().bindTo(registry);
5 new JvmThreadMetrics().bindTo(registry);
6
7 // 通过添加OkHttpMetricsEventListener来收集OkHttpClient指标
8 OkHttpClient client = new OkHttpClient.Builder()
9 .eventListener(OkHttpMetricsEventListener.builder(registry, "okhttp.requests")
10 .tags(Tags.of("foo", "bar"))
11 .build())
12 .build();
13 // 为了配置URI mapper,可以用uriMapper()
14 OkHttpClient client = new OkHttpClient.Builder()
15 .eventListener(OkHttpMetricsEventListener.builder(registry, "okhttp.requests")
16 .uriMapper(req -> req.url().encodedPath())
17 .tags(Tags.of("foo", "bar"))
18 .build())
19 .build();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
还有很多内置的Binder,看图:
img
最后,切记文档是用来查的,此处只是一个引子,有个大概印象,等需要用的时候再细查文档即可。
\6. 文档
http://micrometer.io
http://micrometer.io/docs

View File

@@ -271,4 +271,25 @@ public InitialOrderTest() {
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
- 子类(构造函数)
## 关于静态方法的思考
面相对象的各种对象之间存在依赖和集成关系,根据依赖和继承关系可以绘制面相对象的拓扑。两个对象可能关联同一个关键对象,当其中一个对象修改了关键对象的状态,另一个对象也会感知到这个关键对象的状态变化。
但是静态方法是无状态的,如果静态方法调用N次,其内部创建的对象关系就会生成N次。
静态方法是系统的启动放发,main方法也是静态方法。如果一个模块相对独立,应该也使用静态方法启动。调用者无需创建对象,只需要调用该模块的静态方法,在该静态方法中组建该模块的内部对象拓扑关系。实现一定程度的相互隔离。
关于父类和子类的作用的说明。记得在写毕设的时候,自己把父类当做工具类,子类在调用很多方法的时候,为了少写代码,就直接调用父类中的方法,然后导致父类中的流程函数,会调用子类中的方法,子类中的函数又会调用父类中的方法,非常凌乱,两个类相互冗余。当时也在思考,这些工具函数写在父类中供所有的子类调用与写一个util类有什么区别?
现在发现,应该遵循一些默认的编码规则,父类用来构建整体的流程,而子类用来完善丰富一些子流程。相当于父类在构建主流程的时候,空出一些细节实现的部分,让子类来完善。而不是写一写类似的工具函数,让子类来调用,子类能够看到更加全面丰富的流程,那么父类就没有存在的必要了,父类的作用可能就是一个接口了,只提供一些对外的方法声明。
综上所属:
* 接口:只提供对外的方法声明
* 父类:提供完整的流程,由父类调用未经实现的抽象方法完成整体流程的构建。
* 子类:提供丰富的细节实现,由子类实现抽象方法的细节。
* 工具类:提供给所有子类的通用的处理函数。一般是静态函数

View File

@@ -226,6 +226,19 @@ val=`expr 2 + 2`
echo "两数之和为 : $val"
```
### 其他算术运算方法
(1) 使用let命令进行算术运算,只支持整数运算。
(2) 使用expr命令进行算术运算,只支持整数运算。
(3) 使用bc命令进行算术运算,支持小数运算。
(4) 使用运算语法:$[算术表达式],只支持整数运算。
(5) 使用运算语法:$((算术表达式)),只支持整数运算。
(6) 在初始化变量时,将变量定义为”整数”类型,则可直接进行整数运算。
## 3 控制流运算符
## 4 关键字

View File

@@ -3,19 +3,19 @@
## 1 if条件判断
### 单分支if条件
语法:
```
```sh
if [ 条件判断式 ]
then
程序
fi
```
案例:统计根分区使用率
```
```sh
[root@localhost ~]$ vi sh/if1.sh
#!/bin/bash
#统计根分区使用率
rate=$(df -h | grep "/dev/sda2" | awk '{print $5}’| cut -d "%"-f1)
rate=$(df -h | grep "/dev/sda2" | awk '{print $5}'| cut -d "%"-f1)
#把根分区使用率作为变量值赋予变量rate
if [ $rate -ge 80 ]
#判断rate的值如果大于等于80,则执行then程序

View File

@@ -231,7 +231,22 @@ AAA 87.66
BBB 85.66
CCC 91.66
```
### 内置函数
https://blog.51cto.com/u_15794314/5682471
gsub(r,s) 在整个$0中用s替代r;gsub(r,s,t) 在整个t中用s替代r
gsub函数有点类似于sed查找和替换。它允许替换一个字符串或字符为另一个字符串或字符,并以正则表达式的形式执行。第一个函数作用于记录$0,第二个gsub函数允许指定目标,然而,如果未指定目标,缺省为$0。
index(s,t):函数返回目标字符串s中查询字符串t的首位置。
length(s) :返回s长度
match(s,r): 测试s是否包含匹配r的字符串
split(s,a,fs) 在fs上将s分成序列a
sprint (fmt,exp) :函数类似于printf函数(以后涉及),返回基本输出格式fmt的结果字符串exp。
sub(r,s) 用$0中最左边最长的子串代替s
substr(s,p) 返回字符串s中从p开始的后缀部分
substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分。
match函数测试字符串s是否包含一个正则表达式r定义的匹配。
split使用域分隔符fs将字符串s划分为指定序列a。
## 5 脚本
对于小的单行程序来说,将脚本作为命令行自变量传递给awk是非常简单的,而对于多行程序就比较难处理。当程序是多行的时候,使用外部脚本是很适合的。首先在外部文件中写好脚本,然后可以使用awk的-f选项,使其读入脚本并且执行。

View File

@@ -1,6 +1,6 @@
## 1 创建项目
### maven设置
### 1.1. maven设置
* 配置maven的Aliyun镜像和jdk版本
```xml
<mirrors>
@@ -29,13 +29,13 @@
```
### 创建maven工程
### 1.2. 创建maven工程
```
mvn archetype:generate
```
### 引入依赖
### 1.3. 引入依赖
```
<parent>
@@ -54,7 +54,7 @@ mvn archetype:generate
</dependencies>
```
### 创建主程序
### 1.4. 创建主程序
```java
/**
@@ -71,7 +71,7 @@ public class MainApplication {
```
### 编写业务逻辑
### 1.5. 编写业务逻辑
```java
@RestController
@@ -88,12 +88,12 @@ public class HelloController {
```
### 测试
### 1.6. 测试
直接运行main方法
### 配置
### 1.7. 配置
application.properties
@@ -101,8 +101,8 @@ application.properties
server.port=8888
```
### 简化部署
### 1.8. 简化部署
把项目打成jar包,直接在服务器运行。
```xml
<build>
<plugins>
@@ -117,7 +117,7 @@ server.port=8888
## 2 工程结构
### 推荐工程结构
### 2.1. 推荐工程结构
```
com
+- example
@@ -144,7 +144,7 @@ com
> Spring Boot的应用主类会自动扫描root package以及所有子包下的所有类来进行初始化。
### 非典型结构下的初始化
### 2.2. 非典型结构下的初始化
1. 使用@ComponentScan注解指定具体的加载包
@@ -179,7 +179,7 @@ public class Bootstrap {
## 3 两大特性——依赖管理
### 依赖管理的原理
### 3.1. 依赖管理的原理
通过父项目进行依赖管理。通过starter进行依赖导入。
```xml
<parent>
@@ -196,7 +196,10 @@ public class Bootstrap {
1. 几乎声明了所有的版本,查看Spring-boot-dependencies中的版本。可以自定义properties标签,修改版本号。
2. stater场景启动器。自动引入相关的所有依赖。可以自定义场景启动器,所有场景启动器最基本的以来。spring-boot-starter。引入依赖一部分可以不写版本号。引入非版本仲裁的版本号,必须要写。
### spring-boot-starter-web的依赖树
修改默认的版本号,就近优先原则。
* 查看parent中定义了版本的key,在子项目中覆盖该key,即可修改该版本。
### 3.2. spring-boot-starter-web的依赖树
* (spring-boot-starter)-web
* spring-boot相关的依赖
* ()+logging依赖
@@ -250,9 +253,9 @@ cmd+U 父项目
[INFO] \- org.springframework:spring-expression:jar:5.2.9.RELEASE:compile
```
## 4 两大特性——自动配置
## 4. 4 两大特性——自动配置
### 自动配置的体现
### 4.1. 自动配置的体现
1. 自动配好了SpringMVC
1. 引入了SpringMVC全套组件
@@ -264,8 +267,13 @@ cmd+U 父项目
4. 按需加载所有的自动配置,自动配置都在spring-boot-autoconfigure包中
### @ConfigurationProperties
### 4.2. @ConfigurationProperties
@ConfigurationProperties是springboot提供读取配置文件的一个注解。
它是实现了BeanPostProcessor接口,在bean被实例化后,会调用后置处理,递归的查找属性,通过反射注入值,对大多数属性而言强制需提供其setter和getter方法。
它是实现了BeanPostProcessor接口,在bean被实例化后,会调用后置处理,递归的查找属性,通过反射注入值,对大多数属性而言强制需提供其setter和getter方法。
## 4 启动过程

View File

@@ -170,7 +170,7 @@ public class MyConfig {}
* @ConditionalOnBean(name="bean")当容器中存在指定名称的容器的时候,才会进行注册。
```xml
```java
@=====================测试条件装配==========================
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name = "tom")

View File

@@ -1,52 +1,166 @@
## 1 springboot的启动过程
springcontext.run到底干了什么
```plantuml
@startuml
' 定义对象,第一个对象SpringApplication是Springboot提供的第一个对象公民。
class SpringApplication{
run:通过静态的run方法创建第一个对象SpringApplication,然后执行对象的run方法。
getRunListeners:通过Thread.concurrentThread.getContextClassLoader方法得到主线程,当前线程的类加载器(用于加载字节码文件)
preapareEnviroment:准备属性配置文件。
}
![](image/2023-01-09-10-47-27.png)
@enduml
SpringApplication.run()到底干了什么
```
### 服务构建
* SpringApplication.run该run方法可以启动一个类,也可以启动多个类
调用SpringApplication的静态run方法。通过一系列配置创建SpringApplication类。
1. 初始化资源加载器
2. 初始化服务类型
3. 初始化spring.factories中定义的初始化类。包括Initializer和Listener
4. 找到启动类
```
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
![](image/2023-01-09-10-56-25.png)
```java
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details). The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
```
### 环境准备
调用SpringApplicationContext的run方法。
1. 加载bootstrapContext上下文
2. 获取并注册监听器。
3. 加载环境变量,并发布环境变量加载完成的事件。(通过观察者模式)
![](image/2023-01-09-11-25-19.png)
```java
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
```
### 容器创建
在run方法中创建容器上下文SpringApplicationContext
![](image/2023-01-09-11-39-47.png)
1. 默认创建AnnotationConfigServletWebServerApplicationContext。在该类中调用两个注解处理方法。
```java
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
```
2. 构建conttext。加载Initializer,注册启动参数,加载postProcess.
```java
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
}
```
3. 发布资源监听事件
```java
listeners.contextLoaded(context);
```
### 填充容器——自动装配
![](image/2023-01-09-11-40-17.png)
1. refreshContext(conext)
2. 发布启动完成事件,调用自定义实现的runner接口。
## 2 自动配置加载的过程
### 加载过程
* @SpringBootConfiguration,就是一个@Configuration配置类。定义这是一个配置类。
* @ComponentScan指定包扫描
* @EnableAutoConfiguration
* @AutoConfigurationPackage自动配置包。将该包下的所有配置类导入进去。
* @Import(AutoConfigurationImportSelect.class)导入一个包。在autoconfiguration.jar包下,META_INF/spring.factories文件中,给出了全类名。
* @AutoConfigurationPackage自动配置包。将该包下的所有配置类导入进去。
* @Import(AutoConfigurationPackages.Register.class)利用register,将指定的包下的所有组件注册到容器中。所以默认包路径是Main程序所在的包。
* @Import(AutoConfigurationImportSelect.class)获取所有导入到容器中的配置类。利用Spring工厂加载器,从spring-boot-autoconfigure./META-INF/spring-factories中加载文件。Springboot一启动就要加载的所有配置类。
![](image/2023-01-09-10-44-48.png)
### 自动配置总结
* Spring 加载所有的自动配置类

View File

@@ -1,17 +1,8 @@
# web开发
## 概述
将springboot和springmvc结合起来的讲解
关于父类和子类的作用的说明。记得在写毕设的时候,自己把父类当做工具类,子类在调用很多方法的时候,为了少写代码,就直接调用父类中的方法,然后导致父类中的流程函数,会调用子类中的方法,子类中的函数又会调用父类中的方法,非常凌乱,两个类相互冗余。当时也在思考,这些工具函数写在父类中供所有的子类调用与写一个util类有什么区别?
现在发现,应该遵循一些默认的编码规则,父类用来构建整体的流程,而子类用来完善丰富一些子流程。相当于父类在构建主流程的时候,空出一些细节实现的部分,让子类来完善。而不是写一写类似的工具函数,让子类来调用,子类能够看到更加全面丰富的流程,那么父类就没有存在的必要了,父类的作用可能就是一个接口了,只提供一些对外的方法声明。
综上所属:
* 接口:只提供对外的方法声明
* 父类:提供完整的流程,由父类调用未经实现的抽象方法完成整体流程的构建。
* 子类:提供丰富的细节实现,由子类实现抽象方法的细节。
* 工具类:提供给所有子类的通用的处理函数。

View File

@@ -60,7 +60,8 @@ CREATE TABLE `User` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
```
### 编写领域对象(并不是MVC的一部分。数据层,实现数据访问)
### 编写领域对象
并不是MVC的一部分。数据层,实现数据访问
```
@Data
@@ -73,10 +74,11 @@ public class User {
}
```
### 编写数据访问对象(并非MVC的一部分。服务层,实现业务逻辑)
### 编写数据访问对象
并非MVC的一部分。服务层,实现业务逻辑
* 定义包含插入、删除、查询的抽象接口UserService
```
定义包含有插入、删除、查询的抽象接口UserService
```java
public interface UserService {
/**

View File

@@ -45,3 +45,65 @@ spring.datasource.hikari.connection-timeout=60000//连接超时时间
spring.datasource.hikari.connection-test-query=SELECT 1//用于测试连接是否可用的查询语句
```
## 3 druid数据源
### pom.xml配置druid依赖
```
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
```
### application.properties配置数据库连接信息
以spring.datasource.druid作为前缀
```
spring.datasource.druid.url=jdbc:mysql://localhost:3306/test
spring.datasource.druid.username=root
spring.datasource.druid.password=
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
```
### 配置druid连接池
> 具体的信息可以自己查询相关的内容。
```
spring.datasource.druid.initialSize=10
spring.datasource.druid.maxActive=20
spring.datasource.druid.maxWait=60000
spring.datasource.druid.minIdle=1
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=true
spring.datasource.druid.testOnReturn=false
spring.datasource.druid.poolPreparedStatements=true
spring.datasource.druid.maxOpenPreparedStatements=20
spring.datasource.druid.validationQuery=SELECT 1
spring.datasource.druid.validation-query-timeout=500
spring.datasource.druid.filters=stat
```
### 配置druid监控
* 在pom.xml中增加依赖
```
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
* 在application.properties中添加druid监控配置
```
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.reset-enable=true
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
```

View File

@@ -18,7 +18,8 @@
</dependency>
```
### 在application.properties中配置mysql的链接配置
### 配置数据库连接
在application.properties中配置mysql的链接配置
```
spring.datasource.url=jdbc:mysql://localhost:3306/test

View File

@@ -0,0 +1,20 @@
## 1 Bean生命周期
### 创建
![](image/2023-01-10-20-48-09.png)
### 使用
### 销毁
![](image/2023-01-10-20-49-12.png)
## 2 IOC容器创建
## 3 AOP原理

View File

@@ -1,75 +0,0 @@
```
@RestController
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在/users下
public class UserController {
// 创建线程安全的Map,模拟users信息的存储
static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());
/**
* 处理"/users/"的GET请求,用来获取用户列表
*
* @return
*/
@GetMapping("/")
public List<User> getUserList() {
// 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
List<User> r = new ArrayList<User>(users.values());
return r;
}
/**
* 处理"/users/"的POST请求,用来创建User
*
* @param user
* @return
*/
@PostMapping("/")
public String postUser(@RequestBody User user) {
// @RequestBody注解用来绑定通过http请求中application/json类型上传的数据
users.put(user.getId(), user);
return "success";
}
/**
* 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
*
* @param id
* @return
*/
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// url中的id可通过@PathVariable绑定到函数的参数中
return users.get(id);
}
/**
* 处理"/users/{id}"的PUT请求,用来更新User信息
*
* @param id
* @param user
* @return
*/
@PutMapping("/{id}")
public String putUser(@PathVariable Long id, @RequestBody User user) {
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return "success";
}
/**
* 处理"/users/{id}"的DELETE请求,用来删除User
*
* @param id
* @return
*/
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
users.remove(id);
return "success";
}
}
```

View File

@@ -1,39 +0,0 @@
## swagger2使用
### pom.xml添加依赖
```
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.0.RELEASE</version>
</dependency>
```
### 应用主类中添加@EnableSwagger2Doc注解
```
@EnableSwagger2Doc
@SpringBootApplication
public class Chapter22Application {
public static void main(String[] args) {
SpringApplication.run(Chapter22Application.class, args);
}
}
```
### application.properties中配置文档相关内容,比如
swagger.title=spring-boot-starter-swagger
swagger.description=Starter for swagger 2.x
swagger.version=1.4.0.RELEASE
swagger.license=Apache License, Version 2.0
swagger.licenseUrl=https://www.apache.org/licenses/LICENSE-2.0.html
swagger.termsOfServiceUrl=https://github.com/dyc87112/spring-boot-starter-swagger
swagger.contact.name=didi
swagger.contact.url=http://blog.didispace.com
swagger.contact.email=dyc87112@qq.com
swagger.base-package=com.didispace
swagger.base-path=/**

View File

@@ -1,61 +0,0 @@
## 1 基本配置
### pom.xml配置druid依赖
```
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
```
### application.properties配置数据库连接信息
以spring.datasource.druid作为前缀
```
spring.datasource.druid.url=jdbc:mysql://localhost:3306/test
spring.datasource.druid.username=root
spring.datasource.druid.password=
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
```
### 配置druid连接池
> 具体的信息可以自己查询相关的内容。
```
spring.datasource.druid.initialSize=10
spring.datasource.druid.maxActive=20
spring.datasource.druid.maxWait=60000
spring.datasource.druid.minIdle=1
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=true
spring.datasource.druid.testOnReturn=false
spring.datasource.druid.poolPreparedStatements=true
spring.datasource.druid.maxOpenPreparedStatements=20
spring.datasource.druid.validationQuery=SELECT 1
spring.datasource.druid.validation-query-timeout=500
spring.datasource.druid.filters=stat
```
### 配置druid监控
* 在pom.xml中增加依赖
```
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
* 在application.properties中添加druid监控配置
```
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.reset-enable=true
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
```

View File

@@ -1,12 +0,0 @@
## 自动配置类
### SpringBootApplication注解的详细解释
```
@SpringBootApplication ==>
@SpringBootConfiguration //本身是一个配置类@Configuration,利用容器中的东西完成业务逻辑
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Register.class)利用register,将指定的包下的所有组件注册到容器中。所以默认包路径是Main程序所在的包。
@Import(AutoConfigurationSelector.class)获取所有导入到容器中的配置类。利用Spring工厂加载器,从spring-boot-autoconfigure./META-INF/spring-factories中加载文件。Springboot一启动就要加载的所有配置类。
@ComponentScan //包扫描的范围
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB