Kevad on kahtlemata üks populaarsemaid Java-raamistikke ja ka raskesti taltsutav metsaline. Kuigi selle põhitõdesid on väga lihtne mõista, võtab tugev Kevadine arendaja saamine aega ja vaeva.
Selles artiklis käsitleme kevadel kõige levinumaid vigu, mis on spetsiaalselt suunatud veebirakendustele ja Spring Bootile. Nagu me seda näeme Spring Boot veebisaidil , see võtab a vaatenurgast selle kohta, kuidas valmis tootmisrakendusi tuleks üles ehitada, nii et selles artiklis püütakse seda visiooni jäljendada ja antakse kokkuvõte mõnedest näpunäidetest, mida võiks lisada standardse Sping Boot veebirakenduse väljatöötamisse.
Juhul, kui te ei tea Spring Bootist, kuid soovite siiski mõnda mainitud asja proovida, lõin ma selle artikliga kaasas olev GitHubi hoidla . Kui tunnete end selle artikli ajal mingil hetkel kaotsi läinud, soovitaksin kloonida hoidla ja segada oma kohaliku masina koodi.
Alustasime selle levinud veaga, sest sündroom pole siin leiutatud ”See on tarkvaraarenduse maailmas väga levinud. Sümptomiteks on tavaliselt kasutatava koodi juppide korrapärane ümberkirjutamine ja paljud arendajad näivad seda kannatavat.
Ehkki raamatukogu sisemise töö ja selle rakendamise mõistmine on suures osas hea ja vajalik (ja see võib olla ka suurepärane õppeprotsess), on teie kui tarkvarainseneri arengule kahjulik pidevalt maadelda madalate andmebaaside samade üksikasjadega. taseme rakendamine. Seal on põhjus, miks on olemas sellised abstraktsioonid ja raamistikud nagu Spring, mis on just selleks, et eralduda korduvatest töövihikutest ja võimaldada teil keskenduda kõrgema taseme üksikasjadele - oma domeeni objektidele ja äriloogikale.
Nii et aktsepteerige abstraktsioone - järgmine kord, kui olete mõne konkreetse probleemiga silmitsi, tehke kõigepealt kiire otsing ja tehke kindlaks, kas mõni selle probleemi lahendav kogu on juba Springisse sisse ehitatud; Nendel aegadel leiate tõenäoliselt sobiva lahenduse juba olemas. Kasuliku raamatukogu näitena kasutan märkmeid aadressilt Lomboki projekt selle artikli ülejäänud osas. Lombokit kasutatakse mudelkoodigeneraatorina ja seega ei tohiks teie laiskal arendajal olla probleeme raamatukoguga tutvumisel. Näiteks vaadake, mida Java Bean standard ”Lombokiga:
@Getter @Setter @NoArgsConstructor public class Bean implements Serializable { int firstBeanProperty; String secondBeanProperty; }
Nagu võite ette kujutada, koosneb ülaltoodud kood:
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() { } }
Pidage siiski meeles, et peate tõenäoliselt installima pistikprogrammi juhuks, kui soovite Lombokit kasutada oma IDE-ga. Pistikprogrammi IntelliJ IDEA versiooni leiate siin .
Oma sisemise struktuuri paljastamine pole kunagi hea mõte, kuna see loob teenuse kujunduses paindumatuse ja soodustab seetõttu halbu koode. Sisemine töö filtreerimine ilmneb andmebaasi struktuuri juurdepääsetavaks muutmisest teatud API väljundpunktidest. Oletame näiteks, et järgmine POJO ('Plain Old Java Object') tähistab teie andmebaasi tabelit:
@Entity @NoArgsConstructor @Getter public class TopTalentEntity { @Id @GeneratedValue private Integer id; @Column private String name; public TopTalentEntity(String name) { this.name = name; } }
Oletame, et on olemas väljumispunkt, mis peab andmetele juurde pääsema TopTalentEntity
. Kuigi TopTalentEntity
eksemplaride tagastamine on väga ahvatlev, võib paindlikum lahendus olla uue klassi loomine andmete esitamiseks TopTalentEntity
API väljumispunktis:
@AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; }
Seega ei nõua andmebaasi tagakülje muudatuste tegemine teenuse kihis täiendavaid muudatusi. Mõelge, mis juhtuks, kui TopTalentEntity
-le lisatakse väli 'parool' funktsiooni salvestamiseks räsi kasutajate parooli andmebaasis - ilma mõne pistikuta, näiteks TopTalentData
, paluks kasutajaliidese muutmise unustamine kogemata ebasoovitavat salajast teavet!
Kui teie rakendus kasvab, muutub koodi korraldamine kiiresti palju olulisemaks. Irooniline, et suur osa hea tarkvaratehnika põhimõtetest hakkab mastaabis lagunema - eriti juhtudel, kui rakenduse arhitektuuri kujundus pole hästi läbi mõeldud. Üks levinumaid vigu, millele arendajad kipuvad alla andma, on koodiga seotud probleemide maksimeerimine ja seda on äärmiselt lihtne teha!
müügi prognoosimine on näide ülesandest (n) _____.
Mis tavaliselt lõhub murede lahusus see on lihtsalt uue funktsionaalsuse viskamine olemasolevatele klassidele. See on suurepärane lühiajaline lahendus (alustamiseks on vaja vähem sisestada), kuid paratamatult muutub see probleemiks hiljem, kas testimise, hoolduse või millalgi vahepeal. Mõelge järgmisele kontrollerile, mis tagastab TopTalentData
oma hoidlast:
@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()); } }
Esmapilgul võib tunduda, et sellel koodil pole midagi eriti viga; esitab loendi TopTalentData
mis eraldatakse TopTalentEntity
eksemplaridest. Lähemalt vaadates näeme siiski, et TopTalentController
; see on peamiselt taotluste kaardistamine konkreetsesse väljumispunkti, andmete hankimine hoidlast ja üksuste teisendamine TopTalentRepository
teises vormingus. Puhtam lahendus oleks jagada need mured oma klassidesse. See võib välja näha umbes selline:
@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()); } }
Selle hierarhia täiendav eelis on see, et see võimaldab meil lihtsalt klassi nime kontrollides kindlaks teha, kus funktsionaalsus asub. Samuti saame prooviperioodi jooksul vajaduse korral mõne klassi hõlpsasti välja vahetada.
Järjepidevuse küsimus pole tingimata Springile (või Java-le) omane, kuid see on siiski oluline aspekt, mida kevadiste projektide kallal töötamisel arvestada. Kuigi kodeerimisstiili üle võib vaielda (ja see on tavaliselt meeskonnas või kogu ettevõttes kokkuleppe küsimus), võib ühise standardi omamine olla tohutu tööviljakus. See on väga tõsi, eriti mitme isikliku meeskonnaga; järjepidevus võimaldab tagasilükkamist ilma kulutada palju ressursse lohutades või pakkudes pikki selgitusi erinevate klasside vastutuse kohta.
Mõelgem kevadisele projektile koos erinevate konfiguratsioonifailide, teenuste ja kontrolleritega. Nende nimetamisel semantiliselt järjepidev olemine loob hõlpsasti uuritava struktuuri, kus iga arendaja, olgu see nii uus kui tahes, saab koodis ringi liikuda; lisada konfiguratsiooniklassidele konfiguratsiooniliited, teie teenustele teenuse järelliited ja kontrollerite järelliited.
Järjepidevuse küsimusega on tihedalt seotud serveripoolne tõrkeotsing, mis väärib erilist rõhutamist. Kui olete kunagi pidanud käsitsema halvasti kirjutatud API erandivastusi, siis ilmselt teate, miks - erandite nõuetekohane sõelumine võib olla tülikas ja veelgi tülikam on teha kindlaks, miks need erandid algselt tekkisid.
API arendajana peaksite ideaalis katma kõik väljumispunktid, millega kasutajad silmitsi seisavad, ja tõlkima need levinud veavormingusse. Tavaliselt tähendab see üldise veakoodi ja kirjelduse olemasolu ettekäändelahenduse asemel, näiteks a) teate '500 sisemise serveri viga' tagastamist või b) kasutajale lahenduse otsimist (mida tuleks palavalt vältida, kuna see paljastab) teie sisemine töö, peale selle, et seda on kliendil raske käsitseda).
Tavalise vastuse vormingu tõrke näide võib olla:
@Value public class ErrorResponse { private Integer errorCode; private String errorMessage; }
Midagi sarnast leidub tavaliselt kõige populaarsemates API-des ja see kipub töötama väga hästi, kuna seda saab hõlpsalt ja süstemaatiliselt dokumenteerida. Selle vormingu erandeid saab tõlkida, lisades märkuse @ExceptionHandler
meetodile (märkuse näide on tavalises veas nr 6).
Sõltumata sellest, kas see on lauaarvutites või veebirakendustes, võib kevadel mitmikeermelist käsitlemist olla keeruline või mitte. Paralleelsete programmide hukkamiste põhjustatud probleemid on stressirohked, raskesti mõistetavad ja neid on äärmiselt raske siluda - tegelikult, arvestades probleemi olemust, peate tõenäoliselt siluriga jätkama, kui mõistate, et tegemist on paralleelse täitmisprobleemiga ja kontrollige oma koodi 'käsitsi', kuni leiate juurvea põhjuse. Kahjuks puudub selliste probleemide lahendamiseks regulaarne lahendus; Sõltuvalt konkreetsest juhtumist peate olukorda uurima ja seejärel ründama probleemi nurga alt, mis teile kõige paremini meeldib.
Muidugi tahaksite ideaalselt vältida mitmikeermelisi vigu. Jällegi ei eksisteeri standardset lähenemist, kuid siin on mõned praktilised kaalutlused mitmikeermeliste vigade silumiseks ja ennetamiseks:
Kõigepealt pidage alati meeles 'globaalse riigi' probleemi. Kui loote mitmikeermelist rakendust, tuleks absoluutselt kõike muudetavat jälgida väga hoolikalt ja võimaluse korral täielikult eemaldada. Kui on mõni põhjus, miks globaalne muutuja peaks muutma jääma, kasutage sünkroniseerimine ja jälgige oma rakenduse toimivust, et kinnitada, et see ei kuku uute ooteperioodide tõttu kokku.
See pärineb funktsionaalne programmeerimine ja kohandatud OOP-ga öeldakse, et mutatsioonide ja oleku muutumise klassi tuleks vältida. Lühidalt tähendab see, et jätkate kinnitusmeetodite kasutamist ja privaatsete lõppväljade olemasolu kõigis oma mudeliklassides. Ainus kord, kui selle väärtusi saab muteerida, on ehituse ajal. Nii saate tagada, et ei tekiks vaidlusprobleeme ja et sellele juurdepääsuvad objekti atribuudid annaksid alati õiged väärtused.
Hinnake, kus teie rakendus võib probleeme tekitada, ja registreerige kõik olulised andmed ennetavalt. Vea ilmnemisel olete tänulik, et teil on teavet, mis ütleb, millised taotlused on saadud, ja saate paremini aru, miks teie rakendus ebaõnnestus. Jällegi on vaja märkida, et register tutvustab täiendava faili sisend / väljundit ja seetõttu ei tohiks seda kuritarvitada, kuna see võib teie rakenduse toimimist tõsiselt mõjutada.
Kui peate looma oma lõimed (nt erinevatele teenustele asünkroonsete päringute tegemiseks), kasutage oma lahenduste loomise asemel juba olemasolevaid turvalisi rakendusi. See tähendab enamasti, et peaksite kasutama ExecutorServices Y CompletableFutures Java 8 oma puhta ja funktsionaalse stiiliga lõime loomiseks. Kevad võimaldab klassi kaudu ka asünkroonseid taotlusi töödelda Edasilükatud tulemus .
Kujutame ette, et meie ülaltoodud TopTalenti teenus nõuab uue tipptalendi lisamiseks väljumispunkti. Ütleme nii, et mingil väga mõjuval põhjusel peab iga uus nimi olema täpselt 10 tähemärki pikk. Kehtiv viis selleks võib olla:
@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); }
Kuid ülaltoodu (välja arvatud halvasti ehitatud) ei ole tegelikult 'puhas' lahendus. Otsime rohkem kui ühte tüüpi kehtivust (peamiselt see, et TopTalentData
pole null, Y see TopTalentData.name
pole null, Y see TopTalentData.name
on 10 tähemärki), samuti erandi tegemine, kui andmed on valed.
Seda saab teha palju puhtamalt, kasutades Talveunerežiimi valideerija kevadega. Esiteks peame uuesti arvutama addTopTalent
valideerimise toetamiseks:
@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 }
Lisaks peame märkima, millist omadust soovime klassis TopTalentData
kinnitada:
public class TopTalentData { @Length(min = 10, max = 10) @NotNull private String name; }
Nüüd võtab Spring päringu kinni ja kinnitab selle enne meetodi kasutamist - täiendavat käsitsi testimist pole vaja.
Teine viis, kuidas oleksime sama efekti saavutanud, on oma märkuste loomine. Ehkki tavaliselt kasutate kohandatud märkusi ainult siis, kui teie vajadused ületavad Hibernate sisseehitatud piiratud komplekt , selle näite puhul eeldame, et @ Pikkust pole olemas. Teeksite valideerija, mis kontrollib märgistringi pikkust, luues kaks täiendavat klassi, ühe valideerimise ja teise märkimise omaduste jaoks:
@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) }
Pange tähele, et sellistel juhtudel nõuavad probleemide lahutamise parimad tavad kinnisvara kehtivaks märkimist, kui see on nullmeetodis (s == null
| isValid
), ja seejärel kasutage märkust @NotNull
kui see on kinnistu lisanõue:
public class TopTalentData { @MyAnnotation(value = 10) @NotNull private String name; }
Kui Spring'i eelmistes versioonides oli vaja XML-i, siis praegu saab suurema osa seadistustest teha ainult Java-koodi / märkuste kaudu; XML-konfiguratsioonid on vaid täiendav ja mittevajalik standardne tekstikood.
Selles artiklis (nagu ka kaaslases GitHubi hoidlas) kasutatakse kevade seadistamiseks märkusi ja ta teab, millised neist oad see peaks üle kanduma, kuna juurpakett on märgitud liitmärkega @SpringBootApplication
järgmiselt:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Liitmärkus (selle kohta saate lisateavet Kevadine dokumentatsioon ) annab Springile lihtsalt vihje, et pakendite eemaldamiseks tuleks neid skannida oad . Meie konkreetsel juhul tähendab see, et üleandmiseks kasutatakse ülaltoodud pakendi all (co.kukurin) järgmist:
@Component
(TopTalentConverter
, MyAnnotationValidator
)@RestController
(TopTalentController
)@Repository
(TopTalentRepository
)@Service
(TopTalentService
) klassidKui meil oleks annotatsiooniklassid @Configuration
täiendavaid kontrolliks Java baasi konfiguratsioon.
Serverite arendamisel on levinud probleem eristada erinevat tüüpi konfiguratsioone, tavaliselt teie arendus- ja tootmiskonfiguratsioone. Selle asemel, et mitu korda konfigureerimiskirju käsitsi alistada, kui lähete rakenduse testimisest selle juurutamiseni, oleks kasulikum viis kasutada profiile.
Vaatleme juhtumit, kus kasutate kohalikuks arenduseks mälu sisseehitatud andmebaasi koos tootmisel MySQL-i andmebaasiga. See tähendaks sisuliselt seda, et kasutaksite igale URL-ile juurdepääsemiseks erinevat URL-i ja (loodetavasti) erinevaid volitusi. Vaatame, kuidas seda saab teha kahes erinevas konfiguratsioonifailis:
# 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
Eeldatakse, et te ei soovi koodi ülevaatamise ajal kogemata oma tootmise andmebaasis toimingut sooritada, seega on mõistlik seadistada arendamisel vaikeprofiil. Serveris saate konfiguratsiooniprofiili käsitsi alistada, andes parameetri -Dspring.profiles.active=prod
aastal JVM. Teise võimalusena saate oma OS-i keskkonnamuutujale määrata soovitud vaikeprofiili.
Sõltuvussüstimise korralik kasutamine Springiga võimaldab teil kõik oma objektid koos teisaldada, skannides kõik soovitud konfiguratsiooniklassid; see osutub kasulikuks suhete eraldamisel ja muudab testimise ka palju lihtsamaks. Selliste klasside tihedalt sobitamise asemel:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController() { this.topTalentService = new TopTalentService(); } }
Lubame Springil meie jaoks kirjutada:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController(TopTalentService topTalentService) { this.topTalentService = topTalentService; } }
Misko Hevery de Google'i jutt selgitab sõltuvuse süstimise 'miks' põhjalikult, nii et vaatame, kuidas seda praktikas kasutatakse. Murede lahutamise jaotises (Üldine viga nr 3) lõime teenindusklassi ja kontrolleri. Oletame, et tahame kontrollerit testida eeldusel, et TopTalentService
käitub korralikult. Saame praeguse teenuse juurutamise asemele mannekeeni lisada, pakkudes eraldi konfiguratsiooniklassi:
@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; } }
Seejärel saame mannekeeni sisse süstida, käskides Springil kasutada SampleUnitTestConfig
kui teie konfiguratsioonipakkuja:
@ContextConfiguration(classes = { SampleUnitTestConfig.class })
See võimaldab meil kasutada konteksti seadistust uba valmistatud tellimuse järgi katseseadmesse.
javascripti tõrge pole funktsioon
Kuigi üksuste testimise idee on olnud meil juba pikka aega, näivad paljud arendajad seda 'unustavat' (eriti kui see pole midagi, mida nad vajab ) või lisavad selle lihtsalt ebaoluliseks. See on ilmselgelt asi, mida me ei taha juhtuda, kuna testid peaksid mitte ainult kontrollima teie koodi õigsust, vaid olema ka dokumendid selle kohta, kuidas rakendus peaks erinevates olukordades käituma.
Veebiteenuste testimisel teete harva puhtalt 'puhta' üksuse testimist, kuna HTTP-side nõuab tavaliselt DispatcherServlet
kevade kohta ja vaadake, mis juhtub, kui HttpServletRequest
on tegelikult saadud (muutes selle tõendiks) integratsioon , tegeleda valideerimise, väljaandmise väljaandmisega jne). Puhkus tagab , Java DSL REST-teenuste hõlpsaks testimiseks, parem kui MockMVC, on tõestanud, et see võib anda väga elegantse lahenduse. Mõelge järgmisele sõltuvuse süstimisega koodile:
@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
läbib TopTalentService
vale rakenduse a TopTalentController
samal ajal kui kõik teised klassid teisaldatakse standardkonfiguratsiooni abil, mille järeldavad rakendusklassi paketti manustatud skannimispaketid. RestAssuredMockMvc
kasutatakse lihtsalt kerge keskkonna loomiseks ja saadab päringu GET
väljumiskohta /toptal/get
.
Kevad on väga võimas raamistik, mille käivitamine on lihtne, kuid täieliku meisterlikkuse saavutamiseks on vaja teatavat pühendumist ja aega. Kui võtate aega raamistikuga tutvumiseks, parandab see teie tootlikkust pikas perspektiivis ja aitab teil lõpuks puhtam kood kirjutada ja paremaks arendajaks saada.
Kui otsite sellel teemal rohkem materjali, Kevad tegevuses on hea raamat, mis käsitleb paljusid kevade põhiteemasid.