Skip to content

2.1.2 Tạo một lớp controller

Controllers (bộ điều khiển) là những thành phần chính trong framework MVC của Spring. Nhiệm vụ chính của chúng là xử lý các yêu cầu HTTP và hoặc chuyển tiếp yêu cầu đó tới một view để hiển thị HTML (dành cho trình duyệt), hoặc ghi dữ liệu trực tiếp vào phần thân của phản hồi (RESTful). Trong chương này, chúng ta tập trung vào loại controller sử dụng các view để tạo nội dung cho trình duyệt web. Khi đến chương 7, chúng ta sẽ tìm hiểu cách viết controller xử lý các yêu cầu trong một REST API.

Đối với ứng dụng Taco Cloud, bạn cần một controller đơn giản với các chức năng sau:

  • Xử lý các yêu cầu HTTP GET mà đường dẫn là /design
  • Tạo một danh sách các nguyên liệu
  • Chuyển tiếp yêu cầu cùng dữ liệu nguyên liệu tới một mẫu view để được hiển thị dưới dạng HTML và gửi về trình duyệt web yêu cầu

Lớp DesignTacoController trong liệt kê sau đây đáp ứng các yêu cầu đó.

Liệt kê 2.4 Khởi đầu cho một lớp controller Spring

java
package tacos.web;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;

import lombok.extern.slf4j.Slf4j;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.Taco;

@Slf4j
@Controller
@RequestMapping("/design")
@SessionAttributes("tacoOrder")
public class DesignTacoController {

  @ModelAttribute
  public void addIngredientsToModel(Model model) {
    List<Ingredient> ingredients = Arrays.asList(
      new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
      new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
      new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
      new Ingredient("CARN", "Carnitas", Type.PROTEIN),
      new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
      new Ingredient("LETC", "Lettuce", Type.VEGGIES),
      new Ingredient("CHED", "Cheddar", Type.CHEESE),
      new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
      new Ingredient("SLSA", "Salsa", Type.SAUCE),
      new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
    );

    Type[] types = Ingredient.Type.values();
    for (Type type : types) {
      model.addAttribute(type.toString().toLowerCase(),
      filterByType(ingredients, type));
    }
  }

  @GetMapping
  public String showDesignForm(Model model) {
    model.addAttribute("taco", new Taco());
    return "design";
  }
  private Iterable<Ingredient> filterByType(
        List<Ingredient> ingredients, Type type) {
    return ingredients
        .stream()
        .filter(x -> x.getType().equals(type))
        .collect(Collectors.toList());
  }
}

Điều đầu tiên cần chú ý ở DesignTacoController là tập hợp các annotation được áp dụng ở cấp lớp. Đầu tiên là @Slf4j, một annotation được cung cấp bởi Lombok, sẽ tự động sinh ra một thuộc tính tĩnh Logger (giao diện ghi log đơn giản cho Java - SLF4J, https://www.slf4j.org/) trong lớp tại thời điểm biên dịch. Annotation nhỏ gọn này có cùng hiệu quả như thể bạn tự thêm những dòng sau vào trong lớp:

java
private static final org.slf4j.Logger log =
      org.slf4j.LoggerFactory.getLogger(DesignTacoController.class);

Bạn sẽ sử dụng Logger này một chút sau đó.

Annotation tiếp theo được áp dụng cho DesignTacoController@Controller. Annotation này dùng để xác định lớp này là một controller và đánh dấu nó là ứng viên cho việc quét component, để Spring có thể phát hiện và tự động tạo một instance của DesignTacoController như một bean trong application context của Spring.

DesignTacoController cũng được chú thích với @RequestMapping. Annotation @RequestMapping, khi được áp dụng ở cấp lớp, chỉ định loại yêu cầu mà controller này xử lý. Trong trường hợp này, nó chỉ định rằng DesignTacoController sẽ xử lý các yêu cầu có đường dẫn bắt đầu bằng /design.

Cuối cùng, bạn thấy rằng DesignTacoController còn được anotate với @SessionAttributes("tacoOrder"). Điều này cho biết rằng đối tượng TacoOrder được đưa vào model (một chút sau trong lớp) sẽ được lưu giữ trong session. Điều này quan trọng vì việc tạo một chiếc taco cũng là bước đầu tiên trong việc tạo một đơn hàng, và đơn hàng mà chúng ta tạo cần được giữ trong session để có thể duy trì qua nhiều yêu cầu khác nhau.

XỬ LÝ YÊU CẦU GET

Khai báo @RequestMapping ở cấp lớp được bổ sung thêm bởi annotation @GetMapping được áp dụng cho phương thức showDesignForm(). @GetMapping, kết hợp với @RequestMapping ở cấp lớp, chỉ định rằng khi một yêu cầu HTTP GET đến /design, Spring MVC sẽ gọi showDesignForm() để xử lý yêu cầu đó.

@GetMapping chỉ là một thành viên trong họ các annotation ánh xạ yêu cầu của Spring.

Bảng 2.1 Spring MVC liệt kê tất cả các annotation ánh xạ yêu cầu có sẵn trong Spring MVC

AnnotationDescription
@RequestMappingGeneral-purpose request handling
@GetMappingHandles HTTP GET requests
@PostMappingHandles HTTP POST requests
@PutMappingHandles HTTP PUT requests
@DeleteMappingHandles HTTP DELETE requests
@PatchMappingHandles HTTP PATCH requests

Khi showDesignForm() xử lý một yêu cầu GET đến /design, nó thực sự không làm quá nhiều việc. Việc chính mà nó thực hiện là trả về một giá trị String"design", đây là tên logic của view sẽ được dùng để render model ra trình duyệt. Nhưng trước khi làm điều đó, nó cũng thêm vào Model một đối tượng Taco rỗng với tên key là "design". Điều này sẽ cho phép biểu mẫu có một "tấm bảng trắng" để người dùng tạo ra một chiếc taco theo ý muốn.

Có vẻ như một yêu cầu GET đến /design không làm được gì nhiều. Nhưng thực tế, có nhiều việc xảy ra hơn những gì phương thức showDesignForm() thể hiện. Bạn cũng sẽ thấy một phương thức tên là addIngredientsToModel() được anotate với @ModelAttribute. Phương thức này cũng sẽ được gọi khi một yêu cầu được xử lý và sẽ xây dựng một danh sách các đối tượng Ingredient để đưa vào model. Danh sách này hiện đang được mã hóa cứng. Khi sang chương 3, bạn sẽ lấy danh sách nguyên liệu taco từ cơ sở dữ liệu.

Khi danh sách nguyên liệu đã sẵn sàng, vài dòng tiếp theo trong addIngredientsToModel() sẽ lọc danh sách theo loại nguyên liệu bằng cách sử dụng phương thức trợ giúp tên là filterByType(). Một danh sách các loại nguyên liệu sau đó sẽ được thêm vào đối tượng Model như một thuộc tính, và sau đó được truyền vào showDesignForm(). Model là một đối tượng dùng để truyền dữ liệu giữa controller và view chịu trách nhiệm render dữ liệu đó. Cuối cùng, dữ liệu được đặt trong các thuộc tính của Model sẽ được sao chép vào các thuộc tính của yêu cầu servlet, nơi view có thể tìm thấy và sử dụng để hiển thị một trang trong trình duyệt của người dùng.

Sau addIngredientsToModel() là hai phương thức nữa cũng được anotate với @ModelAttribute. Các phương thức này đơn giản hơn nhiều và chỉ tạo ra một đối tượng TacoOrder mới và một đối tượng Taco để đưa vào model. Đối tượng TacoOrder, đã được nhắc đến trước đó trong annotation @SessionAttributes, giữ trạng thái cho đơn hàng đang được xây dựng khi người dùng tạo các taco qua nhiều yêu cầu. Đối tượng Taco được đưa vào model để đảm bảo rằng view được render để phản hồi yêu cầu GET đến /design sẽ có một đối tượng không phải null để hiển thị.

Lúc này, DesignTacoController của bạn đã bắt đầu hình thành rõ ràng. Nếu bạn chạy ứng dụng ngay bây giờ và trỏ trình duyệt đến đường dẫn /design, các phương thức showDesignForm()addIngredientsToModel() của DesignTacoController sẽ được gọi, đưa nguyên liệu và một Taco rỗng vào model trước khi chuyển tiếp yêu cầu đến view. Nhưng vì bạn chưa định nghĩa view, yêu cầu sẽ dẫn đến một kết quả không mong muốn, gây ra lỗi HTTP 500 (Internal Server Error). Để khắc phục điều đó, hãy chuyển sự chú ý của chúng ta sang phần view, nơi dữ liệu sẽ được kết hợp với HTML để hiển thị trong trình duyệt của người dùng.

Released under the MIT License.