Index
- Index
- 使用 VSCode Dev Containers 開發環境
- 加入 GitLab
- 建立 Java Spring Boot 專案
- 建立 Open API 與 Swagger UI
- Spring Boot Layerd Architecture
- Controllers
- Services
- Repositories
- Models
- Filters
- @Configuration
- Configure File: application.properties
- Dockerfile
- 連接 MongoDB
使用 VSCode Dev Containers 開發環境
開啟 VSCode,點選 F1
或 Ctrl+Shift+P
,輸入 Dev Containers: Open Folder in containers...
。選擇專案目錄,並選用 Java 作為 Dev Containers 為 Image。等待機分鐘後完成 container 啟動後,開啟 TERMINAL,並輸入下列指令來確定 Java 可正確執行:
$ java -version
$ javac -version
因為 Dev Containers 為 Linux 環境,可以用下列列指令, OS 版號,以及查詢 java 安裝位置
# 查詢 OS
cat /etc/os-release
# 查詢 Java 安裝位置
which javac
加入 GitLab
- 在 GitLab Create new project, 選擇 Create blank project.
- 執行下列指令, 以便將 local 的資料上傳到 GitLab.
git init
git remote add origin https://gitlab.com/<group>/<project>.git
git add .
git commit -m "Initial commit"
git push -u origin master
建立 Java Spring Boot 專案
要建立 Spring Boot 專案,可以利用 VS Code 的 Extension: Spring Initializr Java Support。輸入 Ctrl+Shift+X
查詢,並點選 Install in Dev Container: Java
按鍵來安裝。
安裝完成後,開啟 Command Palette Ctrl+Shift+P
或 F1
並輸入 spring 後,選擇 Maven 或 Gradle 來建立專案。
本篇文章以 Spring Boot API 為主,使用 Maven 管理套件。因 Spring Boot 已內建 Tomcat, Jetty, Undertow 等 web server,其輸出檔案採用 Jar 即可。而 dependencies 可勾選 Spring Web, Spring Boot DevTools, 及 Spring Data。
執行 mvn -v
檢查 Marven 是否正確安裝。
接下來透過 Maven 來建立 package
mvn package
假設產出的檔案為 backend-api-0.0.1-SNAPSHOT.jar, 那麼可以執行下列指令開啟
java -jar ./target/backend-api-0.0.1-SNAPSHOT.jar
在開發期間,可以使用下列指令,以便修改程式後,即可立即編譯與重啟
mvn spring-boot:run
建立 Open API 與 Swagger UI
我們可以使用 springdoc-openapi v2.1.0 在 Spring Boot 3.0.0 顯示 swagger ui。請參考 springdoc-openapi v2.1.0 官方文件,只要在 pom.xml 加入下列文字即可:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
網址 http://server:port/context-path/swagger-ui.html 可以顯示 Swagger UI 頁面,而 Open API Spec (JSON) 則是位於 http://server:port/context-path/v3/api-docs 路徑下。
如果只想產出 OpenAPI 文件 (json),而不要產出 UI, 可以指引入下列 dependency:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.1.0</version>
</dependency>
若要使用 spring boot 2.x 版,其套件為:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.7.0</version>
</dependency>
Wwagger-ui 與 open api spec 路徑相同。
Spring Boot Layerd Architecture
原始碼目錄結構建議如下:
src/
├── main/
│ ├── java/
│ │ ├── beginning/
│ │ │ ├── example/
| │ │ │ ├── backendapi/
| │ │ │ │ ├── filters/
| │ │ │ │ ├── configs/
| │ │ │ │ ├── controllers/
| │ │ │ │ ├── services/
| │ │ │ │ ├── repositories/
| │ │ │ │ └── models/
| │ │ │ └── BackendApiApplication.java
| │ │ └── resources/
| │ │ └── application.properties
| │ └── ...
│ └── ...
└── ...
- controllers: 接收 http request 並回應 response。
- models: 定義各項傳送與處理的資料結構。
- services: 處理資料、商業邏輯。
- repositories: 與後端資料庫連結,以存取資料。
- filters: 應用於 middleware 程式,以 OncePerRequestFilter 為 base class,可以在 http request 與 response 加入 log 或安全檢核等作業。
- configs: 實踐 WebMvcConfigurer 以便將上列的 filter 程式加入適當位置。
Controllers
用來簡化了開發 RESTful Web 服務。使用 @RestController Annotation 加註於 class 上方,可以接收 Get、Post、…等等 HTTP Request。
Services
負責處理業務邏輯,並且是其他層(例如控制器)的媒介。Class 上方加註 @Service,Spring 框架就會將於程式啟動時,自動建立 instance。Controller 可以透過 @Autowired 來宣告 service 變數,即可自動取得其 instance 來使用。若系統同時存在多個 service,可以利用 @Qualifier 來指定。
例如程式中包含 userService1, 與 userService2
@Service("userService1")
public class UserService1Impl implements UserService {
// ...
}
@Service("userService2")
public class UserService2Impl implements UserService {
// ...
}
若 controller 選用其中一個 service,範例如下
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(@Qualifier("userService1") UserService userService) {
this.userService = userService;
}
// ...
}
// 也可以不透過建構式,直接將 @Qualifier 設定在 service 變數上,使程式碼較為簡潔
@RestController
public class UserController {
@Autowired
@Qualifier("userService1")
private UserService userService;
// ...
}
若同時使用兩組 service,範例如下
@RestController
public class UserController {
private final UserService userService1;
private final UserService userService2;
@Autowired
public UserController(@Qualifier("userService1") UserService userService1, @Qualifier("userService2") UserService userService2) {
this.userService1 = userService1;
this.userService2 = userService2;
}
// ...
}
Repositories
負責處理資料的存取,例如與 database 連結,進行讀寫作業。
Models
定義資料的物件形式,例如定義 HTTP Request、Response 所需要的資料,或內部轉換所需要的資料格式等。若要透過 JPA 直接對應到 SQL 型態的 database,可以加註 @Entity、@Table、@Column 與其對應,範例如下
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "email")
private String email;
// getter and setter methods...
}
Filters
Spring Boot 的 Filter 為 http request 的 middleware,可以攔截 http request 與 response 的資訊,並進行處理,例如加入 log、安全檢核、加解密等作業。
方式很簡單,只要繼承 OncePerRequestFilter 並複寫 doFilterInternal 方法即可。
但完成 Filter 程式碼後,還需要透過 configure 向 spring 註冊。
@Configuration
利用 @Configuration 將 Filter 定義為 @Bean,以便注入容器 (Dependency Injection Container)。Spring Boot 會在 DI Container 中尋找 interface 的 Filter,在接收到 http request 時來呼叫。Interface Filter 定義如下:
package javax.servlet;
import java.io.IOException;
public interface Filter {
void init(FilterConfig filterConfig) throws ServletException;
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
void destroy();
}
Configure File: application.properties
執行 mvn package 後,會將設定檔案 application.properties 複製到 target/classes 目錄下。application.properties 可以用來存放因安裝環境不同而需要變動的資料,例如資料庫連線位置。
Dockerfile
建立 Docker Image:
docker build -t try-spring-boot .
反覆執行時,舊的 try-spring-boot image 不會刪除,而會將名稱改為 <none>
,下列語法可以刪除這些不用的 <none>
image
docker image prune -f
# 或串聯之前 build 語法
docker build -t try-spring-boot . && docker image prune -f
執行 Container:
docker run -d --name try-spring-boot -p 8080:8080 try-spring-boot
若要查看執行中 container 的 log, 可以使用下列指令
docker logs try-spring-boot
# 或持續輸出 log, 直到輸入 Ctrl-C
docker logs try-spring-boot -f
停止 Container:
docker stop try-spring-boot
開啟網頁:
- 開啟 Swagger UI
- 開啟 OpenAPI Spec
連接 MongoDB
首先,我們可以先試著執行兩個 container,其中一個負責 mongoDB,另一個執行 mongosh,透過 docker network 的指派,來進行連線,與存取資料。下列範例會以 mongo shell 建立並進入 test dbs。
docker network create try-spring-boot-network
docker run --name try-spring-boot-mongo --rm --network try-spring-boot-network -v ${pwd}/temp-db-storage:/data/db -d mongo
docker run --name try-spring-boot-mongo-sh --rm --network try-spring-boot-network -it mongo mongosh --host try-spring-boot-mongo test
在上列指令中,第二段指令,在沒有指定 --hostname 參數下,host name 預設等於 container name。因此 shell 可以經由此 host name 與 mongoDB 進行連線。但本機連接容器仍然需要使用 localhost 來進行連接。
要離開 mongo shell,可以輸入 .exit
即可。
如果要透過環境變數設定帳號與密碼,可以使用下列指令
docker network create try-spring-boot-network
docker run --name try-spring-boot-mongo --rm --network try-spring-boot-network -v ${pwd}/temp-db-storage:/data/db -d -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=example mongo
docker run --name try-spring-boot-mongo-sh --rm --network try-spring-boot-network -it mongo /bin/bash
# 進入 try-spring-boot-mongo-sh 後,輸入下列命令啟動 mongosh
mongosh --host try-spring-boot-mongo --username root --password example --authenticationDatabase admin
若要在 dev container 中與上列所建立的 mongo 連線,則需要先在 ./devcontainer/devcontainer.json 指定相同的 network
{
"name": "Java",
"image": "mcr.microsoft.com/devcontainers/java:0-17",
// 將 dev container 也加入 try-spring-boot-network 網路中
"runArgs": [
"--network=try-spring-boot-network"
],
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "none",
"installMaven": "true",
"installGradle": "false"
}
}
}
此外,修改 application.properties,及 vscode launch.json 來設定連線。
spring.data.mongodb.uri=mongodb://${MONGO_USERNAME:root}:${MONGO_PASSWORD:example}@${MONGO_HOST:mongo}:${MONGO_PORT:27017}/${MONGO_DATABASE:test}?authSource=${MONGO_AUTH_SOURCE:admin}&authMechanism=${MONGO_AUTHMECHANISM:SCRAM-SHA-1}
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "BackendApiApplication",
"request": "launch",
"mainClass": "beginning.example.backendapi.BackendApiApplication",
"projectName": "backend-api",
"env": {
"MONGO_USERNAME": "root",
"MONGO_PASSWORD": "example",
"MONGO_HOST": "try-spring-boot-mongo",
"MONGO_PORT": "27017",
"MONGO_DATABASE": "test",
"MONGO_AUTH_SOURCE": "admin",
"MONGO_AUTHMECHANISM": "SCRAM-SHA-1"
}
}
]
}
要同時啟動多個容器,docker-compose 會是一個更加便利的選擇,透過下列方式,可以將先前的 Java 程式也一併啟用。
version: '3.1'
services:
mongo:
image: mongo
container_name: try-spring-boot-mongo
restart: always
volumes:
- ${PWD}/temp-db-storage:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
networks:
- try-spring-boot-network
mongo-express:
image: mongo-express
container_name: try-spring-boot-mongo-express
restart: always
ports:
- 8081:8081
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: example
ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
networks:
- try-spring-boot-network
try-spring-boot:
image: try-spring-boot
container_name: try-spring-boot
ports:
- "8080:8080"
environment:
MONGO_USERNAME: root,
MONGO_PASSWORD": example,
MONGO_HOST: try-spring-boot-mongo,
MONGO_PORT: 27017,
MONGO_DATABASE: test,
MONGO_AUTH_SOURCE: admin,
MONGO_AUTHMECHANISM: SCRAM-SHA-1
networks:
- try-spring-boot-network
networks:
try-spring-boot-network:
driver: bridge
上列 yaml 可以透過下列指令來啟動或停止
docker-compose up -d
docker-compose down
建立 DevContainer 測試環境
要在 DevContainer 中進行 debug,可以參考專案根目錄的 PowerShell Script setup-mongo-container.ps1
來建立 docker network 並啟動 mongo。同時注意,.vscode/launch.json 應設定相關的 configure.env 資訊。
setup-mongo-container.ps1
內容如下:
# 1. Check if the directory does not exist, then create it
if (!(Test-Path -Path .\temp-db-storage)) {
New-Item -ItemType Directory -Path .\temp-db-storage
}
# 2. Check if the try-spring-boot-network does not exist, then create it
$networkExists = docker network ls --filter name=try-spring-boot-network --format "{{.Name}}" -q
if (!$networkExists) {
docker network create try-spring-boot-network
}
# 3. Check if the container exists, stop and remove it if it does, then run the new container
$containerExists = docker container ls -a --filter name=try-spring-boot-mongo --format "{{.Names}}" -q
if ($containerExists) {
docker container stop try-spring-boot-mongo
docker container rm try-spring-boot-mongo
}
docker run --name try-spring-boot-mongo --rm --network try-spring-boot-network -v ${pwd}/temp-db-storage:/data/db -d -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=example mongo