Docker鏡像構成和定制

Docker鏡像構成和定制

利用 commit 理解鏡像構成

docker commit 命令應用場合

docker commit 命令除了學習之外,還有一些特殊的應用場合,比如被***后保存現場等。但是,不要使用 docker commit 定制鏡像,定制鏡像應該使用 Dockerfile 來完成。

慎用 docker commit

使用 docker commit 意味著所有對鏡像的操作都是黑箱操作,生成的鏡像也被稱為黑箱鏡像,換句話說,就是除了制作鏡像的人知道執行過什么命令、怎么生成的鏡像,別人根本無從得知。而且,即使是這個制作鏡像的人,過一段時間后也無法記清具體在操作的。雖然 docker diff 或許可以告訴得到一些線索,但是遠遠不到可以確保生成一致鏡像的地步。這種黑箱鏡像的維護工作是非常痛苦的。

使用 Dockerfile 定制鏡像

Dockerfile 是一個文本文件,其內包含了一條條的指令(Instruction),每一條指令構建一層,因此每一條指令的內容,就是描述該層應當如何構建。

Dockerfile 指令介紹

COPY 復制文件

格式:
* COPY <源路徑>... <目標路徑>
* COPY ["<源路徑1>",... "<目標路徑>"]
比如:
COPY package.json /usr/src/app/
說明:
<源路徑> 可以是多個,甚至可以是通配符,其通配符規則要滿足 Go 的 filepath.Match 規則,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目標路徑> 可以是容器內的絕對路徑,也可以是相對于工作目錄的相對路徑(工作目錄可以用 WORKDIR 指令來指定)。目標路徑不需要事先創建,如果目錄不存在會在復制文件前先行創建缺失目錄。
注意:
使用 COPY 指令,源文件的各種元數據都會保留。比如讀、寫、執行權限、文件變更時間等。這個特性對于鏡像定制很有用。特別是構建相關文件都在使用 Git 進行管理的時候。

ADD 更高級的復制文件

ADD 指令和 COPY 的格式和性質基本一致。但是在 COPY 基礎上增加了一些功能。
如果 <源路徑> 為一個 tar 壓縮文件的話,壓縮格式為 gzip, bzip2 以及 xz 的情況下,ADD 指令將會自動解壓縮這個壓縮文件到 <目標路徑> 去。
最適合使用 ADD 的場合,就是當我們需要自動解壓縮的場合。如官方鏡像 ubuntu 中:

FROM scratch ##空白鏡像
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...

由于ADD 則包含了更復雜的功能,其行為也不一定很清晰。它不像COPY 的語義很明確,就是復制文件而已。所以,我們還是盡可能使用COPY吧。

CMD 容器啟動命令

CMD 指令就是用于指定默認的容器主進程的啟動命令的。

CMD 指令的格式和 RUN 相似,也是兩種格式:
* shell 格式:CMD <命令>
* exec 格式:CMD ["可執行文件", "參數1", "參數2"...]
* 參數列表格式:CMD ["參數1", "參數2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具體的參數。

在運行時可以指定新的命令來替代鏡像設置中的這個默認命令,比如,nginx:1.7.9 鏡像默認的 CMD 是 /bin/bash ,如果我們直接使用 docker run -it nginx:1.7.9 的話,會直接進入 bash 。
我們也可以在運行時指定運行別的命令,如 docker run -it nginx:1.7.9 cat /etc/os-release。這就是用 cat /etc/os-release 命令替換了默認的 /bin/bash 命令了,輸出了系統版本信息。
在指令格式上,一般推薦使用 exec 格式,這類格式在解析時會被解析為 JSON 數組,因此一定要使用雙引號 ",而不要使用單引號。

shell格式:
CMD echo $HOME
exec格式:
CMD [ "sh", "-c", "echo $HOME" ]
這就是為什么我們可以使用環境變量的原因,因為這些環境變量會被 shell 進行解析處理。

容器中應用在前臺執行和后臺執行的問題

Docker 不是虛擬機,容器中的應用都應該以前臺執行,而不是像虛擬機、物理機里面那樣,用 upstart/systemd 去啟動后臺服務,容器內沒有后臺服務的概念。

比如,關于nginx的啟動,我們錯誤的寫成:

CMD service nginx start
或
CMD systemctl start nginx
然后發現容器執行后就立即退出了。對于容器而言,其啟動程序就是容器應用進程,容器就是為了主進程而存在的,主進程退出,容器就失去了存在的意義,從而退出,其它輔助進程不是它需要關心的東西。
而使用 service nginx start 命令,則是希望 upstart 來以后臺守護進程形式啟動 nginx 服務。通過上面內容我們了解到 CMD service nginx start 會被理解為 CMD [ "sh", "-c", "service nginx start"],因此主進程實際上是 sh。那么當 service nginx start 命令結束后,sh 也就結束了,sh 作為主進程退出了,自然就會令容器退出。

正確的做法是直接執行 nginx 可執行文件,并且以前臺形式運行,如:

CMD ["nginx", "-g", "daemon off;"]

ENTRYPOINT 入口點

ENTRYPOINT 的格式和 RUN 指令格式一樣,分為 exec 格式和 shell 格式。
ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器啟動程序及參數。ENTRYPOINT 在運行時也可以替代,不過比 CMD 要略顯繁瑣,需要通過 docker run 的參數 --entrypoint 來指定。
當指定了 ENTRYPOINT 后,CMD 的含義就發生了改變,不再是直接的運行其命令,而是將 CMD 的內容作為參數傳給 ENTRYPOINT 指令,換句話說實際執行時,將變為:

<ENTRYPOINT> "<CMD>"

那么有了 CMD 后,為什么還要有 ENTRYPOINT 呢?這種 <ENTRYPOINT> "<CMD>" 有什么好處么?讓我們來看兩個場景。

場景一:讓鏡像變成像命令一樣使用

假設我們需要一個得知自己當前公網 IP 的鏡像,那么可以先用 CMD 來實現:

FROM ubuntu:16.04
RUN apt-get update \&& apt-get install -y curl \&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://ip.cn" ]

假如我們使用 docker build -t myip . 來構建鏡像的話,如果我們需要查詢當前公網 IP,只需要執行:

$ docker run myip
當前 IP:61.148.226.66 來自:北京市 聯通

從上面的 CMD 中可以看到實質的命令是 curl,那么如果我們希望顯示 HTTP 頭信息,就需要加上 -i 參數。那么我們可以直接加 -i 參數給 docker run myip 么?

docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".

執行報錯,executable file not found。之前我們說過,跟在鏡像名后面的是 command,運行時會替換 CMD 的默認值。因此這里的 -i 替換了原來的 CMD,而不是添加在原來的 curl -s http://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。
那么如果我們希望加入 -i 這參數,我們就必須重新完整的輸入這個命令:

docker run myip curl -s http://ip.cn -i

這顯然不是很好的解決方案,而使用 ENTRYPOINT 就可以解決這個問題。現在我們重新用 ENTRYPOINT 來實現這個鏡像:

FROM ubuntu:16.04
RUN apt-get update \&& apt-get install -y curl \&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

這次我們再來嘗試直接使用 docker run myip -i:

docker run myip
當前 IP:61.148.226.66 來自:北京市 聯通docker run myip -i
HTTP/1.1 200 OK
...

這次成功了。這是因為當存在 ENTRYPOINT 后,CMD 的內容將會作為參數傳給 ENTRYPOINT,而這里 -i 就是新的 CMD,因此會作為參數傳給 curl,從而達到了我們預期的效果。

場景二:應用運行前的準備工作

啟動容器就是啟動主進程,但有些時候,啟動主進程前,需要一些準備工作。
比如 mysql 類的數據庫,可能需要一些數據庫配置、初始化的工作,這些工作要在最終的 mysql 服務器運行之前解決。
此外,可能希望避免使用 root 用戶去啟動服務,從而提高安全性,而在啟動服務前還需要以 root 身份執行一些必要的準備工作,最后切換到服務用戶身份啟動服務。或者除了服務外,其它命令依舊可以使用 root 身份執行,方便調試等。
這些準備工作是和容器 CMD 無關的,無論 CMD 為什么,都需要事先進行一個預處理的工作。這種情況下,可以寫一個腳本,然后放入 ENTRYPOINT 中去執行,而這個腳本會將接到的參數(也就是 <CMD>)作為命令,在腳本最后執行。比如官方鏡像 redis 中就是這么做的:

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]EXPOSE 6379
CMD [ "redis-server" ]

可以看到其中為了 redis 服務創建了 redis 用戶,并在最后指定了 ENTRYPOINT 為 docker-entrypoint.sh 腳本。

#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; thenchown -R redis .exec su-exec redis "$0" "$@"
fiexec "$@"

該腳本的內容就是根據 CMD 的內容來判斷,如果是 redis-server 的話,則切換到 redis 用戶身份啟動服務器,否則依舊使用 root 身份執行。比如:

docker run -it redis id
uid=0(root) gid=0(root) groups=0(root)

ENV 設置環境變量

這個指令很簡單,就是設置環境變量.

格式有兩種:

* ENV  <key> <value>
* ENV  <key1>=<value1> <key2>=<value2>...

實例如下:

ENV MYSQL_ROOT_PASSWORD="123456" \MYSQL_DATABASE="edusoho" \ MYSQL_USER="edusoho" \MYSQL_PASSWORD="edusoho"

這個例子中演示了如何換行,以及對含有空格的值用雙引號括起來的辦法,這和 Shell 下的行為是一致的。
定義了環境變量,那么在后續的指令中,就可以使用這個環境變量。比如在官方 node 鏡像 Dockerfile 中,就有類似這樣的代碼:

ENV NODE_VERSION 7.2.0RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \&& ln -s /usr/local/bin/node /usr/local/bin/nodejs

在這里先定義了環境變量 NODE_VERSION,其后的 RUN 這層里,多次使用 $NODE_VERSION 來進行操作定制。可以看到,將來升級鏡像構建版本的時候,只需要更新 7.2.0 即可,Dockerfile 構建維護變得更輕松了。
下列指令可以支持環境變量展開:

ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD

可以從這個指令列表里感覺到,環境變量可以使用的地方很多,很強大。通過環境變量,我們可以讓一份 Dockerfile 制作更多的鏡像,只需使用不同的環境變量即可。

ARG 構建參數

格式:

  • ARG <參數名>[=<默認值>]

構建參數和 ENV 的效果一樣,都是設置環境變量。所不同的是,ARG 所設置的構建環境的環境變量,在將來容器運行時是不會存在這些環境變量的。但是不要因此就使用 ARG 保存密碼之類的信息,因為 docker history 還是可以看到所有值的。
Dockerfile 中的 ARG 指令是定義參數名稱,以及定義其默認值。該默認值可以在構建命令 docker build 中用 --build-arg <參數名>=<值> 來覆蓋。
在 1.13 之前的版本,要求 --build-arg 中的參數名,必須在 Dockerfile 中用 ARG 定義過了,換句話說,就是 --build-arg 指定的參數,必須在 Dockerfile 中使用了。如果對應參數沒有被使用,則會報錯退出構建。
從 1.13 開始,這種嚴格的限制被放開,不再報錯退出,而是顯示警告信息,并繼續構建。報錯信息如下例所示:

[Warning] One or more build-args [foo] were not consumed.

VOLUME 定義匿名卷

格式為:

  • VOLUME ["<路徑1>", "<路徑2>"...]
  • VOLUME <路徑>

容器運行時應該盡量保持容器存儲層不發生寫操作,對于數據庫類需要保存動態數據的應用,其數據庫文件應該保存于卷(volume)中,關于Docker 卷的概念和使用,可參考本庫文章
“Docker基本介紹和操作.md”。
為了防止運行時用戶忘記將動態文件所保存目錄掛載為卷,在 Dockerfile 中,我們可以事先指定某些目錄掛載為匿名卷,這樣在運行時如果用戶不指定掛載,其應用也可以正常運行,不會向容器存儲層寫入大量數據。
比如:

VOLUME /data

這里的 /data 目錄就會在運行時自動掛載為匿名卷,任何向 /data 中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態化。
如果我們想把這個匿名卷中的內容掛載到主機上呢?

docker run -itd --name busytest --mount type=bind,source=/teng,target=/data busytest:v1
或
docker run -itd --name busytest -v /teng:/data busytest:v1

EXPOSE 聲明端口

EXPOSE <port> [<port>/<protocol>...]
該EXPOSE指令通知Docker容器在運行時偵聽指定的網絡端口。可以指定端口是偵聽TCP還是UDP,如果未指定協議,則默認為TCP。
EXPOSE 指令是聲明運行時容器提供服務端口,這只是一個聲明,在運行時并不會因為這個聲明應用就會開啟這個端口的服務。在 Dockerfile 中寫入這樣的聲明有兩個好處,一個是幫助鏡像使用者理解這個鏡像服務的守護端口,以方便配置映射;另一個用處則是在運行時使用隨機端口映射時,也就是 docker run -P 時,會自動隨機映射 EXPOSE 的端口。

比如,我這里編寫一個Dockerfile文件:

FROM busybox
VOLUME /data
EXPOSE 80docker build -t busytest:v2 .docker run -itd --name busytest -P busytest:v2docker ps
CONTAINER ID        IMAGE                                      COMMAND                  CREATED             STATUS              PORTS                         NAMES
30614a66bff0        busytest:v2                                "sh"                     3 seconds ago       Up 2 seconds        0.0.0.0:32771->80/tcp         busytest

無論EXPOSE設置如何,您都可以使用-p標志在運行時覆蓋它們。例如

 docker run -itd --name busytest -p 8080:80  busytest:v2docker ps
CONTAINER ID        IMAGE                                      COMMAND                  CREATED             STATUS              PORTS                         NAMES
b07c5575afa1        busytest:v2                                "sh"                     3 seconds ago       Up 2 seconds        0.0.0.0:8080->80/tcp          busytest

WORKDIR 指定工作目錄

格式為 WORKDIR <工作目錄路徑>。
使用 WORKDIR 指令可以來指定工作目錄(或者稱為當前目錄),以后各層的當前目錄就被改為指定的目錄,如該目錄不存在,WORKDIR 會幫你建立目錄。
在Dockerfile中可以多次使用WORKDIR指令。如果提供了相對路徑,則它將相對于前一條WORKDIR指令的路徑 。例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd最終pwd命令的輸出Dockerfile將是 /a/b/c。

該WORKDIR指令可以解析先前使用的環境變量 ENV。您只能使用顯式設置的環境變量Dockerfile。例如:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd最終pwd命令的輸出Dockerfile將是 /path/$DIRNAME

USER 指定當前用戶

格式:
USER <user>[:<group>] or
USER <UID>[:<GID>]

USER 指令和 WORKDIR 相似,都是改變環境狀態并影響以后的層。WORKDIR 是改變工作目錄,USER 則是改變之后層的執行 RUN, CMD 以及 ENTRYPOINT 這類命令的身份。
當然,和 WORKDIR 一樣,USER 只是幫助你切換到指定用戶而已,這個用戶必須是事先建立好的,否則無法切換。
如:

RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

如果以 root 執行的腳本,在執行期間希望改變身份,比如希望以某個已經建立好的用戶來運行某個服務進程,不要使用 su 或者 sudo,這些都需要比較麻煩的配置,而且在 TTY 缺失的環境下經常出錯。建議使用 gosu。

# 建立 redis 用戶,并使用 gosu 換另一個用戶執行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下載 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \&& chmod +x /usr/local/bin/gosu \&& gosu nobody true
# 設置 CMD,并以另外的用戶執行
CMD [ "exec", "gosu", "redis", "redis-server" ]

參考文檔

  • Dockerfile 最佳實踐文檔
  • Dockerfie 官方文檔

轉載于:https://blog.51cto.com/wutengfei/2156797

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/277491.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/277491.shtml
英文地址,請注明出處:http://en.pswp.cn/news/277491.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

孿生網絡跟蹤

github: https://github.com/foolwood/DaSiamRPN paper: https://arxiv.org/pdf/1808.06048.pdf http://openaccess.thecvf.com/content_cvpr_2018/papers/Li_High_Performance_Visual_CVPR_2018_paper.pdf轉載于:https://www.cnblogs.com/heixialee/p/11064568.html

infoseccrypto_java下載_關於php接ICBC的支付接口的解決方案

一&#xff1a;背景&#xff1a; 目前項目使用的是php語言開發&#xff0c;需要接入中國工商銀行的ICBC的線上支付接口。二&#xff1a;遇到的問題&#xff1a;支付時需要對數據簽名&#xff0c;但是銀行那邊不提供php版本的程序&#xff0c;只有java版本的&#xff0c;以下是對…

AS 中 Plugin for Gradle 和 Gradle 之間的版本對應關系

Plugin for Gradle 和 Gradle 之間的版本對應關系 來源&#xff1a;https://developer.android.com/studio/releases/gradle-plugin.html Plugin versionRequired Gradle version1.0.0 - 1.1.32.2.1 - 2.31.2.0 - 1.3.12.2.1 - 2.91.5.02.2.1 - 2.132.0.0 - 2.1.22.10 - 2.132.…

java bean 工廠模式_深入理解Java的三種工廠模式

一、簡單工廠模式簡單工廠的定義&#xff1a;提供一個創建對象實例的功能&#xff0c;而無須關心其具體實現。被創建實例的類型可以是接口、抽象類&#xff0c;也可以是具體的類實現汽車接口public interfaceCar {String getName();}奔馳類public class Benz implementsCar {Ov…

java windows 取所有任務_Win下,通過Jstack截取Java進程中的堆棧信息

在Java軟件的使用過程中&#xff0c;有時會莫名的出現奇怪的問題。而這些問題常常無法使用日志信息定位&#xff0c;這時我們就需要通過查看進程內部線程的堆棧調用關系來分析問題出在哪里。舉個例子&#xff0c;當我們在做某個操作時&#xff0c;莫名的會彈出多個警告框&#…

docker mysql Exit 1

用laradock啟動mysql時&#xff0c;state總是 Exit 1 &#xff0c;docker-compose build后也沒有效果 這時應該在&#xff5e;/.laradock/data&#xff08;.env的DATA_PATH_HOST路徑&#xff09;下&#xff0c;把mysql的數據文件刪除 這種情況常見于mysql安裝多版本&#xff0c…

redis基礎一_常用指令

# Redis configuration file example. # # Note that in order to read the configuration file, Redis must be # started with the file path as first argument: #./redis-server /path/to/redis.conf docker啟動redis: docker run -d -p 6379:6379 -v /home/anmin/Desktop/…

滴滴Booster移動APP質量優化框架 學習之旅 三

推薦閱讀&#xff1a; 滴滴Booster移動App質量優化框架-學習之旅 一 Android 模塊Api化演練 不一樣視角的Glide剖析(一) 滴滴Booster移動App質量優化框架-學習之旅 二對重復資源優化和無用資源優化進行了討論。這里對不可編譯無用assets資源優化進行討論。 先看微信Matrix-ApkC…

java 最后的異常_java – 最后不要拋出堆棧溢出異常

Error不是例外.所以捕捉任何異常都不會捕獲StackOverflowError.所以讓我們先來看一下“明顯的錯誤” – (這段代碼不太適合這個答案后面的說明)&#xff1a;catch(Throwable e){System.out.println("Catch");}如果您進行此更改,您將發現代碼仍然不打印.但是它不會打印…

Colder框架硬核更新(Sharding+IOC)

目錄 引言控制反轉讀寫分離分庫分表 理論基礎設計目標現狀調研設計思路實現之過五關斬六將 動態對象動態模型緩存數據源移植查詢表達式樹深度移植數據合并算法事務支持實際使用展望未來引言 前方硬核警告&#xff1a;全文干貨11000字&#xff0c;請耐心閱讀 遙想去年這個時候&a…

python:小心類實例的屬性動態綁定機制

為什么80%的碼農都做不了架構師&#xff1f;>>> class Test:def __init__(self):self.__keyinitdef get_key(self):return self.__keydef set_key(self,key):self.__keykeyif __name__ __main__:obj Test()print(obj.get_key())#輸出初始值initobj.set_key(通過s…

java 獲取手機歸屬地_java 獲取手機歸屬地

java 獲取手機歸屬地[2021-01-30 20:46:44] 簡介:php去除nbsp的方法&#xff1a;首先創建一個PHP代碼示例文件&#xff1b;然后通過“preg_replace("/(\s|\&nbsp\;| |\xc2\xa0)/", " ", strip_tags($val));”方法去除所有nbsp即可。推薦&#xff1a;…

Atom常用功能插件

minimap代碼縮略圖插件 代碼粒子效果activate-power-mode 代碼格式化插件atom-beautify packages-->Atom Beautify-->Beautify進行格式化 標簽折疊 autoclose-html 轉載于:https://www.cnblogs.com/chenxi-hxl/p/9464120.html

mui aniShow 動畫屬性

aniShow: animationType //mui切換窗口的動畫效果&#xff1b;&#xff08;默認為slide-in-right&#xff09;&#xff1b;1."auto": (String 類型 )自動選擇動畫效果&#xff0c;使用上次顯示窗口設置的動畫效果&#xff0c;如果是第一次顯示則默認動畫效果。 2.&qu…

java中和char功能相反的是_JAVA基礎語法

java的基礎語法Java中的程序可分為結構定義語句和功能執行語句.結構定義語句&#xff1a;用于聲明一個類或方法&#xff0c;功能執行語句用于實現具體的功能。功能執行語句&#xff1a;每條功能執行語句的結尾都必須用英文分號(;)結束。public修飾的類名必須和文件名一致(如果沒…

P1357 花園 (矩陣快速冪+ DP)

題意:一個只含字母C和P的環形串 求長度為n且每m個連續字符不含有超過k個C的方案數 m < 5 n < 1e15 題解:用一個m位二進制表示狀態 轉移很好想 但是這個題是用矩陣快速冪加速dp的 因為每一位的轉移都是一樣的 用一個矩陣表示狀態i能否轉移到狀態j 然后跑一遍 統計答案特…

IDEA設置類、方法注釋模板

類注釋模板 File -> Other Setting -> Default Setting打開默認設置 Editor -> File and Code Templates -> Files -> Class 找到類注釋模板&#xff0c;在public class前面添加模板注釋 /** * program: ${PROJECT_NAME}->${NAME} * description: ${descript…

Element.shadowRoot

Element.shadowRoot轉載于:https://www.cnblogs.com/yishenweilv/p/11083278.html

netty源碼分析之服務端啟動全解析

background netty 是一個異步事件驅動的網絡通信層框架&#xff0c;其官方文檔的解釋為 Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and stream…

捕魚達人java源代碼解析_捕魚達人源碼Java

【實例簡介】捕魚達人源碼Java實現的完整代碼,對應的源碼分析文章http://write.blog.csdn.net/postedit【實例截圖】【核心代碼】Fishlord├── bin│ ├── fishlord│ │ ├── Fish.class│ │ ├── Fishlord.class│ │ ├── Net.class│ │ ├─…