Author: CuongNM49

  • [Spring] ApplicationContext trong Spring Framework

    [Spring] ApplicationContext trong Spring Framework

    Trong bài viết này chúng ta sẽ tìm hiểu chi tiết về ApplicationContext interface.

    ApplicationContext?

    Ta hãy cùng nhớ lại 2 khái niệm DI(Dependency Injection) và IoC(Inversion of Control) gây thương nhớ cho những developer của Spring framework. IoC Container chính là lõi của Spring Framework. IoC Container có chức năng tạo ra các đối tượng, liên kết chúng lại với nhau, cấu hình chúng, và quản lí vòng đời của chúng từ khi tạo ra đến khi bị hủy. IoC container sẽ sử dụng DI để quản lí các thành phần tạo nên một ứng dụng.

    Trong Spring framwork IoC được mô tả qua BeanFactory và ApplicationContext interface. BeanFactory là root interface truy cập vào Spring IoC, cung cấp chức năng cơ bản để quản lí Bean. Còn ApplicationContext là sub-interface cúa BeanFactory interface. Do đó, nó cung cấp tất cả các chức năng cơ bản của BeanFactory cùng nhưng chức năng tiên tiến hơn cho các ứng dụng Spring và phù hợp hơn cho những ứng dụng J2EE.

    Spring Bean

    Trước khi đi chi tiết hơn về ApplicationContext interface ta cần xem qua khái niệm về Spring Bean.

    In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.
    

    Hay hiểu đơn giản với các ứng dụng Spring, Bean là object được tạo ra và quản lí bới Spring IoC container.

    Cấu hình Bean trong Container

    Để ApplicationContext có thể quản lí được các Bean, ứng dụng phải cũng cấp cấu hình bean cho ApplicationContext container. Ta sẽ có những cách khác nhau cấu hình để cấu hình bean:

    1. XML-Based Configuration
    2. Java-Based Configuration
    3. Annotation-Based Configuration

    XML-Based Configuration

    Cuối cùng với cách cấu hình dựa trên XML, ta sẽ khai báo tất cả các cấu hình của Bean trong một file XML.

    Ta sẽ khai báo Bean cho class UserConfiguration trong một file user-config.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"
      xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
      <bean id="userService" class="com.cuongnm.applicationcontext.UserService">
        <constructor-arg name="userRepository" ref="userRepository" />
      </bean>
      
      <bean id="userRepository" class="com.cuongnm.applicationcontext.UserRepository" />
    </beans>
    

    Java-Based Configuration

    Cấu hình dựa trên Java bằng cách sử dụng @Bean annotation bên trong 1 lớp @Configuration. Với mỗi @Bean annotation được khai báo đánh dấu cho một method để tạo ra 1 Spring Bean. Và những phương thức này đc chứa trong 1 class được đánh dấu là @Configuration – một class chứa các cấu hình Spring bean.

    Ví dụ:

    @Configuration
    public class UserConfig {
    
      @Bean
      public UserService userService() {
        return new AccountService(userRepository());
      }
    
      @Bean
      public UserRepository userRepository() {
        return new UserRepository();
      }
    }
    

    Bằng cách đưa ra @Configuration ta sẽ xử lí class UserConfig như thẻ bean trong XML

    Annotation-Based Configuration

    Để sử dụng được phương pháp này, đầu tiên ta sẽ cấu hình trong XML để cho phép sử dụng Annotation-Based Configuration trong ứng dụng.

    <context:annotation-config/>
    <context:component-scan base-package="com.cuongnm.applicationcontext"/>
    

    Thẻ context:annotation-config để khai báo cho việc sử dụng annotation-based mappings và thẻ context:component-scan với tham số base-package cho Spring tìm được package chưa các annotated classes.

    Sau đó, ta sẽ sử dụng các annotation được cung cấp bởi Spring để đánh dấu cho các class, method, constructor, field trong Java để cấu hình cho Bean như @Component, @Controller, @Service, @Repository, @Autowired.

    Ví dụ với class UserService được khai báo là một Bean sử dụng @Component annotation:

    @Component
    public class UserService {
      
    }
    

    Ta có thể lấy ra Bean này bằng cách:

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext/user-bean-config.xml");
    UserService userService = context.getBean(UserService.class);
    assertNotNull(userService);
    

    Cách sử dụng ApplicationContext

    Cũng như việc cấu hình cho Bean thì cũng có rất nhiều cách sử dụng ApplicationContext interface.

    1) ClassPathXmlApplicationContext

    Phương pháp này sẽ tải các Bean config từ các file XML nằm trong đường dẫn classpath.

    ApplicationContext context = new ClassPathXmlApplicationContext ("user-bean-config.xml");
    

    2) FileSystemXmlApplicationContext

    Sử dụng các Bean config từ các file XML trong FileSystem hay từ URL.

    ApplicationContext context = new FileSystemXmlApplicationContext (“c: /myconfig.xml”);
    

    3) AnnotationConfigApplicationContext

    AnnotationConfigApplicationContext được sử dụng với Java-Based Configuration cho các cấu hình Bean.

    Ví dụ:

    public static void main(String[]args){
    /* Creating Spring IoC Container Without XML configuration file*/
    ApplicationContext context= new AnnotationConfigApplicationContext(UserConfig.class);
    MyBean beanObj = context.getBean(UserService.class);
    }
    

    Với ví dụ này ApplicationContext được lấy từ UserConfig class. Chúng ta lấy các cấu hình Bean từ một class được chú thích @Configuratation và nó sẽ được khai báo:

    @Configuration
    public class UserConfig {
    
      @Bean
      public UserService userService() {
        return new AccountService(userRepository());
      }
    
      @Bean
      public UserRepository userRepository() {
        return new UserRepository();
      }
    }
    

    3) XmlWebApplicationContext và AnnotationConfigWebApplicationContext

    XmlWebApplicationContext được sử dụng để đại diện cho Spring container trong ứng dụng Web. Và nó tải các cấu hình bean từ những file XML với mặc định từ đường dẫn “/WEB-INF/applicationContext.xml”. Chúng ta cũng có thể chỉ định được dẫn của nó qua tham số contextConfigLocation của ContextLoaderListener hoặc DispatcherServlet trong web.xml.

    Giống như XmlWebApplicationContext là class tương ứng với ClassPathXmlApplicationContext và FileSystemXmlApplicationContext và được sử dụng để tạo ra ApplicationContext cho các ứng dụng web, tương tự, AnnotationConfigWebApplicationContext là class tương ứng với AnnotationConfigApplicationContext.

    Lời kết

    Tóm tắt lại những gì mình muốn nói ở bài viết này, mục đích giúp chúng ta hiểu về ApplicationContext trong Spring, hiểu cách sử dụng, triển khai ApplicationContext. Cũng như cách gọi Spring container bằng ApplicationContext mang đến nhiều chức năng hơn so với BeanFactory.

    Bài viết được tham khảo từ “https://www.baeldung.com/spring-application-context“.

    Cảm ơn các bạn đã đọc bài viết!

  • [Spring] Sử dụng Spring ResponseStatusException

    [Spring] Sử dụng Spring ResponseStatusException

    Sử dụng Spring ResponseStatusException

    Giới thiệu

    Một ứng dụng RESTful, bằng cách trả về các HTTP status code trong HTTP response nó có thể thông báo về sự thành công hay thất bại của một HTTP request. Ví dụ như nếu người dùng request lên một id không hề tồn tại, các HTTP status code có thể giúp xác định được các vấn đề có thể xảy ra khi xử lí request.

    Trong Spring chúng ta cũng có rất nhiều cách để đặt HTTP status code cho một HTTP response. Tuy nhiên trong bài viết này mình sẽ giới thiệu về một class mới được giới thiệu trong Spring 5 đó chính là ResponseStatusException sẽ hỗ trợ cho ta việc áp dụng HTTP status code.

    @ResponseStatus

    Trước khi tìm hiểu về ResponseStatusException , ta sẽ tìm hiểu qua về @ResponseStatus annotation. Annotation này được giới thiệu trong Spring 3 để giải quyết vấn đề áp dụng HTTP status code cho HTTP response.

    Với annotaion này chúng ta sẽ sử dụng để định nghĩa status code và reason cho HTTP response:

    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    @ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Image not found")
    public class ImageNotFoundException extends Exception{
        //...
    }
    

    Ở trong ví dụ này, nếu Exception được đưa ra trong một xử lí HTTP request, thì trong response sẽ bao gồm HTTP status code được chỉ định sẽ là 404.

    Tuy nhiên với phương pháp sử dụng @ResponseStatus ta có một nhược điểm là nó sẽ tạo ra mối quan hệ phụ thuộc chặt chẽ vói Exception. Và như trường hợp ta xét ở trên thì tất cả các Exception có kiểu ImageNotFoundException khi được đưa ra thì đều cho ta một response với thông báo lỗi và status code là như nhau trong mọi trường hợp.

    ResponseStatusException

    ResponseStatusException được tạo ra nhằm thay thế cho @ResponseStatus và là một base class cho các exception được sử dụng đề apply các HTTP status cho response. Và đây cũng là một RuntimeException.

    Với ResponseStatusException class sẽ cung cấp cho ta 3 contructor:

    Với các đối số truyền vào cho contructor method:

    • status – HTTP status được set cho HTTP response
    • reason – message được hiển thị để giải thích cho exception
    • cause – là một java.lang.Throwable nguyên nhân của ResponseStatusException

    Những điểm mạnh cảu việc dùng ResponseStatusException class:

    • Thứ nhất, việc khai báo và sử dụng dễ dàng
    • Thứ hai, các exception cùng loại ta có thể xử lí riêng biệt và có thể linh hoạt các stutas code khác nhau có thể được set trong response, giảm sự phụ thuộc vào nhau.
    • Thứ ba, ta có thể tránh được việc phải tạo các class exception không cần thiết.
    • Và cuối cùng, class cấp cho ta nhiều quyền hơn trong việc xử lí exception, vì các exception được tạo theo chương trình nên được kiểm soát tốt hơn.

    Ví dụ

    Bây giờ, hãy xem một ví dụ về cách sử dụng ResponseStatusException trong thực tế:

    Ta có 1 class ToDoController khái báo “/todo” mapping truyền vào tham số id để lấy ra Todo object tương ứng

    @RestController
    public class ToDoController {
    
        @Autowired
        private ToDoService toDoService;
    
        @GetMapping(value = "/todo")
        public ToDo getTodo(@RequestParam(value = "id", required = false) Long id) {
            try {
                return toDoService.getTodo(id);
            } catch (TodoNotFoundException e) {
                throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Todo not found", e);
            }
        }
    
    }
    

    Với một exception được khai báo

    public class TodoNotFoundException extends Exception{
    
        public TodoNotFoundException(String errorMessage){
            super(errorMessage);
        }
    }
    

    Spring sẽ cung cấp cho ta một “/error” mapping sẽ trả về response dưới định dạng JSON với HTTP status. Trong ví dụ này khi truyền vào id không hề tồn tại chương trình sẽ trả về ResponseStatusException với response chứa status code tương ứng.

    Đây sẽ là response trong trường hợp có exception (ta sẽ dùng Postman để gửi request):

    Để xem được message về lỗi trong response ta sẽ thêm thuộc tính server.error.include-message=always. Khi đó response trả về sẽ có nội dung:

    {
        "timestamp": "2021-08-03T01:35:20.878+00:00",
        "status": 404,
        "error": "Not Found",
        "message": "Todo not found",
        "path": "/todo"
    }
    

    Ngoài ra với lợi ích là tính linh hoạt khi có thể gán các status code khác nhau cho cùng một exception ta có thể thứ với một ví dụ khác:

    @GetMapping(value = "/todo")
        public ToDo getTodo(@RequestParam(value = "id", required = false) Long id) {
            try {
                return toDoService.getTodo(id);
            } catch (TodoNotFoundException e) {
                throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Provide correct Todo Id", e);
            }
        }
    

    Và response trả về sẽ là:

    {
        "timestamp": "2021-08-03T01:57:41.502+00:00",
        "status": 400,
        "error": "Bad Request",
        "message": "Provide correct Todo Id",
        "path": "/todo"
    }
    

    Lời kết

    Như vậy trong bài viết này, chúng ta đã cùng tìm hiểu về ResponseStatusException – một cách tốt hơn để tạo một HTTP status code trong HTTP response so với annotation @ResponseStatus. Tuy nhiên với việc nên thận trọng với việc sử dụng vì nếu không có phương pháp xử lí exception thống nhất thì việc thực thi một số quy ước trên toàn ứng dụng sẽ khó khăn và có thể xảy ra code bị trùng lặp.

    Cảm ơn các bạn đã đọc bài viết!

    Tài liệu tham khảo: https://www.baeldung.com/spring-response-status-exception

  • Hướng dẫn sử dụng Project Lombok

    Hướng dẫn sử dụng Project Lombok

    Hướng dẫn sử dụng Project Lombok

    Chinh chiến với Java nhiều năm, bạn có cảm thấy nhàm chán khi làm việc với những đoạn code theo khuôn mẫu của nó hay lười biếng phải khai báo các phương thức Getter, Setter cho các class Java? Nếu câu trả lời là có thì hãy sử dụng Project Lombok. Vậy Project Lombok là gì?

    • Lombok?
    • Cài đặt
    • Sử dụng Lombok
    • Lời kết

    Lombok?

    Lombok là một thư viện, một plugin, giúp chúng ta giảm thiểu các đoạn code thừa (boilerplate) bằng cách tự động sinh ra các hàm GetterSetterConstructor, v.v..

    Lombok giúp chúng ta generate code một cách tự động nhưng không giống như cách các IDE làm cho chúng ta. Các IDE generate các phương thức Getter, Setter và một số phương thức khác trong các tập tin .java. Lombok cũng generate các phương thức đó nhưng là trong các tập tin .class file. Không những làm cho code sáng sửa mà còn trông rất hợp lý, dễ quản lý hơn giúp developer tập trung vào tầng nghiệp vụ và logic thay vì mất thời gian làm những việc thừa thãi.

    Cài đặt

    Để sử dụng Lombok trong project ta cần:

    Bước 1

    Thêm lib vào project bằng Maven hoặc Gradle

    Maven

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>
    

    Gradle

    
    // https://mvnrepository.com/artifact/org.projectlombok/lombok
    compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.20'
    

    Bước 2

    Đến đây có thể bạn sẽ thắc mắc tại sao đã thêm Lombok vào project rồi mà còn phải cài thêm Lombok Plugin vào IDE nữa???

    Ví dụ bạn muốn sử dụng Lombok để generate Get/Set thì nó sẽ tự động thêm code vào class đó trước khi thành file .jar. Nhưng các IDE thì chỉ nhìn thấy các dòng code hiện tại của bạn và tham chiếu tới nó, điều này sẽ dẫn đến những thông báo lỗi khi bạn sử dụng hàm Get/Set này.

    Nên để IDE hiểu rằng các class đã có các hàm Get/Set rồi, thì bạn phải cài thêm Lombok Plugin.

    Bây giờ ta sẽ cài đặt cho IntelliJ IDEA

    Với IntelliJ IDEA version 2020.3 trở lên thì IDE đã hỗ trợ Lombok mà ko cần cài plugin(phần hướng dẫn này cho version trước 2020.3 )

    Vào File -> Setting -> Plugin …

    Search “Lombok”, chọn Lombok Plugin và Install.

    Sử dụng Lombok

    Lombok dùng các Annotation để khai báo

    @Data

    Ví dụ này 2 đoạn code sẽ tương đương

    public class User {
    
        private String name;
    
        private int age;
    
        public User() {
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User: " + name
                    + " - " + age;
        }
    }
    
    import lombok.Data;
    
    @Data
    public class User {
    
        private String name;
    
        private int age;
    }
    

    Khi bạn đánh dấu 1 class là @Data, thì nó sẽ generate ra Constructor rỗng hoặc có tham số theo yêu cầu, toàn bộ Get/Set, hàm equals, hashCode, toString().

    @NoArgsConstructor@RequiredArgsConstructor@AllArgsConstructor

    Các annotation được dùng trong trường hợp bạn muốn định nghĩa các Contructor theo yêu cầu khác nhau:

    • @NoArgsConstructor: Hàm khởi tạo rỗng, đã đề cập ở trên
    • @RequiredArgsConstructor: Hàm khởi tạo chứa tất cả thuộc tính, ví dụ Champion(String name, String type)
    • @AllArgsConstructor: Hàm khởi tạo theo yêu cầu. Bạn chỉ muốn hàm khởi tạo có vài thuộc tính do bạn chọn thôi, thì bạn thêm final trước thuộc tính trong class, nó sẽ tự sinh ra Constructor như thế.

    @Getter@Setter

    Được sử dụng trong trường hợp chỉ muốn generate Get/Set và không muốn dùng @Data vì có chức năng không cần thiết.

    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    public class User {
    
        private String name;
    
        private int age;
    }
    

    @Builder

    Thông thường, khi chúng ta cần khởi tạo một đối tượng với rất nhiều thông tin, chúng ta có thể sử dụng Builder Pattern để làm điều này.

    Ví dụ với class User như sau:

    public class Users {
    
        private String name;
    
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User: " + name
                    + " - " + age;
        }
    }
    

    Để tạo đối tượng User với tất cả các thông tin sử dụng Builder Pattern, chúng ta cần tạo một đối tượng UserBuilder như sau:

    public class UserBuilder {
    
        private Users user;
    
        public UserBuilder() {
            user = new User();
        }
    
        public UserBuilder name(String name){
            user.setName(name);
            return this;
        }
    
        public UserBuilder age(int age){
            user.setAge(age);
            return this;
        }
    
        public Users build(){
            return user;
        }
    }
    

    Và dùng UserBuilder này:

    Users users = new UserBuilder()
                    .name("CuongNM")
                    .age(20)
                    .build();
    

    Chắc hẳn ai cũng ngại khi viết 1 class Builder cổ điển như trên, tự dưng phải tạo thêm 1 class nữa, gấp đôi số lượng thuộc tính khai báo, gấp đôi số hàm cần viết.

    Với Lombok vấn đề này đc giải quyết rất nhanh gọn

    @Data
    @Builder
    public class Users {
    
        private String name;
    
        private int age;
    }
    
    Users users = Users.builder()
                    .name("CuongNM")
                    .age(20)
                    .build();
    

    Logging với Project Lombok(@Slf4j)

    Thông thường, khi chúng ta sử dụng các Logging frameworks như Log4J, Logback hay Simple Logging Facade for Java (SLF4J), ta sẽ khai báo như sau:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
     
    public class Example {
     
        public static final Logger LOGGER = LoggerFactory.getLogger(Example.class);
     
        public void print(String message) {
            LOGGER.info(message);
        }
    }
    

    Nhưng đối với Lombok, ta chỉ cần dùng annotation dành cho Logging framework mà chúng ta muốn sử dụng mà thôi, sau đó thì có thể sử dụng như bình thường. Như ví dụ ở đây, mình đang sử dụng Simple Logging Facade for Java(SLF4J) để logging thì mình sẽ khai báo annotation @Slf4j của Lombok dành cho thư viện này như sau:

    import lombok.extern.slf4j.Slf4j;
     
    @Slf4j
    public class Example {
     
        public void print(String message) {
            log.info(message);
        }
    }
    

    Lời kết

    Lombok sinh ra để giúp cho code của chúng ta trở nên ngắn gọn dễ hiểu hơn và nó còn rất nhiều tính năng hay ho khác. Trong khuôn khổ bài viết này mình chỉ giới thiệu về các tính năng cơ bản của Lombok và được mình sử dụng nhiều nhất trong suốt quá trình làm việc.

    Cảm ơn các bạn đã đọc bài viết! Các bạn có thể tìm hiểu thêm tại trang chủ của Lombok.

    Tác giả

    [email protected]