Scoped Value trong Java là một tính năng của Java cho phép chúng ta có thể lấy các immutable data ở bất kỳ đâu trong các đoạn code liên quan với nhau mà chúng ta muốn, không cần phải truyền data này từ phương thức này đến phương thức khác chỉ để sau đó có thể sử dụng ở phương thức cần nó.
Các bạn có thể tưởng tượng một ví dụ như trong một RESTful API để lấy thông tin của một user, khi user request tới, phương thức handle request này sẽ có thông tin user. Tiếp theo, code của chúng ta sẽ cần handle một số business logic mà không cần thông tin user. Chỉ đến khi thao tác với database thì code mới cần thông tin user để lấy data từ database. Thông thường, chúng ta sẽ cần truyền thông tin user trong các phương thức chỉ xử lý business logic để đến khi thao tác với database, chúng ta sẽ lấy thông tin user này ra để sử dụng. Với tính năng Scoped Value từ Java 20, các bạn không cần làm vậy nữa.
Để làm ví dụ, mình có 2 class sau:
1 2 3 4 5 6 7 8 9 |
package com.huongdanjava.java; public class UserService { public boolean validateUser(String username) { return true; } } |
và:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.huongdanjava.java; public class StudentManagement { public String getLoggedUserInfo(String username) { UserService us = new UserService(); if (us.validateUser(username)) { return "Valid user"; } return "Invalid user"; } } |
Và một class với phương thức main() để chạy ví dụ:
1 2 3 4 5 6 7 8 9 10 11 |
package com.huongdanjava.java; public class Application { public static void main(String[] args) { String user = "Khanh"; StudentManagement sm = new StudentManagement(); System.out.println(sm.getLoggedUserInfo(user)); } } |
Class UserService có một phương thức là validateUser() với tham số là username để validate thông tin user. Để đơn giản mình sẽ luôn return true cho phương thức này. Class StudentManagement với phương thức getLoggedUserinfo() thì dùng để lấy thông tin user dựa vào username, sau khi sử dụng class UserService để validate thông tin username. Các bạn có thể thấy, thông tin username sẽ cần truyền qua lại trong quá trình handle request, mặc dù có thể trong 1 phương thức nào đó, thông tin username này là không cần thiết. Chúng ta có thể loại bỏ điều này bằng cách sử dụng Scoped Value của Java để lấy thông tin username khi chúng ta muốn!
Để sử dụng Scoped Value, đầu tiên, các bạn hãy khai báo một biến static cho đối tượng của class ScopedValue trong class Application để biến này có thể access ở bất kỳ đâu trong code:
1 |
public static final ScopedValue<String> LOGGED_USER = ScopedValue.newInstance(); |
Sau khi định nghĩa thông tin username, chúng ta sẽ sử dụng phương thức static where() của class ScopedValue để định nghĩa thông tin username này có thể sử dụng xuyên suốt ở những phương thức mà chúng ta muốn. Ví dụ của mình như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package com.huongdanjava.java; public class Application { public static final ScopedValue<String> LOGGED_USER = ScopedValue.newInstance(); public static void main(String[] args) { String user = "Khanh"; StudentManagement sm = new StudentManagement(); ScopedValue.where(LOGGED_USER, user).run( sm::getLoggedUserInfo ); } } |
Như các bạn thấy, mình đã định nghĩa ScopedValue cho thông tin user sử dụng phương thức where(). Đối tượng trả về của phương thức where() là một đối tượng Carrier. Các bạn cần gọi đến các phương thức mà chúng ta muốn sử dụng ScopedValue trong phương thức run() của đối tượng Carrier này. Phương thức getLoggedUserInfo() cùng những phương thức được gọi bên trong phương thức getLoggedUserInfo() này có thể lấy và sử dụng thông tin user mà chúng ta đã định nghĩa. Phương thức getLoggedUserInfo() không cần phải truyền thông tin user nữa:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.huongdanjava.java; public class StudentManagement { public String getLoggedUserInfo() { UserService us = new UserService(); if (us.validateUser()) { return "Valid user"; } return "Invalid user"; } } |
Phương thức validateUser() trong class UserService cũng không cần phải truyền thông tin user nữa. Nó có thể lấy thông tin user như sau:
1 2 3 4 5 6 7 8 9 10 11 |
package com.huongdanjava.java; public class UserService { public boolean validateUser() { String loggedUser = Application.LOGGED_USER.get(); System.out.println("Logged user: " + loggedUser); return true; } } |
Chạy lại ví dụ trên, các bạn có thể thấy thông tin user được in ra trong phương thức validateUser() của class UserService như sau:
Nếu mình định nghĩa thêm một phương thức khác trong class UserService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package com.huongdanjava.java; public class UserService { public boolean validateUser() { String loggedUser = Application.LOGGED_USER.get(); System.out.println("Logged user: " + loggedUser); return true; } public void doSomeThing() { String loggedUser = Application.LOGGED_USER.get(); System.out.println("Logged user1: " + loggedUser); } } |
và gọi nó bên trong phương thức run() ở trên:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.huongdanjava.java; public class Application { public static final ScopedValue<String> LOGGED_USER = ScopedValue.newInstance(); public static void main(String[] args) { String user = "Khanh"; StudentManagement sm = new StudentManagement(); UserService us = new UserService(); ScopedValue.where(LOGGED_USER, user).run(()-> { sm.getLoggedUserInfo(); us.doSomeThing(); } ); } } |
Phương thức doSomething() cũng lấy được thông tin user như sau:
Các bạn có thể sẽ thấy ScopedValue cũng tương tự như ThreadLocal mà mình đã giới thiệu trong bài viết trước. Cũng đúng đó các bạn, nhưng giữa ScopedValue và ThreadLocal cũng có những điểm khác biệt:
- Giá trị mà ScopedValue chứa là immutable data, nghĩa là giá trị của nó sẽ không thể thay đổi khi đã được khởi tạo. ScopedValue chỉ có phương thức get() để lấy giá trị, còn ThreadLocal thì có cả 2 phương thức get() và set() để lấy và gán giá trị lúc nào chúng ta muốn.
- Giá trị trong ThreadLocal có thể tồn tại mãi mãi trong quá trình chạy của ứng dụng nếu các bạn không remove giá trị đó. Còn giá trị của ScopedValue sẽ bị huỷ sau khi phương thức run() của đối tượng Carrier chạy xong. Các bạn sẽ thấy chương trình của chúng ta bị lỗi, khi cố gắng lấy giá trị của ScopedValue sau khi phương thức run() đã chạy xong:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.huongdanjava.java; public class Application { public static final ScopedValue<String> LOGGED_USER = ScopedValue.newInstance(); public static void main(String[] args) { String user = "Khanh"; StudentManagement sm = new StudentManagement(); UserService us = new UserService(); ScopedValue.where(LOGGED_USER, user).run(()-> { sm.getLoggedUserInfo(); us.doSomeThing(); } ); us.doSomeThing(); } } |
Kết quả: