繁体   English   中英

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

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

问题描述

Spring boot 找不到请求正文中发送的数据。

如下所述,在代码摘录中,我将带有application/x-www-form-urlencoded内容类型application/x-www-form-urlencoded发送到端点POST /cards 好的方法由 Spring boot 调用,但请求正文中的数据未加载到卡片实体中,该实体作为参数传递(请参阅下面的控制台输出)。

版本:

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

控制台输出(在请求过滤器中手动读取请求正文):

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

(在这里,我使用req.getReader()读取 body,但我通常对其进行注释以不消耗缓冲区。)

控制器

@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 表单(使用 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>

卡实体

@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;
    }
}

编辑

我得到了一个很好的答案。 它在向 Card 实体添加空构造函数和 setter 时起作用。 然而,这不是我想要的课程。 我希望卡仅使用具有所有参数的构造函数进行实例化。 您知道如何实现这一目标吗? 我应该创建另一个类来表示表单吗? 或者有没有办法只允许 Spring 使用这样的 setter?

你确定你的Card.java有合适的 getter 和 setter 吗? 通过这种方式,spring 实际上可以填充它试图创建的对象中的数据。

暂无
暂无

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

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