Docker的基础用法

InterviewCoder

# 1 Docker 架构

在这里插入图片描述

镜像 (Image):Docker 镜像 (Image),就相当于是 一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包 含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
容器 (Container):镜像 (Image) 和容器 (Contain er) 的关系,就像是面向对象程序设计中的类和对象一 样,镜像是静态的定义,容器是镜像运行时的实体。容 器可以被创建、启动、停止、删除、暂停等。
仓库 (Repository):仓库可看成一个代码控制中心, 用来保存镜像

# 2 Docker 镜像与镜像仓库

在 docker 中仓库的名字是以应用的名称取名的。

在这里插入图片描述

镜像是静态的,而容器是动态的,容器有其生命周期,镜像与容器的关系类似于程序与进程的关系。镜像类似于文件系统中的程序文件,而容器则类似于将一个程序运行起来的状态,也即进程。所以容器是可以删除的,容器被删除后其镜像是不会被删除的。

# 3 Docker 对象

When you use docker, you are creating and using images, containers, networks, volumes, pluginns, and other objects.(当你使用 docker 时,你正在创建和使用镜像、容器、网络、卷、插件和其他对象)

IMAGES (镜像)

An image is a read-only template with instructions for creating a docker container.(镜像是一个只读模板,它带有创建 docker 容器的指令。)
Often, an image is based on another image, with some additional customization.(通常,一个镜像基于另一个镜像,并带有一些额外的自定义。)
You might create your own images or you might only use those created by others and published in a registry.(您可以创建自己的镜像,也可以使用其他人创建并在仓库中发布的镜像。)
CONTAINERS (容器)

A conntainer is a runnable instance of an image.(容器是镜像的可运行实例。)
You can create, run, stop, move, or delete a container using the docker API or CLI.(您可以通过 docker API 或 CLI 创建、运行、停止、移动或删除容器。)
You can connect a container to one or more networks, attach storage to it, or even create a new image based on its current state.(您可以将容器连接到一个或多个网络,为其附加存储,甚至可以根据其当前状态创建新镜像。)

# 4 Docker 安装及使用

4.1 Docker 安装

1
2
3
4
5
[root@Docker ~]# wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo
[root@Docker ~]# sed -i 's@https://download.docker.com@https://mirrors.tuna.tsinghua.edu.cn/docker-ce@g' /etc/yum.repos.d/docker-ce.repo
[root@Docker ~]# yum clean all
21 文件已删除
[root@Docker ~]# yum -y install docker-ce

# 4.2 Docker 加速

为什么需要加速器?因为 docker 在拉取镜像时,是去国外拉取的所以会比较慢,使用加速器可以解决这个问题

docker-ce 的配置文件是 /etc/docker/daemon.json,此文件默认不存在,需要我们手动创建并进行配置,而 docker 的加速就是通过配置此文件来实现的

# docker 的加速有多种方式

  • docker cn
  • 中国科技大学加速器
  • 阿里云加速器(需要通过阿里云开发者平台注册帐号,免费使用个人私有的加速器)
1
2
3
4
5
6
7
8
9
10
[root@Docker ~]# systemctl enable --now docker    //启动docker服务
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.
[root@Docker ~]# cat > /etc/docker/daemon.json <<EOF
{
"registry-mirrors": ["https://xj3hc284.mirror.aliyuncs.com"]
}
EOF
[root@Docker ~]# systemctl daemon-reload
[root@Docker ~]# systemctl restart docker //重启docker服务

# 4.3 Docker 常用操作

# 4.3.1 docker version (查看版本号)

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
[root@Docker ~]# docker version
Client: Docker Engine - Community //docker客户端版本
Version: 20.10.11
API version: 1.41
Go version: go1.16.9
Git commit: dea9396
Built: Thu Nov 18 00:36:58 2021
OS/Arch: linux/amd64
Context: default
Experimental: true

Server: Docker Engine - Community //docker服务端版本
Engine:
Version: 20.10.11
API version: 1.41 (minimum version 1.12)
Go version: go1.16.9
Git commit: 847da18
Built: Thu Nov 18 00:35:20 2021
OS/Arch: linux/amd64
Experimental: false
containerd: //容器版本
Version: 1.4.12
GitCommit: 7b11cfaabd73bb80907dd23182b9347b4245eb5d
runc:
Version: 1.0.2
GitCommit: v1.0.2-0-g52b36a2
docker-init: //docker初始化版本
Version: 0.19.0
GitCommit: de40ad0

# 4.3.2 docker info (显示整个系统的信息)

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
[root@Docker ~]# docker info
Client: #docker客户端版本信息
Context: default #上下文
Debug Mode: false
Plugins: #插件
app: Docker App (Docker Inc., v0.9.1-beta3)
buildx: Build with BuildKit (Docker Inc., v0.6.3-docker)
scan: Docker Scan (Docker Inc., v0.9.0)

Server: #docker服务端版本信息
Containers: 0 #容器个数
Running: 0 #运行个数
Paused: 0 #暂停状态个数
Stopped: 0 #停止状态个数
Images: 0 #镜像个数
Server Version: 20.10.11 #服务版本号
Storage Driver: overlay2 #存储驱动
Backing Filesystem: xfs #后端文件系统
Supports d_type: true
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file # 日志驱动
Cgroup Driver: cgroupfs
Cgroup Version: 1
Plugins: # 插件
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 7b11cfaabd73bb80907dd23182b9347b4245eb5d
runc version: v1.0.2-0-g52b36a2
init version: de40ad0
Security Options: # 安全选项
seccomp
Profile: default
Kernel Version: 4.18.0-257.el8.x86_64 # 内核版本号
Operating System: CentOS Stream 8 # 操作系统
OSType: linux #操作系统 类型
Architecture: x86_64 #架构
CPUs: 4 # CPU核心数
Total Memory: 7.559GiB #总内存
Name: Docker
ID: 4MEZ:OEWB:CPHP:3PYE:54J3:46XO:B5CX:JHYB:OW5Q:TWJP:PWHO:JJSM
Docker Root Dir: /var/lib/docker #docker默认目录
Debug Mode: false
Registry: https://index.docker.io/v1/ # 仓库地址
Labels: #标签
Experimental: false
Insecure Registries:
127.0.0.0/8
Registry Mirrors: #加速器
https://xj3hc284.mirror.aliyuncs.com/
Live Restore Enabled: false

# 4.3.3 docker search (在 docker hub 中搜索镜像)

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
[root@Docker ~]# docker search nginx
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
nginx Official build of Nginx. 15893 [OK]
jwilder/nginx-proxy Automated Nginx reverse proxy for docker con… 2098 [OK]
richarvey/nginx-php-fpm Container running Nginx + PHP-FPM capable of… 819 [OK]
jc21/nginx-proxy-manager Docker container for managing Nginx proxy ho… 285
linuxserver/nginx An Nginx container, brought to you by LinuxS… 160
tiangolo/nginx-rtmp Docker image with Nginx using the nginx-rtmp… 146 [OK]
jlesage/nginx-proxy-manager Docker container for Nginx Proxy Manager 144 [OK]
alfg/nginx-rtmp NGINX, nginx-rtmp-module and FFmpeg from sou… 110 [OK]
nginxdemos/hello NGINX webserver that serves a simple page co… 79 [OK]
privatebin/nginx-fpm-alpine PrivateBin running on an Nginx, php-fpm & Al… 60 [OK]
nginx/nginx-ingress NGINX and NGINX Plus Ingress Controllers fo… 57
nginxinc/nginx-unprivileged Unprivileged NGINX Dockerfiles 54
nginxproxy/nginx-proxy Automated Nginx reverse proxy for docker con… 28
staticfloat/nginx-certbot Opinionated setup for automatic TLS certs lo… 25 [OK]
nginx/nginx-prometheus-exporter NGINX Prometheus Exporter for NGINX and NGIN… 22
schmunk42/nginx-redirect A very simple container to redirect HTTP tra… 19 [OK]
centos/nginx-112-centos7 Platform for running nginx 1.12 or building … 16
centos/nginx-18-centos7 Platform for running nginx 1.8 or building n… 13
flashspys/nginx-static Super Lightweight Nginx Image 11 [OK]
bitwarden/nginx The Bitwarden nginx web server acting as a r… 11
mailu/nginx Mailu nginx frontend 9 [OK]
sophos/nginx-vts-exporter Simple server that scrapes Nginx vts stats a… 7 [OK]
ansibleplaybookbundle/nginx-apb An APB to deploy NGINX 3 [OK]
arnau/nginx-gate Docker image with Nginx with Lua enabled on … 1 [OK]
wodby/nginx Generic nginx 1 [OK]

# 4.3.4 docker pull (拉取镜像)

1
2
3
4
5
6
7
8
9
10
11
12
[root@Docker ~]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
eff15d958d66: Pull complete
1e5351450a59: Pull complete
2df63e6ce2be: Pull complete
9171c7ae368c: Pull complete
020f975acd28: Pull complete
266f639b35ad: Pull complete
Digest: sha256:097c3a0913d7e3a5b01b6c685a60c03632fc7a2b50bc8e35bcaa3691d788226e
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

# 4.3.5 docker images (列出系统当前镜像)

1
2
3
[root@Docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest ea335eea17ab 13 days ago 141MB

# 4.3.6 docker image history (查看指定镜像的历史)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@Docker ~]# docker image history nginx
IMAGE CREATED CREATED BY SIZE COMMENT
ea335eea17ab 13 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 13 days ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
<missing> 13 days ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
<missing> 13 days ago /bin/sh -c #(nop) COPY file:09a214a3e07c919a… 4.61kB
<missing> 13 days ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB
<missing> 13 days ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b0… 1.96kB
<missing> 13 days ago /bin/sh -c #(nop) COPY file:65504f71f5855ca0… 1.2kB
<missing> 13 days ago /bin/sh -c set -x && addgroup --system -… 61.1MB
<missing> 13 days ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~bullseye 0B
<missing> 13 days ago /bin/sh -c #(nop) ENV NJS_VERSION=0.7.0 0B
<missing> 13 days ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.21.4 0B
<missing> 13 days ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 2 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ADD file:a2405ebb9892d98be… 80.4MB

# 4.3.7 docker image inspect (查看指定镜像的详细信息)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@Docker ~]# docker image inspect nginx
[
{
"Id": "sha256:ea335eea17ab984571cd4a3bcf90a0413773b559c75ef4cda07d0ce952b00291",
"RepoTags": [
"nginx:latest"
],
"RepoDigests": [
"nginx@sha256:097c3a0913d7e3a5b01b6c685a60c03632fc7a2b50bc8e35bcaa3691d788226e"
],
"Parent": "",
"Comment": "",
"Created": "2021-11-17T10:38:14.652464384Z",
"Container": "8a038ff17987cf87d4b7d7e2c80cb83bd2474d66e2dd0719e2b4f7de2ad6d853",
"ContainerConfig": {
"Hostname": "8a038ff17987",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"80/tcp": {}
},

# 4.3.8 删除镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@Docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 57 minutes ago Exited (0) 6 minutes ago youthful_euclid

[root@Docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest ad17c88403e2 12 days ago 143MB
nginx latest ea335eea17ab 13 days ago 141MB

[root@Docker ~]# docker rmi ad17c88403e2
Untagged: httpd:latest
Untagged: httpd@sha256:1d71eef54c08435c0be99877c408637f03112dc9f929fba3cccdd15896099b02
Deleted: sha256:ad17c88403e2cedd27963b98be7f04bd3f903dfa7490586de397d0404424936d
Deleted: sha256:a59e7dfeeb485a8a45b1fcce812b10fbd955d304fa2e9ca43b10b16a8ee1afb8
Deleted: sha256:9592080464aa1890ed187c42a13ecc9f175e975a96a3fad28df0559ad0c08b9d
Deleted: sha256:42d2debfa0c419f7f89affa3e9b62d1b7e54dc6654dbd186d4654ee3661c44c8
Deleted: sha256:136822c50a75392f4ce06461fa4894aa7d1e060ec0dd4782e13e2d9829df50a3
[root@Docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest ea335eea17ab 13 days ago 141MB

# 4.3.9 docker create (创建容器)

此命令只会创建容器,而不会运行容器

1
2
[root@Docker ~]# docker create nginx
8442117bc0c7b85609a77229433413a85ac0fa951fe9c0efc3c340b1299fbd74

# 4.3.10 docker ps (列出容器)

1
2
3
4
5
6
[root@Docker ~]# docker ps         //查看正在运行的容器
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

[root@Docker ~]# docker ps -a //查看所有容器
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 3 minutes ago Created youthful_euclid

# 4.3.11 docker start (启动容器)

1
2
3
4
5
6
[root@Docker ~]# docker start 8442117bc0c7        #启动容器,后面接容器的ID号,ID号可以简写
8442117bc0c7

[root@Docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 8 minutes ago Up 14 seconds 80/tcp youthful_euclid

# 4.3.12 docker attach (进入容器)

在当前 shell 下 attach 连接指定运行镜像,以这种方式进入容器,此时容器会一直占用前台,如果退出容器,容器就会停止

1
2
3
4
5
6
7
8
[root@Docker ~]# docker ps 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 50 minutes ago Up 30 seconds 80/tcp youthful_euclid

[root@Docker ~]# docker attach 8442117bc0c7

[root@Docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

# 4.3.13 docker exec (进入容器)

用这个命令进入容器后台运行就算是退出了容器,容器也不会停止运行

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Docker ~]# docker ps 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 26 minutes ago Up 3 seconds 80/tcp youthful_euclid

[root@Docker ~]# docker exec -it 8442117bc0c7 /bin/bash
root@8442117bc0c7:/# ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@8442117bc0c7:/# exit
exit

[root@Docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 27 minutes ago Up 23 seconds 80/tcp youthful_euclid

# 4.3.14 docker inspect (查看容器的信息详细)

通过查看详细信息可以查看其容器的 IP 地址

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
[root@Docker ~]# docker inspect 8442117bc0c7
[
{
"Id": "8442117bc0c7b85609a77229433413a85ac0fa951fe9c0efc3c340b1299fbd74",
"Created": "2021-12-01T07:28:48.929261547Z",
"Path": "/docker-entrypoint.sh",
"Args": [
"nginx",
"-g",
"daemon off;"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 5390,
"ExitCode": 0,
"Error": "",
"StartedAt": "2021-12-01T07:36:41.636489391Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
......

# 4.3.15 docker logs (查看容器日志)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@Docker ~]# docker logs 8442117bc0c7
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2021/12/01 07:36:41 [notice] 1#1: using the "epoll" event method
2021/12/01 07:36:41 [notice] 1#1: nginx/1.21.4
2021/12/01 07:36:41 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2021/12/01 07:36:41 [notice] 1#1: OS: Linux 4.18.0-257.el8.x86_64
2021/12/01 07:36:41 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2021/12/01 07:36:41 [notice] 1#1: start worker processes
2021/12/01 07:36:41 [notice] 1#1: start worker process 31
2021/12/01 07:36:41 [notice] 1#1: start worker process 32
2021/12/01 07:36:41 [notice] 1#1: start worker process 33
2021/12/01 07:36:41 [notice] 1#1: start worker process 34
172.17.0.1 - - [01/Dec/2021:07:43:23 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
172.17.0.1 - - [01/Dec/2021:07:43:44 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
172.17.0.1 - - [01/Dec/2021:07:43:45 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
172.17.0.1 - - [01/Dec/2021:07:43:46 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
172.17.0.1 - - [01/Dec/2021:07:43:46 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"

# 4.3.16 docker stop (停止容器)

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Docker ~]# docker ps 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 16 minutes ago Up 8 minutes 80/tcp youthful_euclid

[root@Docker ~]# docker stop 8442117bc0c7
8442117bc0c7

[root@Docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

[root@Docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 16 minutes ago Exited (0) 5 seconds ago youthful_euclid

# 4.3.17 docker restart (重启容器)

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Docker ~]# docker ps -a 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 17 minutes ago Exited (0) 46 seconds ago youthful_euclid

[root@Docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

[root@Docker ~]# docker restart 8442117bc0c7
8442117bc0c7

[root@Docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 17 minutes ago Up 3 seconds 80/tcp youthful_euclid

# 4.3.18 docker kill (杀死正在运行的容器)

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Docker ~]# docker ps 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 20 minutes ago Up 2 minutes 80/tcp youthful_euclid

[root@Docker ~]# docker kill 8442117bc0c7
8442117bc0c7

[root@Docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

[root@Docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 20 minutes ago Exited (137) 4 seconds ago youthful_euclid

# 4.3.19 docker run (创建并启动容器)

此命令会做三件事情,首先先查看本地是否都对应的镜像,如果没有就拉取,然后创建容器,然后运行容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@Docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 35 minutes ago Exited (0) 5 minutes ago youthful_euclid

[root@Docker ~]# docker run -it httpd /bin/bash #此命令的作用是,拉取一个httpd的镜像,然后创建容器并运行,-it表示以交互的方式进入终端,/bin/bash表示终端的环境
Unable to find image 'httpd:latest' locally
latest: Pulling from library/httpd
eff15d958d66: Already exists
ba1caf8ba86c: Pull complete
ab86dc02235d: Pull complete
0d58b11d2867: Pull complete
e88da7cb925c: Pull complete
Digest: sha256:1d71eef54c08435c0be99877c408637f03112dc9f929fba3cccdd15896099b02
Status: Downloaded newer image for httpd:latest
root@a01b8d80a160:/usr/local/apache2# ls
bin build cgi-bin conf error htdocs icons include logs modules

# 4.3.20 docker rm (删除容器)

删除一个或者多个容器,只能删除停止状态的容器或者 - f 强制删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@Docker ~]# docker ps 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a01b8d80a160 httpd "/bin/bash" 10 minutes ago Up 3 seconds 80/tcp hardcore_varahamihira

[root@Docker ~]# docker rm a01b8d80a160
Error response from daemon: You cannot remove a running container a01b8d80a1609e81d8d8121b3fc3c0f53a676340b3b093b353a7f9bb498fe0e6. Stop the container before attempting removal or force remove

[root@Docker ~]# docker rm -f a01b8d80a160
a01b8d80a160

[root@Docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

[root@Docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8442117bc0c7 nginx "/docker-entrypoint.…" 48 minutes ago Exited (0) 18 minutes ago youthful_euclid

# 5 Docker event state

在这里插入图片描述

本文来自 CSDN

原文链接:https://blog.csdn.net/weixin_46727129/article/details/121665668

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【Vue】Element UI el-tag 根据不同状态的数据展示不同颜色的标签

InterviewCoder

# 【Vue】Element UI el-tag 根据不同状态的数据展示不同颜色的标签

# Element UI 根据不同状态的数据展示不同颜色的标签

展示标签可以使用 el-tag,不同的数据可以使用三目运算进行管控
代码如下:

1
2
3
4
<el-tag :type="(scope.row.auditstatus == '0' ? '' : (scope.row.auditstatus == '1' ? 'success' : (scope.row.auditstatus == '2' ? 'danger' : (scope.row.auditstatus == '3' ? 'warning' : 'danger'))))" size="mini">
{{ scope.row.auditstatus == '0' ? '初始值' : (scope.row.auditstatus == '1' ? '审核通过' : (scope.row.auditstatus == '2' ? '审核不通过' : (scope.row.auditstatus == '3' ? '待审核' : '删除'))) }}
</el-tag>
123

效果图如下:
在这里插入图片描述
在这里插入图片描述

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【Npm】npm install时卡住怎么办?

InterviewCoder

# 【Npm】npm install 时卡住怎么办?

# 方法一:安装 cnpm 镜像

这个是比较常用的方法,我首先也是使用了这个方法。

cnpm 的安装方法,参考 http://npm.taobao.org/

1
npm install -g cnpm --registry=https:``//registry.npm.taobao.org

在 cmd 中输入以上命令就可以了,然后再使用 cnpm 安装

1
cnpm install -g nodemon

后面的操作跟不使用镜像的操作是差不多的。

# 方法二:使用代理 registry

在网上查阅了一些资料后,决定使用代理的方式,方法也很简单,就是

1
npm config set registry https://registry.npm.taobao.org

然后后续的 install 等命令还是通过 npm 运作,而不是 cnpm。

# 后记补充:

npm install 有 bug, 大家可以安装 yarn 替代。

步骤:

Yarn、React Native 的命令行工具(react-native-cli)

Yarn 是 Facebook 提供的替代 npm 的工具,可以加速 node 模块的下载。React Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。

1
npm install -g yarn react-native-cli

安装完 yarn 后同理也要设置镜像源:

1
yarn config set registry https:``//registry.npm.taobao.org --global``yarn config set disturl https:``//npm.taobao.org/dist --global

如果你遇到 EACCES: permission denied 权限错误,可以尝试运行下面的命令(限 linux 系统): sudo npm install -g yarn react-native-cli.

安装完 yarn 之后就可以用 yarn 代替 npm 了,例如用 yarn 代替 npm install 命令,用 yarn add 某第三方库名代替 npm install --save 某第三方库名。

** 注意:** 目前 npm5(发文时最新版本为 5.0.4)存在安装新库时会删除其他库的问题,导致项目无法正常运行。请尽量使用 yarn 代替 npm 操作。

# 转载与参考

https://blog.csdn.net/WXF_Sir/article/details/112944559

解决 npm install 总是卡住不动的问题

https://www.cnblogs.com/pijunqi/p/14362901.html

解决 npm install 卡住不动的小尴尬
https://www.cnblogs.com/wenbinjiang/p/11062959.html

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

Vue的生命周期

InterviewCoder

看过很多人讲 vue 的生命周期,但总是被绕的云里雾里,尤其是自学的同学,可能 js 的基础也不是太牢固,听起来更是吃力,那我就已个人之浅见,以大白话的方式给大家梳理一下,如有不准确的地方,欢迎指正!🤞🤞

# 什么是生命周期?

生命周期,以个人之浅见,即一个事物从诞生到消亡的一个过程

以人的一生来做类比,其实就可以简单粗暴的将生命周期看作人的一生,人这一出生就开始了一段美好(艰难)的旅程,一生中每个成长的阶段都会对应的去做每个阶段该做的事,比如,上幼儿园,小学,中学,高中,大学,工作(比如我就在辛苦的码字),结婚等等直到百年以后,尘归尘,土归土,这就是人的生命周期!

vue 也有这样的一个生命周期,也会在执行到每个阶段做一些事情,不同的是 vue 在每个对应的阶段是通过生命周期函数去做的,此刻再去看一下 vue 官网对生命周期的描述就好理解多了!

  • vue 官网的描述:

每个 Vue 实例在被创建时都要经过一系列的初始化过程 —— 例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

简单来说就是: 在 Vue 从创建实例到最终完全消亡的过程中,会执行一系列的方法,用于对应当前 Vue 的状态,这些方法我们叫它:生命周期钩子

来看我给大家找的一张图,可以保存起来,等待后学学习使用的深入,再看这张图:
img

根据上图可知,vue 的生命周期一共有 8 个钩子函数,这 8 个函数描绘了一个 vue 的一生,下来我们详细来看看这 8 个生命周期函数,以便更好的理解 Vue 的生命周期!

# vue 的 8 个生命周期函数

  • 配合上图观看
  1. beforeCreate:在实例初始化之后,数据观测 (Data Observer) 和 event/watcher 事件配置之前被调用。
  2. created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer)、属性和方法的运算,watch/event 事件回调;然而,挂载阶段还没开始,$el 属性目前不可见。
  3. beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用。
  4. mounted:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。

如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内(PS:注意 mounted 不会承诺所有的子组件也都一起被挂载。
如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted: ,
vm.$nextTick 会在后面的篇幅详细讲解,这里大家需要知道有这个东西。

  1. beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  2. updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以现在可以执行依赖于 DOM 的操作,然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之(PS:计算属性与 watcher 会在后面的篇幅中进行介绍)。
  3. beforeDestroy:实例销毁之前调用,在这一步,实例仍然完全可用。
  4. destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

# 代码验证:

下面的例子我故意将生命周期钩子函数的顺序打乱,并编号,但它还是会自动按照执行顺序输出,就可以验证其上图中的流程,你也手动试试吧!

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
<div id="app">
<button @click="clickCounter()">点击</button>
<p>{{ count }}</p>
</div>

<script type="text/javascript">
var app = new Vue({
el: '#app',
data:{
count: 1
},
methods:{
clickCounter(){
this.count += 1
}
},
created: function(){
console.log('2. 实例已经创建')
},
beforeCreate: function(){
console.log('1. 实例初始化')
},
mounted:function(){
console.log('4. 挂载到实例')
},
beforeMount:function(){
console.log('3. 挂载开始之前')
},
beforeUpdate: () => {
console.log('数据更新时调用')
},
updated:function(){
console.log('更新数据重新渲染DOM')
},
beforeDestroy:function(){
console.log('实例销毁之前调用')
},
destroyed:function(){
console.log('实例销毁之后调用')
}
})

/*点击页面销毁vue对象, 销毁之后实例将会释放*/
// 销毁之后,再次点击就不起作用了
document.onclick=function(){
app.$destroy();
};
</script>
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748

在这里插入图片描述

  • 注意: 最后我手动将这个实例销毁了,点击之后执行一次,后边再点击就不起作用了,测试的时候先把销毁代码端注释掉,然后再放开,进行测试!

# 3 个关于 vue 组件的生命周期钩子

  1. activated:keep-alive 组件激活时调用(PS:与组件相关,关于 keep-alive 会在讲解组件时介绍)。
  2. deactivated:keep-alive 组件停用时调用(PS:与组件相关,关于 keep-alive 会在讲解组件时介绍)。
  3. errorCaptured :当捕获一个来自子孙组件的错误时被调用,此钩子会收到三个参数:错误对象发生错误的组件实例以及一个包含错误来源信息的字符串,此钩子可以返回 false 以阻止该错误继续向上传播

# 写在最后

生命周期这块知识点,在这一块我们只需要有所了解,对其大概使用有个基本的掌握,等待学习的深入以及理解的深入,在回过头来看着一块的内容,结合 Vue 的源码去看会收获良多!

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

Hexo搭建

InterviewCoder

# hexo 框架个人博客搭建

# 1 环境准备

# 1.1 Node.js 和 npm 安装

1
NoedJS自行百度安装

# 1.2 (选) npm 淘宝镜像

1
npm install -g cnpm --registry.npm.taobao.org

# 1.3 hexo 框架安装

1
cnpm install -g hexo-cli

# 1.4git 安装配置

  1. git 官网 下载对应系统安装包

  2. 运行安装包 一路下一步

  3. 开始菜单运行 Git Bash (运行成功表示 git 安装成功)

  4. git 安装好去 GitHub 上注册一个账号

  5. 设置 git:在 Git Bush 命令行中输入

    1
    2
    3
    4
    # 配置用户名
    git config --global user.name "username" //( "username"是自己的账户名)
    # 配置邮箱
    git config --global user.email "username@email.com" //("username@email.com"注册账号时用的邮箱)

    以上命令执行结束后,可用 git config –global –list 命令查看配置是否 OK

  6. 生成 ssh, 在命令框中输入以下命令

    1
    ssh-keygen -t rsa

连续敲三次回车,结束后去系统盘目录下(一般在 C:\Users\ 你的用户名.ssh)(mac: /Users/ 用户 /.ssh)查看是否有:ssh 文件夹生成
\7. 将 ssh 文件夹中的公钥( id_rsa.pub)添加到 GitHub 管理平台中,在 GitHub 的个人账户的设置中找到如下界面

20181012202024433

title 随便起一个,将公钥( id_rsa.pub)文件中内容复制粘贴到 key 中,然后点击 Ass SSH key 就好啦

# 2 建立本地网站

# 2.1 在本地建立一个文件夹

1
去任意盘符下,建立一个文件夹,这个文件夹就是你的博客文件夹

# 2.2 cmd 命令进入这个文件夹

1
进入文件夹后,路径输入cmd进入命令提示符窗口

# 2.3 hxeo 生成博客

1
sudo hexo init

# 2.4 启动博客

1
hexo s

进入网站 说明建立成功

# 3 上传至 github

# 3.1 建立仓库

  1. 登录 github 新建一个仓库
  2. 仓库名必须是 “<<你的 Github 名字>>.github.io”

# 3.2 在博客目录安装 git 插件

1
cnpm install --save hexo-deployer-git

# 3.3 设置文件 “_config.yml”(最底部)

1
2
3
4
deploy:
type: git
repo: //仓库的ssh地址
branch: master

# 3.4 部署到远端

1
hexo d //需要登陆

# 3.5 访问

”https://<<你的 username>>.github.io“

1
2
3
4
5
6
7
补充命令:

hexo clean 清理博客

hexo generate 同步博客

hexo deploy 提交博客

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【Mysql】Mysql删除重复数据只保留一条

InterviewCoder

# 【Mysql】Mysql 删除重复数据只保留一条

(1)以这张表为例:

1
2
3
4
5
6
7
CREATE TABLE `test`  (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '注解id',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名字',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

INSERT INTO test (id,`name`) VALUES (replace(uuid(),'-',''),'张三'),(replace(uuid(),'-',''),'张三');

表里有两条数据,然后名字是相同的,但是 id 是不同的,现在要求是只留一条数据:

在这里插入图片描述

(2)查询 name 值重复的数据:

现实开发当中可能一个字段无法锁定重复值,可以采取 group by 多个值!利用多个值来锁定重复的行数据!

1
SELECT name FROM test GROUP BY `name` HAVING count( name ) > 1

(3)查询重复数据里面每个最小的 id:

1
SELECT  min(id) as id FROM test GROUP BY `name` HAVING count( name ) > 1

(4)查询去掉重复数据最小 id 的其他数据:也就是要删除的数据!

1
2
3
4
SELECT * FROM test 
WHERE name IN ( SELECT name FROM test GROUP BY `name` HAVING count( name ) > 1 )
AND
id NOT IN (SELECT min( id ) FROM test GROUP BY `name` HAVING count( NAME ) > 1)

(5)删除去掉重复数据最小 id 的其他数据:

可能这时候有人该说了,有了查询,直接改成 delete 不就可以了,真的是这样吗?其实不是的,如下运行报错:

在这里插入图片描述

首先明确一点这个错误只会发生在 delete 语句或者 update 语句,拿 update 来举例 : update A表 set A列 = (select B列 from A表); 这种写法就会报这个错误,原因:你又要修改 A 表,然后又要从 A 表查数据,而且还是同层级。Mysql 就会认为是语法错误!

嵌套一层就可以解决, update A表 set A列 = (select a.B列 from (select * from A表) a); 当然这个只是个示例,这个示例也存在一定的问题,比如 (select a.B列 from (select * from A表) a) 他会查出来多条,然后赋值的时候会报 1242 - Subquery returns more than 1 row

嵌套一层他就可以和 update 撇清关系,会优先查括号里面的内容,查询结果出来过后会给存起来,类似临时表,可能有的人该好奇了, update A表 set A列 = (select B列 from A表); 我明明加括号了呀,难道不算嵌套吗,当然不算,那个括号根本没有解决他们之间的层次关系!

详解看这篇文章:https://blog.csdn.net/weixin_43888891/article/details/127000534

(6)正确的写法:

方式一:

1
2
3
4
DELETE FROM test 
WHERE name IN ( select a.name from (SELECT name FROM test GROUP BY `name` HAVING count( name ) > 1) a)
AND
id NOT IN (select a.id from (SELECT min(id) as id FROM test GROUP BY `name` HAVING count( name ) > 1) a)

注意:删除之前一定要先查询,然后再删除,否则一旦语法有问题导致删了不想删除的数据,想要恢复很麻烦!或者删除前备份好数据,不要嫌麻烦,一旦出问题,才是真正的大麻烦!

方式二:

1
2
3
4
5
6
7
8
DELETE FROM test 
WHERE
id NOT IN (
SELECT
t.id
FROM
( SELECT MIN(id) as id FROM test GROUP BY NAME ) t)

(7)错误的写法: 这块我吃过一次亏,所以专门写出来,避免踩坑!

千万千万不能这么搞,下面这个语法相当于是先按 name 分组,然后查出来大于 1 的,这时候假如大于 1 的有很多,然后外面嵌套的那一层,只取了最小的一条数据,然后再加上使用的是 NOT IN ,最终会导致数据全部被删除!!!

在这里插入图片描述

执行前有四条数据,实际上我们要的是张三留下来一条,然后李四留下来一条

在这里插入图片描述

执行结果:只留下了一条!

在这里插入图片描述

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【Flutter】解决升级Flutter3.0后出现警告Operand of null-aware operation ‘!‘ has type ‘WidgetsBinding‘ which excludes null

InterviewCoder

# 【Flutter】解决升级 Flutter3.0 后出现警告 Operand of null-aware operation ‘!‘ has type ‘WidgetsBinding‘ which excludes null

# 出现场景

Flutter SDK 升级到 3.0,运行时报以下警告。
虽然不影响程序的运行,但是看着很烦。

1
2
3
4
lib/stress_test/stress_test_page.dart:120:22: Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
- 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('../../develop_env/flutter_3.0/packages/flutter/lib/src/widgets/binding.dart').
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
^

# 解决方案

这是因为在 Flutter 3.0 中,binding 的 instance 是不可为空的,所以不需要使用 !

下面有 2 种情况。

# 三方依赖库

如果是依赖的库要使用到了 Binding.instance,去 pub 上看看库的新版本有没有兼容 3.0。如果有就升级库的版本。

比如我的项目用到了 getx 4.6.1,是 Flutter 3.0 出来之前的版本。

1
2
3
4
../../develop_env/flutter_3.0/.pub-cache/hosted/pub.flutter-io.cn/get-4.6.1/lib/get_state_manager/src/simple/get_controllers.dart:96:20: Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
- 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('../../develop_env/flutter_3.0/packages/flutter/lib/src/widgets/binding.dart').
WidgetsBinding.instance!.removeObserver(this);
^

去 pub 上查看更新记录 (changelog),可以看到 4.6.2 兼容了 Flutter 3.0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[4.6.5] #
Fix pub dev score

[4.6.4]
Added backward compatibility with flutter 2.

[4.6.3]
Fix SDK constraints

[4.6.2]
Added compatibility with flutter 3.0

[4.6.1]
Fix GetConnect on Flutter web

所以我们只需要将 get 的版本更改为 4.6.2 或以上即可。

1
2
3
dependencies:
# get: ^4.6.1
get: ^4.6.2

# 本地代码

如果是项目中有用到 Binding.instance,可以使用 dart 命令 dart fix --apply 自动修复,这样就会自动把 instance 后面的 ! 去掉。

1
2
3
4
5
6
7
8
9
10
11
adodeMacBook-Pro:fusion_pro wangyang$ dart fix --apply
Computing fixes in fusion_pro... 105.4s
Applying fixes... 0.0s

lib/pages/splash_page.dart
UNNECESSARY_NON_NULL_ASSERTION • 1 fix

lib/stress_test/stress_test_page.dart
UNNECESSARY_NON_NULL_ASSERTION • 1 fix

2 fixes made in 2 files.

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

SpringBoot自动装配原理

InterviewCoder

自动装配原理:

要学会 SpringBoot 自动装配原理,首先要来看装配了什么?或者说是装配有什么用?

1 . 自动装配了什么?

​ Pom 文件:SpringBoot 帮我们配置好了所有 web 开发常见场景、组件、比如 tomcat、mutipart 文件上传。

​ 配置文件:SpringBoot 帮我们装配好了所有组件或者类需要的配置,这些类都有默认的配置,可以通过 application.ymal 或者 application.properties 来修改:比如 server.port=80 修改端口号,默认为 80

2 . 自动装配有什么用?

​ 简化 SpringMVC 复杂的配置流程、复杂的依赖引入。SpringBoot 旨在:简化开发,约定大于配置。

总结:

  • SpringBoot 先加载所有的自动配置类 xxxAutoConfiguration

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxProperties 里面拿。xxxProperties 和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 定制化配置

    • 用户直接自己 @Bean 替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxAutoConfiguration —> 组件 —> xxxProperties 里面拿值 ----> application.properties

# 1、SpringBoot 特点

# 1.1、依赖管理

  • 父项目做依赖管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
依赖管理    
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>

他的父项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>

几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
  • 开发导入 starter 场景启动器
1
2
3
4
5
6
7
8
9
10
11
12
1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
  • 无需关注版本号,自动版本仲裁
1
2
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。
  • 可以修改默认版本号
1
2
3
4
5
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>

# 1.2、自动配置

  • 自动配好 Tomcat

    • 引入 Tomcat 依赖。
    • 配置 Tomcat
1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
  • 自动配好 SpringMVC

    • 引入 SpringMVC 全套组件
    • 自动配好 SpringMVC 常用组件(功能)
  • 自动配好 Web 常见功能,如:字符编码问题

    • SpringBoot 帮我们配置好了所有 web 开发的常见场景
  • 默认的包结构

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    • 无需以前的包扫描配置
    • 想要改变扫描路径,@SpringBootApplication (scanBasePackages=“com.atguigu”)
      • 或者 @ComponentScan 指定扫描路径
1
2
3
4
5
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
  • 各种配置拥有默认值

    • 默认配置最终都是映射到某个类上,如:MultipartProperties
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
  • 按需加载所有自动配置项

    • 非常多的 starter
    • 引入了哪些场景这个场景的自动配置才会开启
    • SpringBoot 所有的自动配置功能都在 spring-boot-autoconfigure 包里面

# 2、容器功能

# 2.1、组件添加

# 1、@Configuration

  • 基本使用

  • Full 模式与 Lite 模式

    • 示例
    • 最佳实战
      • 配置 类组件之间无依赖关系用 Lite 模式加速容器启动过程,减少判断
      • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用 Full 模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#############################Configuration使用示例######################################################
/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
*
*
*
*/
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}

@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}


################################@Configuration测试代码如下########################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {

public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

//3、从容器中获取组件

Pet tom01 = run.getBean("tom", Pet.class);

Pet tom02 = run.getBean("tom", Pet.class);

System.out.println("组件:"+(tom01 == tom02));


//4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);

//如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
//保持组件单实例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);


User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);

System.out.println("用户的宠物:"+(user01.getPet() == tom));



}
}

# 2、@Bean、@Component、@Controller、@Service、@Repository

# 3、@ComponentScan、@Import

1
2
3
4
5
6
7
8
9
10
11
 * 4@Import({User.class, DBHelper.class})
* 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
*
*
*
*/

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
}

@Import 高级用法: https://www.bilibili.com/video/BV1gW411W7wy?p=8

# 4、@Conditional

条件装配:满足 Conditional 指定的条件,则进行组件注入

img

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
=====================测试条件装配==========================
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name = "tom")
@ConditionalOnMissingBean(name = "tom")
public class MyConfig {


/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/

@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}

@Bean("tom22")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}

public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

boolean tom = run.containsBean("tom");
System.out.println("容器中Tom组件:"+tom);

boolean user01 = run.containsBean("user01");
System.out.println("容器中user01组件:"+user01);

boolean tom22 = run.containsBean("tom22");
System.out.println("容器中tom22组件:"+tom22);


}

# 2.2、原生配置文件引入

# 1、@ImportResource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
======================beans.xml=========================
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<bean id="haha" class="com.atguigu.boot.bean.User">
<property name="name" value="zhangsan"></property>
<property name="age" value="18"></property>
</bean>

<bean id="hehe" class="com.atguigu.boot.bean.Pet">
<property name="name" value="tomcat"></property>
</bean>
</beans>
@ImportResource("classpath:beans.xml")
public class MyConfig {}

======================测试=================
boolean haha = run.containsBean("haha");
boolean hehe = run.containsBean("hehe");
System.out.println("haha:"+haha);//true
System.out.println("hehe:"+hehe);//true

# 2.3、配置绑定

如何使用 Java 读取到 properties 文件中的内容,并且把它封装到 JavaBean 中,以供随时使用;

1
2
3
4
5
6
7
8
9
10
11
12
13
public class getProperties {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pps = new Properties();
pps.load(new FileInputStream("a.properties"));
Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + "=" + strValue);
//封装到JavaBean。
}
}
}

# 1、@ConfigurationProperties

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
/**
* 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
*/
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {

private String brand;
private Integer price;

public String getBrand() {
return brand;
}

public void setBrand(String brand) {
this.brand = brand;
}

public Integer getPrice() {
return price;
}

public void setPrice(Integer price) {
this.price = price;
}

@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}

# 2、@EnableConfigurationProperties + @ConfigurationProperties

# 3、@Component + @ConfigurationProperties

1
2
3
4
5
@EnableConfigurationProperties(Car.class)
//1、开启Car配置绑定功能
//2、把这个Car这个组件自动注册到容器中
public class MyConfig {
}

# 3、自动配置原理入门

# 3.1、引导加载自动配置类

1
2
3
4
5
6
7
8
9
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}


======================

# 1、@SpringBootConfiguration

@Configuration。代表当前是一个配置类

# 2、@ComponentScan

指定扫描哪些,Spring 注解;

# 3、@EnableAutoConfiguration

1
2
3
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

# 1、@AutoConfigurationPackage

自动配置包?指定了默认的包规则

1
2
3
4
5
@Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。

# 2、@Import(AutoConfigurationImportSelector.class)

1
2
3
4
5
6
7
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

# img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

# 3.2、按需开启自动配置项

1
2
虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置。

# 3.3、修改默认配置

1
2
3
4
5
6
7
8
9
10
        @Bean
@ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
给容器中加入了文件上传解析器;

#

# SpringBoot 默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

1
2
3
4
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
}

#

总结:

  • SpringBoot 先加载所有的自动配置类 xxxxxAutoConfiguration

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties 里面拿。xxxProperties 和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 定制化配置

    • 用户直接自己 @Bean 替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration —> 组件 —> xxxxProperties 里面拿值 ----> application.properties

# 3.4、最佳实践

# 4、开发小技巧

# 4.1、Lombok

简化 JavaBean 开发

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
        <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>


idea中搜索安装lombok插件
===============================简化JavaBean开发===================================
@NoArgsConstructor
//@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class User {

private String name;
private Integer age;

private Pet pet;

public User(String name,Integer age){
this.name = name;
this.age = age;
}


}



================================简化日志开发===================================
@Slf4j
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(@RequestParam("name") String name){

log.info("请求进来了....");

return "Hello, Spring Boot 2!"+"你好:"+name;
}
}

# 4.2、dev-tools

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

项目或者页面修改以后:Ctrl+F9;

# 4.3、Spring Initailizr(项目初始化向导)

# 0、选择我们需要的开发场景

img

# 1、自动依赖引入

img

# 2、自动创建项目结构

img

# 3、自动编写好主配置类

img

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

主从同步遇到 Got fatal error 1236 from master when reading data from binary log

InterviewCoder

#

首先遇到这个是因为 binlog 位置索引处的问题,不要 reset slave;

reset slave 会将主从同步的文件以及位置恢复到初始状态,一开始没有数据还好,有数据的话,相当于重新开始同步,可能会出现一些问题;

一般做主从同步,都是要求以后的数据实现主从同步,而对于旧的数据完全可以使用数据库同步工具先将数据库同步,完了再进行主从同步;

好了遇到上面的问题,正确做法是:

1. 打开主服务器,进入 mysql

2. 执行 flush logs;// 这时主服务器会重新创建一个 binlog 文件;

3. 在主服务上执行 show master status; 显示如下:

img

4. 来到从服务器的 mysql;

5.stop slave;

6.change master to master_log_file=‘mysql-bin.000012’,master_log_pos=154;// 这里的 file 和 pos 都是上面主服务器 master 显示的。

7.start slave;// 这时候就应可以了

8.show slave status \G;

img

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【Flutter】Flutter修复Android 12无法构建的问题,自动适配 exported 深入解析避坑.md

InterviewCoder

# 【Flutter】Flutter 修复 Android 12 无法构建的问题,自动适配 exported 深入解析避坑

【摘要】 众所周知,从 Android 12 开始,使用了 TargetSDK 31 之后,四大组件如果使用了 intent-filter, 但是没显性质配置 exported App 将会无法安装,甚至编译不通过…

比如启动的 Activity 就需要设置 exportedtrue ,至于其他组件是否设置为 true 则看它是否需要被其它应用调用。

# 然而这个事情的状态是这样的:

  • 如果出现问题的 AndroidManifest 文件是你本地的,那手动修改即可;
  • 但如果出现问题的是第三方远程依赖,并且对方并没有提供源码和更新,你就无法直接修改;
  • 如果第三方依赖太多,查找哪些出了问题十分费时费力。

# 脚本

所以在之前的 《Android 12 快速适配要点》 一文中提供了一套脚本,专门用于适配 Android 12 下缺少 android:exported 无法编译或者安装的问题,但是在这期间收到了不少问题反馈:

# com.android.tools.build:gradle:4.0.0 以及其下版本

一下脚本经过测试最高可到支持的版本: gradle:4.0.0 & gradle-6.1.1-all.zip

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
/**
* 修改 Android 12 因为 exported 的构建问题
*/
android.applicationVariants.all { variant ->
variant.outputs.all { output ->
output.processResources.doFirst { pm ->
String manifestPath = output.processResources.manifestFile
def manifestFile = new File(manifestPath)
def xml = new XmlParser(false, true).parse(manifestFile)
def exportedTag = "android:exported"
///指定 space
def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')

def nodes = xml.application[0].'*'.findAll {
//挑选要修改的节点,没有指定的 exported 的才需要增加
(it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(androidSpace.exported) == null

}
///添加 exported,默认 false
nodes.each {
def isMain = false
it.each {
if (it.name() == "intent-filter") {
it.each {
if (it.name() == "action") {
if (it.attributes().get(androidSpace.name) == "android.intent.action.MAIN") {
isMain = true
println("......................MAIN FOUND......................")
}
}
}
}
}
it.attributes().put(exportedTag, "${isMain}")
}

PrintWriter pw = new PrintWriter(manifestFile)
pw.write(groovy.xml.XmlUtil.serialize(xml))
pw.close()
}
}

}

# com.android.tools.build:gradle:4.0.0 以上版本

以下脚本经过测试支持的版本: gradle:4.1.0 & gradle-6.5.1-all.zip

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
/**
* 修改 Android 12 因为 exported 的构建问题
*/

android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def processManifest = output.getProcessManifestProvider().get()
processManifest.doLast { task ->
def outputDir = task.multiApkManifestOutputDirectory
File outputDirectory
if (outputDir instanceof File) {
outputDirectory = outputDir
} else {
outputDirectory = outputDir.get().asFile
}
File manifestOutFile = file("$outputDirectory/AndroidManifest.xml")
println("----------- ${manifestOutFile} ----------- ")

if (manifestOutFile.exists() && manifestOutFile.canRead() && manifestOutFile.canWrite()) {
def manifestFile = manifestOutFile
///这里第二个参数是 false ,所以 namespace 是展开的,所以下面不能用 androidSpace,而是用 nameTag
def xml = new XmlParser(false, false).parse(manifestFile)
def exportedTag = "android:exported"
def nameTag = "android:name"
///指定 space
//def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')

def nodes = xml.application[0].'*'.findAll {
//挑选要修改的节点,没有指定的 exported 的才需要增加
//如果 exportedTag 拿不到可以尝试 it.attribute(androidSpace.exported)
(it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(exportedTag) == null

}
///添加 exported,默认 false
nodes.each {
def isMain = false
it.each {
if (it.name() == "intent-filter") {
it.each {
if (it.name() == "action") {
//如果 nameTag 拿不到可以尝试 it.attribute(androidSpace.name)
if (it.attributes().get(nameTag) == "android.intent.action.MAIN") {
isMain = true
println("......................MAIN FOUND......................")
}
}
}
}
}
it.attributes().put(exportedTag, "${isMain}")
}

PrintWriter pw = new PrintWriter(manifestFile)
pw.write(groovy.xml.XmlUtil.serialize(xml))
pw.close()

}

}
}
}

这段脚本你可以直接放到 app/build.gradle 下执行,也可以单独放到一个 gradle 文件之后 apply 引入,它的作用就是:

在打包过程中检索所有没有设置 exported 的组件,给他们动态配置上 exported ,这里有个特殊需要注意的是,因为启动 Activity 默认就是需要被 Launcher 打开的,所以 "android.intent.action.MAIN" 需要 exported 设置为 true 。(PS:更正规应该是用 LAUNCHER 类别,这里故意用 MAIN

而后综合问题,具体反馈的问题有 :

  • label 直接写死中文,不是引用 @string 导致的在 3.x 的版本可以正常运行,但不能打包 ;
  • XmlParser 类找不到,这个首先确定 AGP 版本和 Gradle 版本是否匹配,具体可见 gradle-plugin,另外可以通过 groovy.util.XmlParser 或者 groovy.xml.XmlParser 全路径指定使用 ,如果是 gradle 文件里显示红色并不会影响运行;
  • 运行报错提示 android:exported needs ,这个就是今天需要输入聊的
1
2
3
4
Error: android:exported needs to be explicitly specified for <xxxx>. Apps targeting Android 12 and higher are required to specify an explicit value for `android:exported` when the corresponding component has an intent filter defined.



基于上述脚本测试和反馈,目前的结论是:

gradle:4.2.0 & gradle-6.7.1-all.zip 开始,TargetSDK 31 下脚本会有异常,因为在 processDebugMainManifest (带有 Main) 的阶段,会直接扫描依赖库的 AndroidManifest.xml 然后抛出直接报错,从而进不去 processDebugManifest 任务阶段就编译停止,所以实际上脚本并没有成功运行

所以此时拿不到 mergerd_manifest 下的文件,因为 mergerd_manifestAndroidManifest.xml 也还没创建成功,没办法进入 task ,也就是该脚本目前只能针对 gradle:4.1.0 以及其下版本安装 apk 到 Android12 的机器上, 有 intent-filter 但没有 exoprted 的适配问题,基于这个问题,不知道各位是否有什么好的建议?

# 新脚本

而目前基于这个问题,这里提供了如下脚本,在 gradle:4.2.0 & gradle-6.7.1-all.zip 以及 7.0 的版本上,该脚本的作用是在运行时自动帮你打印出现问题的 aar 包依赖路径和组建名称

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
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
//println("=============== ${variant.getBuildType().name.toUpperCase()} ===============")
//println("=============== ${variant.getFlavorName()} ===============")
def vn
if (variant.getFlavorName() != null && variant.getFlavorName() != "") {
vn = variant.name;
} else {
if (variant.getBuildType().name == "release") {
vn = "Release"
} else {
vn = "Debug"
}
}
def taskName = "process${vn}MainManifest";
try {
println("=============== taskName ${taskName} ===============")
project.getTasks().getByName(taskName)
} catch (Exception e) {
return
}
///你的自定义名字
project.getTasks().getByName(taskName).doFirst {
//def method = it.getClass().getMethods()
it.getManifests().getFiles().each {
if (it.exists() && it.canRead()) {
def manifestFile = it
def exportedTag = "android:exported"
def nameTag = "android:name"
///这里第二个参数是 false ,所以 namespace 是展开的,所以下面不能用 androidSpace,而是用 nameTag
def xml = new XmlParser(false, false).parse(manifestFile)
if (xml.application != null && xml.application.size() > 0) {
def nodes = xml.application[0].'*'.findAll {
//挑选要修改的节点,没有指定的 exported 的才需要增加
//如果 exportedTag 拿不到可以尝试 it.attribute(androidSpace.exported)
(it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(exportedTag) == null

}
if (nodes.application != null && nodes.application.size() > 0) {
nodes.each {
def t = it
it.each {
if (it.name() == "intent-filter") {
println("$manifestFile \n .....................${t.attributes().get(nameTag)}......................")
}
}
}
}
}
}
}
}
}
}



如下图所示,因为目前官方如红色信息内容其实指向并不正确,容易误导问题方向,所以通过上述脚本打印,可以快速查找到问题所在的点,然后通过 tool:replace 临时解决

img

具体为什么之前的脚本在高版本 AGP 下无法使用,原因在于新版本在 processDebugMainManifest ,或者说 processXXXXXXMainManifest 的处理逻辑发生了变化,通过找到 processDebugMainManifest 的实现类,可以看到问题出现就是在于 Merging library manifest

processDebugMainManifest 的实现在 ProcessApplicationManifest 里,对应路径是 ProcessApplicationManifest -> MainfestHelper mergeManifestsForApplication -> MainfestMerger2

错误是在 Merging library manifest 的阶段出现异常,但是这个阶段的 task 里对于第三方依赖路径的输入,主要是从 private fun computeFullProviderList 方法开始,所以输入到 mergeManifestsForApplication 里的第三方路径是通过这个私有方法生成。

img

感觉唯一可以考虑操作的就是内部的 manifests 对象去变换路径,但是它是 private ,并且内部并不能很好复写其内容。

img

另外因为 aar 文件里的 AndroidManifset 是 readOnly ,所以如果真的要修改,感觉只能在输入之前读取到对应 AndroidManifset , 并生成临时文件,在 manifests 对象中更改其路径来完成,不知道大家有没有什么比较好的思路 。

如果有好的解决办法,后续再更新。

# 最后

最后再说一个坑 ,如果你是低版本 Gradle 可以打包成功,但是运行到 Android12 机器的时候,可能会因为没有 exported 遇到安装失败的问题:

1、如果是模拟器 12,你可能会看到如下所示的错误提示 ,提示上显示还是很直观的, 直接告诉你是 android:exported 的问题:

1
2
3
4
5
6
* What went wrong:
Execution failed for task ':app:installDebug'.
> java.util.concurrent.ExecutionException: com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: Failed parse during installPackageLI: /data/app/vmdl487461761.tmp/base.apk (at Binary XML file line #358): xxxxx.Activity: Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present



2、如果你是真机 12,那可能就是这样的提示,提示然是 INSTALL_FAILED_USER_RESTRICTED不得不说小米系统这个安装失败很具误导性,比如 minSDK 太高导致无法安装,在小米上也会是 INSTALL_FAILED_USER_RESTRICTED

img

基本上内容就这些,具体如何进一步优化还待后续测试, 所以针对脚本实现,你还有什么问题或者想法,欢迎评论交流 ~

文章来源: carguo.blog.csdn.net,作者:恋猫 de 小郭,版权归原作者所有,如需转载,请联系作者。

原文链接:carguo.blog.csdn.net/article/details/123438734

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【架构】DDD分层架构模型

InterviewCoder

# 【架构】DDD 分层架构模型

还在单体应用的时候就是分层架构一说,我们用得最多的就是三层架构。而现在已经是微服务时代,在微服务架构模型比较常用的有几个,例如:整洁架构,CQRS(命令查询分离)以及六边形架构。每种架构模型都有自己的应用场景,但其核心都是 “高内聚低耦合” 原则。而运用领域驱动设计(DDD)理念以应对日常加速的业务变化对架构的影响,架构的边界越来越清晰,各司其职,这也符合微服务架构的设计思想。以领域驱动设计(DDD)为理念的分层架构已经成为微服务架构实践的最佳实践方法。

# 一、什么是 DDD 分层架构

# 1. 传统三层架构

要了解 DDD 分层架构,首先先了解传统的三层架构。

DDD分层架构最佳实践

传统三层架构流程:

  • 第一步考虑的是数据库设计,数据表如何建,表之间的关系如何设计
  • 第二步就是搭建数据访问层,如选一个 ORM 框架或者拼接 SQL 操作
  • 第三步就是业务逻辑的实现,由于我们先设计了数据库,我们整个的思考都会围绕着数据库,想着怎么写才能把数据正确地写入数据库中,这时 CRUD 的标准作法就出现了,也就没有太多考虑面向对象,解耦的事情了,这样的代码对日常的维护自然是越来越困难的
  • 第四步表示层主要面向用户的输出

# 2. DDD 分层架构

DDD分层架构最佳实践

为了解决高耦合问题并轻松应对以后的系统变化,我们提出了运用领域驱动设计的理念来设计架构。

此段落部分总结来源于欧创新《DDD 实践课》的《07 | DDD 分层架构:有效降低层与层之间的依赖》读后感

# 1)领域层

首先我们抛开数据库的困扰,先从业务逻辑入手开始,设计时不再考虑数据库的实现。将以前的业务逻辑层(BLL)拆分成了领域层和应用层。

领域层聚焦业务对象的业务逻辑实现,体现现实世界业务的逻辑变化。它用来表达业务概念、业务状态和业务规则,对于业务分析可参照:《使用领域驱动设计分析业务》

# 2)应用层

应用层是领域层的上层,依赖领域层,是各聚合的协调和编排,原则上是不包括任何业务逻辑。它以较粗粒度的封闭为前端接口提供支持。除了提供上层调用外,还可以包括事件和消息的订阅。

# 3) 用户接口层

用户接口层面向用户访问的数据入向接口,可按不同场景提供不一样的用户接口实现。面向 Web 的可使用 http restful 的方式提供服务,可增加安全认证、权限校验,日志记录等功能;面向微服务的可使用 RPC 方式提供服务,可增加限流、熔断等功能。

# 4) 基础设施层

基础设施层是数据的出向接口,封装数据调用的技术细节。可为其它任意层提供服务,但为了解决耦合的问题采用了依赖倒置原则。其它层只依赖基础设施的接口,于具体实现进行分离。

# 二、DDD 分层代码实现

# 1. 结构模型

DDD分层架构最佳实践

# 2. 目录结构

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
.
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── fun
│ │ └── barryhome
│ │ └── ddd
│ │ ├── WalletApplication.java
│ │ ├── application
│ │ │ ├── TradeEventProcessor.java
│ │ │ ├── TradeMQReceiver.java
│ │ │ └── TradeManager.java
│ │ ├── constant
│ │ │ └── MessageConstant.java
│ │ ├── controller
│ │ │ ├── TradeController.java
│ │ │ ├── WalletController.java
│ │ │ └── dto
│ │ │ └── TradeDTO.java
│ │ ├── domain
│ │ │ ├── TradeService.java
│ │ │ ├── TradeServiceImpl.java
│ │ │ ├── enums
│ │ │ │ ├── InOutFlag.java
│ │ │ │ ├── TradeStatus.java
│ │ │ │ ├── TradeType.java
│ │ │ │ └── WalletStatus.java
│ │ │ ├── event
│ │ │ │ └── TradeEvent.java
│ │ │ ├── model
│ │ │ │ ├── BaseEntity.java
│ │ │ │ ├── TradeRecord.java
│ │ │ │ └── Wallet.java
│ │ │ └── repository
│ │ │ ├── TradeRepository.java
│ │ │ └── WalletRepository.java
│ │ └── infrastructure
│ │ ├── TradeRepositoryImpl.java
│ │ ├── WalletRepositoryImpl.java
│ │ ├── cache
│ │ │ └── Redis.java
│ │ ├── client
│ │ │ ├── AuthFeignClient.java
│ │ │ └── LocalAuthClient.java
│ │ ├── jpa
│ │ │ ├── JpaTradeRepository.java
│ │ │ └── JpaWalletRepository.java
│ │ └── mq
│ │ └── RabbitMQSender.java
│ └── resources
│ ├── application.properties
│ └── rabbitmq-spring.xml
└── test
└── java

此结构为单一微服务的简单结构,各层在同一个模块中。

在大型项目开发过程中,为了达到核心模块的权限控制或更好的灵活性可适当调整结构,可参考《 数字钱包系统》系统结构

# 3. 领域层实现(domain)

在业务分析(《使用领域驱动设计分析业务》)之后,开始编写代码,首先就是写领域层,创建领域对象和领域服务接口

# 1)领域对象

这里的领域对象包括实体对象、值对象。

实体对象:具有唯一标识,能单独存在且可变化的对象

值对象:不能单独存在或在逻辑层面单独存在无意义,且不可变化的对象

聚合:多个对象的集合,对外是一个整体

聚合根:聚合中可代表整个业务操作的实体对象,通过它提供对外访问操作,它维护聚合内部的数据一致性,它是聚合中对象的管理者

代码示例:

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 TradeRecord extends BaseEntity {
/**
* 交易号
*/
@Column(unique = true)
private String tradeNumber;
/**
* 交易金额
*/
private BigDecimal tradeAmount;
/**
* 交易类型
*/
@Enumerated(EnumType.STRING)
private TradeType tradeType;
/**
* 交易余额
*/
private BigDecimal balance;
/**
* 钱包
*/
@ManyToOne
private Wallet wallet;

/**
* 交易状态
*/
@Enumerated(EnumType.STRING)
private TradeStatus tradeStatus;

@DomainEvents
public List<Object> domainEvents() {
return Collections.singletonList(new TradeEvent(this));
}
}

// 钱包
public class Wallet extends BaseEntity {

/**
* 钱包ID
*/
@Id
private String walletId;
/**
* 密码
*/
private String password;
/**
* 状态
*/
@Enumerated(EnumType.STRING)
private WalletStatus walletStatus = WalletStatus.AVAILABLE;
/**
* 用户Id
*/
private Integer userId;
/**
* 余额
*/
private BigDecimal balance = BigDecimal.ZERO;

}
  • 钱包交易例子的系统设计中,钱包的任何操作如:充值、消息等都是通过交易对象驱动钱包余额的变化
  • 交易对象钱包对象均为实体对象且组成聚合关系,交易对象是钱包交易业务模型的聚合根,代表聚合向外提供调用服务
  • 经过分析交易对象钱包对象为 1 对多关系(@ManyToOne),这里采用了 JPA 做 ORM 架构,更多 JPA 实践请参考 >>
  • 这里的领域建模使用的是贫血模型,结构简单,职责单一,相互隔离性好但缺乏面向对象设计思想,关于领域建模可参考《领域建模的贫血模型与充血模型》
  • domainEvents () 为领域事件发布的一种实现,作用是交易对象任何的数据操作都将触发事件的发布,再配合事件订阅实现事件驱动设计模型,当然也可以有别的实现方式

# 2)领域服务

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
/**
* Created on 2020/9/7 11:40 上午
*
* @author barry
* Description: 交易服务
*/
public interface TradeService {

/**
* 充值
*
* @param tradeRecord
* @return
*/
TradeRecord recharge(TradeRecord tradeRecord);

/**
* 消费
*
* @param tradeRecord
* @return
*/
TradeRecord consume(TradeRecord tradeRecord);
}



先定义服务接口,接口的定义需要遵循现实业务的操作,切勿以程序逻辑或数据库逻辑来设计定义出增删改查

  • 主要的思考方向是交易对象对外可提供哪些服务,这种服务的定义是粗粒度高内聚的,切勿将某些具体代码实现层面的方法定义出来
  • 接口的输入输出参数尽量考虑以对象的形式,充分兼容各种场景变化
  • 关于前端需要的复杂查询方法可不在此定义,一般情况下查询并非是一种领域服务且没有数据变化,可单独处理
  • 领域服务的实现主要关注逻辑实现,切勿包含技术基础类代码,比如缓存实现,数据库实现,远程调用等

# 3)基础设施接口

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
public interface TradeRepository {
/**
* 保存
* @param tradeRecord
* @return
*/
TradeRecord save(TradeRecord tradeRecord);

/**
* 查询订单
* @param tradeNumber
* @return
*/
TradeRecord findByTradeNumber(String tradeNumber);

/**
* 发送MQ事件消息
* @param tradeEvent
*/
void sendMQEvent(TradeEvent tradeEvent);

/**
* 获取所有
* @return
*/
List<TradeRecord> findAll();
}



  • 基础设施接口放在领域层主要的目的是减少领域层对基础设施层的依赖
  • 接口的设计是不可暴露实现的技术细节,如不能将拼装的 SQL 作为参数

# 4. 应用层实现(application)

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
// 交易服务
@Component
public class TradeManager {

private final TradeService tradeService;
public TradeManager(TradeService tradeService) {
this.tradeService = tradeService;
}


// 充值
@Transactional(rollbackFor = Exception.class)
public TradeRecord recharge(TradeRecord tradeRecord) {
return tradeService.recharge(tradeRecord);
}


// 消费
@Transactional(rollbackFor = Exception.class)
public TradeRecord consume(TradeRecord tradeRecord) {
return tradeService.consume(tradeRecord);
}
}

// 交易事件订阅
@Component
public class TradeEventProcessor {

@Autowired
private TradeRepository tradeRepository;

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, condition = "# tradeEvent.tradeStatus.name() == 'SUCCEED'")
public void TradeSucceed(TradeEvent tradeEvent) {
tradeRepository.sendMQEvent(tradeEvent);
}
}

// 交易消息订阅
@Component
public class TradeMQReceiver {

@RabbitListener(queues = "ddd-trade-succeed")
public void receiveTradeMessage(TradeEvent tradeEvent){
System.err.println("========MQ Receiver============");
System.err.println(tradeEvent);
}
}



应用服务

  • 应用层是很薄的一层,主要用于调用和组合领域服务,切勿包含任何业务逻辑
  • 可包括少量的流程参数判断
  • 由于可能是多个领域服务组合操作调用,如果存在原子性要求可以增加 **@Transactional** 事务控制

事件订阅

  • 事件订阅是进程内多个领域操作协作解耦的一种实现方式,它也是进程内所有后续操作的接入口
  • 它与应用服务的组合操作用途不一样,组合是根据场景需求可增可减,但事件订阅后的操作是相对固化的,主要是满足逻辑的一致性要求

TransactionPhase.AFTER_COMMIT 配置是在前一操作事务完成后再调用,从而减少后续操作对前操作的影响

  • 事件订阅可能会有多个消息主体,为了方便管理最好统一在一个类里处理
  • MQ 消息发布一般放在事件订阅中

消息订阅

  • 消息订阅是多个微服务间协作解耦的一步实现方式
  • 消息体尽量以统一的对象包装进行传递,降低对象异构带来的处理难度

# 5. 基础设施层(infrastructure)

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
@Repository
public class TradeRepositoryImpl implements TradeRepository {

private final JpaTradeRepository jpaTradeRepository;
private final RabbitMQSender rabbitMQSender;
private final Redis redis;

public TradeRepositoryImpl(JpaTradeRepository jpaTradeRepository, RabbitMQSender rabbitMQSender, Redis redis) {
this.jpaTradeRepository = jpaTradeRepository;
this.rabbitMQSender = rabbitMQSender;
this.redis = redis;
}

@Override
public TradeRecord save(TradeRecord tradeRecord) {
return jpaTradeRepository.save(tradeRecord);
}

/**
* 查询订单
*/
@Override
public TradeRecord findByTradeNumber(String tradeNumber) {
TradeRecord tradeRecord = redis.getTrade(tradeNumber);
if (tradeRecord == null){
tradeRecord = jpaTradeRepository.findFirstByTradeNumber(tradeNumber);
// 缓存
redis.cacheTrade(tradeRecord);
}

return tradeRecord;
}

/**
* 发送事件消息
* @param tradeEvent
*/
@Override
public void sendMQEvent(TradeEvent tradeEvent) {
// 发送消息
rabbitMQSender.sendMQTradeEvent(tradeEvent);
}

/**
* 获取所有
*/
@Override
public List<TradeRecord> findAll() {
return jpaTradeRepository.findAll();
}
}


  • 基础设施层是数据的输出向,主要包含数据库、缓存、消息队列、远程访问等的技术实现
  • 基础设计层对外隐藏技术实现细节,提供粗粒度的数据输出服务
  • 数据库操作:领域层传递的是数据对象,在这里可以按数据表的实现方式进行拆分实现

# 6. 用户接口层(controller)

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
@RequestMapping("/trade")
@RestController
public class TradeController {

@Autowired
private TradeManager tradeManager;

@Autowired
private TradeRepository tradeRepository;

@PostMapping(path = "/recharge")
public TradeDTO recharge(@RequestBody TradeDTO tradeDTO) {
return TradeDTO.toDto(tradeManager.recharge(tradeDTO.toEntity()));
}

@PostMapping(path = "/consume")
public TradeDTO consume(@RequestBody TradeDTO tradeDTO) {
return TradeDTO.toDto(tradeManager.consume(tradeDTO.toEntity()));
}

@GetMapping(path = "/{tradeNumber}")
public TradeDTO findByTradeNumber(@PathVariable("tradeNumber") String tradeNumber){
return TradeDTO.toDto(tradeRepository.findByTradeNumber(tradeNumber));
}

}



  • 用户接口层面向终端提供服务支持
  • 可根据不同的场景单独一个模块,面向 Web 提供 http restful,面向服务间 API 调用提供 RPG 支持
  • 为 Web 端提供身份认证和权限验证服务,VO 数据转换
  • 为 API 端提供限流和熔断服务,DTO 数据转换
  • 将数据转换从应用层提到用户接口层更方便不同场景之前的需求变化,同时也保证应用层数据格式的统一性

# 7. 复杂数据查询

以上可见并没有涉及复杂数据查询问题,此问题不涉及业务逻辑处理所以不应该放在领域层处理。

如果复杂数据查询需求较多可采用 CQRS 模式,将查询单独一个模块处理。如果较少可由基础设施层做数据查询,应用层做数据封装,用户接口层做数据调用

  • JPA 不太适合做多表关联的数据库查询操作,可使用其它的灵活性较高的 ORM 架构

在大数据大并发情况下,多表关联会严重影响数据库性能,可以考虑做宽表查询

# 三、综述

DDD 分层主要解决各层之间耦合度问题,做到各层各施其职互不影响。各层中领域层的设计是整个系统的中枢,最能体现领域驱动设计的核心思想。它的良好设计是保证往后架构的可持续性,可维护性。

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

端口占用问题

InterviewCoder

安装好 Maven 之后配置环境变量:

netstat -ano:查询全部活动连接

tasklist :查询全部的进程和 PID

tasklist | findstr “占用端口的进程 PID”

taskkill /f/t /im 占用端口的进程名字.exe

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

【Java】多种代理的实现方式

# 【Java】多种代理的实现方式

# 五种类代理的方式

不出意外 ,你可能只知道两种类代理的方式。一种是 JDK 自带的,另外一种是 CGLIB。

我们先定义出一个接口和相应的实现类,方便后续使用代理类在方法中添加输出信息。

定义接口

1
2
3
4
5
public interface IUserApi {

String queryUserInfo();

}

实现接口

1
2
3
4
5
6
7
public class UserApi implements IUserApi {

public String queryUserInfo() {
return "面试记公众号:InterviewCoder,为了更好的你,也为了更好的世界!";
}

}

好!接下来我们就给这个类方法使用代理加入一行额外输出的信息。

# 0. 先补充一点反射的知识

1
2
3
4
5
6
7
@Test
public void test_reflect() throws Exception {
Class<UserApi> clazz = UserApi.class;
Method queryUserInfo = clazz.getMethod("queryUserInfo");
Object invoke = queryUserInfo.invoke(clazz.newInstance());
System.out.println(invoke);
}

  • 指数:⭐⭐
  • 点评:有代理地方几乎就会有反射,他们是一套互相配合使用的功能类。在反射中可以调用方法、获取属性、拿到注解等相关内容。这些都可以与接下来的类代理组合使用,完成各种框架中的技术场景。

# 1. JDK 代理方式

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

public static <T> T getProxy(Class clazz) throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return (T) Proxy.newProxyInstance(classLoader, new Class[]{clazz}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + " 你被代理了,By JDKProxy!");
return "面试记公众号:InterviewCoder,为了更好的你,也为了更好的世界!";
}
});
}

}

@Test
public void test_JDKProxy() throws Exception {
IUserApi userApi = JDKProxy.getProxy(IUserApi.class);
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}

/**
* 测试结果:
*
* queryUserInfo 你被代理了,By JDKProxy!
* 19:55:47.319 [main] INFO cn.brath.interview.test.ApiTest - 测试结果:面试记公众号:InterviewCoder,为了更好的你,也为了更好的世界!
*
* Process finished with exit code 0
*/

  • 指数:⭐⭐
  • 场景:中间件开发、设计模式中代理模式和装饰器模式应用
  • 点评:这种 JDK 自带的类代理方式是非常常用的一种,也是非常简单的一种。基本会在一些中间件代码里看到例如:数据库路由组件、Redis 组件等,同时我们也可以使用这样的方式应用到设计模式中。

# 2. CGLIB 代理方式

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
public class CglibProxy implements MethodInterceptor {
public Object newInstall(Object object) {
return Enhancer.create(object.getClass(), this);
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我被CglibProxy代理了");
return methodProxy.invokeSuper(o, objects);
}
}

@Test
public void test_CglibProxy() throws Exception {
CglibProxy cglibProxy = new CglibProxy();
UserApi userApi = (UserApi) cglibProxy.newInstall(new UserApi());
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}

/**
* 测试结果:
*
* queryUserInfo 你被代理了,By CglibProxy!
* 19:55:47.319 [main] INFO cn.brath.interview.test.ApiTest - 测试结果:面试记公众号:InterviewCoder,为了更好的你,也为了更好的世界!
*
* Process finished with exit code 0
*/

  • 指数:⭐⭐⭐
  • 场景:Spring、AOP 切面、鉴权服务、中间件开发、RPC 框架等
  • 点评:CGLIB 不同于 JDK,它的底层使用 ASM 字节码框架在类中修改指令码实现代理,所以这种代理方式也就不需要像 JDK 那样需要接口才能代理。同时得益于字节码框架的使用,所以这种代理方式也会比使用 JDK 代理的方式快 1.5~2.0 倍。

# 3. ASM 代理方式

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
public class ASMProxy extends ClassLoader {

public static <T> T getProxy(Class clazz) throws Exception {

ClassReader classReader = new ClassReader(clazz.getName());
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);

classReader.accept(new ClassVisitor(ASM5, classWriter) {
@Override
public MethodVisitor visitMethod(int access, final String name, String descriptor, String signature, String[] exceptions) {

// 方法过滤
if (!"queryUserInfo".equals(name))
return super.visitMethod(access, name, descriptor, signature, exceptions);

final MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);

return new AdviceAdapter(ASM5, methodVisitor, access, name, descriptor) {

@Override
protected void onMethodEnter() {
// 执行指令;获取静态属性
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// 加载常量 load constant
methodVisitor.visitLdcInsn(name + " 你被代理了,By ASM!");
// 调用方法
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
super.onMethodEnter();
}
};
}
}, ClassReader.EXPAND_FRAMES);

byte[] bytes = classWriter.toByteArray();

return (T) new ASMProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
}

}

@Test
public void test_ASMProxy() throws Exception {
IUserApi userApi = ASMProxy.getProxy(UserApi.class);
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}

/**
* 测试结果:
*
* queryUserInfo 你被代理了,By ASM!
* 20:12:26.791 [main] INFO cn.brath.interview.test.ApiTest - 测试结果:面试记公众号:InterviewCoder,为了更好的你,也为了更好的世界!
*
* Process finished with exit code 0
*/

  • 指数:⭐⭐⭐⭐⭐
  • 场景:全链路监控、破解工具包、CGLIB、Spring 获取类元数据等
  • 点评:这种代理就是使用字节码编程的方式进行处理,它的实现方式相对复杂,而且需要了解 Java 虚拟机规范相关的知识。因为你的每一步代理操作,都是在操作字节码指令,例如: Opcodes.GETSTATICOpcodes.INVOKEVIRTUAL ,除了这些还有小 200 个常用的指令。但这种最接近底层的方式,也是最快的方式。所以在一些使用字节码插装的全链路监控中,会非常常见。

# 4. Byte-Buddy 代理方式

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
public class ByteBuddyProxy {

public static <T> T getProxy(Class clazz) throws Exception {

DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(clazz)
.method(ElementMatchers.<MethodDescription>named("queryUserInfo"))
.intercept(MethodDelegation.to(InvocationHandler.class))
.make();

return (T) dynamicType.load(Thread.currentThread().getContextClassLoader()).getLoaded().newInstance();
}

}

@RuntimeType
public static Object intercept(@Origin Method method, @AllArguments Object[] args, @SuperCall Callable<?> callable) throws Exception {
System.out.println(method.getName() + " 你被代理了,By Byte-Buddy!");
return callable.call();
}

@Test
public void test_ByteBuddyProxy() throws Exception {
IUserApi userApi = ByteBuddyProxy.getProxy(UserApi.class);
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}

/**
* 测试结果:
*
* queryUserInfo 你被代理了,By Byte-Buddy!
* 20:19:44.498 [main] INFO cn.brath.interview.test.ApiTest - 测试结果:面试记公众号:InterviewCoder,为了更好的你,也为了更好的世界!
*
* Process finished with exit code 0
*/

  • 指数:⭐⭐⭐⭐
  • 场景:AOP 切面、类代理、组件、监控、日志
  • 点评: Byte Buddy 也是一个字节码操作的类库,但 Byte Buddy 的使用方式更加简单。无需理解字节码指令,即可使用简单的 API 就能很容易操作字节码,控制类和方法。比起 JDK 动态代理、cglib,Byte Buddy 在性能上具有一定的优势。另外,2015 年 10 月,Byte Buddy 被 Oracle 授予了 Duke’s Choice 大奖。该奖项对 Byte Buddy 的 “Java 技术方面的巨大创新” 表示赞赏。

# 5. Javassist 代理方式

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
public class JavassistProxy extends ClassLoader {

public static <T> T getProxy(Class clazz) throws Exception {

ClassPool pool = ClassPool.getDefault();
// 获取类
CtClass ctClass = pool.get(clazz.getName());
// 获取方法
CtMethod ctMethod = ctClass.getDeclaredMethod("queryUserInfo");
// 方法前加强
ctMethod.insertBefore("{System.out.println(\"" + ctMethod.getName() + " 你被代理了,By Javassist\");}");

byte[] bytes = ctClass.toBytecode();

return (T) new JavassistProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
}

}

@Test
public void test_JavassistProxy() throws Exception {
IUserApi userApi = JavassistProxy.getProxy(UserApi.class)
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}

/**
* 测试结果:
*
* queryUserInfo 你被代理了,By Javassist
* 20:23:39.139 [main] INFO cn.brath.interview.test.ApiTest - 测试结果:面试记公众号:InterviewCoder,为了更好的你,也为了更好的世界!
*
* Process finished with exit code 0
*/

  • 指数:⭐⭐⭐⭐
  • 场景:全链路监控、类代理、AOP
  • 点评: Javassist 是一个使用非常广的字节码插装框架,几乎一大部分非入侵的全链路监控都是会选择使用这个框架。因为它不想 ASM 那样操作字节码导致风险,同时它的功能也非常齐全。另外,这个框架即可使用它所提供的方式直接编写插装代码,也可以使用字节码指令进行控制生成代码,所以综合来看也是一个非常不错的字节码框架。

# 四、总结

img

  • 代理的实际目的就是通过一些技术手段,替换掉原有的实现类或者给原有的实现类注入新的字节码指令。而这些技术最终都会用到一些框架应用、中间件开发以及类似非入侵的全链路监控中。
  • 一个技术栈深度的学习能让你透彻的了解到一些基本的根本原理,通过这样的学习可以解惑掉一些似懂非懂的疑问,也可以通过这样技术的拓展让自己有更好的工作机会和薪资待遇。
  • 这些技术学起来并不会很容易,甚至可能还有一些烧脑。但每一段值得深入学习的技术都能帮助你突破一定阶段的技术瓶颈。

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

Maven通过命令提示符引入jar包的流程

InterviewCoder

安装好 Maven 之后配置环境变量:

​ 在系统变量新建 “MAVEN_HOME” 值为:Maven 的安装路径:E:\Maven\apache-maven-3.5.3,接着在 Path 变量中,添加:% MAVEN_HOME%\bin,指定 maven 的 bin 路径。

​ 现在在 CMD 中就可以用 mvn -v 的命令查看是否安装成功:

maven环境

# 引入流程:

​ 接下来找到你要引入的 jar 包的根路径,指定路径 + jar 包名,指定 groupId、artifactId、version 名,就可以引入了。

# 举例:

​ mvn install:install-file -Dfile=D:\jar\WxQyhPrj\0.0.1-SNAPSHOT\WxQyhPrj-0.0.1-SNAPSHOT.jar -DgroupId=com.chis.wx -DartifactId=WxQyhPrj -Dversion=0.0.1-SNAPSHOT -Dpackaging=jar

​ mvn install:install-file -Dfile:Jar 包的绝对路径

​ -DgroupId:Jar 包的 groupId 分组 id

​ -DartiactId:Jar 包的 artifactId 版本 id

​ -Dversion:Jar 包的 version 版本

其中
– DgroupId 和 DartifactId 构成了该 jar 包在 pom.xml 的坐标, 对应依赖的 DgroupId 和 DartifactId

– Dfile 表示需要上传的 jar 包的绝对路径

– Dpackaging 为安装文件的种类

– DgroupId 和 DartifactId 构成了该 jar 包在 pom.xml 的坐标, 对应依赖的 DgroupId 和 DartifactId

– Dfile 表示需要上传的 jar 包的绝对路径

– Durl 私服上仓库的 url 精确地址 (打开 nexus 左侧 repositories 菜单,可以看到该路径)

– DrepositoryId 服务器的表示 id,在 nexus 的 configuration 可以看到

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

Linux以及Windows部署Seata服务

InterviewCoder

1. 确保 linux 有 Nacos 正常启动,并知晓正确的端口 用户名 密码

  1. https://github.com/seata/seata.git 克隆 seata 项目到本地
  2. 在 Github 下载对应你项目 SpringCloudAlibaba 建议版本的 Seata,我的版本是 2.5.5,建议 Seata 版本 1.3.0
  3. 下载好后进入 seata/conf 配置目录,修改 file.conf
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

## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db" #如果单机:file 如果是集群:db 并在下方配置你的Mysql数据源

## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}

## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://sh-cynosdbmysql-grp-o5n8fbw2.sql.tencentcdb.com:20345/seata_server"
user = "brath"
password = "Lgq081538"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}

}

5. 修改 registry.conf

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
registry {
type = "nacos" //配置nacos

nacos {
application = "seata-server"
serverAddr = "nacosip+端口" //nacosip+端口
group = "SEATA_GROUP"
namespace = "命名空间" //如果默认可以不填
cluster = "default" //如果是集群请填写集群名
username = "账号"
password = "密码"
}
file {
name = "file.conf"
}
}

config {
type = "nacos"

nacos {
serverAddr = "nacosip+端口" //nacosip+端口
namespace = "命名空间" //如果默认可以不填
group = "SEATA_GROUP"
username = "账号"
password = "密码"
}

file {
name = "file.conf"
}
}

6. 在克隆好的项目中找到 script/confing-center 找到 config.txt 文件

修改

1
store.mode=db

修改数据源

1
2
3
store.db.url=localhost:3306/seata_server?useUnicode=true&rewriteBatchedStatements=true
store.db.user=****
store.db.password=****

进入 script\config-center\nacos

如果是 windows 系统请确保安装了 GIT 以及 GIT bash

点击 nacos-config.sh 会自动把 config.txt 配置文件,配置到你的 Nacos 客户端中指定命名空间的配置中心供你的 Seata 使用

7. 进入 Seata/bin

1
sh seata-server.sh #启动seata服务

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!