Hướng dẫn sử dụng RestTemplate trong Spring Boot

RestTemplate là mộ trong những thành phần cốt lõi của Spring framework cho phép thực thi các HTTP request và nhận các các response tương ứng. Bản thân RestTemplate là một high-level API cho phép loại bỏ các mã code nhàm chám để cấu hình một java.net.HttpURLConnection sử dụng để gửi và nhận các request/response mà Java SDK cung cấp.

Để hiểu rõ hơn, nếu sử dụng HttpURLConnection để gửi một request thì chúng ta phải thực thi các công việc sau lặp đi lặp lại nhiều lần:

  • Khởi tạo URL object
  • Cấu hình HTTP request.
  • Thực thi HTTP request.
  • Nhận phản hồi response.
  • Chuyển đổi HTTP response sang Java Object.
  • Xử lý exception.

Khi sử dụng RestTemplate, tất cả những công việc trên được chạy ngầm và chúng ta không cần quan tâm đến chúng.

Tuy nhiên, bắt đầu từ phiên bản Spring 5, non-blocking và reactive WebClient được giới thiệu để thay thế RestTemplate. WebClient hỗ trợ synchronous và asynchronous HTTP request và cả các kịch bản streaming. Vì thế RestTemplate sẽ được đánh dấu deprecated trong các phiên bản sau và sẽ không được cập nhật thêm các tính năng mới.

Maven dependency

Để sử dụng RestTemplate trong Spring Boot chúng ta cần thêm các dependency sau:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

Project Setup

RestTemplate có thể sử dụng để request đến một Rest API bất kỳ, tuy nhiên để đảm bảo ví dụ có thể hoạt động trong khuôn khổ bài viết này, mình sẽ sử dụng RestTemplate request đến chính server hiện tại.

package com.deft.resttemplate.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

    private long id;
    private String firstName;
    private String lastName;
    private long yearlyIncome;
}

Trong đó sử dụng mình sử dụng  Lombok để giảm bớt các các bước triển khai constructor, getter, setter v.v

Tiếp theo định nghĩa một MockRestAPI định nghĩa các API mà sau đó chúng ta sẽ dùng RestTemplate để request đến.

package com.deft.resttemplate.mockapi;

import com.deft.resttemplate.entity.Employee;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@RestController
@RequestMapping("/mock")
public class MockRestAPI {

    private static final Map<Long, Employee> employees;
    private static Long nextID = 1L;

    static {
        employees = new HashMap<>();
        employees.put(nextID, new Employee(nextID++, "John", "Doe", 80000));
        employees.put(nextID, new Employee(nextID++, "Mary", "Jackson", 75000));
        employees.put(nextID, new Employee(nextID++, "Peter", "Grey", 60000));
        employees.put(nextID, new Employee(nextID++, "Max", "Simpson", 67000));
        employees.put(nextID, new Employee(nextID++, "Lisa", "O'Melly", 45000));
        employees.put(nextID, new Employee(nextID++, "Josephine", "Rose", 52000));
    }

    @GetMapping("/{id}")
    public Employee getEmployee(@PathVariable Long id) {
        Optional<Employee> employee = Optional.ofNullable(employees.get(id));
        return employee.orElse(null);
    }

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Employee> create(@RequestBody Employee newEmployee) {
        newEmployee.setId(nextID++);
        employees.put(newEmployee.getId(), newEmployee);

        return ResponseEntity.status(HttpStatus.CREATED)
                .header("Location", "/rest/employees/" + newEmployee.getId())
                .body(newEmployee);
    }

    @PutMapping(path = "/{id}",
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Employee> update(@PathVariable long id, @RequestBody Employee request) {
        if (!employees.containsKey(id)) {
            return ResponseEntity.notFound().build();
        } else {
            Employee employee = employees.get(id);
            employee.setFirstName(request.getFirstName());
            employee.setLastName(request.getLastName());
            employee.setYearlyIncome(request.getYearlyIncome());

            return ResponseEntity.ok(employee);
        }
    }

    @DeleteMapping(path = "/{id}")
    public ResponseEntity<Void> delete(@PathVariable long id) {
        Employee removedEmployee = employees.remove(id);

        return removedEmployee != null ? ResponseEntity.ok().build() : ResponseEntity.notFound().build();
    }
}

Các RestTemplate methods

RestTemplate hỗ trợ hầu hết các HTTP request method như GET, POST, PUT và DELETE. Chúng ta sẽ cùng nhau tìm hiểu từng phần một ngay sau đây.

Cấu hình RestTemplate Bean

Có nhiều cách để khởi tạo một RestTemplate object, tuy nhiên mình sẽ khởi tạo RestTemplate bean và sử dụng nó trong xuyên suốt ứng dụng.

package com.deft.resttemplate.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;

@Configuration
public class Config {

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory("http://localhost:8080/mock");
        restTemplate.setUriTemplateHandler(defaultUriBuilderFactory);
        return restTemplate;
    }
}

GET

Để triển khai một GET HTTP request, chúng ta có 2 method có thể sử dụng:

  • getForEntity
  • getForObject
package com.deft.resttemplate.controller;

import com.deft.resttemplate.entity.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ControllerExample {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/get-for-object")
    public Employee getForObject() {
        int id = 1;
        return restTemplate.getForObject("/" + id, Employee.class);
    }

    @GetMapping("/get-for-entity")
    public Employee getForEntity() {
        int id = 2;
        ResponseEntity<Employee> entity = restTemplate.getForEntity("/" + id, Employee.class);
        return entity.getBody();
    }
}

Chúng ta có thể kiểm thử bằng cách chạy ứng dụng và request đến /get-for-object hoặc /get-for-entity để xem kết quả

curl http://localhost:8080/get-for-object
{"id":1,"firstName":"John","lastName":"Doe","yearlyIncome":80000}
//-----
curl http://localhost:8080/get-for-entity
{"id":2,"firstName":"Mary","lastName":"Jackson","yearlyIncome":75000}

POST

Tương tự, có 3 cách để thực thị một HTTP Post trong RestTemplate:

  • postForObject
  • postForLocation
  • postForEntity

Trong đó postForObjectpostForObject lần lượt nhận về các response là các Object type được chỉ định khi gọi, còn postForLocation chỉ nhận về một URI thay vì Object.

@PostMapping("/post-for-object")
public Employee postForObject() {
    Employee employee = new Employee();
    employee.setFirstName("Deft");
    employee.setLastName("blog");
    employee.setYearlyIncome(2020);
    return restTemplate.postForObject("/", employee, Employee.class);
}

@PostMapping("/post-for-location")
public URI postForLocation() {
    Employee employee = new Employee();
    employee.setFirstName("Deft");
    employee.setLastName("blog");
    employee.setYearlyIncome(2020);
    return restTemplate.postForLocation("/", employee);
}

@PostMapping("/post-for-entity")
public Employee postForLo() {
    Employee employee = new Employee();
    employee.setFirstName("Deft");
    employee.setLastName("blog");
    employee.setYearlyIncome(2020);
    HttpEntity<Employee> entity = restTemplate.postForEntity("/", employee, Employee.class);
    return entity.getBody();
}

Output

curl -X POST http://localhost:8080/post-for-object
{"id":7,"firstName":"Deft","lastName":"blog","yearlyIncome":2020}

// -------------------------
curl -X POST http://localhost:8080/post-for-location
"/rest/employees/8"

// -------------------------

curl -X POST http://localhost:8080/post-for-entity
{"id":9,"firstName":"Deft","lastName":"blog","yearlyIncome":2020}

PUT

HTTP PUT method được dùng để cập nhật một entity cụ thể trong, đều này đồng nghĩa bạn phải gửi một Employee hoàn chỉnh bao gồm các thông tin ban đầu và các thông tin đã được thay đổi.

Để thực thi một PUT HTTP request trong RestTemplate chúng ta có thể sử dụng put() method được hỗ trợ sẵn.

@PutMapping("/put/{id}")
public void put(@PathVariable Long id) {
    Employee employee = new Employee();
    employee.setId(id);
    employee.setFirstName("Deft");
    employee.setLastName("blog");
    employee.setYearlyIncome(2021);
    restTemplate.put("/" + id, employee);
}

Output

curl -X PUT http://localhost:8080/put/1
// --------------
curl http://localhost:8080/get-for-object
{"id":1,"firstName":"Deft","lastName":"blog","yearlyIncome":2021

Tuy nhiên, nếu chúng ta muốn thực thi PUT method và nhận về response chứa các thông tin được thay đổi và mã trạng thái thực thi thì có thể sử dụng exchange()

@PutMapping("/put-ex/{id}")
public Employee putExchange(@PathVariable Long id) {
    Employee employee = new Employee();
    employee.setId(id);
    employee.setFirstName("Deft");
    employee.setLastName("blog");
    employee.setYearlyIncome(2021);
    return restTemplate.exchange( "/" + id,
            HttpMethod.PUT,
            new HttpEntity<>(employee),
            Employee.class,
            Long.toString(id)).getBody();
}

Output

curl -X PUT http://localhost:8080/put-ex/2
// ---------------
{"id":2,"firstName":"Deft","lastName":"blog","yearlyIncome":2021}

DELETE

Trong trường hợp muốn xóa một entity thông qua ID thì chúng ta có thể sử dụng delete() hoặc exchange() tương tự như PUT.

@DeleteMapping("/delete/{id}")
public void delete(@PathVariable String id) {
    restTemplate.delete("/" + id);
}

@DeleteMapping("/delete-ex/{id}")
public ResponseEntity<Void> deleteEx(@PathVariable Long id) {
    return restTemplate.exchange( "/" + id,
            HttpMethod.DELETE,
            null,
            Void.class,
            id);
}

Kết bài

RestTemplate trong các dự án spring cũ có thể là lựa chọn tốt. Tuy nhiên nếu bạn bắt đầu một dự án spring từ khi đọc bài viết này, thì nên cân nhắc sử dụng WebClient với nhiều tính năng mới và tối ưu hóa hiệu năng hơn.

Cuối cùng mã nguồn được mình công khai trên gitlab để các bạn tiện theo dõi và kiểm thử: resttemplate

Using RestTemplate in Spring

2 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x