To Spring Web MVC framework παρέχει το μοντέλο αρχιτεκτονικής Model-View-Controller(MVC) και έτοιμα components, ώστε να δημιουργήσουμε ευέλικτες και με χαμηλή σύζευξη εφαρμογές. To MVC pattern διαχωρίζει τα διάφορα κομμάτια της εφαρμογής (input logic, business logic και UI logic) και παρέχει χαμηλή σύζευξη μεταξύ αυτών των στοιχείων.
Το Spring MVC, όπως και άλλα web frameworks, είναι σχεδιασμένο γύρω από το front controller pattern, όπου ένα κεντρικό Servlet , το Dispatcher Servlet, παρέχει έναν μοιραζόμενο αλγόριθμο για την επεξεργασία των αιτημάτων, ενώ η πραγματική δουλειά ανατίθεται και πραγματοποιείται απο ρυθμιζόμενα components.
Όταν γίνεται ένα HTTP αίτημα στο DispatcherServlet, πραγματοποιείται η εξής ακολουθία γεγονότων:
Για να δημιουργήσουμε το DispatcherServlet, χρειάζεται XML ή Java Configuration. Παρακάτω δίνεται το Java configuration.
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
public Class<?>[] getRootConfigClasses() {
return null;
}
@Override
public Class<?>[] getServletConfigClasses() {
return new Class<?>[] { MyWebConfig.class };
}
@Override
public String[] getServletMappings() {
return new String[] { "/" };
}
}
Εάν χρησιμοποιούμε Spring Boot, χρησιμοποιώντας το starter dependency spring-boot-starter-web, το Spring Boot παρέχει το configuration και δημιουργεί το DispatcherServlet για εμάς.
Το Spring MVC παρέχει ένα annotation-based προγραμματιστικό μοντέλο, όπου τα @Controller και @RestController components, χρησιμοποιούν annotations, για να δηλώσουν request mappings, request input, exception handling κ.α. Οι annotated controllers έχουν ευέλικτες υπογραφές μεθόδων και όυτε κάνουν extend base κλάσεις, ούτε υλοποιούν συγκεκριμένες διεπαφές.
Μπορούμε, να δηλώσουμε controller beans, χρησιμοποιώντας τον τυπικό τρόπο ορισμού των Spring beans, στο WebApplicationContext του Servlet. Το @Controller stereotype καθιστά δυνατή, την εύρεση του controller bean από το μηχανισμό εύρεσης components του Spring και υποδεικνύει ότι είναι ένα web component. Το παρακάτω παράδειγμα δείχνει έναν controller, που ορίζεται με annotations;
@Controller
public class HelloController {
@Override
public String handle (Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
Η μέθοδος δέχεται ένα Model και επιστρέφει ένα όνομα view ως String.
Το @RestController είναι ένα σύνθετο annotation, το οποίο περιέχει τα annotations @Controller και @ResponseBody και υποδεικνύει έναν controller, του οποίου όλες οι μέθοδοι κληρονομούν το type-level @ResponseBody annotation, όποτε γράφουν κατευθείαν στο response body και δεν κάνουν render ένα HTML template.
Το annotation @RequestMapping χρησιμοποιείται για την αντιστοίχιση αιτημάτων με μεθόδους του controller. Έχει διάφορα attributes, ώστε να πραγματοποιείται αντιστοίχιση σύμφωνα με την HTTP μέθοδο, τα request parameters, τα headers και media types. Μπορεί να χρησιμοποιηθεί σε επίπεδο κλάσης, ώστε να εκφράσει μοιραζόμενες αντιστοιχίσεις ή σε επίπεδο μεθόδου, για να αντιστοιχιστεί με ένα συγκεκριμένο endpoint.
Υπάρχουν επίσης συντομεύσεις του @RequestMapping, ανάλογα με την HTTP μέθοδο :
Το επόμενο παράδειγμα έχει mapping επιπέδου τύπου και μεθόδου:
@RestController
@RequestMapping("/persons")
public class PersonController {
// GET /persons/11
@RequestMapping("/{id}")
public Person getPerson (@PathVariable Long id) {
// ...
}
// POST /persons
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add (@RequestBody Person person) {
// ...
}
}
Μπορούμε να δηλώσουμε URL variable και πάρουμε την τιμή τους με το annotation @PathVariable.
// GET /owners/11/pets/20
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet (@PathVariable Long ownerId, @PathVariable Long petId) {
// ownerId = 11
// petId = 20
}
Μπορούμε να ορίσουμε το Content-Type του αιτήματος.
@PostMapping( path = "/path", consumes = "application/json")
public void addPet (@RequestBody ) Pet pet{
// ...
}
Μπορούμε να ορίσουμε το περιεχόμενο της απάντησης, ανάλογα με το Accept header του αιτήματος.
@GetMapping( path = "/pets/{petId}", produces = "application/json;charset=UTF-8")
@ResponseBody
public Pet getPet (@PathVariable ) String petId{
// ...
}
Με το annotation @RequestParam, μπορούμε να κάνουμε bind τις παραμέτρους του αιτήματος του Servlet (τις παράμετρους του query ή δεδομένα της φόρμας) σε μια παραμέτρος μεθόδου ενός controller.
@Controller
@RequestMapping("/pets")
public class EditPetForm {
@GetMapping(
public String setupForm (@RequestParam("petId" int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
}
Εξ' ορισμού, οι παράμετροι μεθόδων με το annotation αυτό είναι υποχρεωτικές, αλλα μπορούμε να τις κάνουμε προαιρετικές, θέτοντας το required flag την τιμή false.
Αφού ενεργοποιηθεί το MultipartResolver, το περιεχόμενο των POST αιτημάτων με multipart/form-data (αρχεία, φωτογραφίες) γίνεται parse και είναι προσβάσιμο ως απλή παράμετρος αιτήματος.
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload (@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
store the bytes somewhere//
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
Μπορούμε να χρησιμοποιήσουμε το annotation @RequestBody, ώστε να διαβαστεί το request body και να γίνει deserialize σε ένα Object μέσω του HttpMessageConverter. Εάν χρησιμοποιήσουμε και το annotation javax.validation.Valid, τότε εφαρμόζεται Standard Bean Validation.
@PostMapping("/accounts")
public void String handleFormUpload (@Valid @RequestBody Account account){
// ...
}
Μπορούμε να χρησιμοποιήσουμε το annotation @ResponseBody σε μια μέθοδο, ώστε η απάντηση να γίνει deserialized στο response body μέσω του HttpMessageConverter.
@GetMapping("/accounts/{id}")
@ResponseBody(
public Account handle (){
// ...
}
Το annotation @ResponseEntity είναι σαν το @ResponseBody, αλλά μπορούμε να επιστρέψουμε status και headers.
@GetMapping("/accounts/{id}")
public ResponseEntity<?> getAccount (){
Account account = ...;
return new ResponseEntity<Account>( account, HttpStatus.BAD_REQUEST);
}