Spring MVC

To Spring Web MVC framework παρέχει το μοντέλο αρχιτεκτονικής Model-View-Controller(MVC) και έτοιμα components, ώστε να δημιουργήσουμε ευέλικτες και με χαμηλή σύζευξη εφαρμογές. To MVC pattern διαχωρίζει τα διάφορα κομμάτια της εφαρμογής (input logic, business logic και UI logic) και παρέχει χαμηλή σύζευξη μεταξύ αυτών των στοιχείων.

Mvc Diagram
Dispatcher Servlet

Το Spring MVC, όπως και άλλα web frameworks, είναι σχεδιασμένο γύρω από το front controller pattern, όπου ένα κεντρικό Servlet , το Dispatcher Servlet, παρέχει έναν μοιραζόμενο αλγόριθμο για την επεξεργασία των αιτημάτων, ενώ η πραγματική δουλειά ανατίθεται και πραγματοποιείται απο ρυθμιζόμενα components.

Dispatcher Servlet

Όταν γίνεται ένα HTTP αίτημα στο DispatcherServlet, πραγματοποιείται η εξής ακολουθία γεγονότων:

  1. Όταν λάβει ένα HTTP request, το DispatcherServlet συμβουλεύεται το HandlerMapping, για να καλέσει τον κατάλληλο Controller.
  2. O Controller παίρνει το αίτημα και καλεί την κατάλληλη μέθοδο, που θα το διαχειριστεί, ανάλογα με την HTTP μέθοδο. Η μέθοδος του Controller θα δημιουργήσει τα δεδομένα του Model, σύμφωνα με το business logic και θα επιστρέψει το όνομα του DispatcherServletView στο DispatcherServlet.
  3. Το DispatcherServlet περνάει το λογικό όνομα του View στον ViewResolver και αυτος επιστρέφει την πραγματική υλοποίηση του View.
  4. Το DispatcherServlet περνάει τα δεδομένα στο View και επιστρέφει την απάντηση στον browser του client.

Για να δημιουργήσουμε το 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 για εμάς.

Annotated Controllers

Το 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.

Request Mapping

Το 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 Variables

Μπορούμε να δηλώσουμε 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
                                }
                
            
Consumable Media Types

Μπορούμε να ορίσουμε το Content-Type του αιτήματος.

                

                                @PostMapping( path = "/path", consumes  = "application/json")
                                public  void addPet (@RequestBody ) Pet pet{
                                  // ...
                                }
                
            
Producible Media Types

Μπορούμε να ορίσουμε το περιεχόμενο της απάντησης, ανάλογα με το Accept header του αιτήματος.

                

                                @GetMapping( path = "/pets/{petId}", produces   = "application/json;charset=UTF-8")
                                @ResponseBody
                                public  Pet getPet (@PathVariable  ) String petId{
                                  // ...
                                }
                
            
@RequestParam

Με το 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.

Multipart

Αφού ενεργοποιηθεί το 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";
                                }
                        }
                
            
@RequestBody

Μπορούμε να χρησιμοποιήσουμε το annotation @RequestBody, ώστε να διαβαστεί το request body και να γίνει deserialize σε ένα Object μέσω του HttpMessageConverter. Εάν χρησιμοποιήσουμε και το annotation javax.validation.Valid, τότε εφαρμόζεται Standard Bean Validation.

                
                        @PostMapping("/accounts")
                        public  void String handleFormUpload (@Valid @RequestBody Account account){
                        // ...
                        } 
                
            
@ResponseBody

Μπορούμε να χρησιμοποιήσουμε το annotation @ResponseBody σε μια μέθοδο, ώστε η απάντηση να γίνει deserialized στο response body μέσω του HttpMessageConverter.

                
                        @GetMapping("/accounts/{id}")
                        @ResponseBody(
                        public  Account handle (){
                        // ...
                        } 
                
            
@ResponseEntity

Το annotation @ResponseEntity είναι σαν το @ResponseBody, αλλά μπορούμε να επιστρέψουμε status και headers.

                
                        @GetMapping("/accounts/{id}")
                        public  ResponseEntity<?> getAccount (){
                            Account account = ...; 
                            return new ResponseEntity<Account>( account, HttpStatus.BAD_REQUEST);
                        } 
                
            
Πηγές