Η άνοιξη είναι αναμφίβολα ένα από τα πιο δημοφιλή πλαίσια Java, και επίσης ένα δύσκολο θηρίο να εξημερώσει. Ενώ τα βασικά του στοιχεία είναι πολύ εύκολα κατανοητά, το να γίνετε ένας ισχυρός προγραμματιστής της άνοιξης απαιτεί λίγο χρόνο και προσπάθεια.
Σε αυτό το άρθρο θα καλύψουμε μερικά από τα πιο συνηθισμένα λάθη την άνοιξη, ειδικά προσανατολισμένα σε εφαρμογές Ιστού και το Spring Boot. Όπως το βλέπουμε τον ιστότοπο Spring Boot , αυτό παίρνει ένα άποψη σχετικά με τον τρόπο κατασκευής έτοιμων εφαρμογών παραγωγής, οπότε αυτό το άρθρο θα προσπαθήσει να μιμηθεί αυτό το όραμα και να παρέχει μια σύνοψη ορισμένων συμβουλών που θα μπορούσαν να ενσωματωθούν στην ανάπτυξη μιας τυπικής εφαρμογής ιστού Sping Boot.
Σε περίπτωση που δεν γνωρίζετε για το Spring Boot, αλλά θα θέλατε να δοκιμάσετε μερικά από τα πράγματα που αναφέρονται, δημιούργησα ένα αποθετήριο GitHub που συνοδεύει αυτό το άρθρο . Εάν αισθάνεστε χαμένοι σε οποιοδήποτε σημείο κατά τη διάρκεια αυτού του άρθρου, θα συνιστούσα να κλωνοποιήσετε το αποθετήριο και να βλάψετε τον κωδικό στον τοπικό υπολογιστή σας.
Ξεκινήσαμε με αυτό το κοινό λάθος επειδή το σύνδρομο « δεν εφευρέθηκε εδώ Είναι πολύ κοινό στον κόσμο της ανάπτυξης λογισμικού. Τα συμπτώματα περιλαμβάνουν την επανεγγραφή κομματιών κώδικα που χρησιμοποιούνται συχνά και πολλοί προγραμματιστές φαίνεται να υποφέρουν από αυτό.
Ενώ η κατανόηση των εσωτερικών λειτουργιών μιας βιβλιοθήκης και η εφαρμογή της είναι σε μεγάλο βαθμό καλή και απαραίτητη (και, επίσης, μπορεί να είναι μια σπουδαία διαδικασία μάθησης), είναι επιζήμιο για την ανάπτυξή σας ως μηχανικός λογισμικού να αντιμετωπίζετε συνεχώς τις ίδιες λεπτομέρειες του χαμηλού- εφαρμογή σε επίπεδο. Υπάρχει ένας λόγος που υπάρχουν αφαιρέσεις και πλαίσια όπως το Spring, το οποίο είναι ακριβώς για να ξεφύγετε από επαναλαμβανόμενα βιβλία εργασίας και να σας επιτρέψουμε να εστιάσετε σε λεπτομέρειες υψηλότερου επιπέδου - τα αντικείμενα του τομέα σας και τη λογική της επιχείρησής σας.
Αποδεχτείτε λοιπόν τις αφαιρέσεις - την επόμενη φορά που θα αντιμετωπίσετε ένα συγκεκριμένο πρόβλημα, κάντε πρώτα μια γρήγορη αναζήτηση και καθορίστε εάν οποιαδήποτε βιβλιοθήκη που λύνει αυτό το πρόβλημα είναι ήδη ενσωματωμένη στην Άνοιξη. Σε αυτές τις περιόδους, είναι πιθανό να βρείτε ήδη μια κατάλληλη λύση. Ως παράδειγμα μιας χρήσιμης βιβλιοθήκης, θα χρησιμοποιήσω τους σχολιασμούς από Έργο Lombok στο υπόλοιπο αυτού του άρθρου. Το Lombok χρησιμοποιείται ως μοντέλο δημιουργίας κώδικα και έτσι ο τεμπέλης προγραμματιστής μέσα σας δεν θα πρέπει να έχει πρόβλημα να εξοικειωθείτε με τη βιβλιοθήκη. Για παράδειγμα, δείτε τι ' Ιάβα Φασόλι πρότυπο Με τον Λομπόκ:
@Getter @Setter @NoArgsConstructor public class Bean implements Serializable { int firstBeanProperty; String secondBeanProperty; }
Όπως μπορείτε να φανταστείτε, ο παραπάνω κώδικας συντάσσει:
public class Bean implements Serializable { private int firstBeanProperty; private String secondBeanProperty; public int getFirstBeanProperty() { return this.firstBeanProperty; } public String getSecondBeanProperty() { return this.secondBeanProperty; } public void setFirstBeanProperty(int firstBeanProperty) { this.firstBeanProperty = firstBeanProperty; } public void setSecondBeanProperty(String secondBeanProperty) { this.secondBeanProperty = secondBeanProperty; } public Bean() { } }
Ωστόσο, λάβετε υπόψη ότι πιθανότατα θα πρέπει να εγκαταστήσετε ένα πρόσθετο σε περίπτωση που θέλετε να χρησιμοποιήσετε το Lombok με το IDE σας. Μπορείτε να βρείτε την έκδοση IntelliJ IDEA της προσθήκης εδώ .
Η έκθεση της εσωτερικής σας δομής δεν είναι ποτέ καλή ιδέα, καθώς δημιουργεί δυσκαμψία στο σχεδιασμό των υπηρεσιών και συνεπώς προωθεί κακές πρακτικές κώδικα. Το 'φιλτράρισμα' της εσωτερικής εργασίας εκδηλώνεται καθιστώντας τη δομή της βάσης δεδομένων προσβάσιμη από ορισμένα σημεία εξόδου API. Για παράδειγμα, ας υποθέσουμε ότι το ακόλουθο POJO ('Plain Old Java Object') αντιπροσωπεύει έναν πίνακα στη βάση δεδομένων σας:
@Entity @NoArgsConstructor @Getter public class TopTalentEntity { @Id @GeneratedValue private Integer id; @Column private String name; public TopTalentEntity(String name) { this.name = name; } }
Ας υποθέσουμε ότι υπάρχει ένα σημείο εξόδου που πρέπει να έχει πρόσβαση στα δεδομένα TopTalentEntity
. Παρόλο που είναι πολύ δελεαστικό να επιστρέφετε παρουσίες TopTalentEntity
, μια πιο ευέλικτη λύση μπορεί να είναι η δημιουργία μιας νέας κλάσης για την αναπαράσταση των δεδομένων TopTalentEntity
στο σημείο εξόδου API:
@AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; }
Επομένως, η πραγματοποίηση αλλαγών στο πίσω μέρος της βάσης δεδομένων σας δεν θα απαιτήσει πρόσθετες αλλαγές στο επίπεδο υπηρεσίας. Σκεφτείτε τι θα συνέβαινε εάν προστέθηκε ένα πεδίο «κωδικός πρόσβασης» στο TopTalentEntity
για να αποθηκεύσετε τη λειτουργία χασίσι του κωδικού πρόσβασης των χρηστών στη βάση δεδομένων - χωρίς κάποια εφαρμογή σύνδεσης όπως TopTalentData
, ξεχνώντας να αλλάξετε την υπηρεσία front-end θα εκθέσει κατά λάθος ανεπιθύμητες, μυστικές πληροφορίες!
Καθώς η εφαρμογή σας μεγαλώνει, η οργάνωση κώδικα γίνεται γρήγορα πολύ πιο σημαντική. Κατά ειρωνικό τρόπο, πολλές από τις αρχές της καλής μηχανικής λογισμικού αρχίζουν να καταρρέουν σε κλίμακα - ειδικά σε περιπτώσεις όπου ο σχεδιασμός της αρχιτεκτονικής εφαρμογών δεν έχει μελετηθεί καλά. Ένα από τα πιο συνηθισμένα λάθη που οι προγραμματιστές τείνουν να υποκύψουν είναι η μεγιστοποίηση των ανησυχιών κώδικα και είναι εξαιρετικά εύκολο να το κάνετε!
κατασκευή και προγραμματισμός ενός ρομπότ
Αυτό που συνήθως σπάει το διαχωρισμός των ανησυχιών απλώς «ρίχνει» μια νέα λειτουργικότητα σε υπάρχουσες τάξεις. Αυτή είναι μια εξαιρετική βραχυπρόθεσμη λύση (απαιτεί λιγότερη πληκτρολόγηση για να ξεκινήσετε), αλλά αναπόφευκτα γίνεται πρόβλημα αργότερα, είτε κατά τη διάρκεια της δοκιμής, της συντήρησης, είτε κάποια στιγμή στο μεταξύ. Εξετάστε τον ακόλουθο ελεγκτή, ο οποίος επιστρέφει TopTalentData
από το αποθετήριο σας:
@RestController public class TopTalentController { private final TopTalentRepository topTalentRepository; @RequestMapping('/toptal/get') public List getTopTalent() { return topTalentRepository.findAll() .stream() .map(this::entityToData) .collect(Collectors.toList()); } private TopTalentData entityToData(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } }
Με την πρώτη ματιά, μπορεί να φαίνεται ότι δεν υπάρχει τίποτα ιδιαίτερα λάθος με αυτόν τον κώδικα. παρέχει μια λίστα TopTalentData
που εξάγεται από τις εμφανίσεις TopTalentEntity
. Κοιτώντας πιο κοντά, ωστόσο, μπορούμε να δούμε ότι υπάρχουν κάποια πράγματα που επιτυγχάνονται από TopTalentController
; Είναι κυρίως χαρτογράφηση αιτημάτων σε ένα συγκεκριμένο σημείο εξόδου, εξαγωγή δεδομένων από ένα αποθετήριο και μετατροπή οντοτήτων που λαμβάνονται από TopTalentRepository
σε διαφορετική μορφή. Μια καθαρότερη λύση θα ήταν να διαχωρίσετε αυτές τις ανησυχίες στις δικές σας τάξεις. Μπορεί να μοιάζει κάπως έτσι:
@RestController @RequestMapping('/toptal') @AllArgsConstructor public class TopTalentController { private final TopTalentService topTalentService; @RequestMapping('/get') public List getTopTalent() { return topTalentService.getTopTalent(); } } @AllArgsConstructor @Service public class TopTalentService { private final TopTalentRepository topTalentRepository; private final TopTalentEntityConverter topTalentEntityConverter; public List getTopTalent() { return topTalentRepository.findAll() .stream() .map(topTalentEntityConverter::toResponse) .collect(Collectors.toList()); } } @Component public class TopTalentEntityConverter { public TopTalentData toResponse(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } }
Ένα επιπλέον πλεονέκτημα αυτής της ιεραρχίας είναι ότι μας επιτρέπει να προσδιορίσουμε πού βρίσκεται η λειτουργικότητα, απλά ελέγχοντας το όνομα της τάξης. Επίσης, κατά τη διάρκεια της δοκιμαστικής περιόδου μπορούμε εύκολα να αντικαταστήσουμε οποιαδήποτε από τις τάξεις με μια πλαστή εφαρμογή εάν είναι απαραίτητο.
Το ζήτημα της ασυνέπειας δεν είναι απαραιτήτως μοναδικό για την Spring (ή Java), αλλά εξακολουθεί να είναι μια σημαντική πτυχή που πρέπει να ληφθεί υπόψη όταν εργάζεστε σε έργα Spring. Ενώ το στυλ κωδικοποίησης μπορεί να συζητηθεί (και είναι συνήθως θέμα συμφωνίας εντός μιας ομάδας ή ολόκληρης της εταιρείας), το να έχεις ένα κοινό πρότυπο μπορεί να είναι ένα τεράστιο όφελος παραγωγικότητας. Αυτό είναι πολύ αλήθεια, ειδικά με πολυ-προσωπικές ομάδες. Η συνέπεια επιτρέπει την απόρριψη χωρίς να ξοδεύετε πολλούς πόρους παρηγοριά ή παρέχοντας μακροχρόνιες εξηγήσεις σχετικά με τις ευθύνες των διαφόρων τάξεων.
Σκεφτείτε ένα έργο Spring με διαφορετικά αρχεία διαμόρφωσης, υπηρεσίες και ελεγκτές. Όντας σημασιολογικά συνεπής κατά την ονομασία τους, δημιουργείται μια εύχρηστη δομή, όπου κάθε προγραμματιστής, ανεξάρτητα από το πόσο καινούργιος, μπορεί να περιηγηθεί στον κώδικα. επισυνάψτε επιθήματα διαμόρφωσης σε τάξεις διαμόρφωσης, επίθημα υπηρεσίας στις υπηρεσίες σας και επίθημα ελεγκτή για τους ελεγκτές σας, για παράδειγμα.
Στενά συνδεδεμένη με το ζήτημα της συνέπειας είναι ο χειρισμός σφαλμάτων από διακομιστή, ο οποίος αξίζει ιδιαίτερη έμφαση. Εάν είχατε ποτέ να χειριστείτε αποκρίσεις εξαιρέσεων από ένα κακώς γραμμένο API, ίσως γνωρίζετε γιατί - μπορεί να είναι δυσκίνητο να αναλύσετε σωστά τις εξαιρέσεις και ακόμη πιο δυσκίνητο είναι να προσδιορίσετε γιατί αρχικά προέκυψαν αυτές οι εξαιρέσεις.
Ως προγραμματιστής API, θα πρέπει ιδανικά να καλύψετε όλα τα σημεία εξόδου που αντιμετωπίζουν οι χρήστες και να τα μεταφράσετε σε μια κοινή μορφή σφάλματος. Αυτό συνήθως σημαίνει ότι υπάρχει ένας γενικός κωδικός σφάλματος και περιγραφή, αντί της λύσης πρόφασης, όπως α) επιστροφή του μηνύματος 'Σφάλμα εσωτερικού διακομιστή 500' ή β) επιτρέποντας στον χρήστη να αναζητήσει τη λύση (την οποία θα πρέπει να αποφεύγεται έντονα, καθώς εκθέτει την εσωτερική σας δουλειά, εκτός από το ότι είναι δύσκολο να χειριστεί ο πελάτης).
Ένα παράδειγμα ενός κοινού σφάλματος μορφής απόκρισης μπορεί να είναι:
@Value public class ErrorResponse { private Integer errorCode; private String errorMessage; }
Κάτι παρόμοιο με αυτό βρίσκεται συνήθως στα πιο δημοφιλή API και τείνει να λειτουργεί πολύ καλά καθώς μπορεί να τεκμηριωθεί εύκολα και συστηματικά. Οι εξαιρέσεις σε αυτήν τη μορφή μπορούν να μεταφραστούν παρέχοντας τον σχολιασμό @ExceptionHandler
σε μια μέθοδο (ένα παράδειγμα σχολιασμού βρίσκεται στο Κοινό σφάλμα # 6).
Ανεξάρτητα από το αν πρόκειται για επιτραπέζιους υπολογιστές ή εφαρμογές ιστού, την άνοιξη ή όχι, το multithreading μπορεί να είναι δύσκολο να χειριστεί. Τα προβλήματα που προκαλούνται από τις παράλληλες εκτελέσεις προγραμμάτων είναι αγχωτικά, αόριστα και εξαιρετικά δύσκολο να εντοπιστούν σφάλματα - στην πραγματικότητα, δεδομένης της φύσης του προβλήματος, μόλις συνειδητοποιήσετε ότι αντιμετωπίζετε ένα πρόβλημα παράλληλης εκτέλεσης, πιθανότατα θα πρέπει να προχωρήσετε εντελώς στο πρόγραμμα εντοπισμού σφαλμάτων και ελέγξτε τον κωδικό σας 'με το χέρι' μέχρι να εντοπίσετε την αιτία του ριζικού σφάλματος. Δυστυχώς, δεν υπάρχει τακτική λύση για την επίλυση τέτοιων προβλημάτων. Ανάλογα με τη συγκεκριμένη περίπτωσή σας, θα πρέπει να μελετήσετε την κατάσταση και στη συνέχεια να επιτεθείτε στο πρόβλημα από τη γωνία που πιστεύετε καλύτερα.
Φυσικά, θα θέλατε να αποφύγετε εντελώς τα σφάλματα πολλαπλών νημάτων. Και πάλι, μια τυπική προσέγγιση δεν υπάρχει, αλλά εδώ είναι μερικές πρακτικές εκτιμήσεις για τον εντοπισμό σφαλμάτων και την πρόληψη σφαλμάτων πολλαπλών νημάτων:
Πρώτον, θυμηθείτε πάντα το πρόβλημα «παγκόσμια κατάσταση». Εάν δημιουργείτε μια εφαρμογή πολλαπλών νημάτων, απολύτως όλα όσα είναι τροποποιήσιμα θα πρέπει να παρακολουθούνται πολύ στενά και, εάν είναι δυνατόν, να αφαιρούνται εντελώς. Εάν υπάρχει κάποιος λόγος για τον οποίο η καθολική μεταβλητή πρέπει να παραμείνει τροποποιήσιμη, χρησιμοποιήστε το συγχρονισμός και παρακολουθήστε την απόδοση της εφαρμογής σας για να επιβεβαιώσετε ότι δεν παρουσιάζεται σφάλμα λόγω των νέων περιόδων αναμονής.
Αυτό προέρχεται από λειτουργικός προγραμματισμός και, προσαρμοσμένο στο OOP, λέει ότι πρέπει να αποφεύγεται η τάξη της μεταβλητότητας και της μεταβαλλόμενης κατάστασης. Αυτό, με λίγα λόγια, σημαίνει να προχωρήσετε σε μεθόδους καρφιτσώματος και να έχετε ιδιωτικά τελικά πεδία σε όλες τις κατηγορίες μοντέλων σας. Η μόνη φορά που οι τιμές του μπορούν να μεταλλαχθούν είναι κατά τη διάρκεια της κατασκευής. Με αυτόν τον τρόπο μπορείτε να διασφαλίσετε ότι δεν προκύπτουν προβλήματα διαφωνίας και ότι οι ιδιότητες αντικειμένου που έχουν πρόσβαση σε αυτό θα παρέχουν πάντα τις σωστές τιμές.
Αξιολογήστε πού η εφαρμογή σας θα μπορούσε να προκαλέσει προβλήματα και να καταγράψετε προληπτικά όλα τα κρίσιμα δεδομένα. Εάν παρουσιαστεί σφάλμα, θα είστε ευγνώμονες που έχετε πληροφορίες που αναφέρουν ποια αιτήματα ελήφθησαν και θα έχετε καλύτερη αντίληψη για το γιατί απέτυχε η αίτησή σας. Είναι απαραίτητο να σημειωθεί, πάλι, ότι το μητρώο εισάγει το πρόσθετο αρχείο I / O και, επομένως, δεν πρέπει να γίνει κατάχρηση, καθώς θα μπορούσε να επηρεάσει σοβαρά την απόδοση της εφαρμογής σας.
Όταν πρέπει να δημιουργήσετε τα δικά σας νήματα (π.χ. για να κάνετε ασύγχρονα αιτήματα σε διαφορετικές υπηρεσίες), χρησιμοποιήστε ξανά τις ήδη υπάρχουσες ασφαλείς εφαρμογές, αντί να δημιουργήσετε τις δικές σας λύσεις. Αυτό σημαίνει κυρίως ότι πρέπει να χρησιμοποιήσετε Υπηρεσίες Υ Ολοκληρώσιμα Java 8 με καθαρό και λειτουργικό στιλ για δημιουργία νήματος. Το Spring επιτρέπει επίσης την επεξεργασία ασύγχρονων αιτημάτων μέσω της τάξης Αποτέλεσμα .
Ας υποθέσουμε ότι η υπηρεσία TopTalent, που φαίνεται παραπάνω, απαιτεί ένα σημείο εξόδου για την προσθήκη νέου Top Talent. Επιπλέον, ας πούμε ότι, για κάποιο πολύ έγκυρο λόγο, κάθε νέο όνομα πρέπει να έχει μήκος 10 χαρακτήρων. Ένας έγκυρος τρόπος για να γίνει αυτό μπορεί να είναι:
@RequestMapping('/put') public void addTopTalent(@RequestBody TopTalentData topTalentData) { boolean nameNonExistentOrHasInvalidLength = Optional.ofNullable(topTalentData) .map(TopTalentData::getName) .map(name -> name.length() == 10) .orElse(true); if (nameNonExistentOrInvalidLength) { // throw some exception } topTalentService.addTopTalent(topTalentData); }
Ωστόσο, τα παραπάνω (εκτός από την κακή κατασκευή) δεν είναι πραγματικά μια «καθαρή» λύση. Ψάχνουμε για περισσότερους από έναν τύπους ισχύος (κυρίως, ότι TopTalentData
δεν είναι μηδενικό, Υ ότι TopTalentData.name
δεν είναι μηδενικό, Υ ότι TopTalentData.name
έχει 10 χαρακτήρες), καθώς και την εξαίρεση εάν τα δεδομένα δεν είναι έγκυρα.
Αυτό μπορεί να γίνει πολύ πιο καθαρό χρησιμοποιώντας το Επικυρωτής αδρανοποίησης με την Άνοιξη. Πρώτον, πρέπει να επαναπροσδιορίσουμε το addTopTalent
για να υποστηρίξει την επικύρωση:
@RequestMapping('/put') public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) { topTalentService.addTopTalent(topTalentData); } @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) { // handle validation exception }
Επιπλέον, θα πρέπει να υποδείξουμε ποια ιδιότητα θέλουμε να επικυρώσουμε στην κλάση TopTalentData
:
public class TopTalentData { @Length(min = 10, max = 10) @NotNull private String name; }
Τώρα το Spring θα αναχαιτίσει το αίτημα και θα το επικυρώσει πριν από την επίκληση της μεθόδου - δεν υπάρχει ανάγκη για επιπλέον μη αυτόματη δοκιμή.
Ένας άλλος τρόπος που θα μπορούσαμε να επιτύχουμε το ίδιο αποτέλεσμα είναι να δημιουργήσουμε τους δικούς μας σχολιασμούς. Αν και συνήθως, θα χρησιμοποιείτε προσαρμοσμένους σχολιασμούς μόνο όταν οι ανάγκες σας υπερβαίνουν το Ενσωματωμένο περιορισμένο σετ αδρανοποίησης , για αυτό το παράδειγμα θα υποθέσουμε ότι το @Length δεν υπάρχει. Θα κάνατε έναν επικυρωτή που θα ελέγχει το μήκος της συμβολοσειράς χαρακτήρων δημιουργώντας δύο επιπλέον κλάσεις, μία για επικύρωση και μία για ιδιότητες σχολιασμού:
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = { MyAnnotationValidator.class }) public @interface MyAnnotation { String message() default 'String length does not match expected'; Class[] groups() default {}; Class[] payload() default {}; int value(); } @Component public class MyAnnotationValidator implements ConstraintValidator { private int expectedLength; @Override public void initialize(MyAnnotation myAnnotation) { this.expectedLength = myAnnotation.value(); } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) }
Λάβετε υπόψη ότι σε αυτές τις περιπτώσεις, οι βέλτιστες πρακτικές για τον διαχωρισμό των ανησυχιών απαιτούν να επισημάνετε μια ιδιότητα ως έγκυρη εάν είναι μηδενική (s == null
στη μέθοδο isValid
) και, στη συνέχεια, να χρησιμοποιήσετε έναν σχολιασμό @NotNull
εάν πρόκειται για πρόσθετη απαίτηση για το ακίνητο:
public class TopTalentData { @MyAnnotation(value = 10) @NotNull private String name; }
Ενώ απαιτείται XML σε προηγούμενες εκδόσεις της Άνοιξης, επί του παρόντος το μεγαλύτερο μέρος της διαμόρφωσης μπορεί να γίνει αποκλειστικά μέσω κώδικα Java / σχολιασμών. Οι διαμορφώσεις XML είναι απλώς πρόσθετος και περιττός τυπικός κωδικός κειμένου.
Αυτό το άρθρο (καθώς και το συνοδευτικό αποθετήριο GitHub) χρησιμοποιεί σχολιασμούς για να διαμορφώσει το Spring και γνωρίζει ποια φασόλια θα πρέπει να μεταφερθεί, επειδή το ριζικό πακέτο έχει επισημανθεί με σύνθετο σχολιασμό @SpringBootApplication
, όπως αυτό:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Ο σύνθετος σχολιασμός (μπορείτε να μάθετε περισσότερα σχετικά με αυτό στο Άνοιξη τεκμηρίωσης ) απλώς δίνει στο Spring μια υπόδειξη ότι τα πακέτα πρέπει να σαρωθούν για αφαίρεση φασόλια . Στην ειδική μας περίπτωση, αυτό σημαίνει ότι θα χρησιμοποιηθούν τα ακόλουθα παρακάτω για το πακέτο (co.kukurin) για μεταφορά:
@Component
(TopTalentConverter
, MyAnnotationValidator
)@RestController
(TopTalentController
)@Repository
(TopTalentRepository
)@Service
(TopTalentService
) τάξειςΕάν είχαμε τάξεις σχολιασμού @Configuration
πρόσθετα θα ελέγχονταν από τη διαμόρφωση βάσης Java.
Ένα κοινό πρόβλημα στην ανάπτυξη διακομιστή είναι η διάκριση μεταξύ διαφορετικών τύπων διαμόρφωσης, συνήθως των διαμορφώσεων ανάπτυξης και παραγωγής. Αντί να παρακάμπτετε με μη αυτόματο τρόπο πολλές καταχωρήσεις διαμόρφωσης κάθε φορά που πηγαίνετε από τη δοκιμή της εφαρμογής σας στην ανάπτυξη της, ένας πιο χρήσιμος τρόπος θα ήταν να χρησιμοποιήσετε προφίλ.
Εξετάστε την περίπτωση όπου χρησιμοποιείτε μια ενσωματωμένη βάση δεδομένων μνήμης για τοπική ανάπτυξη, με μια βάση δεδομένων MySQL στην παραγωγή. Αυτό θα σήμαινε ουσιαστικά ότι θα χρησιμοποιούσατε μια διαφορετική διεύθυνση URL και (ελπίζουμε) διαφορετικά διαπιστευτήρια για πρόσβαση σε καθένα. Ας δούμε πώς μπορεί να γίνει αυτό σε δύο διαφορετικά αρχεία διαμόρφωσης:
# set default profile to 'dev' spring.profiles.active: dev # production database details spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal' spring.datasource.username: root spring.datasource.password:
spring.datasource.url: 'jdbc:h2:mem:' spring.datasource.platform: h2
Υποτίθεται ότι δεν θα θέλατε κατά λάθος να εκτελέσετε μια ενέργεια στη βάση δεδομένων παραγωγής κατά την αναθεώρηση του κώδικα, επομένως είναι λογικό να ορίσετε το προεπιλεγμένο προφίλ στην ανάπτυξη. Στον διακομιστή, μπορείτε να παρακάμψετε μη αυτόματα το προφίλ διαμόρφωσης παρέχοντας μια παράμετρο -Dspring.profiles.active=prod
στο JVM. Εναλλακτικά, μπορείτε να ορίσετε τη μεταβλητή περιβάλλοντος του λειτουργικού σας συστήματος στο επιθυμητό προεπιλεγμένο προφίλ.
Η σωστή χρήση της έγχυσης εξάρτησης με το Spring σημαίνει ότι σας επιτρέπει να μεταφέρετε όλα τα αντικείμενα μαζί, σαρώνοντας όλες τις επιθυμητές τάξεις διαμόρφωσης. Αυτό αποδεικνύεται χρήσιμο κατά το διαχωρισμό των σχέσεων και καθιστά επίσης τη δοκιμή πολύ πιο εύκολη. Αντί να ταιριάζει σθεναρά με τάξεις όπως αυτό:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController() { this.topTalentService = new TopTalentService(); } }
Επιτρέπουμε στην Άνοιξη να κάνει τα γραπτά για εμάς:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController(TopTalentService topTalentService) { this.topTalentService = topTalentService; } }
Misko Hevery de Google ομιλία εξηγεί σε βάθος το «γιατί» της έγχυσης εξάρτησης, οπότε ας δούμε πώς χρησιμοποιείται στην πράξη. Στην ενότητα διαχωρισμού των ανησυχιών (Κοινό λάθος # 3), δημιουργήσαμε μια τάξη υπηρεσιών και έναν ελεγκτή. Ας πούμε ότι θέλουμε να δοκιμάσουμε τον ελεγκτή με την υπόθεση ότι TopTalentService
συμπεριφέρεται σωστά. Μπορούμε να εισαγάγουμε ένα εικονικό αντικείμενο στη θέση της τρέχουσας εφαρμογής υπηρεσίας παρέχοντας μια ξεχωριστή κλάση διαμόρφωσης:
@Configuration public class SampleUnitTestConfig { @Bean public TopTalentService topTalentService() { TopTalentService topTalentService = Mockito.mock(TopTalentService.class); Mockito.when(topTalentService.getTopTalent()).thenReturn( Stream.of('Mary', 'Joel').map(TopTalentData::new).collect(Collectors.toList())); return topTalentService; } }
Στη συνέχεια, μπορούμε να εγχύσουμε το πλαστό αντικείμενο λέγοντας στην Spring να χρησιμοποιήσει SampleUnitTestConfig
ως πάροχος διαμόρφωσης:
@ContextConfiguration(classes = { SampleUnitTestConfig.class })
Αυτό μας επιτρέπει να χρησιμοποιήσουμε τη ρύθμιση περιβάλλοντος για την ένεση του φασόλι έγινε για παραγγελία σε μονάδα δοκιμής.
Παρόλο που η ιδέα της δοκιμής μονάδας είναι εδώ και πολύ καιρό, πολλοί προγραμματιστές φαίνεται να 'ξεχνούν' να το κάνουν αυτό (ειδικά αν δεν είναι κάτι που απαιτεί ή απλώς το προσθέτουν ως κάτι ασήμαντο. Αυτό είναι προφανώς κάτι που δεν θέλουμε να συμβεί, καθώς οι δοκιμές όχι μόνο πρέπει να επαληθεύουν την ορθότητα του κώδικα σας, αλλά και να χρησιμεύουν ως τεκμηρίωση σχετικά με τον τρόπο συμπεριφοράς της εφαρμογής σε διαφορετικές καταστάσεις.
Όταν δοκιμάζετε υπηρεσίες ιστού, σπάνια κάνετε καθαρά «καθαρή» δοκιμή μονάδας, καθώς η επικοινωνία HTTP συνήθως απαιτεί από εσάς να καλέσετε DispatcherServlet
της Άνοιξης και δείτε τι συμβαίνει όταν ένα HttpServletRequest
λαμβάνεται στην πραγματικότητα (καθιστώντας την απόδειξη του ενσωμάτωση , ασχολείται με την επικύρωση, τη δημοσίευση έκδοσης, κ.λπ.). Διασφάλιση REST , ένα Java DSL για εύκολο έλεγχο των υπηρεσιών REST, καλύτερα από το MockMVC, έχει αποδείξει ότι μπορεί να δώσει μια πολύ κομψή λύση. Εξετάστε το ακόλουθο κομμάτι κώδικα με ένεση εξάρτησης:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { Application.class, SampleUnitTestConfig.class }) public class RestAssuredTestDemonstration { @Autowired private TopTalentController topTalentController; @Test public void shouldGetMaryAndJoel() throws Exception { // given MockMvcRequestSpecification givenRestAssuredSpecification = RestAssuredMockMvc.given() .standaloneSetup(topTalentController); // when MockMvcResponse response = givenRestAssuredSpecification.when().get('/toptal/get'); // then response.then().statusCode(200); response.then().body('name', hasItems('Mary', 'Joel')); } }
SampleUnitTestConfig
περνά μια ψευδή εφαρμογή του TopTalentService
α TopTalentController
ενώ όλες οι άλλες κατηγορίες μεταφέρονται χρησιμοποιώντας την τυπική διαμόρφωση, που συνάγεται από τα πακέτα σάρωσης που είναι ενσωματωμένα στο πακέτο κλάσης εφαρμογών. RestAssuredMockMvc
χρησιμοποιείται απλώς για να δημιουργήσει ένα ελαφρύ περιβάλλον και στέλνει ένα αίτημα GET
στο σημείο εξόδου /toptal/get
.
Η άνοιξη είναι ένα πολύ ισχυρό πλαίσιο, το οποίο είναι εύκολο να ξεκινήσει, αλλά απαιτεί κάποια αφοσίωση και χρόνο για να επιτευχθεί πλήρης γνώση. Αν αφιερώσετε χρόνο για να εξοικειωθείτε με το πλαίσιο θα βελτιώσετε την παραγωγικότητά σας μακροπρόθεσμα και τελικά θα σας βοηθήσουμε να γράψετε καθαρότερο κώδικα και να γίνετε καλύτερος προγραμματιστής.
εργαλείο που χρησιμοποιείται για την οπτικοποίηση όλων των δυνατών συνδυασμών
Εάν ψάχνετε περισσότερο υλικό σε αυτό το θέμα, Άνοιξη σε δράση είναι ένα καλό βιβλίο, το οποίο καλύπτει πολλά από τα βασικά θέματα της Άνοιξης.