vlambda博客
学习文章列表

读书笔记《building-restful-web-services-with-spring-5-second-edition》构建REST客户端和错误处理

Chapter 10. Building a REST Client and Error Handling

在前面的章节中,我们介绍了 RESTful Web 服务的服务器端,包括 CRUD 操作。在这里,我们可以检查如何在代码本身中使用这些 API。 REST 客户端将帮助我们实现这一目标。

在本章中,我们将讨论以下主题:

  • RestTemplate in Spring
  • Basic setup for building a RESTful service client with Spring
  • Calling a RESTful service in the client
  • Defining the error handler
  • Using the error handler

Building a REST client


到目前为止,我们已经创建了一个 REST API 并在 SoapUI、Postman 或 JUnit 测试等第三方工具中使用它.在某些情况下,您可能必须使用常规方法(服务或其他控制器方法)本身来使用 REST API,例如服务 API 中的支付 API 调用。当您在代码中调用第三方 API(例如 PayPal 或天气 API)时,它将很有用。在这种情况下,拥有一个 REST 客户端将有助于完成工作。

在这里,我们将讨论如何构建一个 REST 客户端以在我们的方法中使用另一个 REST API。在继续之前,我们将讨论一下 Spring 中的 RestTemplate

RestTemplate

RestTemplate 是一个 Spring 类,用于使用来自的 REST API客户端通过HTTP。通过使用 RestTemplate,我们也可以将 REST API 使用者保持在同一个应用程序中,因此我们不需要第三方应用程序或其他应用程序来使用我们的 API。 RestTemplate 可用于调用 GET, POST, PUTDELETE 和其他高级 HTTP 方法(OPTIONS

Note

默认情况下,RestTemplate 类依赖 JDK 建立 HTTP 连接。您可以切换到使用不同的 HTTP 库,例如 Apache HttpComponents 和 Netty。

首先,我们将在 AppConfig 类中添加一个 RestTemplate bean 配置。在下面的代码中,我们将看到如何配置 RestTemplate bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class AppConfig {
  @Bean
  public RestTemplate restTemplate() {
      return new RestTemplate();
  }
}

在前面的代码中,我们已经提到了这个带有 @Configuration 注解的类来配置类中的所有 bean。我们还在这个类中引入了 RestTemplate bean。通过在 AppConfig 类中配置 bean,我们告诉应用程序提到的 bean 可以在应用程序的任何地方使用。当应用程序启动时,它会自动初始化 bean 并准备好在任何需要的地方使用模板。

现在,我们可以通过在任何类中简单地使用 @Autowire 注释来使用 RestTemplate。为了更好地理解,我们创建了一个名为 ClientController 的新类,并在该类中添加了一个简单的方法:

@RestController
@RequestMapping("/client")
public class ClientController {  
  private final Logger _log = LoggerFactory.getLogger(this.getClass());    
  @Autowired
  RestTemplate template;  
  @ResponseBody
  @RequestMapping("/test") 
  public Map<String, Object> test(){
    Map<String, Object> map = new LinkedHashMap<>();
    String content = template.getForObject("http://localhost:8080/", String.class); 
    map.put("result", content);    
    return map;
  }  
}

在前面的代码中,我们使用了 RestTemplate 并调用了 getForObject 方法来使用 API。默认情况下,我们使用 String.class 来保持我们的代码简单易懂。

当你调用这个API http://localhost:8080/client/test/,你会得到如下结果:

{
  result: "{\"result\":"\Aloha\"}"
}

在前面的过程中,我们在另一个 REST API 中使用了 RestTemplate。在实时场景中,您可能会使用与调用第三方 REST API 相同的方法。

让我们在另一个方法中获取单个用户 API:

@ResponseBody
  @RequestMapping("/test/user") 
  public Map<String, Object> testGetUser(){
    Map<String, Object> map = new LinkedHashMap<>();
    User user = template.getForObject("http://localhost:8080/user/100", User.class); 
    map.put("result", user);    
    return map;
  }

通过调用上述API,您将获得单个用户。为了调用这个 API,我们的 User 类应该被序列化,否则你可能会得到一个 unserialized object 错误。让我们通过实现 Serializable 并添加序列版本 ID 来序列化我们的 User 类。

Note

您可以在 Eclipse 中通过右键单击类名并生成序列号来创建序列版本 ID。

序列化 User 类后,如下所示:

public class User implements Serializable {  
  private static final long serialVersionUID = 3453281303625368221L;  
  public User(){ 
  }
  private Integer userid;  
  private String username;   
  public User(Integer userid, String username){
    this.userid = userid;
    this.username = username;
  }
  public Integer getUserid() {
    return userid;
  }
  public void setUserid(Integer userid) {
    this.userid = userid;
  }
  public String getUsername() {
    return username;
  }
  public void setUsername(String username) {
    this.username = username;
  }  
  @Override
  public String toString() {
    return "User [userid=" + userid + ", username=" + username + "]";
  }
}

最后,我们可以在浏览器中调用http://localhost:8080/client/test/user客户端API,得到如下结果:

{
  result: {
    userid: 100,
    username: "David"
  }
}

为了便于理解,我们只使用了 GET 方法。但是,我们可以在 RESTPOST 方法和 add 参数id288564749" class="indexterm"> 消费者。

Error handling


到目前为止,在我们的应用程序中,我们还没有定义 任何特定的错误处理程序来捕获错误并将其传送到正确的格式.通常当我们处理 REST API 中的意外情况时,它会自动抛出一个 HTTP 错误,例如 404404 等错误会在浏览器中显式显示。这通常没问题;但是,无论事情是对还是错,我们都可能需要 JSON 格式的结果。

在这种情况下,将错误转换为 JSON 格式将是一个好主意。通过提供 JSON 格式,我们可以保持我们的应用程序干净和标准化。

在这里,我们将讨论如何管理错误并在出现问题时以 JSON 格式显示它们。让我们创建一个通用的错误处理程序类来管理我们所有的错误:

public class ErrorHandler {
  @ExceptionHandler(Exception.class)
  public @ResponseBody <T> T handleException(Exception ex) {    
    Map<String, Object> errorMap = new LinkedHashMap<>();
    if(ex instanceof org.springframework.web.bind.MissingServletRequestParameterException){      
      errorMap.put("Parameter Missing", ex.getMessage());
      return (T) errorMap;
    }    
    errorMap.put("Generic Error ", ex.getMessage());
    return (T) errorMap;
  }
}

前面的类将充当我们应用程序中的常见错误处理程序。在 ErrorHandler 类中,我们使用 @ExceptionHandler< 创建了一个名为 handleException 的方法/code> 注释。此注解将使该方法接收应用程序中的所有异常。一旦我们得到异常,我们就可以根据异常的类型来管理要做什么。

在我们的代码中,我们只使用了两种情况来管理我们的异常:

  • Missing parameter
  • General error (everything else other than missing parameter)

如果我们在调用任何 REST API 时遗漏了一个参数,它将转到第一种情况 Parameter Missing,否则它将转到 Generic错误 默认错误。我们简化了流程,使新用户可以理解。但是,我们可以在这个方法中添加更多的案例来管理更多的异常。

一旦我们完成了错误处理程序,我们将不得不在我们的应用程序中使用它。可以通过多种方式应用错误处理程序。扩展错误处理程序是使用它的最简单方法:

@RestController
@RequestMapping("/")
public class HomeController extends ErrorHandler {    
    // other methods
  @ResponseBody
  @RequestMapping("/test/error") 
  public Map<String, Object> testError(@RequestParam(value="item") String item){
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("item", item);    
    return map;
  }   
}

在前面的代码中,我们只是在 HomeController 类中扩展了 ErrorHandler。通过这样做,我们将所有错误场景绑定到 ErrorHandler 以正确接收和处理。此外,我们创建了一个名为 testError 的测试方法来检查我们的错误处理程序。

为了调用这个API,我们需要传递item作为参数;否则它将在应用程序中引发错误。由于我们已经定义了 ErrorController 类并扩展了 HomeController 类,缺少参数将带您进入前面提到的第一个场景.

只需在浏览器或任何 REST 客户端 (Postman/SoapUI) 中尝试以下 URL:http://localhost:8080/test/error

如果您尝试前面的端点,您将得到以下结果:

{
  Parameter Missing: "Required String parameter 'item' is not present"
}

由于我们在错误处理程序中定义了 JSON 格式,如果任何 REST API 抛出异常,我们将得到 JSON 格式的错误。

Customized exception

到目前为止,我们只探讨了应用程序抛出的错误。但是,我们可以定义自己的错误并在需要时抛出它们。以下代码将向您展示如何创建 customized 错误并将其抛出到我们的应用程序中:

@RestController
@RequestMapping("/")
public class HomeController extends ErrorHandler {  
    // other methods  
  @ResponseBody
  @RequestMapping("/test/error/{id}")
  public Map<String, Object> testRuntimeError(@PathVariable("id") Integer id){    
    if(id == 1){
      throw new RuntimeException("some exception");
    }    
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("result", "one");    
    return map;
  }
}

在前面的代码中,我们使用 RuntimeException 创建了一个自定义异常。这只是向您展示自定义异常如何在错误处理中工作的测试代码。在接下来的章节中,我们将在我们的应用程序中应用此自定义异常。

如果调用 http://localhost:8080/test/error/1 API,会出现如下错误,这是由于我们的条件匹配导致的:

{
  Generic Error : "some exception"
}

Summary


在本章中,我们学习了使用 RestTemplate 构建 RESTful Web 服务客户端。此外,我们还介绍了错误处理程序和集中式错误处理程序来处理所有容易出错的情况。在接下来的章节中,我们将讨论扩展我们的 Spring 应用程序并讨论一些关于微服务的话题,因为这些主题正在迅速增长。