3 cách thực hiện Dependency Injection (DI) và vấn đề với @Autowired trong Spring

Lời mở đầu

Khi code các ứng dụng backend bằng Java với Spring Framework, khả năng cao là bạn đã gặp qua một đoạn code sử dụng @Autowired. Tuy nhiên, nếu bạn sử dụng một số phần mềm lint để kiểm tra code, bạn có thể thấy các phần mềm sẽ cảnh báo @Autowired is deprecated (Autowired không được khuyến khích sử dụng nữa, cách làm không tốt)

image - quochung.cyou PTIT
  • Nhìn sơ qua thì đây có vẻ là một cách đơn giản và nhanh gọn để thực hiện DI qua Spring. Chỉ cần khai báo các dependency rồi thêm một annotation (@Autowired), các phần liên kết sẽ được Spring xử lí
  • Tuy nhiên, cách làm phổ biến này lại có thể dẫn đến nhiều vấn đề về sau này. Hãy thử xem qua các thủ pháp DI được sử dụng qua Spring Framework và các ưu nhược điểm của chúng

3 cách thực hiện Dependency Injection trong Spring Framework

Field Injection

  • Field Injection chính là tên gọi của cách inject dependency khi ta khai báo bằng @Autowired
  • Về cơ bản khi khai báo annotation này trên 1 field, method, constructor. Spring sẽ tự tìm dependency phù hợp và kết nối chúng với nhau.
  • @Autowired về mặc định không sai, tuy nhiên nếu sử dụng không hợp lí chúng có thể gây ra một số vấn đề về sau.
  • Ví dụ với một code như sau:
image 1 - quochung.cyou PTIT
  • Khai báo một Multiplier trong Calculator nhanh chóng bằng @Autowired sẽ không báo lỗi gì khi compile, về cách hoạt động, khi chạy chương trình, Spring sẽ tự tìm kiếm dependency và kết nối chúng với nhau
  • Tuy nhiên, vì chúng tự động, nên ta thiếu kiểm soát hơn phần này. Tức là lúc này ta không có cách nào để thêm thủ công dependency nữa cho các hoàn cảnh khác ngoài lúc chạy ứng dụng (VD khi chạy test)
image 2 - quochung.cyou PTIT
  • Ví dụ như code trên sẽ không báo lỗi, tuy nhiên khi chạy test thì chúng có thể throw NullPointer
  • Lí do là ở file test trên thì chúng ta đang không có chỗ nào liên kết với Spring cả, vì vậy Spring sẽ không quản lý các bean mà ta đã tạo (Khai báo @Component ở các class kia) nên chúng sẽ không nối vào nhau được -> Ta sẽ bị null pointer vì Multiplier không được khởi tạo lên
  • Để xử lý vấn đề này, thì nếu sử dụng 2 thủ pháp DI bên dưới (Constructor Injection và Setter Injection) , ta có thể có nhiều khả năng kiểm soát hơn và thêm dependency một cách thủ công mà không qua Spring, tạo nhiều khả năng hơn cho việc test
  • Hoặc, ta có thể khai báo @SpringBootTest để Spring quản lý file test này và tự động thực hiện việc gắn các bean mà không cần gắn chay
image 20 - quochung.cyou PTIT

Setter Injection

image 3 - quochung.cyou PTIT
  • Cách làm trên như tên của nó, ta sẽ set một dependency của class lớn hơn ở thời điểm runtime
  • Cách làm này cho phép ta dễ dàng thay đổi dependency của một class trong runtime, ví dụ như đổi 1 repo khác 1 service nào đó, …
  • Tuy nhiên, ngoài ưu điểm là sự flexible khi set ở runtime, thì nó cũng đi cùng vài nhược điểm khác
  • Do được set trong runtime, ta đôi khi có thể gặp phải NullPointer khi dependency chưa được khởi tạo, là null, … (vì lúc này chúng là optional, không bắt buộc với class nữa)
  • Do nó lỏng lẻo hơn nên lúc này với các method trong class gọi đến dependency, ta không được đảm bảo là dependency đã được set, hoặc ta lại phải thêm code để kiểm tra là có dependency chưa, 1 là thêm code, 2 là dẫn tới nullpointer
image 21 - quochung.cyou PTIT
  • Với cách làm như thế này, ta có thể không cần Spring quản lý các bean mà có thể test chay hơn, có nhiều quyền kiểm soát hơn vào việc quản lý các dependency

Constructor Injection

image 4 - quochung.cyou PTIT
  • Như tên của thủ pháp, ta sẽ thêm dependency cho 1 class ngay tại thời điểm khởi tạo class cha
  • Ở file test trên, mình đang sử dụng mock để dựng class multiplier lên theo kèm để gắn vào Calculator qua constructor
  • Việc này đảm bảo 1 class luôn có đủ dependency để khởi tạo (tuỳ theo cách custom constructor)
  • Hoặc mình hoàn toàn có thể làm như thế này
image 22 - quochung.cyou PTIT
  • (Việc sử dụng mock cho phép nhiều khả năng khác trong test hơn, ví dụ mình có thể kiểm tra xem một hàm trong class đó được gọi bao nhiêu lần, … , chúng được quản lý theo dạng Proxy Design Pattern, mình sẽ nói ở bài viết khác)

Bạn có thể đọc thêm bài viết này về một số khái niệm trong Spring như IoC, Bean, …

Tham khảo:

Cài đặt WordPress với Docker, Nginx, MariaDB trên Ubuntu 22

Giới thiệu

Ở bài viết này chúng ta sẽ tiến hành tạo một website WordPress thông qua Docker Compose, Nginx, MariaDB trên Ubuntu 22.04. Về phần Docker mình sẽ sử dụng Portainer để quản lý container.

Cài đặt Docker và Portainer

Cài đặt Docker trên Ubuntu

sudo apt update
sudo apt upgrade
sudo apt install docker.io -y

Tự động bật Docker khi máy chủ khởi động

sudo -i
systemctl start docker
systemctl enable docker

Cài đặt Portainer

docker volume create portainer_data
docker run -d -p 9000:9000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer
docker ps

Truy cập Portainer

Sau khi cài đặt xong, bạn hãy lên web và truy cập vào http://localhost:9000 để vào giao diện của Portainer

image - quochung.cyou PTIT

Vào lần truy cập đầu tiên, bạn sẽ được hỏi để tạo mật khẩu truy cập Portainer.

Vì mình quản lý từ local nên mình sẽ chọn Local ở bước tiếp theo

02 connect local - quochung.cyou PTIT

Cài đặt WordPress

Cài đặt Nginx

Bạn vào mục Container và tạo một container cho nginx như sau

image 1 - quochung.cyou PTIT
+ Name: nginx (Tùy ý bạn chọn để đặt tên cho container)
+ Image: nginx
+ Manual network port publishing:
Chọn port truy cập cho nginx, mặc định ở phần container phải là 80, và host là port bạn truy cập đến nginx
Hoặc bật Publish all exposed network ports to random host ports để tự động chọn port ngẫu nhiên 

Ở đây mình chọn 80:80

Chọn Deploy Container để tiếp tục

Tiếp theo thử truy cập localhost:(cổng bạn chọn) để kiểm tra xem nginx đã hoạt động chưa?

welcome to nginx window 5x3 2 - quochung.cyou PTIT

Cài đặt MariaDB

Tiếp tục tạo Container cho MariaDB như sau

image 2 - quochung.cyou PTIT
+ Name: mariadb (Tùy ý bạn chọn để đặt tên cho container)
+ Image: mariadb 
+ Manual network port publishing:
Chọn port truy cập cho mariadb , mặc định ở phần mariadb phải là 3306, và host là port bạn truy cập đến nginx
Hoặc bật Publish all exposed network ports to random host ports để tự động chọn port ngẫu nhiên 

Ở đây mình chọn 4166:3306

Chọn Deploy Container để tiếp tục

Sau đó truy cập vào Console và tạo database cho wordpress

mysql -u root -p
create database wordpress;

Cài đặt WordPress

Tạo container wordpress như sau

image 3 - quochung.cyou PTIT
+ Name: wordpress (Tùy ý bạn chọn để đặt tên cho container)
+ Image: wordpress 
+ Manual network port publishing:
Chọn port truy cập cho wordpress, mặc định ở phần container phải là 80, và host là port bạn truy cập đến wordpress
Hoặc bật Publish all exposed network ports to random host ports để tự động chọn port ngẫu nhiên 

Ở đây mình chọn 4167:80

Chọn Deploy Container để tiếp tục

Cấu hình Nginx

Truy cập vào Console nginx và gõ lệnh sau

apt -y update
apt -y install nano
nano /etc/nginx/conf.d/wordpress.conf

Bạn tạo config file theo mẫu sau với ip, port theo bạn tạo

server {
    listen       80;
    server_name  wordpress.quochung.cyou; 

location / {
    proxy_pass       http://ip:4167;
    proxy_redirect             off;
    proxy_set_header Upgrade   $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Như vậy mình đã trỏ subdomain wordpress.quochung.cyou về IP của nginx trên cổng http 80. Sau đó nginx sẽ chuyển đến http://ip:4167 (Trang của wordpress)

Bạn save file và gõ

nginx -t

Để kiểm tra xem config đã đúng chưa

Sau khi xong khởi động lại container nginx, truy cập tên miền đã tạo để kiểm tra kết quả

WordPress setup wizard - quochung.cyou PTIT