Kevad on vaieldamatult üks populaarsemaid Java-raamistikke ja ka taltsutatav vägev metsaline. Ehkki selle põhimõisteid on üsna lihtne mõista, nõuab tugevaks kevade arendajaks saamine aega ja vaeva.
Selles artiklis käsitleme kevadel levinumaid vigu, mis on spetsiaalselt suunatud veebirakendustele ja Spring Bootile. As Spring Boot'i veebisait osutab, võtab Spring Boot arvamusega seisukoht selle kohta, kuidas tootmiskõlbulikke rakendusi tuleks üles ehitada, nii et selles artiklis püütakse seda vaadet jäljendada ja anda ülevaade mõnest näpunäidetest, mis on hästi kaasatud Spring Boot'i tavalisse veebirakenduste arendusse.
Kui te pole Spring Bootiga eriti kursis, kuid soovite siiski mõnda mainitud asja proovida, olen ma loonud sellele artiklile lisatud GitHubi hoidla . Kui tunnete end artikli ajal mingil hetkel kaotsi, soovitan kloonida hoidla ja mängida kohalikus masinas oleva koodiga.
Lõpetame selle levinud veaga, sest ' pole siin leiutatud ”Sündroom on tarkvaraarenduse maailmas üsna tavaline. Tundub, et selle all kannatavad sümptomid, sealhulgas tavaliselt kasutatava koodi regulaarselt ümber kirjutamine ja paljud arendajad.
Ehkki konkreetse raamatukogu sisemuse ja selle rakendamise mõistmine on enamasti hea ja vajalik (ja see võib olla ka suurepärane õppeprotsess), on teie kui tarkvarainseneri arengule kahjulik pidevalt tegeleda sama madalama taseme rakendusega üksikasjad. On olemas põhjus, miks eksisteerivad sellised abstraktsioonid ja raamistikud nagu Spring, mis on just selleks, et eraldada teid korduvast käsitsi töötamisest ja võimaldada teil keskenduda kõrgema taseme detailidele - oma domeeni objektidele ja äriloogikale.
Nii et võtke omaks abstraktsioonid - järgmine kord, kui olete mõne konkreetse probleemiga silmitsi, tehke kõigepealt kiire otsing ja tehke kindlaks, kas seda probleemi lahendav raamatukogu on juba Springisse integreeritud; tänapäeval on tõenäoline, et leiate sobiva olemasoleva lahenduse. Kasuliku raamatukogu näitena kasutan Projekt Lombok selle artikli ülejäänud näidetes toodud märkused. Lombokit kasutatakse katlakoodigeneraatorina ja loodetaval arendajal ei tohiks loodetavasti olla probleeme raamatukoguga tutvumisel. Näiteks vaadake, mida tavaline Java bean ”Näeb välja nagu Lomboki puhul:
@Getter @Setter @NoArgsConstructor public class Bean implements Serializable { int firstBeanProperty; String secondBeanProperty; }
Nagu võite ette kujutada, koostatakse ü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() { } }
Pange siiski tähele, et peate tõenäoliselt installima pistikprogrammi juhuks, kui kavatsete oma IDE-ga kasutada Lombokit. IntelliJ IDEA pistikprogrammi versioon on leitav siin .
kuidas lisada Wordpressile api
Oma sisemise struktuuri paljastamine pole kunagi hea mõte, sest see loob teenuse kujundamisel paindumatuse ja soodustab seetõttu halbu kodeerimistavasid. Lekkivad sisemised osad ilmnevad andmebaasistruktuuri teatud API-punktidest juurdepääsetavaks muutmise kaudu. 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 lõpp-punkt, mis peab juurde pääsema TopTalentEntity
andmed. Nii ahvatlev kui tagasipöördumine võib olla TopTalentEntity
juhtudel oleks paindlikum lahendus uue klassi loomine, et tähistada TopTalentEntity
andmed API lõpp-punkti kohta:
@AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; }
Nii ei nõua andmebaasi back-endis muudatuste tegemine teeninduskihis täiendavaid muudatusi. Mõelge, mis juhtuks juhul, kui TopTalentEntity
väljale parool lisatakse kasutajate parooliräsi andmebaasi salvestamiseks - ilma pistikuta nagu TopTalentData
, paljastaksite teenuse esiotsa muutmise unustades kogemata väga ebasoovitava salajase teabe!
Kui teie rakendus kasvab, hakkab koodide korraldamine muutuma üha olulisemaks küsimuseks. Irooniline, et enamus headest tarkvaratehnika põhimõtetest hakkavad mastaapselt lagunema - eriti juhtudel, kui rakenduse arhitektuuri kujundamisele pole palju mõeldud. Üks levinumaid vigu, mida arendajad kipuvad järele andma, on koodiprobleemide segamine ja seda on äärmiselt lihtne teha!
Mis tavaliselt puruneb murede lahusus on lihtsalt uue funktsionaalsuse „viskamine” olemasolevatesse klassidesse. See on loomulikult suurepärane lühiajaline lahendus (alustajate jaoks nõuab see vähem tippimist), kuid paratamatult muutub see probleemiks teel edasi, olgu see siis testimise, hoolduse või kuskil 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()); } }
Esialgu ei pruugi tunduda, et sellel koodilõigul on midagi eriti valesti; see pakub TopTalentData
loendi mida otsitakse TopTalentEntity
-st juhtumeid. Lähemalt vaadates näeme aga, et tegelikult on mõned asjad, mis TopTalentController
esineb siin; nimelt on see konkreetse lõpp-punkti taotluste kaardistamine, hoidlast andmete hankimine ja TopTalentRepository
-lt saadud üksuste teisendamine teises vormingus. Puhtam lahendus oleks nende probleemide lahutamine 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 klassi nime kontrollides kindlaks teha funktsionaalsuse asukoha. Pealegi võime testimise käigus vajaduse korral mõne klassi hõlpsasti asendada mõnitava rakendusega.
Järjepidevuse teema ei pruugi tingimata olla ainuüksi Springile (või Java-le), kuid on siiski oluline aspekt, mida tuleb kevade projektide kallal töötamisel arvesse võtta. Kuigi kodeerimisstiil võib olla vaieldav (ja see on tavaliselt meeskonnas või kogu ettevõttes kokkuleppe küsimus), osutub ühise standardi omamine suureks tootlikkuse abivahendiks. See kehtib eriti mitme inimese meeskondade kohta; järjepidevus võimaldab kätteandmist ilma, et palju ressursse kulutataks käes hoidmisele või esitataks pikki selgitusi erinevate klasside kohustuste kohta
Mõelge kevadisele projektile koos selle erinevate konfiguratsioonifailide, teenuste ja kontrolleritega. Nende nimetamisel semantiliselt järjepidev olemine loob hõlpsasti otsitava struktuuri, kus iga uus arendaja saab koodi ümber käia; konfigureerimisliidete lisamine teie konfiguratsiooniklassidele, teenuse järelliited teie teenustele ja kontrolleri sufiksid teie kontrolleritele.
Järjepidevuse teemaga tihedalt seotud vääritega töötlemine serveripoolel väärib erilist rõhutamist. Kui pidite kunagi käsitsema halvasti kirjutatud API erandivastusi, siis ilmselt teate, miks - erandite õigesti sõelumine võib olla piin ja veelgi valusam põhjuse väljaselgitamine, miks need erandid üldse tekkisid.
API-arendajana soovite ideaalis katta kõik kasutajale suunatud otspunktid ja tõlkida need tavalisse veavormingusse. See tähendab tavaliselt üldise veakoodi ja kirjelduse olemasolu, mitte kopeerimislahendust a) teate „500 sisemise serveri viga” tagastamiseks või b) korstnajälje lihtsalt kasutajale viskamiseks (mida peaks tegelikult iga hinna eest vältima) kuna see paljastab teie sisemisi osi lisaks sellele, et seda on keeruline kliendipoolselt käsitseda).
Tavalise tõrketeate vormingu näide võib olla:
@Value public class ErrorResponse { private Integer errorCode; private String errorMessage; }
Midagi sarnast on tavaliselt kõige populaarsemates API-des ja see töötab hästi, kuna seda saab hõlpsalt ja süstemaatiliselt dokumenteerida. Erandite sellesse vormingusse tõlkimise saab anda @ExceptionHandler
meetodi annotatsioon (annotatsiooni näide on levinud veast nr 6).
Sõltumata sellest, kas seda leidub töölaua- või veebirakendustes, kevad või mitte kevad, võib multilõngamine olla kõva pähkel. Programmide paralleelse käivitamise põhjustatud probleemid on närvesöövalt raskesti tabatavad ja neid on sageli äärmiselt raske siluda - tegelikult tuleneb probleemi olemusest, kui mõistate, et tegelete paralleelse käivitamise probleemiga peate siluri täielikult loobuma ja kontrollima oma koodi käsitsi, kuni leiate algvea põhjuse. Kahjuks ei eksisteeri selliste küsimuste lahendamiseks küpsiste lõikuri lahendust; sõltuvalt konkreetsest juhtumist peate olukorda hindama ja seejärel ründama probleemi nurga alt, mida peate kõige paremaks.
Ideaalis sooviksite muidugi mitmikeermelisi vigu täielikult vältida. Jällegi ei ole selleks kõigile ühesugust lähenemisviisi, kuid siin on mõned praktilised kaalutlused silumiseks ja mitmikeermeliste vigade ennetamiseks:
Esiteks pidage alati meeles 'globaalse riigi' küsimust. Kui loote mitmetoimelist rakendust, tuleks hoolikalt jälgida ja võimalusel täielikult eemaldada kõike, mis on globaalselt muudetav. Kui on põhjust, miks globaalne muutuja peab muutma jääma, kasutage seda hoolikalt sünkroniseerimine ja jälgige oma rakenduse toimivust, et veenduda, et see pole hiljuti kehtestatud ooteperioodide tõttu aeglane.
See pärineb otse funktsionaalne programmeerimine ning OOP-ga kohandatud väidab, et tuleks vältida klassi muutlikkust ja oleku muutmist. Lühidalt tähendab see setter-meetoditest loobumist ja privaatsete lõplike väljade olemasolu kõigis oma mudeli klassides. Ainus kord, kui nende väärtused muteeruvad, on ehituse ajal. Nii võite olla kindel, et vaidlusprobleeme ei teki ja objekti omadustele juurdepääs annab alati õiged väärtused.
Hinnake, kus teie rakendus võib probleeme tekitada, ja logige kõik olulised andmed ennetavalt sisse. Vea ilmnemisel olete tänulik, et teil on teavet selle kohta, millised taotlused on saadud, ja saate parema ülevaate selle kohta, miks teie rakendus ebaõigesti käitus. Jällegi on vaja arvestada, et logimine toob sisse täiendava faili I / O ja seetõttu ei tohiks seda kuritarvitada, kuna see võib teie rakenduse toimimist tõsiselt mõjutada.
Alati, kui vajate oma lõimete kudemist (nt erinevatele teenustele asünkroonitaotluste esitamiseks), kasutage oma lahenduste loomise asemel uuesti olemasolevaid turvalisi rakendusi. See tähendab enamasti kasutamist ExecutorServices ja Java 8 korralik funktsionaalne stiil CompletableFutures lõime loomiseks. Spring võimaldab ka asünkroonset päringute töötlemist Edasilükatud tulemus klass.
ostujõu porteri viis jõudu
Kujutame ette, et meie varasem TopTalenti teenus nõuab uute tipp-talentide lisamiseks lõpp-punkti. Oletagem, et mingil tõeliselt mõjuval põhjusel peab iga uus nimi olema täpselt 10 tähemärki pikk. Üks viis selle tegemiseks võib olla järgmine:
@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 (lisaks halvasti ülesehitatud) ei ole tegelikult „puhas” lahendus. Kontrollime rohkem kui ühte tüüpi kehtivust (nimelt, et TopTalentData
ei ole null, ja see TopTalentData.name
pole null, ja see TopTalentData.name
on 10 tähemärki), samuti erandi tegemine, kui andmed on valed.
Seda saab rakendades palju puhtamalt teostada Talveunerežiimi valideerija kevadega. Refrakteerime kõigepealt addTopTalent
meetod 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 TopTalentData
-is märkima, millist omadust soovime kinnitada klass:
public class TopTalentData { @Length(min = 10, max = 10) @NotNull private String name; }
Nüüd võtab Spring taotluse kinni ja kinnitab selle enne meetodi kasutamist - pole vaja kasutada täiendavaid käsitsi teste.
Teine viis, kuidas oleksime sama saavutanud, on oma märkuste loomine. Kuigi tavaliselt kasutate kohandatud märkusi ainult siis, kui teie vajadused ületavad Hibernate sisseehitatud piirangute komplekt , selle näite jaoks teeskleme, et @ Pikkust pole olemas. Teeksite valideerija, mis kontrollib stringi pikkust, luues kaks täiendavat klassi, ühe valideerimiseks ja teise omaduste märkimiseks:
@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 nendel juhtudel nõuavad probleemide lahutamise parimad tavad kinnisvara kehtivaks märkimist, kui see on null (s == null
meetodi isValid
sees) ja seejärel kasutage @NotNull
märkus, kui see on kinnistu lisanõue:
public class TopTalentData { @MyAnnotation(value = 10) @NotNull private String name; }
Kui Spring oli eelmiste Springsi versioonide jaoks hädavajalik, siis tänapäeval saab suurema osa seadistustest teha ainult Java-koodi / märkuste abil; XML-konfiguratsioonid kujutavad endast lihtsalt täiendavat ja mittevajalikku katlakoodi koodi.
See artikkel (ja ka sellega kaasnev GitHubi hoidla) kasutab märkeid kevade ja kevade konfigureerimiseks. Teab, milliseid ube see peaks ühendama, kuna juurpakett on märgitud tähisega @SpringBootApplication
liitmärkus, näiteks:
@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, milliseid pakendeid tuleks ubade hankimiseks skannida. Meie konkreetsel juhul tähendab see, et juhtmete jaoks kasutatakse ülemise (co.kukurin) pakendi all järgmist:
@Component
(TopTalentConverter
, MyAnnotationValidator
)@RestController
(TopTalentController
)@Repository
(TopTalentRepository
)@Service
(TopTalentService
) klassidKui meil oleks veel täiendavaid @Configuration
märkustega klassides kontrollitakse ka Java-põhist konfiguratsiooni.
Serveri arendamisel on sageli probleemiks erinevate konfiguratsioonitüüpide - tavaliselt teie tootmis- ja arenduskonfiguratsioonide - eristamine. Selle asemel, et asendada käsitsi erinevad konfiguratsioonikirjed iga kord, kui lähete üle testimiselt rakenduse juurutamisele, oleks tõhusam viis kasutada profiile.
Vaatleme juhtumit, kus kasutate kohaliku arenduse jaoks mälusisest andmebaasi, mille tootmisel on MySQL-i andmebaas. Sisuliselt tähendaks see, et kasutate mõlemale juurdepääsemiseks erinevat URL-i ja (loodetavasti) erinevaid volitusi. Vaatame, kuidas seda saaks teha kaks erinevat konfiguratsioonifaili:
# 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
Eeldatavasti ei taha te koodiga nokitsedes oma tootmisandmebaasis kogemata ühtegi toimingut sooritada, seega on mõttekas vaikeprofiil seada devile. Serveris saate seejärel konfiguratsiooniprofiili käsitsi alistada, andes -Dspring.profiles.active=prod
parameeter JVM-ile. Teise võimalusena saate oma OS-i keskkonnamuutujale määrata ka soovitud vaikeprofiili.
Sõltuvussüstimise korralik kasutamine Springiga tähendab seda, et lubate tal kõik oma objektid kokku juhtida, skannides kõiki soovitud konfiguratsiooniklassid; see osutub kasulikuks suhete lahutamiseks ja muudab testimise palju lihtsamaks. Selle asemel, et tihedalt siduda klassid, tehes midagi sellist:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController() { this.topTalentService = new TopTalentService(); } }
Lubame Springil juhtmestiku meie jaoks teha:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController(TopTalentService topTalentService) { this.topTalentService = topTalentService; } }
Misko Hevery Google'i jutt selgitab põhjalikult sõltuvuse süstimise „miks”, nii et vaatame hoopis, kuidas seda praktikas kasutatakse. Murede lahutamist käsitlevas jaotises (levinud vead nr 3) lõime teenuse ja kontrolleri klassi. Oletame, et tahame kontrollerit testida eeldusel, et TopTalentService
käitub õigesti. Teenuse tegeliku juurutamise asemele saab sisestada mõnitava objekti, pakkudes eraldi konfiguratsiooniklassi:
iooniline 1 kuni iooniline 2
@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; } }
Siis saame proovieseme süstida, käskides Springil kasutada SampleUnitTestConfig
konfiguratsiooni tarnijana:
@ContextConfiguration(classes = { SampleUnitTestConfig.class })
See võimaldab meil kasutada kontekstikonfiguratsiooni, et sisestada kohandatud uba ühikutesti.
Ehkki ühikutestimise idee on olnud meil juba pikka aega, näib, et paljud arendajad kas 'unustavad' seda teha (eriti kui see pole nii nõutud ) või lisage see lihtsalt tagantjärele. See pole ilmselgelt soovitav, sest 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 ainult puhtaid ühikuteste, kuna HTTP kaudu toimuv suhtlus nõuab tavaliselt Springsi DispatcherServlet
ja vaadake, mis juhtub, kui tegelik HttpServletRequest
on vastu võetud (muutes selle integratsioon testimine, valideerimine, jadastamine jne). PUHKUS kindel , Java DSL REST-teenuste hõlpsaks testimiseks, lisaks MockMVC-le, on osutunud väga elegantseks lahenduseks. Mõelge järgmisele sõltuvuse süstimisega koodijupile:
@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
esitab TopTalentService
mõnitava rakenduse sisse TopTalentController
samal ajal kui kõik teised klassid on juhtmega ühendatud, kasutades rakendusklassi paketist juurdunud pakettide põhjal tuletatud standardset konfiguratsiooni. RestAssuredMockMvc
kasutatakse lihtsalt kerge keskkonna seadistamiseks ja GET
taotlus /toptal/get
lõpp-punkt.
Kevad on võimas raamistik, millega on lihtne alustada, kuid täieliku meisterlikkuse saavutamiseks on vaja pühendumist ja aega. Kui võtate aega raamistikuga tutvumiseks, parandab see teie pikas perspektiivis kindlasti teie tootlikkust ja aitab lõpuks kirjutada puhtama koodi ja saada paremaks arendajaks.
Kui otsite täiendavaid ressursse, Kevad tegevuses on hea praktiline raamat, mis hõlmab paljusid kevade põhiteemasid.