20 个基本和高级 REST API 面试问题

REST API 面试问题:欢迎来到 REST API 的世界!无论您是刚刚开始您的旅程还是旨在加深理解,这本包含 20 个面试问题的集合都将指导您了解基本和高级的概念。准备好用简单明了的语言揭开表述性状态转移 (REST) 的神秘面纱。让我们深入研究并增强您对 RESTful API 开发的掌握!

什么是 REST API?
REST API 是表述性状态传输应用程序编程接口的缩写,是指导 Web 服务设计和交互的一组原则。它们为各种软件应用程序提供了标准化方法,以便通过互联网进行有效通信。RESTful API 植根于简单性、可扩展性和无状态性,使用独特的 URI 来识别资源并采用标准 HTTP 方法,例如 GET、POST、PUT 和 DELETE。

在 RESTful 架构中,通信是无状态的,这意味着每个客户端请求都包含所有必要的信息。为了优化 REST API,高效的资源识别和 HTTP 方法的利用至关重要。这些 API 通常利用 JSON 或 XML 进行数据交换,提供简单性、灵活性和轻松集成。REST API 受到 Web 应用程序、移动应用程序和分布式系统的欢迎,是现代开发的基石,有望简化通信和优化性能。

1.什么是 REST,它与其他 Web 服务架构有何不同?
REST(即表述性状态传输)是一种用于设计网络应用程序的架构风格。它依赖于客户端和服务器之间的无状态通信,并且使用标准 HTTP 方法(GET、POST、PUT、DELETE)表示和操作资源。与 SOAP(简单对象访问协议)不同,REST 不需要 XML 进行通信,并且通常被认为更轻量级。

示例:当您访问网站(例如www.example.com)时,您的浏览器使用 HTTP GET 方法从服务器请求 HTML 页面。服务器以请求的 HTML 进行响应,您的浏览器将呈现网页。

2.解释REST中资源的概念
在 REST 中,资源是关键的抽象。它们可以是可以识别和操纵的任何实体或对象,例如用户、产品或服务。资源通常使用 URI(统一资源标识符)来表示。

示例:在博客应用程序中,单个博客文章可以被视为资源。每个博客文章都可以通过 URI 来唯一标识/posts/1,其中“1”是特定文章的标识符。

3.描述REST中GET和POST方法的区别
让我们深入研究 REST 上下文中 GET 和 POST 方法之间的差异,并提供代码片段来说明每个方法。

获取方法:
GET 方法用于从指定资源请求数据。这是一种安全且幂等的操作,这意味着它不应该对服务器产生任何副作用,并且多次发出相同的请求应该会产生相同的结果。它主要用于数据检索。

示例代码片段(使用 Spring MVC):

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api/books")
public class BookController {
 
    @GetMapping(
"/{id}")
    public String getBookById(@PathVariable Long id) {
        
// Logic to retrieve book data by ID
        return
"Book with ID " + id + ": The Great Gatsby";
    }
}

在此示例中,该getBookById方法根据指定的 ID 处理用于检索图书数据的 GET 请求。

POST方法:
POST方法用于将需要处理的数据提交到指定的资源。它不是安全且不一定幂等的操作,这意味着它可能会对服务器产生副作用,并且多次发出相同的请求可能会导致不同的结果。它通常用于在服务器上创建或更新资源。

示例代码片段(使用 Spring MVC):

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api/books")
public class BookController {
 
    @PostMapping
    public String createBook(@RequestBody String bookData) {
        
// Logic to process and create a new book based on the provided data
        return
"New book created: " + bookData;
    }
}

在此示例中,该createBook方法处理创建新书的 POST 请求。书籍数据在请求正文中提供。

概括:

  • GET方法:用于检索数据,不应该有副作用,并且是幂等的。
  • POST方法:用于提交待处理的数据,可能有副作用,并且不一定是幂等的。

4. REST 中 HTTP 状态代码的用途是什么?
HTTP 状态代码指示客户端向服务器请求的成功或失败。他们提供有关请求结果的信息并指导客户如何继续。

示例: 200 状态代码表示响应成功,而 404 状态代码表示未找到所请求的资源。在代码片段中,200 OK和404 Not Found是 HTTP 状态代码的

5.解释REST API中速率限制的作用以及它如何帮助维护系统稳定性
速率限制是一种用于控制客户端在特定时间范围内可以向服务器发出的请求数量的技术。它有助于防止滥用、防止分布式拒绝服务 (DDoS) 攻击并确保资源的公平使用。

示例:服务器可能将客户端限制为每分钟 100 个请求。如果客户端超过此限制,服务器会响应 429 Too Many Requests 状态,指示客户端应放慢其请求速度。

6. 什么是内容压缩,它如何提高 RESTful API 的性能?
内容压缩涉及使用压缩算法减少通过网络发送的数据的大小。这可以通过减少响应时间和带宽使用来显着提高 REST API 的性能。

示例:服务器可以在将响应数据发送到客户端之前使用 gzip 或 deflate 对其进行压缩。客户端收到压缩数据后,将其解压使用。此过程最大限度地减少了通过网络传输的数据量,从而加快了响应时间。

下面是一个使用 Spring Security 和 OAuth2 库的 Java 简化示例来演示 OAuth 2.0 身份验证:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .oauth2Login()
                .userInfoEndpoint()
                    .userAuthoritiesMapper(userAuthoritiesMapper());
    }
 
    @Bean
    public GrantedAuthoritiesMapper userAuthoritiesMapper() {
        return new UserAuthoritiesMapper();
    }
}
 
@RestController
public class HomeController {
 
    @GetMapping(
"/")
    public String home() {
        return
"Welcome to the home page!";
    }
 
    @GetMapping(
"/user")
    public String user(@AuthenticationPrincipal OAuth2User principal) {
        return
"Welcome, " + principal.getAttribute("name") + "!";
    }
}

在此示例中,该类SecurityConfig将 Spring Security 配置为使用 OAuth 2.0 进行身份验证。该类HomeController包含两个端点 -/用于所有用户可访问的主页和/user仅可供经过身份验证的用户访问的特定于用户的页面。

这是基本设置,您可能需要根据要集成的 OAuth 提供商以及应用程序的要求对其进行自定义。此外,您通常会使用 Spring Boot 等库来进一步简化设置。

7.解释REST中DELETE方法的作用
REST 中的 DELETE 方法用于请求移除或删除指定 URI 处的资源。当客户端想要指示应从服务器删除资源时,此方法至关重要。
下面是一个使用 Spring MVC 的简单示例来演示 DELETE 方法的处理:

示例代码片段:

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.HashMap;
import java.util.Map;
 
@RestController
@RequestMapping("/api/books")
public class BookController {
 
    
// Sample in-memory database for illustration purposes
    private final Map<Long, Book> bookDatabase = new HashMap<>();
 
    @DeleteMapping(
"/{id}")
    public String deleteBook(@PathVariable Long id) {
        
// Logic to delete the book with the specified ID
        if (bookDatabase.containsKey(id)) {
            bookDatabase.remove(id);
            return
"Book with ID " + id + " deleted successfully.";
        } else {
            return
"Book with ID " + id + " not found.";
        }
    }
 
    
// Sample Book class
    static class Book {
        private final Long id;
        private final String title;
 
        public Book(Long id, String title) {
            this.id = id;
            this.title = title;
        }
 
        
// Getters for id and title
    }
}

在这个例子中:
  • 该类BookController定义了一个端点/api/books。
  • 该deleteBook方法处理删除具有特定 ID 的图书的 DELETE 请求。
  • 该@DeleteMapping("/{id}")注释指定,​​针对使用图书 ID 的动态路径变量向端点发出的 DELETE 请求,调用此方法。

当客户端向 发送 DELETE 请求时/api/books/{id},表示要删除指定 ID 的图书。然后,服务器处理该请求,从内存数据库(或真实数据库)中删除相应的书籍,并以成功消息或指示操作结果的适当状态进行响应。

需要注意的是,DELETE 方法是幂等的,这意味着多次发出相同的请求应该与发出一次具有相同的效果。两次删除资源不应产生额外的副作用。

8. REST 上下文中的内容协商是什么?
内容协商是 RESTful API 的一个重要方面,它允许客户端和服务器就资源的首选表示格式进行通信。协商过程涉及客户端在请求中表达其偏好,以及服务器根据这些偏好以最合适的表示进行响应。

下面是一个使用 Spring MVC 的示例代码片段,用于说明基于AcceptHTTP 请求中的标头的内容协商:
示例代码片段:

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api/resource")
public class ContentNegotiationController {
 
    @GetMapping(produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
    public Resource getResource() {
        
// Logic to retrieve resource data
        Resource resource = new Resource(1,
"Sample Resource");
        return resource;
    }
 
    
// Sample Resource class
    static class Resource {
        private final int id;
        private final String content;
 
        public Resource(int id, String content) {
            this.id = id;
            this.content = content;
        }
 
        
// Getters for id and content
    }
}

在这个例子中:
  • 该类ContentNegotiationController定义了一个端点/api/resource。
  • 该getResource方法处理资源的 GET 请求。
  • produces注释中的属性指定@GetMapping响应支持的媒体类型。在这种情况下,JSON 和 XML 均受支持。

当客户端向 发送请求时/api/resource,它可以包含一个Accept标头来表达其首选表示格式。例如:
  • Accept: application/json表明客户端更喜欢 JSON。
  • Accept: application/xml表明客户端更喜欢 XML。

然后,服务器使用标头中的信息Accept来确定最合适的表示格式并做出相应的响应。

此内容协商过程允许客户端和服务器有效地协同工作,确保通信采用双方都能理解和处理的格式。

9.解释REST中OPTIONS方法的作用
REST 中的 OPTIONS 方法通常用于查询目标资源的通信选项和功能。它帮助客户端了解服务器对特定资源支持哪些操作。

下面是一个使用 Spring MVC 的示例代码片段,演示了 RESTful API 的 OPTIONS 方法的处理:

示例代码片段:

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api/resource")
public class ResourceController {
 
    @CrossOrigin(origins =
"[url=http://allowed-origin.com/]http://allowed-origin.com[/url]") // Enable CORS for the example
    @RequestMapping(method = RequestMethod.OPTIONS)
    public void options() {
        
// Logic to handle OPTIONS request
        
// Provide information about supported methods, headers, etc.
        
// This method typically does not return a body in the response.
    }
 
    @RequestMapping(method = RequestMethod.GET)
    public String getResource() {
        
// Logic to handle GET request for the resource
        return
"This is the resource content.";
    }
}

在这个例子中:
  • 该类ResourceController定义了一个端点/api/resource。
  • 该options方法处理 OPTIONS 请求。此方法可以提供有关指定资源支持的方法、标头和其他通信选项的信息。
  • 该getResource方法处理资源的 GET 请求。

该@CrossOrigin注释用于为示例启用跨域资源共享 (CORS)。它允许来自指定源的请求向 OPTIONS 端点发出请求。

当客户端向 发送 OPTIONS 请求时/api/resource,服务器会使用有关该资源支持的方法、标头和其他通信选项的信息进行响应。

10. RESTful 身份验证如何工作?
RESTful 身份验证涉及通过验证客户端身份来保护 API。常见方法包括 API 密钥、OAuth 令牌或 JWT(JSON Web 令牌)。

示例:使用 JWT,客户端在请求标头中包含令牌。服务器验证令牌以确保客户端具有必要的权限。如果令牌有效,服务器将处理该请求。

示例代码片段(使用 Spring Security):

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
 
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
 
@Service
public class JwtUtil {
 
    private final String secret = "your-secret-key";
    private final long expirationTime = 86400000;
// 1 day in milliseconds
 
    
// Generate a JWT token
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }
 
    
// Create the token
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expirationTime))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
 
    
// Extract username from the token
    public String extractUsername(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
    }
 
    
// Validate the token
    public boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }
 
    
// Check if the token is expired
    private boolean isTokenExpired(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getExpiration().before(new Date());
    }
}

在这个例子中:
  • 该类JwtUtil是一项服务,提供从 JWT 令牌生成、验证和提取信息的方法。
  • 该generateToken方法为给定用户创建 JWT 令牌。
  • 该extractUsername方法从令牌中提取用户名。
  • 该validateToken方法通过比较用户名和过期时间来检查令牌是否有效。
  • 该isTokenExpired方法检查令牌是否已过期。

您通常会将此 JWT 实用程序类与 Spring Security 配置结合使用,以保护您的 RESTful API 端点。实际实现可能会根据您的具体要求和使用的 Spring Security 版本而有所不同。

11. REST 背景下的 HATEOAS 是什么?
HATEOAS(超媒体作为应用程序状态引擎)涉及在 API 响应中包含超媒体链接,允许客户端动态导航应用程序。

示例代码片段:

import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api/posts")
public class HATEOASController {
 
    @GetMapping(
"/{id}")
    public EntityModel<Post> getPostById() {
        
// Logic to retrieve a post by ID
        Post post = new Post(1L,
"Sample Post Content");
 
        
// Create a self-link for the resource
        Link selfLink = Link.of(
"/api/posts/" + post.getId());
 
        
// Wrap the Post object in an EntityModel with self-link
        EntityModel<Post> resource = EntityModel.of(post, selfLink);
 
        
// Add additional links for related resources or actions
        resource.add(Link.of(
"/api/posts/" + post.getId() + "/comments", "comments"));
 
        return resource;
    }
 
    
// Sample Post class
    static class Post {
        private final Long id;
        private final String content;
 
        public Post(Long id, String content) {
            this.id = id;
            this.content = content;
        }
 
        
// Getters for id and content
    }
}

在这个例子中:
  • 该类HATEOASController定义了一个getPostById通过 ID 检索帖子的方法。
  • 类Post代表帖子的结构。
  • 用于EntityModel<Post>包装Post对象并包含链接等超媒体控件。
  • /api/posts/{id}为资源创建自链接 ( ),并为相关资源或操作添加附加链接(例如评论)。

当客户端向端点发出请求时/api/posts/{id},响应将包括超媒体链接,允许客户端动态发现并导航到相关资源或操作。实际的链接和结构可能会根据您的 API 的要求而有所不同。

12.解释RESTful API方法中幂等性的概念。
无论执行多少次,幂等操作都会产生相同的结果。在 REST 上下文中,像 GET、PUT 和 DELETE 这样的幂等方法无论调用一次还是多次都应该具有相同的效果。

示例: DELETE 操作是幂等的,因为两次删除资源与删除一次的结果相同 - 资源被删除。

13. HTTP 标头中 ETag 的用途是什么?它与 RESTful API 中的缓存有何关系?
ETag(实体标签)是一个 HTTP 标头,为资源提供唯一标识符。它通常与缓存机制结合使用,以确定资源自上次请求以来是否已被修改。

示例:当客户端请求资源时,服务器将资源与 ETag 一起发送。在后续请求中,客户端可以在 If-None-Match 标头中包含 ETag。如果资源未更改(如 ETag 所示),服务器将响应 304 Not Modified 状态,并且客户端可以使用其缓存的副本。

示例代码片段:

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api/posts")
public class PostController {
 
    private String currentETag =
"123456"; // Example ETag, typically generated based on resource state
 
    @GetMapping(
"/{id}")
    public ResponseEntity<String> getPost(@RequestHeader(value =
"If-None-Match", required = false) String ifNoneMatch) {
        if (ifNoneMatch != null && ifNoneMatch.equals(currentETag)) {
            
// If the ETag matches, return 304 Not Modified
            return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
        }
 
        
// Logic to retrieve and return the post content
        String postContent =
"This is the content of the post.";
 
        
// Set the ETag in the response headers
        HttpHeaders headers = new HttpHeaders();
        headers.setETag(currentETag);
 
        return new ResponseEntity<>(postContent, headers, HttpStatus.OK);
    }
}

在这个例子中:
  • 该currentETag变量表示与资源关联的当前 ETag(可以根据内容或其他因素生成)。
  • 在该getPost方法中,If-None-Match检查标头。如果它与当前的 ETag 匹配,则返回 304 Not Modified 响应,表明客户端可以使用其缓存的副本。
  • 如果 ETag 不匹配或未提供,服务器将返回完整资源以及当前 ETag,客户端可以缓存它以供将来请求。

注意:为了简单起见,本示例使用 Spring 框架,实际实现可能会根据您使用的 Web 框架或库而有所不同。

14.什么是速率限制,它如何有助于 RESTful API 稳定性?
速率限制是一种用于控制客户端在特定时间范围内可以向服务器发出的请求数量的技术。它有助于防止滥用、防止分布式拒绝服务 (DDoS) 攻击并确保资源的公平使用。

示例:服务器可能将客户端限制为每分钟 100 个请求。如果客户端超过此限制,服务器会响应 429 Too Many Requests 状态,指示客户端应放慢其请求速度。

示例代码片段:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.concurrent.TimeUnit;
 
@RestController
public class RateLimitedController {
 
    @RequestMapping("/resource")
    @RateLimit(perSecond = 5)
    public String rateLimitedResource() {
        
// Logic for the rate-limited resource
        return
"This is a rate-limited resource.";
    }
}

在这个例子中:
  • 该RateLimit注释是一个自定义注释,您可以定义它来应用速率限制行为。
  • 注释的属性perSecond指定每秒允许的最大请求数。
  • 如果客户端超过指定的速率限制,服务器可以响应自定义错误,例如429 Too Many Requests。

请记住,RateLimit注释及其行为需要作为应用程序的一部分来实现,或者使用支持速率限制的第三方库来实现。上面的示例仅作为概念说明,实际实现可能会根据您使用的框架或库而有所不同。

15.什么是内容压缩,它如何提高 RESTful API 的性能?
内容压缩涉及使用压缩算法减少通过网络发送的数据的大小。这可以通过减少响应时间和带宽使用来显着提高 REST API 的性能。

示例:服务器可以在将响应数据发送到客户端之前使用 gzip 或 deflate 对其进行压缩。客户端收到压缩数据后,将其解压使用。此过程最大限度地减少了通过网络传输的数据量,从而加快了响应时间。

示例代码片段(使用 Gzip):

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
 
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPOutputStream;
 
@RestController
@RequestMapping("/api/data")
public class CompressedDataController {
 
    @GetMapping(value =
"/compressed", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<byte[]> getCompressedData() throws IOException {
        
// Simulated data to be sent
        String responseData =
"This is the response data for compression.";
 
        
// Compressing data using Gzip
        byte[] compressedData = compressData(responseData);
 
        
// Set Content-Encoding header to inform the client that data is compressed
        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.CONTENT_ENCODING,
"gzip");
 
        return ResponseEntity.ok()
            .headers(headers)
            .body(compressedData);
    }
 
    private byte[] compressData(String data) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {
            gzipOutputStream.write(data.getBytes());
        }
        return byteArrayOutputStream.toByteArray();
    }
}

在这个例子中:

  • 端点getCompressedData返回JSON数据,注释produces的属性GetMapping指示响应应该是JSON格式。
  • 该compressData方法使用Gzip来压缩数据。
  • 标Content-Encoding头设置为“gzip”以通知客户端响应已被压缩。

在现实场景中,您可能需要考虑使用 Web 服务器或专用压缩库提供的内置压缩功能。

16. 什么是 OAuth 2.0,它如何用于 RESTful API 中的身份验证?
OAuth 2.0 是一个授权框架,允许第三方应用程序在不暴露其凭据的情况下获得对用户资源的有限访问权限。它通常用于 RESTful API 中的身份验证。

示例代码片段(使用 Spring Security OAuth2):

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.builders.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.builders.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.builders.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.builders.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 
@Configuration
@EnableWebSecurity
public class OAuth2Config {
 
    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfig extends ResourceServerConfigurerAdapter {
        @Override
        public void configure(HttpSecurity http) throws Exception {
            // Configure security rules for resource server endpoints
            http
                .authorizeRequests()
                .antMatchers(
"/api/**").authenticated();
        }
 
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            
// Resource server configuration
            resources.resourceId(
"resource-server-rest-api");
        }
    }
 
    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            
// Configure OAuth clients (applications)
            clients.inMemory()
                .withClient(
"client-id")
                .secret(
"client-secret")
                .authorizedGrantTypes(
"authorization_code", "refresh_token", "password")
                .scopes(
"read", "write")
                .accessTokenValiditySeconds(3600)
                .refreshTokenValiditySeconds(86400);
        }
 
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            
// Configure endpoints and token store
            endpoints.tokenStore(new InMemoryTokenStore());
        }
    }
}

在这个例子中:
  • 该类ResourceServerConfig配置资源服务器的安全规则,指定需要/api身份验证的端点。
  • 该类AuthorizationServerConfig配置授权服务器,指定 OAuth 客户端(应用程序)和支持的授权类型。
  • 客户端可以通过遵循 OAuth 2.0 授权流程(例如授权码、密码或客户端凭据)来获取访问令牌。
  • ResourceServerSecurityConfigurer使用特定的资源 ID 配置资源服务器。

为简单起见,此示例使用内存中令牌存储。在生产环境中,您可能会使用持久令牌存储​​,例如数据库支持的令牌存储。

17. 解释 WebSocket 的概念及其在增强 RESTful API 方面的作用。
WebSockets 通过单个长期连接提供全双工通信通道,允许客户端和服务器之间进行实时双向通信。

示例代码片段(使用 Spring WebSockets):
下面是一个使用 Spring WebSockets 实现具有实时双向通信的简单聊天应用程序的示例。此示例包括 WebSocket 端点、消息处理以及向多个客户端的广播。

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
 
@Configuration
@EnableWebSocket
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
 
    @Override
    public void configureMessageBroker(org.springframework.messaging.simp.config.MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes(
"/app");
    }
 
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint(
"/ws-chat").withSockJS();
    }
}
 
@Controller
public class ChatController {
 
    @MessageMapping(
"/chat")
    @SendTo(
"/topic/messages")
    public Message sendMessage(Message message) {
        
// Simulate processing or validation of the message
        
// (e.g., persisting to a database, applying business rules)
 
        return new Message(message.getSender(), message.getContent());
    }
}
 
public class Message {
    private String sender;
    private String content;
 
    
// Constructors, getters, and setters
}

在这个例子中:
  • 该类WebSocketConfig配置 WebSocket 消息代理。
  • 该类ChatController处理 WebSocket 消息。当客户端向 发送消息时/app/chat,消息将被路由到该sendMessage方法,并且响应将广播到订阅的所有客户端/topic/messages。
  • 该类Message表示聊天消息的结构。

此示例使用 Spring 框架以及基于 WebSocket 的 STOMP 消息传递协议。它演示了 WebSocket 的双向通信功能,允许多个客户端发送和接收实时消息。

要运行此示例,您可能需要在项目中包含必要的依赖项,例如 Spring Boot、Spring WebSockets 和 WebSocket 客户端库。

18. 什么是 GraphQL,它与传统的 RESTful API 有何不同?
GraphQL 是一种 API 查询语言,允许客户端仅请求他们需要的数据。它为传统 RESTful API 提供了更灵活、更高效的替代方案。
示例代码片段(使用 GraphQL Java):

import graphql.GraphQL;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.DataFetcher;
import graphql.schema.StaticDataFetcher;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.GraphQLException;
 
public class GraphQLExample {
 
    public static void main(String[] args) {
        // GraphQL schema definition in SDL (Schema Definition Language)
        String schemaDefinition =
"type Query { hello: String }";
 
        
// Parse the schema definition
        SchemaParser schemaParser = new SchemaParser();
        SchemaGenerator schemaGenerator = new SchemaGenerator();
        GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(schemaParser.parse(schemaDefinition), buildWiring());
 
        
// Create a GraphQL instance
        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
 
        
// Query execution
        String query =
"{ hello }";
        System.out.println(graphQL.execute(query).toSpecification());
    }
 
    private static RuntimeWiring buildWiring() {
        
// Define the data fetcher for the 'hello' field
        DataFetcher<String> helloDataFetcher = new StaticDataFetcher<>(
"Hello, GraphQL!");
 
        
// Create runtime wiring
        return RuntimeWiring.newRuntimeWiring()
                .type(
"Query", typeWriting -> typeWriting
                        .dataFetcher(
"hello", helloDataFetcher))
                .build();
    }
}

在这个例子中:
  • GraphQL 架构是使用字符串中的架构定义语言 (SDL) 进行定义的schemaDefinition。
  • 和SchemaParser类SchemaGenerator用于解析和生成可执行模式。
  • 该buildWiring方法为“hello”字段设置一个基本数据获取器,提供静态响应。
  • GraphQL创建实例,并执行一个简单的查询 ( ) "{ hello }"。

这是一个最小的示例,展示了使用 GraphQL Java 库实现 GraphQL 的基本结构

19. 解释 RESTful API 中版本控制的概念并提供使用基于 URL 的方法的示例
API 版本控制是提供 API 的多个版本的做法,以便在不破坏现有客户端的情况下进行更改。

示例代码片段:
在此示例中,我将在 Java 中使用 Spring MVC 演示一种简单的基于 URL 的版本控制方法:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api")
public class VersionedController {
 
    
// Version 1 of the API
    @GetMapping(value =
"/v1/posts")
    public String getPostsV1() {
        return
"Version 1: List of Posts";
    }
 
    
// Version 2 of the API
    @GetMapping(value =
"/v2/posts")
    public String getPostsV2() {
        return
"Version 2: List of Posts";
    }
}

在这个例子中:
  • 该类VersionedController有两个方法getPostsV1和getPostsV2,代表不同版本的 API。
  • 该注释指定每个版本(版本 1 和版本 2)@GetMapping的端点。/v1/posts/v2/posts

客户端可以通过将版本号附加到基本 URL(例如/api/v1/posts或 )来访问所需版本的 API /api/v2/posts。这种基于 URL 的版本控制方法可以明确区分 API 版本。

还有其他版本控制方法,例如基于标头的版本控制、媒体类型版本控制或使用查询参数。版本控制策略的选择取决于 API 的具体要求和约定。

20.什么是 RESTful 超媒体控件,它们如何促进 API 的可发现性和灵活性?
RESTful 超媒体控制涉及在 API 响应中包含链接和操作,从而允许客户端动态发现可用资源并与之交互。
示例代码片段(使用 Spring HATEOAS):

import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api/posts")
public class HypermediaController {
 
    @GetMapping(
"/{id}")
    public EntityModel<Post> getPostById() {
        
// Logic to retrieve a post by ID
        Post post = new Post(1L,
"Sample Post Content");
 
        
// Create a self-link for the resource
        Link selfLink = Link.of(
"/api/posts/" + post.getId());
 
        
// Wrap the Post object in an EntityModel with self-link
        EntityModel<Post> resource = EntityModel.of(post, selfLink);
 
        
// Add additional links for related resources or actions
        resource.add(Link.of(
"/api/posts/" + post.getId() + "/comments", "comments"));
 
        return resource;
    }
 
    
// Sample Post class
    static class Post extends RepresentationModel<Post> {
        private final Long id;
        private final String content;
 
        public Post(Long id, String content) {
            this.id = id;
            this.content = content;
        }
 
        
// Getters for id and content
    }
}

在这个例子中:
  • 该类HypermediaController定义了一个getPostById通过 ID 检索帖子的方法。
  • 该类Post是一个包含 HATEOAS 支持 ( RepresentationModel) 的表示模型。
  • 用于EntityModel<Post>包装Post对象并包含链接等超媒体控件。
  • 表示selfLink到资源本身的链接 ( /api/posts/{id})。
  • 添加了相关资源或操作(例如评论)的附加链接。

当客户端向端点发出请求时/api/posts/{id},响应将包括超媒体控件,允许客户端动态发现并导航到相关资源或操作。实际的链接和结构可能会根据您的 API 的要求而有所不同。

这些 Java 示例涵盖了 RESTful API 开发中的一系列基本和高级概念。在现实场景中,需要额外考虑,例如错误处理、安全性和优化。

三、结论
在对 20 个基本 REST API 问题的探索中,我们涵盖了从基础知识到高级主题的所有内容。我们深入研究了 HTTP 方法等基本概念、OAuth 2.0 等身份验证方法以及内容协商的重要性。我们看到了 Spring 框架,尤其是 Spring MVC 和 Spring Security,如何成为构建强大的 RESTful API 的强大盟友。

理解 REST API 不仅仅是一项技术技能;更是一项技能。它是在网络不同部分之间建立无缝、高效和安全通信的网关。无论您是刚刚开始还是想加深知识,这些问题和示例都应该成为有价值的见解。因此,继续在您的项目中实现这些,并使您的应用程序能够巧妙地进行对话和交互。快乐编码!