Exception handling trong ứng dụng Spring Boot (Spring Boot Global Exception Handler)

Exception trong Spring Boot

Exception là đối tượng đại diện cho lỗi xảy ra khi chương trình chạy. Nếu không được xử lý kịp thời, exception có thể làm chương trình bị dừng đột ngột. Trong bài viết này, chúng ta sẽ tìm hiểu cách xây dựng cơ chế xử lý exception trong ứng dụng Spring Boot.

@ControllerAdvice và @ExceptionHandler

Aspect Oriented Programming (Spring AOP)

Spring Boot tận dụng Lập trình Hướng khía cạnh (Aspect Oriented Programming – AOP) để cung cấp một cơ chế xử lý exception hiệu quả.

@ControllerAdvice và @ExceptionHandler

Spring Boot sử dụng hai annotation @ControllerAdvice và @ExceptionHandler để bắt và xử lý tất cả các exception xảy ra trong ứng dụng.

@ControllerAdvice
class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException::class)
    fun handleRuntimeException(exception: RuntimeException): ResponseEntity<BaseResponse<Any>> {
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(BaseResponse(0, exception.message))
    }
}Code language: Kotlin (kotlin)

Global Exception Handler

Khi dự án phát triển, số lượng exception có thể tăng lên đáng kể. Để giảm thiểu các vấn đề phát sinh và đơn giản hóa việc quản lý lỗi, ta cần xây dựng một cơ chế xử lý exception tập trung, hay còn gọi là GlobalExceptionHandler.

Khi một exception phát sinh, Spring Boot sẽ chuyển chúng đến GlobalExceptionHandler. Tuỳ thuộc vào loại exception, GlobalExceptionHandler sẽ tạo ra các thông báo lỗi tương ứng để trả về cho client.

Ví dụ bạn có lớp CustomerController như dưới đây:

@RestController
@RequestMapping("/customers")
class CustomerController {
    @Autowired
    lateinit var service: CustomerService

    @GetMapping("/{id}")
    fun findById(
        @PathVariable id: Long,
    ): ResponseEntity<Customer> {
        val student = service.findCustomerById(id)
        student?.let {
            return ResponseEntity.ok(BaseResponse(data = student))
        } ?: run {
            return ResponseEntity.notFound().build()
        }
    }
}Code language: Kotlin (kotlin)

Lớp CustomerService:

@Service
class CustomerService {
    @Autowired
    lateinit var repository: CustomerRepository

    fun findCustomerById(id: Long): Customer? {
        return repository.findById(id).orElse(null)
    }
}Code language: Kotlin (kotlin)

Hiện tại, chúng ta thiếu cơ chế bắt và xử lý lỗi một cách hiệu quả. Hãy tạo một lớp exception để quản lý các lỗi có thể xảy ra, chẳng hạn như lỗi không tìm thấy dữ liệu (Not Found): NotFoundException

class NotFoundException(message: String) : RuntimeException(message)Code language: Kotlin (kotlin)

Sửa phương thức findCustomerById như sau:

fun findCustomerById(id: Long): Customer {
        return repository.findById(id)
            .orElseThrow { NotFoundException("Customer Not Found!") }
}Code language: Kotlin (kotlin)

Thay đổi này giúp trả về exception thay vì giá trị null khi không tìm thấy customer theo ID.

Tiếp theo sửa phương thức findById trong CustomerController như sau:

@GetMapping("/{id}")
    fun findById(
        @PathVariable id: Long,
    ): ResponseEntity<BaseResponse<Customer>> {
        return ResponseEntity.ok(BaseResponse(data = service.findCustomerById(id)))
}Code language: Kotlin (kotlin)

Nhờ thay đổi này, ta không cần kiểm tra null cho customer nữa, vì nếu không tìm thấy, exception sẽ được xử lý trong lớp service. Điều này giúp giảm thiểu độ phức tạp của code trong lớp controller.

Tuy nhiên, nếu dừng lại ở đây, exception vẫn chưa được xử lý triệt để và có thể gây ra lỗi không mong muốn cho client.

Xây dựng GlobalExceptionHandler

Để giải quyết chúng ta sẽ tạo lớp GlobalExceptionHandler với @ControllerAdvice annotation. Lớp này có nhiệm vụ lắng nghe các exception trả về từ tất cả các lớp controller trong dự án.

@ControllerAdvice
class GlobalExceptionHandler {
    @ExceptionHandler(NotFoundException::class)
    fun handleNotFoundException(exception: NotFoundException): ResponseEntity<BaseResponse<Any>> {
        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(BaseResponse(0, exception.message))
    }

    @ExceptionHandler(RuntimeException::class)
    fun handleRuntimeException(exception: RuntimeException): ResponseEntity<BaseResponse<Any>> {
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(BaseResponse(0, exception.message))
    }
}Code language: Kotlin (kotlin)

Tùy thuộc vào loại exception nhận được, chúng sẽ được xử lý và trả về thông báo lỗi phù hợp cho client.

Tổng kết

Trong bài viết này, chúng ta đã tìm hiểu cơ chế xử lý exception tập trung với GlobalExceptionHandler trong Spring Boot. Việc tích hợp cơ chế này vào dự án của bạn sẽ giúp việc quản lý exception trở nên dễ dàng hơn, đồng thời giảm thiểu sự phức tạp và làm sạch code trong các lớp Controller và Service.

vinhvd

Hello there! 👋 I'm Vinh.