【问题标题】:Can Spring Boot annotations @RequestBody and @RequestParam be used equivalently to pass parameters to the backend?Spring Boot 注解@RequestBody 和@RequestParam 是否可以等效地用于向后端传递参数?
【发布时间】:2021-09-16 23:48:34
【问题描述】:

通过一个简单的 Spring Boot 应用程序,我试图了解 Spring Boot 注释 @RequestParam@RequestBody 之间的区别以及它们各自的用途。 Spring 文档将 @RequestParam 定义为“表示方法参数应绑定到 Web 请求参数的注解”,将 @RequestBody 定义为“表示方法参数应绑定到 Web 请求正文的注解”。 因此,可以公平地假设注释和相关方法都可以用作等效的替代方法(类似结果的不同方法)以将参数传递给后端应用程序,例如插入值“电子邮件”、“用户名”和“密码”在用户数据库中(只是为了简单而忘记密码的必要加密)。

因此,可以预期下面的代码被剪断了

@PostMapping
public User save(@RequestBody User user) {
    return userService.createUser(user);
}

相当于

@PostMapping(value = "/users")
public ResponseEntity<User> createUser(
        @RequestParam  String email,
        @RequestParam String username,
        @RequestParam  String password

) {
    try {

        User user = new User();
        user.setEmail(email);
        user.setUsername(username);
        user.setPassword(password);

        userService.createUser(user);
        return ResponseEntity.noContent().build();

    } catch (Exception exception) {
        return new ResponseEntity<>(HttpStatus.I_AM_A_TEAPOT);
    }
}

使用 Postman 尝试使用 @RequestBody 替代方案会得到 200 OK 响应:

但尝试使用 @RequestParam 替代方法会导致 400“错误请求”错误:

为什么这种@RequestParam 方法会触发错误,而显然等效的方法@RequestBody 会按预期工作?

为完整起见,用户模型如下:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String email;
    private String username;
    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

仓库如下:

public interface UserRepository extends JpaRepository<User, Long> {}

用户服务界面如下:

public interface UserService {
    List<User> getAllUsers();
    User getUserById(Long id);
    User createUser(User user);
    void deleteUser(Long id);
}

用户服务如下:

@Service
public class UserServiceImpl implements UserService {
    private UserRepository userRepository;

    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }

    @Override
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    @Override
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

【问题讨论】:

  • 嗨迈克尔,很抱歉,现在应该清楚了
  • 您的编译器是否配置为在类文件中包含参数名称? javac 默认是丢弃它们。包含它们的参数是-parameters。如果没有该标志,Spring 无法将每个参数映射到传入的查询参数名称。如果您不确定,可以将名称显式传递给每个@RequestParam(name="email")
  • 顺便说一句,邮递员中查询参数值周围的引号是多余的,我认为您最终会将引号作为字符串的一部分进入数据库。不应该让请求失败
  • 您在 Postman 的设置选项卡上做了哪些更改?那里有一个绿点。您的日志中还应该有更详细的错误。
  • 感谢这些好建议。但是,明确传递名称,例如 @RequestParam(name = "email") String email 并不能解决问题。请注意,错误现在是 405 'Method not allowed'

标签: java spring-boot


【解决方案1】:

这两种方法在功能上是等效的,如果不是在预期用途上。

您不应该在参数值周围使用引号。如果您删除",那么它应该可以工作,至少它可以在我将您的代码复制到最新版本的 Postman 的新 Spring Boot 项目中。

Postman 在“设置”选项卡上还有一个选项可以自动对 url 进行编码。如果您启用该选项,那么引号将被编码并成为您的参数值的一部分。它默认启用,但在您的屏幕截图上有一个绿点,表示您更改了某些内容。我的猜测是您设置的某个设置导致了错误的请求(可能禁用了 url 的编码)。

当我禁用编码时,我在日志中收到此错误:

java.lang.IllegalArgumentException:
Invalid character found in the request target 
[/users?email=bla&username=hello&password="bla" ]. 
The valid characters are defined in RFC 7230 and RFC 3986

【讨论】:

  • 非常感谢 Vlad L 测试了代码并提出了这些建议。在 postman 中,现在启用了 url 编码,删除了参数周围的引号,现在没有绿点,但仍然触发了 405 错误。但是,您的测试和建议应该使我们接近解决方案...
【解决方案2】:

首先,感谢您的支持和您花在测试代码上的时间! @requestParam 和 @requestBody 确实可以用作将值传递到后端的两种替代方法,如前面的答案中所述(谢谢!)。仍然需要解释为什么使用我的问题中提供的代码不是这种情况,因为按照指示在邮递员中修改请求并没有帮助,并且使用 cUrl 发布请求会导致完全相同的 405 错误。所以必须在代码中找到解决方案。花了一段时间才找到它,但我的控制器类顶部有一个 @RequestMapping(value="/users") 注释,必须禁用它。请注意,显然,@RequestMapping 的错误使用只会干扰@RequestParam,但不会干扰@RequestBody。 下面的代码现在可以工作了(这里省略了@GetMapping 和@deleteMapping),并给出了与问题中给出的@RequestBody 完全相同的结果。

@RestController
//@RequestMapping(value="/users")
@CrossOrigin
public class UserController {

    @Autowired
    private UserServiceImpl userService;

    @PostMapping(value = "/users")
    public ResponseEntity<Object> createUser(
            @RequestParam(name = "email") String email,
            @RequestParam(name = "username") String username,
            @RequestParam(name = "password") String password
   ) {
        try {
            User user = new User();
            user.setEmail(email);
            user.setUsername(username);
            user.setPassword(password);
            userService.createUser(user);
            return ResponseEntity.noContent().build();
        } catch (Exception exception) {
            return new ResponseEntity<>(HttpStatus.I_AM_A_TEAPOT);
        }
    }

【讨论】:

    【解决方案3】:

    您在 url 中为 @RequestParam 传递请求参数值的方式不正确。对于多个参数,您可以传递如下值:

    http://<url>:<port>/user/101?param1=10&param2=20
    

    并参考以下答案:

    How to put RequestParam value as URL

    Pass multiple parameters to rest API - Spring

    【讨论】:

    • 不正确怎么办?对我来说看起来不错,除了值周围的多余引号,但那些不应该使请求失​​败
    • Postman中传递参数的方式是localhost:8080/…" 所以应该是正确的吧?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-21
    • 2022-06-30
    • 1970-01-01
    • 1970-01-01
    • 2018-07-11
    • 2020-06-25
    相关资源
    最近更新 更多