2015-05-12 4 views
6

Hai avuto un problema complicato con un'applicazione Spring Boot che ho cercato di risolvere da un po 'di tempo e spero che qualcuno possa aiutarmi. Ho rimosso tutte le altre parti del progetto e ho cercato di renderlo il più semplice possibile. Se vai a localhost: 8080, ci sarà un modulo con due caselle di testo per inserire due nomi e un pulsante Invia. Il primo nome verrà memorizzato nell'oggetto Nominee, il secondo nell'oggetto Submitter. Quando fai clic su Invia, eseguirà la convalida sui campi per assicurarsi che nessuno di essi sia vuoto. Pubblicherò il codice qui sotto e spiegherò il mio problema alla fine.Convalida del modulo di primavera con due oggetti

Application.java

package nomination; 
 

 
import org.springframework.boot.SpringApplication; 
 
import org.springframework.boot.autoconfigure.SpringBootApplication; 
 
import org.springframework.context.annotation.Bean; 
 
import org.springframework.jms.annotation.EnableJms; 
 
import org.springframework.jms.config.JmsListenerContainerFactory; 
 
import org.springframework.jms.config.SimpleJmsListenerContainerFactory; 
 
import org.springframework.web.servlet.config.annotation.EnableWebMvc; 
 

 
@SpringBootApplication 
 
@EnableJms 
 
@EnableWebMvc 
 
public class Application { 
 

 
    public static void main(String[] args) throws Exception { 
 
     // Launch the application 
 
     SpringApplication.run(Application.class, args); 
 
    } 
 

 
}

WebController.java

package nomination; 
 

 
import javax.validation.Valid; 
 
import org.slf4j.Logger; 
 
import org.slf4j.LoggerFactory; 
 
import org.springframework.ui.Model; 
 
import org.springframework.web.bind.WebDataBinder; 
 
import org.springframework.web.bind.annotation.InitBinder; 
 
import org.springframework.web.bind.annotation.ModelAttribute; 
 
import org.springframework.stereotype.Controller; 
 
import org.springframework.validation.BindingResult; 
 
import org.springframework.web.bind.annotation.RequestMapping; 
 
import org.springframework.web.bind.annotation.RequestMethod; 
 
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 
 
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 
 

 
@Controller 
 
public class WebController extends WebMvcConfigurerAdapter { 
 
    protected static final Logger LOG = LoggerFactory.getLogger(WebController.class); 
 

 
    @InitBinder("nominee") 
 
    protected void initNomineeBinder(WebDataBinder binder) { 
 
     binder.setValidator(new NomineeValidator()); 
 
    } 
 

 
    @InitBinder("submitter") 
 
    protected void initSubmitterBinder(WebDataBinder binder) { 
 
     binder.setValidator(new SubmitterValidator()); 
 
    } 
 

 
    @Override 
 
    public void addViewControllers(ViewControllerRegistry registry) { 
 
     registry.addViewController("/success").setViewName("success"); 
 
    } 
 

 
    @RequestMapping(value="/", method=RequestMethod.GET) 
 
    public String showForm(Model model) { 
 
     model.addAttribute("nominee", new Nominee()); 
 
     model.addAttribute("submitter", new Submitter()); 
 
     return "form"; 
 
    } 
 

 
    @RequestMapping(value="/", method=RequestMethod.POST) 
 
    public String checkPersonInfo(@ModelAttribute(value="nominee") @Valid Nominee nominee, 
 
            @ModelAttribute(value="submitter") @Valid Submitter submitter, 
 
            BindingResult bindingResult, @Valid Model model) { 
 
     LOG.info("Nominee to string: " + nominee.toString()); 
 
     LOG.info("Submitter to string: " + submitter.toString()); 
 
     LOG.info("bindingResult to string: " + bindingResult.toString()); 
 
     if (bindingResult.hasErrors()) { 
 
      return "form"; 
 
     } 
 

 
     return "redirect:/success"; 
 
    } 
 

 
}

Nominee.java

package nomination; 
 

 
import lombok.Data; 
 

 
@Data 
 
public class Nominee { 
 
    private String name; 
 
}

NomineeValidatior.java

package nomination; 
 

 
import org.springframework.validation.Errors; 
 
import org.springframework.validation.ValidationUtils; 
 
import org.springframework.validation.Validator; 
 

 
public class NomineeValidator implements Validator { 
 

 
    public boolean supports(Class clazz) { 
 
     return Nominee.class.equals(clazz); 
 
    } 
 

 
    public void validate(Object object, Errors errors) { 
 
     ValidationUtils.rejectIfEmpty(errors, "name", "name", "This field is empty."); 
 
    } 
 
}

Submitter.java

package nomination; 
 

 
import lombok.Data; 
 

 
@Data 
 
public class Submitter { 
 
    private String sname; 
 
}

SubmitterValidator.java

package nomination; 
 

 
import org.springframework.validation.Errors; 
 
import org.springframework.validation.ValidationUtils; 
 
import org.springframework.validation.Validator; 
 

 
public class SubmitterValidator implements Validator { 
 

 
    public boolean supports(Class clazz) { 
 
     return Submitter.class.equals(clazz); 
 
    } 
 

 
    public void validate(Object object, Errors errors) { 
 
     ValidationUtils.rejectIfEmpty(errors, "sname", "sname", "This field is empty."); 
 
    } 
 
}

form.html

<html><body> 
 
    <form role="form" th:action="@{/}" method="post"> 
 
     <h2>Nominee details</h2> 
 
     <table> 
 
      <tr> 
 
       <td>Name:</td> 
 
       <td> 
 
        <input type="text" th:field="${nominee.name}" /> 
 
       </td> 
 
       <td><p th:if="${#fields.hasErrors('nominee.name')}" th:errors="${nominee.name}">Empty field</p></td> 
 
      </tr> 
 
     </table> 
 
     <h2>Your details</h2> 
 
     <table> 
 
      <tr> 
 
       <td>Your name:</td> 
 
       <td> 
 
        <input type="text" th:field="${submitter.sname}" /> 
 
       </td> 
 
       <td><p th:if="${#fields.hasErrors('submitter.sname')}" th:errors="${submitter.sname}">Empty field</p></td> 
 
      </tr> 
 
     </table> 
 
     <div> 
 
      <button type="submit">Submit nomination</button> 
 
     </div> 
 
    </form> 
 
</body></html>

successo.html

<html><body>Form successfully submitted.</body></html>

Se lascio il primo campo di testo vuoto (e riempire o non compilare il secondo campo di testo), viene visualizzato un messaggio di errore sullo schermo che dice:

Whitelabel errore Pagina

questa applicazione non ha alcuna mappatura esplicita per/errore, in modo che state vedendo questo come ripiego. Mar 12 maggio 13:10:17 AEST 2015 Si è verificato un errore imprevisto (tipo = Richiesta non valida, stato = 400). Convalida non riuscita per oggetto = 'nominee'. Conteggio errori: 1

Non so come risolvere il problema in modo che lasciare la prima casella di testo vuota non causi una pagina di errore whitelabel. Se lascio vuoto il secondo campo di testo, ma compilo il primo, si comporta in modo perfettamente normale, quindi non sono sicuro del perché causi un errore se provo il contrario. Qualsiasi aiuto per risolverlo sarebbe molto apprezzato.

Inoltre, potresti aver notato che dovevo usare "nome" e "sname" come variabili in Nominee e Submitter, se li impostassi entrambi su "nome", allora non funzionerebbe correttamente. Se c'è un modo per modificarlo in modo che entrambi possano usare "nome", mi piacerebbe sapere come.

Modifica: ha trovato una soluzione. Nel WebController, checkPersonInfo richiede un BindingResult separato per ogni oggetto da convalidare. BindingResult deve essere presente nei parametri del metodo immediatamente dopo ogni oggetto @Valid.

Così, in WebController.java, questo:

@RequestMapping(value="/", method=RequestMethod.POST) 
public String checkPersonInfo(@ModelAttribute(value="nominee") @Valid Nominee nominee, 
           @ModelAttribute(value="submitter") @Valid Submitter submitter, 
           BindingResult bindingResult, @Valid Model model) { 
    LOG.info("Nominee to string: " + nominee.toString()); 
    LOG.info("Submitter to string: " + submitter.toString()); 
    LOG.info("bindingResult to string: " + bindingResult.toString()); 
    if (bindingResult.hasErrors()) { 
     return "form"; 
    } 

    return "redirect:/success"; 
} 

deve diventare questo:

@RequestMapping(value="/", method=RequestMethod.POST) 
public String checkPersonInfo(@ModelAttribute(value="nominee") @Valid Nominee nominee, 
           BindingResult bindingResultNominee, 
           @ModelAttribute(value="submitter") @Valid Submitter submitter, 
           BindingResult bindingResultSubmitter) { 
    LOG.info("Nominee to string: " + nominee.toString()); 
    LOG.info("Submitter to string: " + submitter.toString()); 
    if (bindingResultNominee.hasErrors() || bindingResultSubmitter.hasErrors()) { 
     return "form"; 
    } 

    return "redirect:/success"; 
} 

(L'oggetto modello è stato rimosso in quanto non è mai effettivamente utilizzato da nessuna parte, se si ha bisogno di convalidare con @Valid, quindi aggiungere un terzo oggetto BindingResult.)

+0

Provare a eseguire il log di 'org.springframework.web' in DEBUG per vedere se la console dice qualcosa di interessante. – chrylis

+0

Solo curioso di sapere. Perché stai andando per la convalida lato server invece della convalida lato client (basata su javascript) qui – Thomas

+0

Nessun vero motivo. Usando Spring Boot per la prima volta, semplicemente giocando con esso per insegnare a me stesso come funziona, ho visto che aveva una propria implementazione della validazione del modulo e voleva provare a farlo funzionare. – Jordan

risposta

7

Trovato una soluzione. Nel WebController, checkPersonInfo richiede un BindingResult separato per ogni oggetto da convalidare. BindingResult deve essere presente nei parametri del metodo immediatamente dopo ogni oggetto @Valid.

Quindi, in WebController.java, questo:

@RequestMapping(value="/", method=RequestMethod.POST) 
public String checkPersonInfo(@ModelAttribute(value="nominee") @Valid Nominee nominee, 
           @ModelAttribute(value="submitter") @Valid Submitter submitter, 
           BindingResult bindingResult, @Valid Model model) { 
    LOG.info("Nominee to string: " + nominee.toString()); 
    LOG.info("Submitter to string: " + submitter.toString()); 
    LOG.info("bindingResult to string: " + bindingResult.toString()); 
    if (bindingResult.hasErrors()) { 
     return "form"; 
    } 

    return "redirect:/success"; 
} 

deve diventare questo:

@RequestMapping(value="/", method=RequestMethod.POST) 
public String checkPersonInfo(@ModelAttribute(value="nominee") @Valid Nominee nominee, 
           BindingResult bindingResultNominee, 
           @ModelAttribute(value="submitter") @Valid Submitter submitter, 
           BindingResult bindingResultSubmitter) { 
    LOG.info("Nominee to string: " + nominee.toString()); 
    LOG.info("Submitter to string: " + submitter.toString()); 
    if (bindingResultNominee.hasErrors() || bindingResultSubmitter.hasErrors()) { 
     return "form"; 
    } 

    return "redirect:/success"; 
} 

(L'oggetto modello è stato rimosso in quanto non è mai effettivamente utilizzato da nessuna parte, se avete bisogno di convalidare con @Valid allora si sarebbe aggiunto un terzo oggetto BindingResult.)

2

Il caso normale è utilizzare gli oggetti Dto per quel caso. Ciò significa che crei un oggetto contenente tutti i campi modulo pertinenti e convalidi in base a ciò.

Ad esempio:

@Data 
public class MyDto { 

    private Nominee nominee; 

    private Submitter submitter; 
} 

@RequestMapping(value="/", method=RequestMethod.POST) 
public String checkPersonInfo(@Valid MyDto dto, BindingResult bindingResult) { 
    LOG.info("Nominee to string: " + dto.getNominee().toString()); 
    LOG.info("Submitter to string: " + dto.getSubmitter().toString()); 
    LOG.info("bindingResult to string: " + bindingResult.toString()); 
    if (bindingResult.hasErrors()) { 
     return "form"; 
    } 
    return "redirect:/success"; 
} 

Inoltre è possibile aggiungere qualcosa di simile al controller.

@ExceptionHandler(Throwable.class) 
public ModelAndView processError(Throwable ex) { 
    ex.toString(); 
    // do something and return a ModelAndView object as you like. 
}