简体   繁体   English

Spring Boot 不会将表单发送的数据绑定到 POST 端点

[英]Spring boot doesn't bind the data sent by form to POST endpoint

Description of the problem问题描述

Spring boot cannot find the data sent in request body. Spring boot 找不到请求正文中发送的数据。

As specified below, in code extracts, I send form with application/x-www-form-urlencoded content-type to the endpoint POST /cards .如下所述,在代码摘录中,我将带有application/x-www-form-urlencoded内容类型application/x-www-form-urlencoded发送到端点POST /cards The good method is called by Spring boot but data from the request body aren't loaded in card entity, which is passed as parameter (see console output below).好的方法由 Spring boot 调用,但请求正文中的数据未加载到卡片实体中,该实体作为参数传递(请参阅下面的控制台输出)。

Versions:版本:

  1. Spring boot: 2.3.4.RELEASE弹簧靴:2.3.4.RELEASE
  2. spring-boot-starter-freemarker: 2.3.4.RELEASE spring-boot-starter-freemarker: 2.3.4.RELEASE

Console output (with request body read manually in request filter):控制台输出(在请求过滤器中手动读取请求正文):

2020-10-21 00:26:58.594 DEBUG 38768 --- [nio-8080-exec-1] c.b.c.c.f.RequestResponseLoggingFilter   : New request method=POST path=/cards content-type=application/x-www-form-urlencoded
2020-10-21 00:26:58.595 DEBUG 38768 --- [nio-8080-exec-1] c.b.c.c.f.RequestResponseLoggingFilter   : RequestBody: title=First+card&seoCode=first-card&description=This+is+the+first+card+of+the+blog&content=I+think+I+need+help+about+this+one...
    
### createNewCard ###
    
card: Card<com.brunierterry.cards.models.Card@34e63b41>{id=null, seoCode='null', publishedDate=null, title='null', description='null', content='null'}
result: org.springframework.validation.BeanPropertyBindingResult: 0 errors
model: {card=Card<com.brunierterry.cards.models.Card@34e63b41>{id=null, seoCode='null', publishedDate=null, title='null', description='null', content='null'}, org.springframework.validation.BindingResult.card=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
2020-10-21 00:26:58.790 TRACE 38768 --- [nio-8080-exec-1] c.b.c.c.f.RequestResponseLoggingFilter   : Response to request method=POST path=/cards status=200 elapsedTime=196ms

(Here I read body with req.getReader() , but I comment it usually to not consume the buffer.) (在这里,我使用req.getReader()读取 body,但我通常对其进行注释以不消耗缓冲区。)

Controller控制器

@Controller
public class CardController implements ControllerHelper {


    @PostMapping(value = "/cards", consumes = MediaType.ALL_VALUE)
    public String createNewCard(
            @ModelAttribute Card card,
            BindingResult result,
            ModelMap model
    ) {
        System.out.println("\n### createNewCard ###\n");
        System.out.println("card: "+card);
        System.out.println("result: "+result);
        System.out.println("model: "+model);

        return "/cards/editor";
    }

    @GetMapping(value = "/cards/form")
    public String newPost(
            Model model
    ) {
        model.addAttribute("card", Card.defaultEmptyCard);
        return "/cards/editor";
    }
}

HTML form (wrote with freemarker template): HTML 表单(使用 freemarker 模板编写):

 <form action="/cards"
          method="POST"
          modelAttribute="card"
          enctype="application/x-www-form-urlencoded"
    >
        <div class="form-group">
            <label for="title">Title & SEO slug code</label>
            <div class="form-row">
                <div class="col-9">
                    <@spring.formInput
                    "card.title"
                    "class='form-control' placeholder='Title'"
                    />
                    <@spring.showErrors "<br>"/>
                </div>
                <div class="col-2">
                    <@spring.formInput
                    "card.seoCode"
                    "class='form-control' placeholder='SEO slug code' aria-describedby='seoCodeHelp'"
                    />
                    <@spring.showErrors "<br>"/>
                </div>
                <div class="col-1">
                    <@spring.formInput
                    "card.id"
                    "DISABLED class='form-control' placeholder='ID'"
                    />
                </div>
            </div>
            <div class="form-row">
                <small id="seoCodeHelp" class="form-text text-muted">
                    Keep SEO slug very small and remove useless words.
                </small>
            </div>
        </div>
        <div class="form-group">
            <label for="description">Description</label>
            <@spring.formInput
            "card.description"
            "class='form-control' placeholder='Short description of this card..' aria-describedby='descriptionHelp'"
            />
                <small id="descriptionHelp" class="form-text text-muted">
                    Keep this description as small as possible.
                </small>
            </div>
        <div class="form-group">
            <label for="content">Content</label>
            <@spring.formTextarea
            "card.content"
            "class='form-control' rows='5'"
            />
        </div>
        <button type="submit" class="btn btn-primary">Save</button>
    </form>

Card entity卡实体

@Entity
public class Card implements Comparable<Card> {

    protected Card() {}

    public static final Card defaultEmptyCard = new Card();

    private final static Logger logger = LoggerFactory.getLogger(Card.class);

    @Autowired
    private ObjectMapper objectMapper;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    @NotBlank(message = "Value for seoCode (the slug) is mandatory")
    @Column(unique=true)
    private String seoCode;

    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate publishedDate;

    @NotBlank(message = "Value for title is mandatory")
    private String title;

    @NotBlank(message = "Value for description is mandatory")
    private String description;

    @NotBlank(message = "Value for content is mandatory")
    private String content;

    public boolean hasIdUndefine() {
        return null == id;
    }
    public boolean hasIdDefined() {
        return null != id;
    }

    public Long getId() {
        return id;
    }

    public String getSeoCode() {
        return seoCode;
    }

    public LocalDate getPublishedDate() {
        return publishedDate;
    }

    public String getTitle() {
        return title;
    }

    public String getDescription() {
        return description;
    }
    public String getContent() {
        return content;
    }

    private String formatSeoCode(String candidateSeoCode) {
        return candidateSeoCode.replaceAll("[^0-9a-zA-Z_-]","");
    }

    private Card(
            @NonNull String rawSeoCode,
            @NonNull String title,
            @NonNull String description,
            @NonNull String content,
            @NonNull LocalDate publishedDate
    ) {
        this.seoCode = formatSeoCode(rawSeoCode);
        this.title = title;
        this.description = description;
        this.content = content;
        this.publishedDate = publishedDate;
    }

    public static Card createCard(
            @NonNull String seoCode,
            @NonNull String title,
            @NonNull String description,
            @NonNull String content,
            @NonNull LocalDate publishedDate
    ) {
        return new Card(
                seoCode,
                title,
                description,
                content,
                publishedDate
        );
    }

    public static Card createCard(
            @NonNull String seoCode,
            @NonNull String title,
            @NonNull String description,
            @NonNull String content
    ) {
        LocalDate publishedDate = LocalDate.now();
        return new Card(
                seoCode,
                title,
                description,
                content,
                publishedDate
        );
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Card card = (Card) o;
        return Objects.equals(id, card.id) &&
                seoCode.equals(card.seoCode) &&
                publishedDate.equals(card.publishedDate) &&
                title.equals(card.title) &&
                description.equals(card.description) &&
                content.equals(card.content);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, seoCode, publishedDate, title, description, content);
    }

    @Override
    public String toString() {
        return "Card<"+ super.toString() +">{" +
                "id=" + id +
                ", seoCode='" + seoCode + '\'' +
                ", publishedDate=" + publishedDate +
                ", title='" + title + '\'' +
                ", description='" + description + '\'' +
                ", content='" + content + '\'' +
                '}';
    }

    public Either<JsonProcessingException,String> safeJsonSerialize(
            ObjectMapper objectMapper
    ) {
        try {
            return Right(objectMapper.writeValueAsString(this));
        } catch (JsonProcessingException e) {
            logger.error(e.getMessage());
            return Left(e);
        }
    }

    public Either<JsonProcessingException,String> safeJsonSerialize() {
        try {
            return Right(objectMapper.writeValueAsString(this));
        } catch (JsonProcessingException e) {
            logger.error(e.getMessage());
            return Left(e);
        }
    }

    @Override
    public int compareTo(@NotNull Card o) {
        int publicationOrder  = this.publishedDate.compareTo(o.publishedDate);
        int defaultOrder  = this.seoCode.compareTo(o.seoCode);
        return publicationOrder == 0 ? defaultOrder : publicationOrder;
    }
}

Edit编辑

I got a good answer.我得到了一个很好的答案。 It works when adding empty constructor and setters to the Card entity.它在向 Card 实体添加空构造函数和 setter 时起作用。 However, it's not the class I want.然而,这不是我想要的课程。 I want card to be only instantiated with a constructor that have all parameters.我希望卡仅使用具有所有参数的构造函数进行实例化。 Do you have an idea about how to achieve that ?您知道如何实现这一目标吗? Should I create another class to represent the form ?我应该创建另一个类来表示表单吗? Oris there a way to only allow Spring to use such setters ?或者有没有办法只允许 Spring 使用这样的 setter?

Did you make sure that you Card.java has the appropriate getters and setters?你确定你的Card.java有合适的 getter 和 setter 吗? This way spring can actually populate the data in the object it is trying to create.通过这种方式,spring 实际上可以填充它试图创建的对象中的数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM