spring boot
Tartalomjegyzék
alapok
-
Program módosítás frissítése
A pom.xml-be szükséges egy bejegyzés, hogy ne kelljen program módosításkor az alkalmazást leállítani és újraindítani:
Továbbá a netbeans-ben a project properties menünél: compile: compile on save: yes
-
No war!
Ne készítsünk war fájlt. A spring boot arra lett kitalálva hogy a jar fájlt futtassunk,
melyet nem kell telepíteni alkalmazás szerverre.
Ha mégis war fájlra lenne szükség:
war készítése
-
Futtatás "java -jar" módon
A pom.xml-be be szükséges a "spring-boot-maven-plugin":
Ez a plugin arra szolgál, hogy a project build-nél előállít egy nagy méretű, kb. 20 Mbyte-os jar-t is (fat jar),
ami az összes függőséget tartalmazza, tehát tartalmazza a spring-es jar-okat is.
Az eredeti jar is megmarad egy ".jar.original" kiterjesztéssel.
A "fat jar" viszont így már önmagában képes futni:
Dokumentáció
-
Jar fájl futtatása esetén System.exit() metódussal leállítható az egész webalkalmazás,
pont úgy mint egy sima java application esetén.
probléma, megoldás
scanBasePackages
-
Látszólag minden jól van beállítva a programban, de futtatásnál beállítási hiányosságokat jelez a spring.
-
Megoldás: ellenőrizzük, hogy az alkalmazást futtató osztályban (pl. Run.java),
jól van-e beállítva az a package, mely alatt a spring a bean-eket keresi.
Tehát ennek az annotációnak a helyes beállítása alapvető:
Can't call url
-
A log-ok alapján látszólag elindult az alkalmazás, a log nem tartalmaz hibaüzenetet,
azonban az alkalmazás egyetlen url-je sem hívható, mintha el sem indult volna.
-
Lehetséges, hogy az alkalmazás mégsem indult el teljesen. Például tartalmaz egy event listener-t,
ami adatbázis műveletet végez, az pedig végtelen ideig egy adatbázis lock miatt várakozik.
Ezért nincs hibaüzenet a log-ban. Csak az event listener után indul az embedded tomcat, ezután hívható
az alkalmazás. Ha a tomcat elindult, azt mindenképp látjuk a log-ban ebben a formában:
Sőt, a log végének tartalmaznia kell az elindítás idejét is, például:
vagy
Így tehát, a log-ot megvizsgálva következtethetünk a problémára.
Circular reference
-
Nem indul el az alkalmazás, a következő hiba keletkezik:
Tehát látszólag a spring-bean-ek körbehivatkozzák egymást, és a spring nem tudja hogy hozza őket létre.
-
Megoldás: a @Lazy annotációval jelezzük a spring-nek, hogy csak később hozza létre azt a bean-t
amelyikkel probléma van:
StackOverflowError
-
Ennek több oka lehet. Lehetséges, hogy egy java metódusban exception keletkezik, és az AOP be van állítva
ennek a hibának a kezelésére. Azonban az aop-metódus is meghívja ezt a hibás metódust,
melyben ismét exception keletkezik, és ismét az aop-metódusra kerül a vezérlést ...stb.
A végén a kimeneten csak StackOverflowError keletkezik, a valódi hiba rejtve marad.
-
Megoldás:
az AOP-ban a hiba kezelő metódus legelőszőr a LOG-ba írja bele a hibát, így a hiba biztos látszódni fog.
Array binding
-
Html űrlap esetén ha egyetlen input mező tartalmát tömbként, list-ként fogadja a form objektum,
és mező tartalmaz vessző karaktert, akkor a spring az adatot szétszedi a vesszők mentén több tömbelemre,
az adat nem marad egyben. Gyakorlatilag ez akkor fordul elő, amikor ugyanolyan néven több checkbox van,
de csak egyetlen van kipipálva, és annak value-ja ráadásul vesszővel elválasztva tartalmaz adatot.
-
Megoldás: form objektumban tömböt, list-et csak checkbox-ok esetén alkalmazzunk, abban az esetben ha a megjelenő
checkboxok számát a model szabályozza. Továbbá a checkboxok value attribútuma semmiképp se tartalmazzon
vessző karaktert.
-
Működés: a text ➔ String[] konverziót a StringArrayPropertyEditor osztály valósítja meg,
a konstruktorban megadható más karakter, vagy akár null is:
Sajnos arra nem ad megoldást amikor text ➔ List<String> konverzió történik.
Ezekre az esetekre saját PropertyEditor-t kell fejleszteni.
Model has no value for key
-
A model has no value for key hibaüzenet esetén a stack trace nem tartalmaz általunk írt java sorban
keletkező hibát. Ebből arra lehet következtetni, hogy a hiba nem az általunk írt java kódban van.
Ilyen eset amikor egy controller nem a megfelelő query paraméterrel van meghívva, de ilyen eset az is
amikor a kontroller lefut, de a választ nem tudja a spring feldolgozni. Ezesetben pont az utóbbi fordul elő:
Ugyanis a spring okoskodik, és ha az url-ben kapcsos zárójelet talál, akkor megpróbálja feloldani a modelben
található kulcs alapján.
Ha pedig nem talál ilyet, akkor exception keletkezik ezzel a hibaüzenettel:
@ApplicationScope
-
Application scope bean létrehozása, mely ezzel egyenértékű:
-
Ennek az annotációnak a használata nem szükséges, ha elhagyjuk, akkor is egyetlen példány lesz a spring bean-ből.
-
Az alkalmazás indulásakor a spring framework az összes application scope bean-t automatikusan létrehozza,
tehát nem várja meg hogy hivatkozzon rá valami eljárás.
Egy adott application scope bean-nél ezt úgy lehet megakadályozni, hogy ellátjuk
@Lazy annotációval is. Ebben ez esetben a bean csak akkor fog létrejönni amikor megtörténik a rá való
hivatkozás a futó programkódban.
A session, request, prototype bean-ek viszont nem jönnek létre automatikusan a program indulásakor,
csak amikor a futó programkód hivatkozik rájuk.
@Autowired
-
Az @Autowired annotáció csak spring bean-en belül működik.
-
2 spring bean összekötésére szolgál. Bármilyen scope-nál működik.
Ha application scope bean (A) hivatkozik request scope bean-re (R) akkor minden request-nél
más-más (R) bean jön létre (A) bean-ben.
Ez akkor is működik ha a konstruktornál használjuk az @Autowired annotációt.
-
El lehet vele érni olyan objektomokat is, amit nem mi állítottunk be spring bean-nek, hanem a keretrendszer:
-
Ha már egy bean konstruktorában hozzá akarunk férni másik bean-hez akkor a konstruktor szintjén kell
definiálni az @Autowired-t.
(Különben null értéket kapnánk a bean-re hivatkozva)
-
Működés: az @Autowired annotációval nem a megnevezett osztály egy példányát kapjuk meg,
hanem egy BEAN PROXY-t, lásd:
így valószínű hogy egy request scope bean (R) esetén is csak 1 bean proxy példány van, és ennek metódusát hívva,
más-más bean-hez delegálja tovább a kérést.
-
Bean nevesítés:
Akkor hasznos ha ugyanolyan típusú spring bean-ből több van, és így a típus alapján a spring nem tudná
meghatározni melyik bean-re hivatkozunk.
Ekkor a @Qualifier annotációt is alkalmazni kell:
@Bean
-
Metódusra alkalmazható annotáció egy konfigurációs osztályon belül.
A metódus által visszaadott objektum példány spring bean lesz.
A spring bean default neve a metódusnév lesz.
-
A default bean név helyett megadható egy másik név:
@Component
-
Ez egy régi annotáció, mely helyett inkább ezek egyikét használjuk:
@Configuration
-
Osztályra alkalmazható annotáció, az osztályból konfigurációs osztály lesz, mely spring bean.
@ConfigurationProperties
-
A @ConfigurationProperties annotációt használhatjuk például egy DataSource-t visszaadó metódusra,
jelezvén hogy milyen jellemzőket kell beolvasni a DataSource felépítéséhez.
-
A @ConfigurationProperties annotációt használhatjuk spring-bean osztályra is,
amikor az osztály tagjai az application.yml egy-egy jellemzőjének felelnek meg (CP-osztály).
-
A @ConfigurationProperties paramétereként megadható egy prefix, melyen belül olvassuk a jellemzőket,
így általában az application.yml csak egy részét használjuk.
-
A CP-osztály akkor is hasznos, ha az application.yml-ből összetett típusba (List, Map)
szeretnénk beolvasni, mert erre a @Value nem alkalmas.
application.yml:
CP-osztály:
-
Ha a CP-osztályban sima Map-et definiálunk statikus típusként,
akkor a spring LinkedHashMap-et használ dinamikus típusként,
így a map kulcsainak sorrendje megegyezik a yaml fájlban definiált kulcsok sorrendjével!
-
A CP-osztály tagjai @Value annotációval is elláthatók, így hozzáférhetünk bármelyik application.yml jellemzőhöz,
még azokhoz is amelyek nem a prefix-en belül vannak.
-
A CP-osztályban beállított tagok lehetnek null értékűek is, ha az application.yml nem tartalmaz ilyen jellemzőt.
Ez a probléma kiküszöbölhető, ha az osztály tagjaira validáció van előírva.
-
Ha a CP-osztályban String-be olvasunk be adatot, és az application.yml megfelelő tagja null, akkor a CP-osztályban
ez nem null lesz, hanem üres String.
-
Validáció
-
pom.xml kiegészítése:
-
A CP-osztály ellátása @Validated annotációval (org.springframework.validation.annotation package)
-
A CP-osztály kívánt tagjainak ellátása valamelyik annotációval:
@NotNull, @NotEmpty, @Size, ...
(javax.validation.constraints package)
-
Baeldung példa
@Controller
-
Osztályra alkalmazható annotáció, az osztályból controller lesz, mely spring bean.
-
Az osztályt nem szokás ellátni scope-annotációval, így egyetlen példány lesz belőle.
-
A spring bean default neve az osztálynév lesz kis kezdőbetűvel.
-
A default bean név helyett megadható egy másik név, hiszen egy projektben lehetséges hogy például
van két HelpController nevű osztály, melyeknek ugyanaz lenne a nevük, ami nem megengedett. Ennek elkerülése:
@ControllerAdvice
-
Controllereknek adott javaslatok beállítása.
-
Például az űrlapmezők editorának beállítása, mely kiterjed a teljes alkalmazásra:
@CookieValue
-
Ezzel az annotációval lehet beolvasni egy cookie értékét, például egy controller metódusban.
-
Alapesetben a beolvasott cookie-nak léteznie kell, különben exception keletkezik.
Ha nem kötelező léteznie, akkor állítsuk be a required = false paramétert, így ha a cookie nem létezik,
akkor null érték kerül a változóba, és exception sem keletkezik.
-
Példa a "JSESSIONID" beolvasására (a kis és nagybetűk számítanak).
Ha nincs ilyen cookie akkor default értékként "?" -et vesz fel a változó:
Példa a "teszt" nevű cookie beolvasására, a spring itt adatkonverziót is végez.
Ha a cookie nem létezik akkor a "t" változóba null kerül:
-
Cookie írására a spring nem ad speciális módszert, hanem elkérve a HttpServletResponse objektumot,
ahhoz kell a cookie-t hozzáadni:
-
JSESSIONID cookie
Ennek tartalma megegyezik a HttpSession.getId() értékkel. Azonban ha egy webalkalmazást újratelepítünk,
akkor hiába marad meg a JSESSIONID cookie a web böngészőben, és küldi fel a következő http-kéréskor
a webalkalmazásnak, az egy érvénytelen JSESSIONID-nek érzékeli, és új értékkel írja felül.
@CrossOrigin
-
A cross origin engedélyezést lehet megvalósítani ezzel az annotációval.
-
A @CrossOrigin annotáció metódusra, vagy egy egész osztályra is alkalmazható.
-
Ha a hívás mindenhonnan engedélyezett, az többféleképpen is írható:
-
A hívás csak az adott helyekről engedélyezett:
-
Globálisan is beállítható a cross origin az egész alkalmazásra.
Egy @Configuration annotációval ellátott osztályba, mely implementálja a WebMvcConfigurer-t,
el kell helyezni ezt a metódust:
Így sehol nem kell használni a @CrossOrigin annotációt.
-
Figyelem!
A cross origin miatt a programban lévő interceptorhoz befuthat OPTIONS típusú kérés is.
Ezzel vizsgálja a kliens hogy a szerver hívható-e. A szerver a http válaszban jelzi, hogy engedélyezett-e
a hívás (ezt a spring végzi).
Fontos hogy az interceptor ezeket a kéréseket ne akadályozza, ezért az interceptor preHandle()
metódusát ezzel kell kezdeni:
-
dokumentáció
@DateTimeFormat
-
Form bean annotáció mellyel adott mezőre beállítható a dátum formázás.
-
Amennyiben egy globális initbinder-ben már beállítottunk formattert a Date adattípusra,
a @DateTimeFormat nem jut érvényre.
-
Példa a form bean egy tagjának formázására:
@DependsOn
-
A @DependsOn annotációval beállítható, hogy a spring az annotációval megjelölt bean-t
csak az után hozza létre,
miután az annotációban megadott nevű bean-eket már létrehozta.
-
Az elsoBean csak az után jön létre, hogy létrejött a masodikBean és a harmadikBean:
@EventListener
-
Ha az alkalmazás indításával egyidőben szeretnénk programkódot futtatni,
akkor az @EventListener lehet rá megoldás.
-
Az event listener több különböző eseményre is ráköthető.
Ilyen esemény a ContextRefreshedEvent, amikor már a spring context teljesen kész állapotban van,
viszont a http kiszolgáló még nincs aktiválva, vagyis a felhasználók még nem hívhatják meg az alkalmazást.
-
Egy másik esemény az ApplicationStartedEvent, amikor a spring alkalmazás már teljesen elindult,
a http kiszolgáló is aktiválva van.
-
A metódus nevek nem számítanak, csak a metódus paramétere dönti el milyen eseményre fog lefutni.
-
Az event listener használatára egy jó példa, amikor memória adatbázist használunk, és az alkalmazás
indulásakor szeretnénk felépíteni az adatbázis struktúráját, de amíg ez nincs kész, a felhasználók
nem hívhatják meg az alkalmazást. Az Application context beállítását is el lehet itt végezni.
Továbbá kiírjuk a konzolra, hogy az alkalmazás 100%-ban elindult.
@GetMapping
-
Rövidítése a következőnek:
@InitBinder
-
Az @InitBinder annotációval megjelölt metódusban form bean, és http request paraméter-re vonatkozó
beállítások adhatók meg.
Ilyen beállítás például converter, validátor hozzárendelése form bean-hez és request paraméterhez.
-
Ha az @InitBinder-ben nem adunk meg neveket, akkor mindenre vonatkozik.
-
Az @InitBinder megadható globálisan, vagyis minden controller-re vonatkozóan,
ha a @Configuration és a @ControllerAdvice annotációval is ellátott osztályban alkalmazzuk.
-
Az @InitBinder megadható controller-re vonatkozóan, ha a controlleren belüli metódusra alkalmazzuk.
-
@InitBinder példák egy controlleren belül. A form bean model attribútum neve legyen: "bean"
-
A form bean 2 mezőjét máshogy formázzuk:
-
A form bean összes dátum mezőjét ugyanúgy formázzuk:
-
A form bean-hez standard spring validátort rendelünk.
Ha elhagynánk a "bean" megnevezést akkor a controlleren belüli összes form bean-re érvényes lenne.
-
A "d" nevű request paramétert ÉÉÉÉ formában kapjuk http get-tel, és egy Date típusban fogadjuk:
A konverzióhoz ez szükséges:
-
A http post request body részében json-t kapunk, melyet a MuveletJson osztályba konvertálunk:
A MuveletJson osztályt szeretnénk validálni a MuveletValidator osztállyal:
-
Editorok regisztrálására az @InitBinder-ben:
@Lazy
-
A @Lazy annotációval jelezhető, hogy valamilyen spring bean-t később hozzon létre a spring framework.
-
Ha egy @Configuration annotációval ellátott osztályt megjelöljük a @Lazy annotációval is,
akkor az osztályban lévő @Bean-nel jelölt spring bean-ek is csak később jönnek létre,
amikor már futó programkód hivatkozik rájuk.
-
Ha egy @Component-nel ellátott osztályt ellátjuk a @Lazy annotációval is,
és erre az osztályra hivatkozó @Autowired is el van látva a @Lazy annotációval,
akkor ez a spring bean is csak később jönnek létre, amikor már futó programkód hivatkozik rá.
@ModelAttribute
-
A @ModelAttribute két helyen használható:
- Controller metódus megjelölése.
- Controller metódus paraméterének megjelölése.
-
Controller metódus megjelölése:
A @ModelAttribute -tal jelölt metódusokat a spring automatikusan mindig lefuttatja ha ehhez a controllerhez
kerül a vezérlés,
méghozzá úgy, hogy a @ModelAttribute -tal jelölt metódusok futnak le legelőszőr, majd utána fut le a
@RequestMapping -gel jelölt metódus.
A @ModelAttribute -tal jelölt metódusnak átadható paraméter is, például a @RequestParam,
vagyis ugyanazok amik átadhatók egy @RequestMapping -gel jelölt metódusnak is.
A példában a metódus visszatérési értéke töltődik a model-be az annotációnál megadott névvel,
tehát a pontos idő kerül a model-be "datum" kulccsal:
A @ModelAttribute -tal jelölt metódus több adatot is beállíthat a model-be. Ekkor nem kell semmit beállítani
a @ModelAttribute annotáció paramétereként, viszont a metódusba be kell kérni paraméterként a Model-t,
hogy ebbe több adatot is be lehessen állítani:
-
Controller metódus paraméterének megjelölése:
Tipikusan form submit-ot fogadó metódusban fordul elő, amikor a model-be amúgy is bekerülő form bean model-ben
elfoglalt attribútum nevét módosítjuk ezzel az annotációval. Itt a model-be "bean" névvel kerül be az adatokkal
feltöltött form bean:
@PathVariable
-
URL útvonalon lévő paraméter kezelésére szolgál.
-
A "required = false" beállítás nem működik, vagy nem úgy ahogy sejtjük.
Ugyanis ha az url-ből kihagyjuk az értéket akkor a controller handler nem kezeli le, mivel nem ez az URL !
-
Példa:
@PostConstruct
-
A @PostConstruct annotációval ellátott metódus azért előnyösebb mint a konstruktor,
mert ekkor már megtörtént a bean-ben a dependency injection.
A konstruktorban hiába hivatkozunk az @Autowired, vagy @Value által jelölt változókra,
azok még null értéket tartalmaznak.
-
Tehát egy bean létrehozásakor a spring lefuttatja a konstruktort, majd végrehajtja a dependency injection-t,
és utána lefuttatja a @PostConstruct annotációval ellátott metódus.
-
A bean életciklusa során csak egyszer hívódik meg a @PostConstruct annotációval ellátott metódus.
@PostMapping
-
Rövidítése a következőnek:
@Primary
-
A @Primary annotációt akkor érdemes használni, ha több azonos típusú spring-bean van.
A @Primary abban segít, hogy ne kelljen minden spring-bean-re név szerint hivatkozni.
Ugyanis ha nem használunk nevet (csak a típust), akkor a spring azt a bean-t használja amelyik a
@Primary annotációval van ellátva.
Példa:
Ezesetben a @Primary miatt az egyik bean-re úgy lehet hivatkozni, hogy nem adjuk meg a nevét,
a másikat pedig csak név alapján lehet elérni:
@Profile
-
Program argumentumként vesszővel elválasztva megadható több aktív profile, például:
Ha ez meg van adva, akkor felülírja az application.yml-ben megadott értéket!
-
application.yml-ben megadva az aktív profile-ok:
-
A @Profile annotációval lehet jelezni hogy az adott metódus vagy osztály csak akkor hajtódjon végre
ha a felsorolt profile-ok közül legalább egy aktív.
Tehát ha a beállítjuk hogy az "xx" és "yyy" profile is aktív:
akkor ezek a metódusok mind végrehajtódnak:
-
A @Profile a következőkkel együtt használható:
-
Aktív profile-ok lekérdezése Environment-ből:
@Qualifier
-
Spring bean-re való hivatkozásnál a bean megnevezésére szolgál,
az @Autowired annotációval együtt használatos.
A használatát lásd az @Autowired-nél!
@Repository
-
Ezt az annotációt olyan spring-bean-re kell alkalmazni mely adatbázis műveleteket végez, vagyis DAO osztályokra.
Pontosabban kifejezi a spring-bean funkcióját, mintha a @Component-et alkalmaznánk rá.
@RequestBody
-
A @RequestBody annotációval jelezhető, hogy a http request body egy változóba kerüljön.
-
A @RequestBody egyetlen paramétere a "required".
Default értéke true, így ha nem jön adat a http request body-ban, az exceptiont okoz.
-
Példa JSON fogadásra Map-be töltéssel:
Tudja fogadni még az összetettebb json struktúrákat is.
Például: {"x" : 12, "y" : [1,2,3]} esetén a fogadoMap-be "y" kulcsnál egy List objektumot rak be.
Például: {"x" : 12, "y" : {"aa" : 4000, "bb" : true}} esetén a fogadoMap-be "y" kulcsnál egy Map objektumot rak.
-
Példa JSON fogadásra, egy java osztállyal:
Ha a json-ben egy várt adat hiányzik, akkor a fogadoJson megfelelő helyére null kerül.
Ha a json több adatot tartalmaz, ezek figyelmen kívül lesznek hagyva.
Eltérő adattípus exception-t okoz, de például a json-ben kapott string "13" -ast átkonvertálja,
ha a fogadoJson Integer típus-ba kéri.
-
Példa XML fogadásra, egy java osztállyal:
az xml-t fogadó java osztály:
mely fogadni tudja a következő xml-t:
Feltétlenül szükséges az xml-t fogadó osztályban az @XmlRootElement(name = "..."),
ahol a "..." az xml gyökérelemének a neve.
Ha az xml-ben egy várt adat hiányzik, akkor a fogadoXml megfelelő helyére null kerül.
Ha az xml több adatot tartalmaz, ezek figyelmen kívül lesznek hagyva.
Eltérő adattípus NEM OKOZ exception-t, a fogadoXml megfelelő helyére null kerül (mivel nincs xsd alapú ellenőrzés).
-
Példa BYTE-SOROZAT fogadására, byte-tömbbel:
Bármilyen típusú adatot tud fogadni, nem okoz problémát az üres http request body sem.
-
Http request header elemének változóba töltésére szolgál.
Default-ban kötelező lennie a megadott header elemnek, de ez átállítható,
illetve beállítható egy default érték is. Példa:
@RequestMapping
-
Egy controllerben lehet definiálni hogy milyen URL-re, request metódusra, url paraméterre, ... fusson le.
-
Osztály szinten, és metódus szinten is definiálható. Ha mindkét szinten definiálva van az url,
akkor ezek egybefűzésekor fut le a metódus.
-
Egyszerre több URL-t is be lehet állítani. Például megvalósítható a "home page"
kezelése is a "/" URL kezelésével:
-
A @RequestMapping url leíró stringjében a következő speciális leírók lehetnek:
-
Példa: controller osztály:
-
Példa:
Fogadja a következő url-eket:
Nem fogja el a következő url-eket:
-
Példa: Fogadja azokat az url-eket ahol a "home" és az "xy" között csak egyetlen elem van:
-
Példa: Fogadja azokat az url-eket ahol a "home" és az "xy" között tetszőlegesen sok elem van.
-
method
Szűrni lehet vele hogy milyen http kérés típusra fusson a controller metódus, például:
-
params
Szűrni lehet vele hogy milyen paraméterek esetén fusson a controller metódus.
Például csak akkor fusson ha kap "x" ÉS "y" paramétert is:
A params értékeit lehet tagadni is, lásd api doc.
-
produces
Be lehet állítani vele a kimenet típusát, mely a http válasz fejlécében a "Content-Type"
értékeként megjelenik.
Az érték megadható konkrét string-ként, vagy használható a MediaType osztály:
-
consumes
Szűrni lehet vele a fogadott http kérés "Content-Type" típusát.
Csak azokat a http kéréseket fogadja melynek a header-jében a "Content-Type" érték megegyezik a
consumes-nél felsorolt egyik értékkel, például:
A consumes értékeit lehet tagadni is, lásd api doc.
-
headers
Szűrni lehet vele, hogy http kérés esetén milyen http header előfordulása esetén fusson a controller.
Például csak akkor, ha a header-ben szerepel a "Connection: keep-alive":
A headers értékeit lehet tagadni is, lásd api doc.
@RequestParam
-
URL-ben kapott paraméterek fogadása egy controller metódusban.
-
Attribútumok:
-
required: boolean
Alapértelmezett érték: true
True esetén megköveteli, hogy az url-ben szereplő érték java változóba konvertálásakor
ne legyen a java változó értéke null.
Tehát függ a programban beállított konverziótól (PropertyEditor, Converter).
Ha nem teljesül a feltétel, akkor exception keletkezik.
-
defaultValue: String
Ha az url-ben lévő érték java változóba konvertálásakor null vagy üres string kerülne, akkor helyette
a java változóba ez a default érték kerül.
Ez az érték a required által már nem lesz ellenőrizve, tehát ezek egyike is lehet:
-
Kötelező paraméter esetén
Ha a paraméter nem szerepel az url-ben, és a defaultValue sincs megadva, akkor Exception keletkezik.
-
Nem kötelező paraméter esetén
Ha a paraméter nem szerepel az url-ben, és a defaultValue nincs megadva, akkor az Object leszármazott
változóba a spring null értéket tesz.
-
Ha az url paramétert vele megegyező nevű java változóba fogadjuk, akkor használható a rövidebb forma:
-
Ha az url paramétert más nevű java változóba fogadjuk, akkor csak a hosszabb forma használható:
@RequestScope
-
Request scope bean létrehozása, mely ezzel egyenértékű:
@ResponseBody
-
Ezzel az annotációval elérhető hogy a controller metódus által visszaadott objektum ne megjelenítendő
view-ként legyen értelmezve, hanem ez az objektum kerüljön ki a kimentre.
-
Osztályra és metódusra is használható.
-
Példa:
-
Ha a controller metódus nem String típussal tér vissza akkor a "produces" határozza meg milyen
formátumra kell alakítani a kimenetet.
Például egy json kimenet előállítása:
-
Tetszőleges bináris kimenet előállítása, ahol nincs a metódusnak visszatérési értéke, hiszen közvetlenül
kezeljük a HttpServletResponse objektumot:
-
El lehet érni, hogy mégis egy html oldal legyen a kimenet, de ekkor a html oldal nevét nem string-ként kell
visszaadni, hanem az alábbi módon:
A model beállítása a szokásos módon történhet.
-
Szintén el lehet érni, hogy mégis inkább egy redirect történjen:
@ResponseStatus
-
A @ResponseStatus annotációval beállítható, hogy egy controller metódus a http válaszban milyen
státusz kódot adjon, például:
@RestController
-
Olyan @Controller, melynek minden metódusára a @ResponseBody vonatkozik.
@Scheduled
-
Időzítéssel lehet elérni, hogy valamelyik bean valamelyik metódusa, felhasználói beavatkozás nélkül,
előre meghatározott időpontokban fusson le.
-
Az időzítés csak akkor működik, ha valamelyik @Configuration annotációval ellátott osztály meg van jelölve
az @EnableScheduling annotációval is.
-
Az időzítendő bean metódust el kell látni a @Scheduled annotációval.
-
Példa: a bean metódus vége után 5 másodperc szünet következik, majd újra futtatja az időzítő a metódust:
-
Példa: a bean metódus kezdete után már időzíti a következő futtatást 5 másodperc múlva.
Ebben az esetben az időzítő számára lényegtelen mennyi ideig fut a metódus:
-
Példa: unix szabványos cron időzítés, minden másodpercben fut:
-
Példa: unix szabványos cron időzítés, minden munkanap fut hétfőtől péntekig, minden nap egyszer, 22:30-kor:
-
A cron időzítés részletesebben megtekinthető a CronSequenceGenerator osztály api dokumentációban.
-
Egy cron-kifejezésről eldönthető hogy helyes-e:
-
Ellenőrizhető hogy egy cron-kifejezés milyen dátumokra fog időzíteni:
@Service
-
Ezt az annotációt olyan spring-bean-re kell alkalmazni mely általános műveleteket végez.
Pontosabban kifejezi a spring-bean funkcióját, mintha a @Component-et alkalmaznánk rá.
@SessionAttributes
-
Controller osztályhoz tartozó annotáció.
Model attribútumok és session attribútumok közötti szinkronizációs hatást vált ki a @SessionAttributes
annotációban megadott attribútumokkal.
A szinkronizációs hatást a controller metódusaira fejti ki.
-
Működés:
-
Controller metódus futása előtt:
A @SessionAttributes-ban felsorolt névvel a session-ben lévő attribútumot a spring automatikusan berakja
a model-be is,
ugyanezzel az attribútum névvel.
-
Controller metódus futása után:
A @SessionAttributes-ban megadott névvel a model-ben lévő attribútumot a spring automatikusan
berakja a session-be is,
ugyanezzel az attribútum névvel.
-
Form post-ot fogadó controller metódus futása előtt:
Ha a metódusban a @ModelAttribute és @SessionAttribute nevek megegyeznek, akkor a spring nem hoz létre
új bean példányt, hanem a session-ben lévőt használja, ebbe kerülnek a post adatok. Jól lehet használni
ha a bean-be beállítunk egy olyan értéket amit a form nem adna vissza, és az értéket meg akarjuk őrizni.
Vagy pedig egy lekérdezés szűrőfeltételeit szeretnénk megőrizni,
hogy legközelebb erre a lapra navigálva a szűrőfeltételek megmaradjanak.
-
Példák:
-
Amikor már nincs szükség a @SessionAttributes által megnevezett, a session-be tárolt attribútumokra,
akkor ezt kell hívni:
Kitörli a session-ből azokat az attribútumokat amiket felsoroltunk a @SessionAttributes annotációban.
Nem probléma ha a setComplete() nincs meghívva, ugyanis ha több controller ugyanazt az attribútum nevet használja,
akkor a session-ben ezek egymást felülírják, tehát ugyanezzel a névvel csak egy tud tárolódni.
@SessionScope
-
Session scope bean létrehozása, mely ezzel egyenértékű:
-
Ezeket a bean-eket meg lehet semmisíteni a HttpSession.invalidate() metódussal,
és a következő http request-nél a spring újakat fog belőlük létrehozni.
Vagy pedig bekövetkezik a session timeout, és a következő http request-nél a spring szintén újakat
fog belőlük létrehozni.
@SpringBootApplication
-
Spring boot alkalmazást indító osztály annotációja, mely 3 annotációt helyettesít:
-
Mivel a @ComponentScan annotációt is magába foglalja, meg lehet adni azokat a java csomagokat melyekben a spring
a komponenseket keresni fogja.
-
Ha a @SpringBootApplication-t a scanBasePackages beállítása nélkül alkalmazzuk, akkor ezt az osztályt
tartalmazó csomagban, illetve alcsomagjaiban fogja a spring keresni a komponenseket:
-
Ha a @SpringBootApplication-t a scanBasePackages beállítással alkalmazzuk, akkor pont ezekben a csomagokban,
és alcsomagjaikban fogja keresni a spring a komponenseket, a @SpringBootApplication-t tartalmazó package-ben nem:
@Value
-
A @Value annotáció spring bean-eken belül működik.
Az aktuális környezeti beállítások kérdezhetők le vele, és ezt egy bean tagjának lehet értékül adni.
-
Az aktuális környezeti beállításokat a spring csak egyszer állítja össze, az alkalmazás indulásakor!
Tehát program futás közben hiába módosítanánk egy külső application.yml fájlban,
ez a változás nem jut érvényre még akkor sem ha request-scope bean-t alkalmazunk @Value annotációval,
vagy az Environment objektumot olvassuk be.
-
A @Value miatt a spring a környezeti beállítás property-t megpróbálja konvertálni a @Value változójába,
a változó típusának megfelelően.
Ha ez nem sikerül, az alkalmazás el sem indul, és a hibaszöveg tartalmazza a konverziós hibát.
-
Lásd még: "application.yml" leírás.
-
Példa, az aktuális szerver port lekérdezésére, mely alapján egy változót állítunk be:
-
Példa 2:
-
Default értékek megadása
Az alkalmazás el sem indul, exception keletkezik, ha nem létezik az a környezeti beállítás amire
a @Value hivatkozik.
Erre találták ki a default értéket, melyet a környezeti beállítás hiányában fel tud venni a változó.
A defult érték megadása a ":" karakterrel lehetséges:
Tehát a null default értékhez, spEL-t kell alkalmazni.
-
Dokumentáció
active mq
actuator
-
Az actuator segítségével monitorozni lehet az alkalmazást, megtekinthető a memória használat,
a bean-ek, session-ök ...stb.
-
Az actuator aktiválása után bizonyos path-ok (endpoint-ok) hívhatók lesznek,
melyek text (json) kimenetként szolgáltatják az adatokat az alkalmazás állapotáról.
-
Az actuator aktiválása:
1. lépés: pom.xml kiegészítése:
2. lépés: application.yml kiegészítése, hogy az összes actuator endpoint-hoz hozzá lehessen férni:
-
Endpoint-ok:
Ezek a path-ok akkor is működnek, ha van egy saját interceptorunk, mely nem engedné őket meghívni.
Valszeg az actuator úgy oldja ezt meg, hogy egy filtert csinál, ami előbb fut le mint a saját interceptorunk,
és ez szolgálja ki az alábbi path-okat:
-
dokumentáció
ApplicationContext
-
Spring bean elérését teszi lehetővé dinamikusan, például:
-
Hogy az ApplicationContext-hez bármelyik java osztályban hozzá lehessen férni, a MyApplication.java osztályban
készítsünk egy @PostConstruct metódust. Ebben a metódusban hozzáférünk az ApplicationContext-hez,
és az értékét tegyük bele az osztály "context" névvel ellátott statikus tagjába.
Így bármelyik osztály hozzá tud férni az ApplicationContext-hez:
és használhatja ennek metódusait, például hozzáférhet a spring bean-ekhez:
ApplicationRunner
-
Ha az alkalmazás indításával egyidőben szeretnénk programkódot futtatni, akkor az ApplicationRunner interface-t
implementáló osztály lehet rá megoldás.
Figyelem: amíg az ApplicationRunner osztály run() metódusa fut, addig is meg lehet hívni az
alkalmazást http kéréssel!
Ha ezt nem akarjuk, akkor az ApplicationRunner helyett @EventListener-t kell használni!
-
Példa:
-
application.yml
-
Dokumentáció:
Externalized Configuration
-
Alkalmazás beállítások tárolására szolgál. Default helye a projektben:
vagy érdemes a config alkönyvtárba tenni ha a profile-ok miatt több van belőle:
-
Az application.yml fontosabb beállításai:
-
Ezen a porton fut az alkalmazás. Default érték: 8080
-
Context path. Default érték: nincs context path
Állhat több tagból is:
-
Aktív profile-ok, vesszővel elválasztva
-
Session timeout.
Ha csak számértéket adunk meg, akkor másodpercben értendő.
A számérték után megadhatunk duration értéket is.
A beállítástól függetlenül minimum 1 percig él a session, és percekre kerekít,
lásd: HttpSession.getMaxInactiveInterval()
-
A környezeti beállítások megadhatók külső és belső application.yml fájlokban, program indítás argumentumként,
operációs rendszer környezeti változókban stb.
Meg van határozva melyiknek mekkora a prioritása, lásd a spring boot dokumentáció
"24. Externalized Configuration" fejezetét.
-
Bármilyen bejegyzés ami be van állítva az application.yml fájlban, program indítás argumenként is megadható,
ha ugyanezt a kulcsot használjuk "--" jellel kiegészítve, tehát a parancssor argumentum az erősebb.
De az operációs rendszerben beállított környezeti változók is erősebbek mint a belső
application.yml bejegyzések.
-
Nemcsak a spring-es, hanem saját környezeti beállítások is használhatók, például: "my.proba"
-
Példa: tegyük fel hogy a belső application.yml tartalmazza hogy a program melyik porton fusson:
Ezesetben a program a 48001-es porton indul el:
Ezesetben a program a 48002-es porton indul el, mivel a macOS környezeti változója erősebb mint a
belső application.yml beállítás:
Ezesetben a program a 48003-as porton indul el, mivel a program indítás argumentumként megadott érték erősebb
mint a macOS környezeti változója, és a belső application.yml beállítás:
-
Példa: programon belül tároljuk a különböző környezetekhez tartozó beállításokat,
profile függő application-{profile}.yml fájlokban,
és a program indítási parancssorban állítjuk be a spring.profiles.active értékét ("teszt" vagy "eles").
application.yml:
application-teszt.yml:
application-eles.yml:
-
Külső (nem a jar-ba csomagolt) application.yml:
-
A working directory-ba, vagy az alatta lévő "/config" könyvtárba kell tenni a yaml-fájlt,
nem pedig a jar-t tartalmazó könyvtárba.
-
Ha program futás közben módosítjuk a yaml-fájlt, a spring ezt figyelmen kívül hagyja.
Spring Cloud környezetben + a @RefreshBean annotáció használatával lehet ezen a működésen változtatni.
application restart
-
Lehetőség van arra, hogy a program önmagát újraindítsa, mely akkor lehet hasznos ha változtak a külső
property (yaml) fájlok, és azok új értékeivel kell a programnak működnie. Ehhez módosítani kell az alkalmazást
elindító java osztályt úgy, hogy létrehozzuk a statikus restart() metódust:
Ezután nincs más teendő mint pl. egy tetszőleges URL meghívására (/restart) a controller
elindítsa ezt a fenti metódust:
-
dokumentáció
bean validation
-
Lásd még: java info ➔ bean validation
-
Spring esetén a bean validáció automatikusan történik meg a standard java-val ellentétben, mivel a validáció
aktiválását, és a hibakezelést a spring elvégzi helyettünk. Ehhez az automatizmushoz szükséges a pom.xml
kiegészítése a spring-boot-starter-validation dependency-vel.
-
A spring létrehozza a ValidatorFactory és Validator osztályokat, így ha mégis manuális akarjuk
ezeket használni, akkor csak be kell húzni az osztályba:
-
@Valid
Rest service esetén a json-t fogadó java objektumban szükségesek a validációs annotációk.
Ezenkívül a controller metódusban a json-t fogadó java objektum elé a @Valid annotáció is szükséges,
hogy a spring aktiválja a validációt.
-
@Validated
Ezzel az annotációval request paramétert lehet validálni úgy, hogy a controller osztályra
kell helyezni a @Validated annotációt, és a controller metódusban a paraméter elé kell elhelyezni
a validációs annotációt:
BindingResult
-
A BindingResult objektummal végezhető fontosabb műveletek:
Lekérdezhető (de nem írható át) az eredeti érték amit a felhasználó a felületen bevitt:
Field error beállítása:
Global error beállítása:
BuildProperties
-
Ennek az osztálynak a használatával hozzáférhetünk többek között a pom.xml-ben lévő program verzióhoz,
és a program build időpontjához is ahelyett,
hogy a maven replacer plugint használnánk.
-
A pom.xml-ben lévő spring-boot-maven-plugin-t ki kell egészíteni egy új execution résszel:
-
A pom.xml kiegészítése, és egy program build után már használható a BuildProperties osztály
egy tetszőleges spring bean-ben:
-
Használat thymeleaf-ben:
-
Működés: program build esetén a spring-boot-maven-plugin elkészít egy fájlt, melybe belerakja a build-információkat,
és a spring ebből a fájlból tudja kiolvasni:
cache
-
Cache-Control
Ezzel a http response header-rel lehet vezérelni, hogy a böngésző használjon-e cache-t.
Cache letiltása esetén elég ha ezt tartalmazza a http-header:
Ha viszont az a cél, hogy az adott fájlt a böngésző cache-ben tárolja egy ideig, akkor egy jó beállítás:
A fenti példában a fájl maximum 1024 másodpercet (bő 17 percet) tölthet a publikus cache-ben.
Ha a fájl kritikus információt tartalmaz, melyet nem láthat más felhasználó,
akkor a private cache használatát kell a header-ben beállítani.
-
Cache beállítás weblapok számára
Weblapok esetén nem ajánlott a cache használata, a böngészőnek mindig a szerverhez kell fordulnia, hogy
friss információval rendelkező lapok jelenjenek meg. Ennek eléréséhez készíthetünk saját interceptort mely
beállítja a http-header-t, de van erre kész megoldás is, a WebContentInterceptor osztály,
amit be kell állítani, és regisztrálni:
-
Cache beállítás static resource-ok számára
A statikus resource-ok nem változnak a program futása során, ezért ezeket mindenképp érdemes a cache-ben tárolni.
Tipikus eset amikor az alkalmazás minden lapja ugyanazt a háttérképet tartalmazza.
Ha a háttérkép nem tárolódik cache-ben, így mindig a szerverről töltődik le, villogás tapasztalható.
A statikus resource-okra vonatkozó cache beállítható a WebMvcConfigurer-t implementáló osztályban:
A fenti példa ezt a http response header-t eredményezi:
Egy másik lehetőség, hogy az application.yml fájlban állítjuk be ezeket, például a max-age értékét:
-
Firefox F12
Ebben az ablakban a Hálózat fülön az Átküldve oszlopban is látható,
hogy a böngésző a szerverről töltötte le, vagy a cache-ből vette elő a fájlt.
A szerverről letöltött fájl esetén látható a fájl mérete,
cache esetén viszont a gyorsítótárban szöveg jelenik meg.
conversion
Http-get paraméter → java változó
Html form ↔ java változó
-
Konverziót megvalósító osztályok:
- Converter (Spring 3.0-tól)
- Formatter (Spring 3.0-tól)
- PropertyEditor (JavaSE része)
-
Egy konkrét mezőre is beállítható ?
- Converter: nem állítható be
- Formatter: Controller @InitBinder metódusban: WebDataBinder.addCustomFormatter(...)
- PropertyEditor: Controller @InitBinder metódusban: WebDataBinder.registerCustomEditor(...)
-
Konvertálható adattípusok ?
- Converter: bármilyen típus - bármilyen típus
- Formatter: bármilyen típus - String
- PropertyEditor: bármilyen típus - String
-
A kétirányú konverzió leírásához hány osztály kell ?
- Converter: 2
- Formatter: 1
- PropertyEditor: 1
-
Beépített locale függő konvertálást is tud ?
- Converter: nem tud
- Formatter: igen tudja, a konverziós metódusok kapnak Locale paramétert
- PropertyEditor: nem tud
-
Üres form-mezőre is fut ?
- Converter: igen
- Formatter: nem
- PropertyEditor: igen
-
Fut-e a konverzió ha egy url-paramétert (@RequestParam, @PathVariable) kell konvertálni változóba ?
- Converter: igen
- Formatter: igen
- PropertyEditor: igen
-
Converter:
-
Saját konvertert a Converter interface implementálásával lehet készíteni.
-
Ha olyan osztályra készítünk konvertert, melynél a spring-nek nincs beépített konvertere
(pl. String -> java.awt.Color konverter),
akkor az általunk készített konverter működik.
-
Ha olyan osztályra készítünk konvertert, melynél a spring már rendelkezik beépített konverterrel
(pl. String -> BigDecimal konverter) akkor a működés a következő:
Lefut az általunk készített konverter. Ha sikeresen átkonvertálta az értéket, akkor a művelet befejeződött.
Ha nem konvertálta át sikeresen, és bármilyen exception-t dob, akkor a spring még lefuttatja a
beépített konverterét (hátha annak sikerül).
Ha a beépített sikeresen lefut, akkor sikeres a konverzió és nincs hiba,
ha nem akkor valóban typemismatch hiba keletkezik.
-
A spring beépített konverterét nem tudtam kikapcsolni.
-
Formatter:
-
print() működése:
Ha egy típushoz rendelt formatter print() metódusa hibát dob, akkor a spring nem próbálja meg az ugyanilyen
típushoz rendelt beépített formatterét meghívni, így a hiba megmarad.
Ha egy mezőhöz van hozzárendelt formatter, akkor sem hívja meg a saját globális formatterünket,
hátha annak sikerülne a print().
-
parse() működése:
Ha egy típushoz rendelt formatter parse() metódusa hibát dob, akkor a spring meghívja az ugyanilyen
típushoz rendelt beépített formattert,
hátha annak sikerül a parse() végrehajtása, és így lehet hogy mégsem keletkezik hiba.
Ha egy mezőhöz rendelt formatter parse() metódusa dob hibát, akkor a spring nem próbálja meghívni az
ugyanilyen típushoz rendelt beépített formattert.
-
Összegzés:
-
Saját konverziós eljárás esetén a legjobb ha Formatter-eket készítünk, és ezeket beállítjuk globálisan.
Ha egy-egy mezőhöz más kell, akkor azokhoz más formattert állítsunk be.
-
String típusú változó esetén ne "string to string" formattert készítsünk, hanem erre a célra
PropertyEditor-t használjunk.
Yaml → java változó
-
Az application.yaml-ból való beolvasás esetén a default konverzióra érdemes hagyatkozni, ne módosítsunk rajta!
-
Egyes leírások megemlítik, hogy a Converter osztályt + @ConfigurationPropertiesBinding
annotációt lehetne használni magunk által megírt konverzióra, de a mintapéldák sem működnek.
-
A Converter a dokumentációja alapján a null értékre nem is fut, pedig a yaml-ban beállítható null is.
Ha a yaml null értéket tartalmaz a fogadó String típusú változóba üres string kerül, nem pedig null.
-
Ha a yaml-ban null van, annak mi a típusa, milyen típusra kellene converter-t írni?
Json ↔ java változó
-
Erre nem hatnak a PropertyEditor, Converter beállítások, mivel a Jackson saját maga kezeli a konverziót.
duration
-
Az idő, időtartam jellegű property-k esetén a számérték után megadható egy mértékegység (duration) is.
Ezek többfélék lehetnek, ezek közül néhány fontosabb:
-
Példa: session timeout beállítása 3 órára:
Environment
-
A program indításakor aktuális környezeti beállítások lekérdezése.
Ha a program futása közben módosítunk egy külső application.yml fájlban,
az már nem módosítja az Environment-et!
-
Dokumentáció:
Externalized Configuration
-
Lásd még: "application.yml" leírás, @Value
-
Példa:
-
Példa:
- null, ha sehol sincs definiálva
- "xy", ha csak az application.yml-ben van definiálva hogy: "my.proba: xy"
- "xy", ha csak a program indításnál van definiálva hogy: "--my.proba=xy"
-
"xy", ha az application.yml-ben "my.proba: tz" van, a program indításnál pedig: "--my.proba=xy",
tehát a program indítási beállítás az "erősebb"
exception handler
-
Exception keletkezéskor a spring azt a lehető legkisebb szintű exception handlert hívja meg,
mely alkalmas az exception kezelésére.
-
1. szintű exception handler:
Egy controlleren belül keletkező exception kezelésére szolgál, így az @ExceptionHandler annotációval
ellátott metódust a controlleren belül kell elhelyezni. Egy controlleren belül több @ExceptionHandler lehet,
melyek más-más exception-t kezelnek le. A metódusban használhatjuk az Exception objektumot paraméterként,
így a spring átadja a metódusnak a keletkező exception-t.
Például 2 exception-t egyszerre lekezelő handler:
-
2. szintű exception handler:
Az összes controlleren belül keletkező exception kezelésére szolgál. Felépítése megegyezik az 1. szintű handler-rel,
de a metódust nem controller-ben kell elhelyezni, hanem a @ControllerAdvice annotációval ellátott osztályban.
Interceptorban keletkező exception-t nem minden esetben lehet vele elkapni. Ha interceptorban keletkezik exception,
de a hívott url nem létezik, akkor mindenképp a 3-as szintű exception handler fog lefutni a 2-es helyett.
-
3/A. szintű exception handler:
Általunk készített osztály, mely implementálja az ErrorController-t, és az "/error" url-t kezeli le.
Ebben az osztályban csak egy controller metódus lehet, ami az "/error" url-t kezeli le, így abban kell elágaztatni,
hogy különböző hibák esetén mi történjen. Ezen a szinten minden exception elkapható, olyanok is melyek nem
kaphatók el 1. és 2. szintű @ExceptionHandler-rel, mert nem controlleren belül keletkezik a hiba, például:
- Thymeleaf view értelmezési hiba.
- Semmilyen controllerhez sem tartozó URL meghívása.
Ha ehhez az exception handler-hez még külön információt is el akarunk juttatni, akkor egy spring-bean -be
berakva az információt, ez a handler is ki tudja olvasni, és ez működik a @RequestScope bean-ekkel is. Példa:
-
3/B. szintű exception handler:
A spring beépített exception handlere, mely egy "Whitelabel Error Page" feliratú weblapot jelenít meg.
Ez egy olyan oldal, mely csak nagyon felületes információt szolgáltat a keletkezett exception-ről.
Ha készítünk 3/A szintű exception handler-t, az elfedi a 3/B szintű exception handler-t, így a 3/B sosem jelenik meg.
-
4. szintű exception handler:
A spring beépített, nem kikapcsolható exception handlere.
Ha a hiba eljut erre a szintre, akkor a spring egy http 500-as hibaoldalt küld,
mely a stack trace-t is tartalmazhatja.
A hiba a log-állományból kideríthető.
-
Ha az 1-es, vagy 2-es szintű exception handlerben exception keletkezik,
akkor a 3-as szintű exception handlert hívja meg a spring.
-
Ha az 3-as szintű exception handlerben exception keletkezik, akkor a 4-es szintű exception handler fut le.
file download
-
Fájl letöltés a ResponseEntity osztály segítségével:
-
Fájl letöltés direkt HttpServletResponse írással:
file upload
Általános eset: normál html
-
Html-ben a <form> elemnél beállítandó attribútum:
-
Html-ben a fájl feltöltés mezője:
-
Form bean adattípus mely fogadja a feltöltött fájlt (fájlokat):
-
A MultipartFile objektum segítségével a fájl tartalmát bájtsorozatként, és stream-ként is megkaphatjuk:
-
Így kell vizsgálni hogy nincs feltöltött fájl, ugyanis microsoft edge böngészőnél a multipartFile
objektum lehet null is:
-
Maximális fájl méret beállítása az application.yml fájlban,
és érdemes beállítani a http request maximális méretét is:
-
Fájl feltöltés könyvtárának beállítása az application.yml fájlban.
Így a feltöltött fájlok közvetlenül a "/tmp" könyvtárba kerülnek, nem pedig annak valamelyik alkönyvtárába:
Ez a beállítás csak azért szükséges, hogy egy hibát elkerüljünk:
Program induláskor a tomcat alkönyvtárakat hoz létre a "/tmp" alatt, melyet fájl feltöltéshez is használ.
Azonban az operációs rendszer a "/tmp"
könyvtár tartalmát kiürítheti, ha pl. 10 napig nem módosult a könyvtár tartalma.
Ezután a fájl feltöltés nem fog működni, a következő hiba keletkezik:
Failed to parse multipart servlet request; nested exception is java.io.IOException:
The temporary upload location [/tmp/tomcat.8789959165287795690.50001/work/Tomcat/localhost/context-path]
is not valid
-
Ha tomcat-et használunk az alkalmazás futtatásához, és túl nagy méretű fájlt (>20MB) töltünk fel, akkor a tomcat
connection reset
-et csinál,
vagyis megszakítja a http kapcsolatot, így a felhasználó nem spring-es hibaüzenetet kap,
hanem a böngésző üzen neki vissza, hogy az oldal nem elérhető.
Ha ezt nem szeretnénk akkor át kell állítani a max swallow size értéket az application.yml fájlban:
-
A feltöltés folyamata:
A feltöltés úgy történik, hogy a kliens elkezdi küldeni a fájlt. Ha a köldött adatmennyiség eléri a
max swallow size értékét, akkor connection reset történik.
Ha nem történik connection reset a feltöltés végéig (mert a max swallow végtelenre van állítva,
vagy nagyobbra mint a feltöltött adatmennyiség), akkor a spring kapja meg a vezérlést, és ellenőrzi hogy az
application.yml-ben beállított értéket meghaladta-e az adatmennyiség.
Ha meghaladja akkor MaxUploadSizeExceededException keletkezik, mely a MultipartException leszármazottja.
A MaxUploadSizeExceededException-t nem lehet lekezelni a form post-ot fogadó controllerben,
vagyis nem működik az 1-es szintű exception handler,
de le lehet kezelni 2-es, vagy 3-as szintű exception handlerben.
-
Egy jó megoldás:
A "max swallow size" méretet be kell állítani akkorára, amekkora adatmennyiséget megengedünk (pl. 5 mbyte).
Így egy óriási fájl feltöltésekor pár másodpercen belül bekövetkezne a connection reset, a feltöltés nem tart
percekig, és nem terheli a hálózatot. Hogy a felhasználó rendes hibaüzenetet kapjon, egy javascript-nek kell
ellenőriznie mekkora a fájl mérete, és a javascript-nek nem szabad engednie a fájl feltöltését,
ha az túllépi a megengedett méretet.
A javascript megoldás további előnye, hogy különböző weblapokon különböző fájl méreteket lehet beállítani.
Speciális eset: frontend framework
-
Ebben az esetben egy frontend framework segítségével történik a fájl feltöltés, a feltöltési folyamat kliens
oldala tetszőlegesen programozható.
-
Nem túl nagy fájlok esetén a fájl base64 kódolással szövegre alakítható, így egy json belsejében küldhető
a szerver felé, pont úgy mintha egy szöveg lenne.
-
Túl nagy fájlok esetén a json-be csomagolás nem célravezető, hiszen azt egy java objektumban fogadja a spring,
így az egész a memóriában jön létre egyszerre, és lehet hogy ott nem fér el. Ezért jobb lenne helyette stream-et
alkalmazni.
Egy lehetséges megoldás:
A szerver hívást bontsuk 2 részre:
-
A frontend az 1. hívásban json-ben küldi a kisebb adatokat, melyet a szerver egy session bean-ben tárol,
és válaszként visszaadja, hogy a hozzátartozó nagy fájlt milyen azonosítóval kell felküldeni a 2. hívásban.
-
A frontend a 2. hívásban csak a fájl tartalmát küldi a request body-ban bájtsorozatként, és a http-header-ben
beállítja az 1. hívásban kapott fájl azonosítót. A 2. hívást a spring így tudja fogadni stream-ként:
-
Az ilyen módon történő fájl feltöltésnek gyakorlatilag nincs korlátja, az általános esetnél leírt beállítások
hatástalanok, hiszen ez nem multipart.
Az 1 gigabájtnál nagyobb méretű fájl feltöltése is sikerült.
Érdekes hogy még ez az application.yml beállítás sem tudja korlátozni:
form
-
Az űrlapokhoz tartozó form objektum tagjai private-ok legyenek,
és szükséges a hozzájuk tartozó getter + setter metódusok.
Ha a form objektum tagjai public-ok, és nem tartozik hozzájuk getter + setter, akkor sajnos a thymeleaf
nem tudja a form tagjait írni, és olvasni, exception keletkezik.
Formatter
-
String és más adattípus közötti konverzió, melynek hatása ezekre az esetekre vonatkozik:
- Form mező és form bean közötti konverzió mindkét irányban.
- URL-ben lévő request paraméter konvertálása @RequestParam változóba.
-
Hasonló a converterhez, de annál azért jobb mert a locale beállítást is megkapja paraméterként,
tehát locale függő konverziót lehet benne megvalósítani.
-
A formatter beregisztrálása az alkalmazásba az @InitBinder annotációval történik.
-
Formatter példa java.util.Date típus esetén:
A dátum a képernyőn "éééé.hh.nn" formában jelenjen meg, továbbá az "éééé.hh.nn" stringként bevitt adat a
Date típusú model-be bekerülhessen:
interceptor
-
Interceptor használatával be lehet avatkozni egy url hívás folyamatába,
meg lehet akadályozni hogy egy controller-hez kerüljön a vezérlés, módosítani lehet a http headert.
-
Az interceptor egy olyan @Service annotációval jelölt bean, melynek metódusai alapesetben minden
http request-re lefutnak.
-
Az interceptornak implementálnia kell a HandlerInterceptor interface-t.
-
Több funkció esetén javasolt több interceptor használata, egyikben pl. a login ellenőrzése,
a másikban a http header beállítása történhet meg.
-
Az interceptorok lefutási sorrendje megegyezik a regisztrációs sorrenddel,
azonban biztosabb ha az order() metódussal állítjuk be.
-
Alapesetben az interceptor minden url-hívásra fut, ha csak szimplán regisztráljuk.
Lefut ha az url-hez tartozik controller, ha nem tartozik, ha az url static resource-ra hivatkozik...
Ez a működés módosítható az interceptor regisztrációjánál, ezekkel a metódusokkal:
-
excludePathPatterns()
Az itt megadott url-ekre nem fog futni.
-
addPathPatterns()
Ha ezt a metódust használjuk, akkor kizárólag csak az itt megadott url-ekre fog futni.
-
Ha egy controller redirect-ál egy másik controllerhez, vagyis egy url hívásakor 2 controller is fut,
akkor az interceptor is lefut mindkét alkalommal.
-
Végrehajtási folyamat 2 interceptor esetén:
-
preHandle() metódus
Ha szükséges, megakadályozza hogy a controller-hez kerüljön a vezérlés, ez a visszatérési értékétől függ.
Ha szükséges, ebben a metódusban érdemes a HttpServletResponse módosítását is elvégezni, pl. a header beállítást.
-
True visszatérési érték esetén:
A fenti végrehajtási folyamat folytatódik.
-
False visszatérési érték esetén:
A fenti végrehajtási folyamat megszakad. A kimenet normál 200-as státuszú, 0 byte-os lesz.
Ezért érdemes egy http redirect-et is küldeni, hogy egy másik controller-hez kerüljön a vezérlés.
-
Exception esetén:
A fenti végrehajtási folyamat megszakad.
Az exception handler-t pont úgy keresi meg a spring, mintha a controllerben keletkezett volna.
-
postHandle() metódus
Általában üresen hagyjuk.
Azelőtt fut le hogy a spring a view renderelést elvégezte volna. Mivel ModelAndView objektumot kap
paraméterként szükség esetén módosíthatja.
Itt is lehetséges a HttpServletResponse módosítása, azonban nem érdemes ebben a metódusban ezt elvégezni.
Ugyanis lehet, hogy a HttpServletResponse már nem módosítható (committed=true állapotban van) azon esetekben
amikor a view-t már nem kell renderelni pl:
- a controller @ResponseBody módon már előállította a kimenetet
- controller helyet static resource-ra hivatkozik az url, mely kész van
-
afterCompletion() metódus
Általában üresen hagyjuk.
Azután fut le hogy a spring a view renderelést elvégezte. Itt már hatástalan a HttpServletResponse módosítása
hiszen már committed=true állapotban van.
-
Interceptor ne fusson a globális error handler előtt, vagyis a "/error" url-re!
Ugyanis ha az interceptor preHandle() metódusban keletkezik a hiba, akkor sose jutna el a vezérlés a "/error"-hoz,
hiszen az előtt is lefutna a preHandle() ami ismét hibát generálna. Így a spring beépített 4-es szintű exception
handlere lépne működésbe.
-
Példa: Ha a felhasználó nincs bejelentkezve és nem publikus lapra szeretne navigálni, akkor a felhasználót a
bejelentkező lapra irányítjuk:
A fenti interceptor regisztrációja egy konfigurációs osztályban:
-
Példa 2 interceptor regisztrációjára, ahol a lefutási sorrendjük is be van állítva.
A 2. interceptor nem fut a "/error" url-re, és a static resource-ra.
Jackson
-
Spring Boot használatával automatikusan bekerül a project-be a Jackson library.
Ez a library json konvertálást végez java objektumba, és vissza.
-
A Jackson a json-re konvertáláskor megtartja az osztályban lévő tagok sorrendjét.
Szintén megtartja a LinkedHashMap osztályba berakott kulcs-érték párok sorrendjét.
Ha egy osztály-tag, vagy map-kulcs értéke null, akkor is megjelenik a json-ben.
-
Az osztályt nem kötelező @Getter/@Setter annotációkkal ellátni, elég ha a tagok láthatósága: public
-
Osztály ➔ json konverzió
Használható kész osztály is, például: Map
-
Json ➔ osztály konverzió
Ha nincs saját osztályunk, használjuk a JsonNode-ot:
Mindig osztályt adjunk meg, ne pedig egy generikus típust, különben nem lesz típus helyes a betöltés.
Tehát ne egy osztály List<Szemely> típusú tagját adjuk meg a beolvasáshoz.
Osztály esetén a Jackson a reflection api segítségével felderíti az osztály belsejében lévő generikus tagokat,
és az adatokat típus helyesen tölti be.
-
ObjectMapper beállítások
A saját programkódunk által létrehozott ObjectMapper példány néhány beállítása:
-
Nem keletkezik UnrecognizedPropertyException, ha a json több adatot tartalmaz,
mint amennyit az osztály fogad:
-
LocalDate, LocalTime típusokat tartalmazó osztály kezelése:
-
Formázott json előállítása, melyben sortörés és betolás is szerepel:
-
Az osztály, és tagjai ellátható annotációkkal mely a konverziót vezérlik:
-
@JsonFormat
Megadható vele a json-ben szereplő adat formátuma. Példák:
Ha az osztály LocalDate típusú adatot tartalmaz, akkor alapesetben a json-ben "éééé-hh-nn" formában kell az
adatnak szerepelni.
LocalTime esetén pedig a json-ben "óó:pp:mm", vagy "óó:pp" formában kell az adatnak lennie.
Ha a json-ben más az adat formátum, vagy az osztályból más formátumú json-t szeretnénk előállítani, akkor az osztályban
használni kell a fenti annotációt:
-
@JsonProperty
A json kulcsok tartalmazhatnak olyan karaktereket (space, kötőjel, ...) ami miatt ugyanilyen nevű java változó nem
hozható létre.
Így az osztályban eltérő nevű tagot kell létrehozni, és az annotációval jelezni, hogy a json-ben
ez milyen névvel szerepel:
-
@JsonIgnore
Az osztály ezen tagja nem lesz feltöltve a json-ből, még akkor sem ha ugyanilyen névvel tartalmaz a json adatot.
Az osztály ➔ json konverzió során ez a tag nem kerül be a json-be.
-
@JsonAnySetter
Json ➔ osztály konverzió során, lehet olyan eset amikor a json több adatot tartalmaz mint amit az osztály fogad.
Vagy a json nem fix szerkezetű, hanem egyes kulcsai mindig változó dátumok.
Ha ezeket az adatokat is be akarjuk olvasni, akkor egy Map típusú tagot is fel kell venni, melybe ezen kulcs-érték
párok bekerülnek:
-
@JsonIgnoreProperties
Erre az annotációra általában nincs szükség, szebb megoldás az Objectmapper beállítása, lásd fent.
Json ➔ osztály konverzió során, ha a művelet saját programkódunk által létrehozott ObjectMapper-rel történik,
és az osztály kevesebb tagot tartalmaz mint a json-ben lévők, akkor UnrecognizedPropertyException keletkezik.
Az exception keletkezését el lehet kerülni ezzel az annotációval.
Az annotációt osztály szintű, és arra az osztályra kell helyezni, melyben a json-höz képest kevesebb tag van,
nem pedig a legfelső osztályra amit az ObjectMapper-ben leírtunk:
-
AC/DC szabály automatikus spring-konverzió esetén
Ez az eset, amikor egy controller @RequestBody-val fogadja a json-t.
Szintén ez az eset, amikor RestTemplate hívással egy osztályba fogadjuk a válasz json-t.
- Nem probléma ha a json kevesebb adatot tartalmaz, az osztály egyes tagjai null értéket vesznek fel.
- Nem probléma ha a json több adatot tartalmaz, a json egyes adatai nem kerülne be az osztályba.
-
AC/DC szabály általunk létrehozott ObjectMapper esetén
Ez az eset, amikor a saját programkódunk hozza létre az ObjectMapper példányt mely elvégzi a konverziót.
-
UnrecognizedPropertyException keletkezik ha a json több adatot tartalmaz, mint amennyit az osztály fogad.
Ezen a default működésen lehet módosítani az ObjectMapper beállítással, lásd fent.
-
Nem probléma ha a json kevesebb adatot tartalmaz, az osztály egyes tagjai null értéket vesznek fel.
-
Dokumentáció: Jackson annotációk
JsonNode
-
JsonNode objektumba konvertálható egy rest service json-válasza, de a Jackson által is készíthető JsonNode objektum:
-
A JsonNode osztállyal csak olvasható a json-t, módosítása nem lehetséges.
-
A json struktárán belüli kulcsokat field-nek nevezi.
-
A json struktúrán belüli tömböket úgy tekinti, mintha azok is objektumok lennének, melynek kulcsai a tömb indexei.
Ezek az indexek használhatók a get(), path() metódusokban, de vigyázat:
az indexeket nem string-ként, hanem int-ként kell megadni!
-
Json minta a lenti példákhoz:
-
Node-on belüli kulcsok (field-ek) lekérdezése:
Tömb típusú node esetén ezzel a módszerrel nem kapjuk meg a tömb indexeit.
-
Tömb méretének lekérdezése:
-
Létezés lekérdezése:
-
Node-on belüli node elérése:
Tehát a path() a get()-hez hasonló, de path()-nál nem keletkezik NullPointerException!
-
Null érték lekérdezése:
-
Típus lekérdezése:
-
Értékek elérése:
locale
-
Két beállítás is szükséges ahhoz, hogy helyes legyen a locale értelmezése, az egyik a JVM-re hat,
a másik a spring-re:
A metódusnak kötelező "localeResolver" nevűnek lennie, különben nem lesz hatása!
A metódusnak egy LocaleResolver implementációt kell visszaadnia, például:
- FixedLocaleResolver
- SessionLocaleResolver.
Az így beállított locale kihat a thymeleaf-re is, például a #dates.dayOfWeekName() más neveket ad vissza.
logging
-
Az application.yml fájlban a logging.file.name értékét érdemes beállítani,
ez definiálja a log-könyvtárat és a log-fájlnevet is:
Ha ez be van állítva, akkor a logging.file.path nem jut érvényre, tehát vagy az egyiket vagy a másikat érdemes
beállítani.
-
A System.out.print()-tel való kiírás csak a konzolra megy, a log-ba nem!
-
A beállított log-fájlba ír a spring.
Ha programból szeretnénk ugyanebbe a log-fájlba írni, akkor a java.util.logging csomag segítségével
be kell állítani egy loggert:
majd ezt felhasználva beleírni:
de a log level-t hordozhatja a metódusnév is:
ahol a severe a legmagasabb szintű logolást jelzi, mely a log-fájlban ERROR-ként jelenik meg.
-
Dokumentáció: a spring boot refdoc dokumentációban rákeresni a "logging.file.name" részre.
messages.properties
-
Üzenetek tárolására szolgál, helye a projektben:
Beállítható hogy más helyen legyen, de ez nem ajánlott!
Azért nem, mert program indításkor azonnal keletkezhet hiba, de a messages.properties helyét beállító bean még
nem jött létre.
A spring keresi a fájlt, de nem találja, és a valódi hiba helyett a messages.properties hiányára
utaló hiba jelenik meg a log-ban.
Például az src/main/resources/config könyvtárban lévő messages.properties helyének beállítása,
mely csak akkor működik, ha a metódus neve messageSource:
-
Lehet locale függő is, például ilyen fájlnévvel:
-
A messages.properties-ben tárolt szövegekben paraméterek is lehetnek: {0}, {1}, {2}, ...
Például egy messages.properties bejegyzés:
A message-re hivatkozhat például egy BindingResult, és a message paramétereket is kitöltheti:
-
Ha kevesebb paramétert adunk át, akkor a hibaszövegben a "{n}" megmarad, nem cserélődik ki.
-
Ha több paramétert adunk át, a feleslegesek figyelmen kívül maradnak.
-
Egy bean-ben kiolvasható a message szövege:
-
Typemismatch hibaüzenetek:
Form binding esetén a spring végrehajtja az automatikus adatkonverziót, hogy a form bean megfelelő tagjaiba
az űrlap értékek bekerülhessenek.
Amennyiben az adatkonverzió sikertelen, a mezőhöz a spring hibaüzenetet rendel.
Példa: Ha a konverzió Long adattípusba történik, a spring ezeket a messages.properties bejegyzéseket
keresi ebben a sorrendben:
Példa: Ha a konverzió long adattípusba történik, a spring ezeket a messages.properties bejegyzéseket
keresi ebben a sorrendben:
Ha a fenti messages.properties bejegyzések egyike sem létezik, a spring akkor is hozzárendel hibaüzenetet
a mezőhöz, például:
A hibaüzenet tartalmazhatja a {0} paramétert, de abba a spring csak a mező nevét rakja, ami nem hasznos
információ a felhasználóknak, így használata nem javasolt.
mixed response
-
Lehetséges, hogy egy controller metódusnak tetszőleges response-t kell készítenie, a request paraméterétől függően.
Ekkor így néz ki a controller metódus:
-
Adott response-t milyen objektum visszaadásával lehet elérni:
ModelAndView
-
A ModelAndView objektumot controller metódus visszatérési értékeként használhatjuk.
Akkor hasznos, ha a controller metódus már el van látva @ResponseBody annotációval, de mégis szeretnénk,
hogy a spring a visszaadott stringet ne http body-nak, hanem egy megjelenítendő view-nak értelmezze:
-
Ha a ModelAndView konstruktorban nem adunk meg model-t, akkor is érvényesülnek a rajta kívül megadott
model beállítások:
-
Továbbá meg lehet adni a http státuszt is:
-
Http redirect válasz is készíthető, külső és belső címre is, továbbá használható a RedirectAttributes osztály is:
port foglaltság
-
JAR-ként futtatott spring boot alkalmazásnál egy szabad port-ra van szükség.
A foglalt portokat a következő paranccsal lehet kilistázni:
Linux esetén:
Macos esetén:
Windows esetén:
program arguments
-
A program argumentumokhoz hozzá lehet férni bármelyik bean-ben:
Az így injektált objektum adott metódusával hozzá lehet férni az argumentumok tömbjéhez:
prototype scope
-
Akkor használunk prototype scope-ot, amikor magunk szeretnénk vezérelni,
hogy egy spring-bean-ből mikor készüljön új példány.
Egy metóduson belül akár 3 példányt is kérhetünk.
Ezt a működést nem lehetne elérni a new operátorral, hiszen nem a spring konténerben keletkezne a bean!
-
Fontos, hogy a prototype bean-eket sose húzzuk be @Autowired-del, mert így elég nehéz pontosan értelmezni
mikor keletkezik új példány belőle. A legjobb, ha a példányosításukat programból vezéreljük úgy, hogy elkérünk egy
új példányt az application context-ből, mely a prototype bean esetén mindig új példányt ad:
-
A prototype bean scope annotációja:
-
A spring konténer nem kezeli a prototype bean-t a teljes életciklusa alatt.
Az ApplicationContext.getBean() metódusa elkészíti a prototype bean-t, odaadja az őt hívónak,
de a spring konténerben nem marad benne, nincs rá hivatkozás. Csak a hívón múlik meddig tartja meg a prototype bean-t.
Ha nincs rá hivatkozás, a garbage collector fogja törölni a memóriából.
rabbitMQ
1. módszer
-
Az 1. módszer jobban konfigurálható mint a második, ezzel tetszőleges sok rabbithoz lehet kapcsolódni,
és a működés is jobban átlátható.
-
Dokumentációk
-
Nem létező queue
A rabbitMQ-hoz való kapcsolódáskor exception keletkezik ha a host, port, virtual-host, usernév, password
hibásan van beállítva, de nem keletkezik exception ha nem létező queue-ba küldjük az üzenetet.
Ez azért nem probléma mert a queue dinamikusan van kezelve, tehát egy nem létező queue-ba való íráskor
a queue automatikusan létrejön, és belekerül az üzenet.
-
A rabbitMQ használatához ki kell egészíteni a pom.xml-t:
-
Amikor a rabbitMQ osztályait használjuk, szinte mindig ebből a package-ből kell importálni:
-
Consumer elindítása
A channel objektumhoz kell a consumer objektumot hozzárendelni, a basicConsume() metódussal.
A hozzárendelés után a consumer rögtön aktív, és fogadja az üzeneteket.
Fontos hogy a 2. paraméter false legyen, így nincs automatikus ACK.
ahol a consumerReader, az általunk írt üzenet fogadó.
-
Consumer leállítása
A leállítás nem történik meg rögtön, ezzel a metódussal csak jelezzük a consumer-nek, hogy amint teheti álljon le.
-
ACK
A consumer így tud egy üzenetre ACK választ adni, melynek hatására az üzenet törlődik a queue-ból:
-
NACK
A consumer NACK-t hajt végre, és az üzenet a queue-ban marad, ha ezek egyike teljesül:
- Exception-t dob kifelé
- Nem hajtja végre a basicAck()-t és a basicReject()-et sem
Továbbá direkt is kiadhat NACK-t, és az utolsó boolean paraméterrel szabályozhatja,
hogy a queue-ban maradjon-e az üzenet:
-
REJECT
A consumer így tud egy üzenetre REJECT választ adni,
ahol a boolean paraméter szabályozza hogy a queue-ban marad-e az üzenet:
-
Példakódok
-
RabbitConstant.java
RabbitMQ kapcsolódás értékei.
-
RabbitController.java
Üzenet küldése, és a MainApplication.runConsumer kapcsoló állítása, mellyel a consumer indítási
vagy leállítási szándéka jelezhető.
-
ConsumerService.java
Consumer kezelése.
Időnként új channel-t nyit, így leszakadás esetén újra tud kapcsolódni.
A MainApplication.runConsumer alapján elindítja, vagy leállítja a consumer-t.
-
ConsumerReader.java
Consumer. Olvassa az üzeneteket, és figyel a channel nyitottságára.
2. módszer
-
A Rabbit Message Queue használatához be kell illeszteni a pom.xml-be:
-
Az application.yml fájlba vigyük fel a message queue elérését. Beletehetjük a saját jellemzőket is, például:
-
Üzenet küldése a message queue-ba:
Ha hibás a küldés (rossz exchange név, rossz queue név, nincs jogosultság, ...),
nem keletkezik exception, vagyis nem lehet tudni sikeres volt-e a művelet!
-
Üzenet kiolvasása listener segítségével a message queue-ból, egy spring-bean osztályban.
A példában a queue név nem fixen van beállítva,
hanem a MainApplication spring-bean queueName tagjából vesszük, vagyis spEL-t alkalmazunk.
-
A listener folyamatosan figyeli van-e üzenet a queue-ban. Az nem probléma ha a listener leáll miközben a queue-ban
keletkeznek üzenetek. Amikor a listener újraindul, felismeri a queue-ban lévőket.
-
Amíg a listener metódus feldolgoz egy üzenetet, addig nem hívódik meg újra a metódus, tehát az üzenetek sorban
állnak a feldolgozásra várva. Ez a default működés.
-
Ha egy queue-hoz több listener tartozik, akkor a queue-ban keletkező üzenetet csak az egyik listener fogja megkapni.
Tehát vigyázzunk ha ugyanaz a program több környezetben van, mert egymás elől elhalásszák az üzeneteket,
illetve nem kiszámítható melyik fogja feldolgozni.
-
Üzenetet az exchange-nek kell küldeni, ő továbbítja egy vagy több queue-ba.
Ha az exchange direkt módra van állítva, akkor a megnevezett queue-nak küldi, ha topic módú,
akkor többnek. Topic-nál használható a "*" karakter.
-
Üzenetre reagálni 3 módon lehet:
-
Nyugtázni ACK-val (acknowledge). Ekkor az üzenet törlődik a queue-ból.
Ha a listener metódus lefut exception keletkezése nélkül, akkor az egy ACK-val egyenlő.
-
Nem nyugtázni NACK-val (no acknowledge). Ekkor az üzenet nem törlődik, hanem megmarad a queue-ban.
Az üzenet a sor legvégére kerül, így csak a többi várakozó üzenet után fogja a listener ismét megkapni.
Ha a listener metódus exception-t dob, akkor az egy NACK-val egyenlő.
-
Visszautasítani REJECT-tel. Ekkor az üzenet törlődik a queue-ból.
Ha a listener metódus az AmqpRejectAndDontRequeueException-t dobja, akkor az egy REJECT-tel egyenlő.
-
A listener-ben nem szükséges Channel paramétert fogadni.
A Channel objektum segítségével lehet manuálisan is ACK, NACK, REJECT-et kiadni a message-re.
-
Message listener ki/be kapcsolása
Ehhez nem szükséges a programot leállítani, hanem pl. egy controller metódust elég meghívni.
Ebben a metódusban a következők szükségesek:
Továbbá, az application.yml fájlban beállítható, hogy program indításakor mi legyen a default, vagyis fusson-e:
redirect
-
Ha egy controller metódus visszatérési értéke String, akkor egy "redirect:" kezdetű Stringgel küldhet
ki redirect-et, például:
-
Saját alkalmazáson belüli redirect. A context path-t nem kell beleírni:
-
Redirect külső címre:
-
A "redirect:" konstans string megtalálható a spring keretrendszer UrlBasedViewResolver
osztály egyik statikus tagjaként:
-
Ha egy controller metódus @ResponseBody annotációval van ellátva, akkor a metódusba állítsuk be
paraméterként a HttpServletResponse objektumot, és ennek a sendRedirect() metódusával végezhetjük
el a redirect-et. Ezesetben nem számít, hogy a metódus milyen visszatérési értéket ad vissza.
Ez a módszer lehet hogy nem korrekt, ugyanis ilyenkor általában webservice-ként működő controller metódusról van szó.
A kliens általában nem egy web böngésző, így a neki visszaküldött redirect-et lehet hogy nem tudja kezelni.
-
Ha a redirect címhez paramétereket is szeretnénk hozzáfűzni, akkor a RedirectAttributes osztályt kell használni.
RedirectAttributes
-
Http redirect esetén használatos (akár alkalmazáson belüli lapra, akár külső lapra navigálunk),
ha a meghívott lapnak paramétereket szeretnénk átadni.
-
A RedirectAttributes objektumhoz a controller metódus paramétereként férünk hozzá.
-
A RedirectAttributes gondoskodik a paraméterek url-escape -eléséről is.
-
Az átadott paraméter lehet normál paraméter, ami az url-ben meg fog jelenni, ez alkalmazáson belüli,
és azon kívüli url-re is működik.
-
Az átadott paraméter lehet flash paraméter ami nem jelenik meg az url-ben, ennek csak alkalmazáson belüli
redirect-nél van értelme.
Flash paraméter esetén a hívott lap ezeket a paramétereket a Model objektumba automatikusan megkapja.
Akkor használjuk ha nem akarjuk hogy a felhasználó lássa az adatot az url-ben, vagy nem fér be az adat az url-be.
-
A flash paraméter azért is hasznos, mert az a módszer nem működik, hogy az egyik controller feltölti
a Model objektumot, redirect-ál egy másik controllerhez, az pedig kiolvasná mit rakott az előző controller a Model-be.
-
Példa hívóra:
példa a meghívottra:
-
Angular kliens esetén az url # karaktert szokott tartalmazni. Ezesetben ne használjuk a
RedirectAttributes osztályt, mert az manipulálja az url összeállítását, és el fogja tüntetni a # karaktert,
és az utána álló részeket. Helyette a teljes url-t manuálisan állítsuk össze:
ResponseEntity
-
Szabályozható vele a http response code, és a http header is úgy,
hogy közben az eredeti objektumot is vissza tudjuk adni.
-
Az eredeti controller metódus egy Member példánnyal tért vissza, de szükség volt a http response kód beállítására is,
így be kellett csomagolni a Member-t egy ResponseEntity objektumba:
-
A http header, és a http response code is be van állítva a szöveges kimenethez:
-
Lásd még: file download
RestTemplate
-
Ezzel az osztállyal főleg rest service-t lehet hívni, de soap service hívása is lehetséges!
-
Figyelem !
Ha a meghívott url-ben query paraméter is van, akkor az url-t template-ként kell megadni,
melyben a paraméter értékek kapcsos zárójelben szerepelnek, és később adjuk meg ezek értékeit!
-
Rest service hívása http-get módon, a választ string-ben kérjük
-
Rest service hívása http-get módon, query paraméterekkel, a választ JsonNode-ban kérjük
-
Rest service hívása http-post módon, a kérést json-ben küldjük, a választ json-ben kapjuk
-
Rest service hívása http-get vagy http-post módon, http-header beállítással
-
Soap service hívása
A soapAction értéke megtalálható a SoapUI alkalmazásban, ha onnan sikerült megszólítani az interface-t,
és a Raw fülnél megnézzük a kérést.
-
Hibakezelés
Lehetséges, hogy a hívott rest service értelmes választ ad, de a válasz http státusza nem 200-as.
Ilyen esetben RestClientResponseException keletkezik,
és csak az exception kezelő ágban lehet a válaszhoz hozzáférni.
Lehetséges az is, hogy egy nemlétező url kerül meghívásra, vagy tűzfal nem engedi meghívni az url-t,
ilyen esetben pedig RestClientException keletkezik.
-
Timeout
Megadható egy időtartam, melynek leteltekor exception keletkezik, ha nem sikerült lebonyolítani a kommunikációt:
Ez a timeout beállítás csak erre a restTemplate példányra lesz érvényes!
sentry
-
A sentry egy központi exception, log gyűjtő szerver, melyet egyszerre több alkalmazás tud használni.
A sentry webes felületén tekinthetők meg az alkalmazás hibák, és log-ok.
-
Sentry használatának beállítása spring boot projekt esetén:
-
A sentry webes felületén készíteni kell egy sentry-project-et, melynek keletkezik egy dsn száma.
Ezt a számot kell beállítani a spring-es projektben, hogy ehhez a sentry-project-hez történjen a logolás...
-
pom.xml kiegészítése:
-
application.yml kiegészítése:
soap
-
spring.io guides dokumentáció:
Consuming a SOAP web service
Ezt a dokumentációt követve néhány kiegészítés szükséges:
-
A pom.xml -be ez a dependency is kell:
-
A maven-jaxb2-plugin nem készít java forráskódot, csak bytekódot generál a target könyvtárba.
-
Ha a maven-jaxb2-plugin arra panaszkodik hogy a wsdl-url nem szolgáltatja a last-modified header-t,
akkor erőltetni lehet a kód generálást a pom.xml-ben a maven-jaxb2-plugin <configuration> részében:
-
Ha a wsdl-url https protokollon keresztül érhető el, lehetséges hogy a maven-jaxb2-plugin
hibásnak érzékeli az url-hez tartozó https tanúsítványt annak ellenére, hogy web-böngészőben helyesnek látszik.
Ilyen eset amikor a tanúsítvány *-gal kezdődő címre lett kiállítva.
Ilyenkor nem hajlandó legenerálni a java osztályokat.
A probléma megoldása, hogy web böngészőből letöltjük a tanúsítványt,
és a netbeans alatt futó JDK-ban lévő cacerts tanúsítvány tárolóba betöltjük. Ettől kezdve a java meg fog bízni
ebben a tanúsítványban. Betöltéskor a trust? kérdésre yes választ kell adni.
A cacerts tanúsítvány tároló default jelszava changeit.
Tehát a JDK "bin" könyvtárában állva, ki kell adni a következő parancsokat:
-
Ha több wsdl-url-ből is szeretnénk generálni, akkor a maven-jaxb2-plugin leírásban
több <execution>-t kell megadni:
-
Ha a kommunikáció sikertelen, lehetséges hogy a SOAP protokoll verziója nem megfelelő. Ennek lecserélése:
spEL
-
Az spEL, vagyis Spring Expression Language stringeken belül alkalmazható, egyik hasznos felhasználása amikor
annotációkon belül szeretnénk dinamikus, más helyről származó adatot használni. Formátum:
-
Példa: @Value annotációnál null mint default érték megadása:
-
1. példa
@RabbitListener annotációnál, a queue nevét a MainApplication spring-bean tartalmazza:
-
2. példa
Az application.yml másodpercben tartalmazza a delay idejét. Ezt az értéket veszi fel a Property spring-bean.
A @Scheduled annotációnál nem a fixedDelay van alkalmazva, hiszen az numerikus, oda nem írható spEL.
Helyette a fixedDelayString van használva, és a benne szereplő spEL kifejezésben még egy
szorzás művelet is szerepel:
spring beans
-
Spring bean-nek nevezzük azokat az objektum példányokat, melyeket a spring az IoC konténerben
(Application Context-ben) tárol.
Ezek az osztályok a következő annotációk egyikével vannak ellátva:
és szintén spring bean az olyan objektum példány, amit ezzel az annotációval ellátott metódus ad vissza:
-
Ha egy bean-nek nem adjuk meg a scope-ját, akkor a default scope (singleton) érvényes, vagyis egyetlen példány
jön létre belőle az IoC konténerben.
-
A @Controller annotációval is bean-t definiálunk, és mivel nem szoktunk neki scope-ot megadni,
a default scope (singleton) érvényesül, így egyetlen példány lesz belőle az egész alkalmazásra vonatkozóan.
Természetesen adhatunk neki scope-ot, és akkor ennek megfelelően működik.
-
Az @Autowired annotáció csak bean-en belül használható, és más bean-re lehet vele hivatkozni.
-
Bean scope-ok megadása a következő annotációkkal javasolt:
-
Application scope bean sosem jár le, mindig ugyanaz a példány van, hacsak nem telepítjük újra az alkalmazást.
-
Ha a session lejár, és session scope bean-re hivatkozunk, akkor a spring automatikusan újra létrehozza a bean-t,
és beteszi a session-be.
Tehát a session scope bean mindig elérhető. Legfeljebb egy új példánnyal találkozunk.
-
A bean-nek kell lennie paraméter nélküli konstruktorának, különben exception keletkezik,
mert a spring nem tudja példányosítani.
-
A bean csak akkor jön létre ha hivatkozunk rá, de nem elég a @Autowired, hanem tényleg hivatkozni kell a példányra.
-
@PostConstruct, @PreDestroy
A bean létrehozás, és megszüntetés eseményt el lehet kapni ha 2 metódust készítünk,
és ezekkel az annotációkkal látjuk el őket.
-
@Lazy
Ez az annotáció például egy controllerben alkalmazható. Lényege hogy a @Autowired-del allátott bean-t
ne húzza be rögtön az alkalmazás inicializálásnál. Ilyenkor még nincs request, így egy request-scope bean
behúzása ebben a fázisban exception-t okoz a request hiánya miatt. Így a @Lazy-vel ellátott controller
csak akkor lesz létrehozva, ha egy http-request által meghívásra kerül. Ekkor már van request objektum is,
ezért a @Autowired bean is behúzásra kerülhetnek.
-
Bean proxy:
Tegyük fel hogy egy controllerhez @Autowired-del be van húzva egy request bean.
Valójában ez csak egy bean proxy példány,
ehhez férünk hozzá (elvileg a bean leszármazottjának kéne lennie, és csak egyetlen példányban van meg).
Amikor a controller fut, a bean még nem lesz aktiválva, vagyis nem fut le a konstruktora.
Akkor sem fut le ha a bean-nek közvetlen módon érjük el az egyik tagját (bean.random),
sőt ez a tag abnormális értéket tartalmaz.
Ez azért van mert a proxy osztályhoz férünk hozzá, és ő nem delegálja a kérést a velódi bean-hez.
Ha viszont a bean-nek valamelyik metódusát hívjuk (bean.getRandom()) akkor aktiválódik a bean konstruktora.
Viszont ezután is fontos, hogy a bean tagjának normális várt értékét csak valamilyen metódusán keresztül
kaphatjuk meg.
Ez azért van mert a proxy osztály metódusának hívásakor delegálja a kérést a velódi bean-hez.
Hogy ilyen hibát ne lehessen elkövetni, valójában a bean nem statikus tagjainak private-nak kell lenniük,
és a tagokhoz csak getter, setter metódusokkal kell hozzáférést adni, így a kérést a proxy delegálja a
valódi bean-hez.
-
Bean név, egyediség:
Az IoC konténerben nem lehet ugyanolyan névvel több bean osztály!
Ha egy bean-nek nem adunk nevet, akkor csak az osztálynévből képződik a neve kis kezdőbetűvel
(a package név nincs benne).
Tehát ha létrehozzuk a package1.Proba, és a package2.Proba bean-eket akkor konfliktus lesz hiszen
mindkettő "proba" névvel akar bekerülni az IoC konténerbe, így az alkalmazás el sem indul.
Ezt elkerülhetjük ha más-más nevet adunk a két bean-nek.
-
Az IoC konténerben lévő bean-ek listázása:
static resource
-
Az alkalmazásban lévő static resource-ok (css, js, image, ...) elérhetők url-en keresztül
a controllerek megkerülésével.
Ehhez az application.yml fájlban definiálni kell hol találhatók ezek a könyvtárak.
A default beállításban 4 könyvtár is szerepel:
A static resource keresése az alkalmazásban csak akkor történik meg, ha az adott url-hez nincs fogadó controller!
Ezesetben a spring.web.resources.static-locations -ben megadott könyvtárakban keres balról jobbra,
és azt a resource-t adja vissza, melyet legelőször megtalál, és az fájl, nem pedig könyvtár.
-
Példa: állítsuk be az application.yml fájlban a static resource keresést 2 könyvtárra:
a hívott url legyen:
amennyiben nincs /css/base.css url-t kezelő controller, akkor a jar-fájl következő két könyvtárban
keres a spring:
Amennyiben egyik fájl sem létezik, az exception handler hívódik meg.
swagger
-
Egy spring boot-os alkalmazásban előszőr a rest service-eket kell elkészíteni, majd ezután kell
"ráereszteni" a swagger-t, hogy derítse fel az alkalmazás milyen rest service-eket tartalmaz.
Ehhez az alkalmazást ki kell egészíteni a swagger eszközeivel.
-
A spring boot alkalmazás lehet jar, és war is, mindkettőhöz működik ez a swagger kiegészítés.
-
A rest controller metódusoknak lehetőleg ne Object legyen a visszatérési értéke, mert akkor a swagger nem tudja
a valódi struktúrát felderíteni, hanem valami generikusabb, például egy saját JsonResponse osztály.
-
Ha a rest controller osztály metódusait magyarázattal akarjuk ellátni, mely megjelenik majd a swagger felületén,
akkor a metódusok elé ilyen annotáció szükséges:
OpenAPI swagger (ajánlott)
-
Sokkal egyszerűbb beépíteni a programba mint a Springfox swaggert, és ez már támogatja a Java17 + Spring Boot 3-at is.
-
OpenAPI v2
➔ Java 17 vagy ennél magasabb java verzió esetén
-
OpenAPI v1
➔ Java 17-nél kisebb verzió esetén
-
A programban csak a pom.xml -t kell kiegészíteni:
és a swagger felület már hívható ezen az url-en:
Springfox swagger (kevésbé ajánlott)
-
Nehezebb beépíteni a programba mint az OpenAPI swaggert, és nem támogatja a Java 17 + Spring Boot 3-at.
Ha mégis ezt építjük be egy Java 17-es projektbe, akkor exception keletkezik, melyben a HttpServlet osztályt
nem találja, ugyanis pont a Java 17-től ez az osztály már egy új jakarta package-be került át.
-
Az alkalmazás módosítása, hogy kapjunk egy webes swagger felületet:
-
pom.xml kiegészítése
-
Swagger config osztály elkészítése
-
application.yml módosítása
A swagger felületen az api json a "v2/api-doc" címen jelenik meg.
Ennek az url-nek az átdefiniálása, és egy hiba kiküszöbölése, melynek hiányában az alkalmazás el sem indul:
-
Ha nem akarjuk, hogy egy interceptor lefusson a swagger által készített url-ekre,
akkor ezt a címet le kell tiltani az interceptor regisztrációjában:
-
Ha az alkalmazás módosítása elkészült, akkor elérhető a swagger felület:
Az url végéről nem hagyható le a "/" jel!
systemd service
-
Az elkészült spring boot jar-fájlt telepíthetjük egy unix szerverre úgy, hogy init.d vagy systemd
service-ként fusson.
A systemd service a jobb, és modernebb módszer, ennek használata ajánlott.
A módszer előnye hogy rendszer boot után is automatikusan elindul!
A jar-fájl szerverre másolásán kívül, el kell készíteni egy "service leíró" fájl is,
például myapp.service névvel,
és a szerver /etc/systemd/system könyvtárába kell másolni. A tulajdonosa és a csoportja is
"root" legyen.
-
Példa myapp.service fájlra, melyben az ExecStart sor a hosszúsága miatt a "\" karakterrel meg van törve:
-
User=tomcat8
Annak beállítása, hogy milyen unix user nevében fusson az alkalmazás, a példában tomcat8 nevében fut.
-
WorkingDirectory=...
Mindenképpen legyen beállítva, hogy a relatív url-ek ehhez tudjanak viszonyulni.
-
Restart=always
A spring alkalmazás leállása esetén (akár System.exit()-et hajt végre, akár összeomlik), a systemd service
újraindítja az alkalmazást. Az indítási kísérletek között eltelt időt a RestartSec szabályozza,
a fenti példában ez 10 másodpercenként történik meg.
-
-Djava.security.egd=file:/dev/./urandom
Ez mindenképp kell, különben lassan indulna el az alkalmazás, valami unix-os véletlenszám generátor miatt...
-
Ha mindkét fájl felkerült a szerverre, a service elindítható, leállítható systemd service parancsokkal.
-
systemd service leállítása és elindítása. Akkor is működik ha előtte még nem futott:
-
systemd service indítás:
-
systemd service leállítás:
-
systemd service beállítása úgy, hogy rendszer boot után is elinduljon:
-
systemd service státusz megtekintés:
Státusz listázásakor látható egy "enable" vagy "disable" szócska,
ami jelzi hogy rendszer boot után elindulna-e a service.
-
Ha megváltozik a "service" fájl, akkor utána ez is szükséges hogy életbe lépjen a módosítás:
-
systemd verziójának lekérdezése:
-
A {serviceName} a fenti példában "myapp" vagyis a service fájl neve, a kiterjesztés nélkül.
-
Új programverzió telepítése deploy script-tel, systemd service újraindítással
Hogy gyorsabban lehessen egy új jar-fájlt telepíteni, érdemes készíteni egy jar telepítő deploy scriptet.
Előszőr az elkészült jar-fájl fel kell másolni a szerverre, a saját könyvtárunkba (/home/xesj).
Ezután el kell indítani a telepítő scriptet, ami elvégzi a szükséges teendőket:
a jar-fájl nevéből levágja a verziószámot, bemásolja a szükséges könyvtárba,
és elindítja a restart scriptet (systemctl restart):
-
Új programverzió telepítése a systemd service újraindítása nélkül
Ha a program el van látva exit url-lel, vagyis ezt az url-t meghívva végrehajt egy System.exit()-et,
akkor így lehet eljárni:
- Felül kell írni a szerveren lévő jar-fájlt
- Meg kell hívni az alkalmazás exit url-jét
A jar felülírásával még nem fog az új programverzió futni, hiszen az a memóriából fut.
A System.exit() hatására viszont a program leáll, és a service újraindítja az egész JVM-et, ami beolvassa
az új jar-t a fájlrendszerből.
unit test
-
Ki kell egészíteni a pom.xml fájlt:
-
Nem kell kiegészíteni a pom.xml-t a junit-ra vonatkozó bejegyzésekkel,
mivel a spring-boot-starter-test betölti a junit függőségeket!
-
Példa egy teszt osztályra, melyet olyan környezetben szeretnénk futtatni,
mintha az alkalmazás a következő paraméterekkel indult volna:
Ebben az esetben a teszt osztályt a következő annotációkkal kell ellátni:
Az @ExtendWith a spring - junit összekapcsolása miatt szükséges.
A @SpringBootTest annotációnál meg kell adni a fő osztályt (vagy osztályokat),
mely alapján a spring megtalálja a többi komponenst. Érdemes azt az osztályt megadni,
melyben szerepel a @SpringBootApplication annotáció, mert az rámutat a komponensek gyökér könyvtárára.
Itt adhatók meg a parancssor egyéb kapcsolói is, vagy a spring property-k (--).
Ha nem spring alkalmazást tesztelünk, mert például csak egy library-t írtunk a spring-hez,
akkor készíthető egy @Configuration annotációval ellátott osztály, mely csak a teszt-nek szóló,
és erre kell hivatkozni.
Az @ActiveProfiles annotációval beállíthatók a teszt futásakor aktív profile-ok.
Ezek az annotációk biztosítják, hogy a teszt osztály spring-es környezetben fusson,
így például a teszt osztályban használható a szokásos @Autowired annotáció is.
Ha az embedded szervert is el akarjuk indítani a teszt futásakor, akkor ezt is be kell állítani:
-
Ha http-hívásokkal is szeretnénk tesztelni az alkalmazást, akkor használható a MockMvc osztály,
de jobb ha inkább az ismerős RestTemplate osztályt használjuk.
Ehhez a spring biztosít egy teszt-hez készült példányt, amit behúzhatunk mint dependency-t, és használhatjuk:
-
Netbeans-ben a teszt osztályok neveinek Test-re kell végződniük,
különben a Clean and Build nem veszi őket figyelembe, nem futtatja le a teszteket.
-
Dokumentáció: Spring Boot refdoc 45. fejezet: Testing
utility
-
A spring utility-ket ez a package tartalmazza: org.springframework.util
Azonban ezen osztályok nagy része belső használatra készült, nem biztos hogy jó ötlet ezekre építeni,
mert változhatnak. Vannak kivételek, lásd lejjebb.
-
FileSystemUtils.deleteRecursively(...)
Fájl vagy könyvtár törlése.
Könyvtár esetén a teljes belső tartalmát is törli.
Nem probléma ha nem létezik a fájl vagy könyvtár, emiatt nem dob exception-t.
-
FileSystemUtils.copyRecursively(forrás, cél)
Fájl vagy könyvtár másolása.
Fájl másolásakor:
Ha a cél már létezik, akkor FileAlreadyExistsException-t dob.
Könyvtár másolásakor:
A forrás könyvtár teljes belső tartalmát másolja, de a forrás könyvtárat nem.
Ha a cél könyvtár nem létezik, akkor létrehozza, az összes szülő könyvtárával együtt.
Ha a cél könyvtár már létezik, akkor sem dob exception-t.
A cél könyvtár tartalmát nem törli a másolás előtt.
validation / @
-
Az annotációval jelölt bean validáció lényege, hogy a form bean tagjait, vagy az egész osztályt
annotációval látunk el, mely automatikusan validációt jelent a form bean számára.
Ezeket az annotációkat nevezzük validációs annotációknak.
-
Ha a standard JavaEE validációs annotációkat szeretnénk használni, akkor a "javax.validation.constraints"
package-en belüli annotációk szükségesek. Ezen kívül van még hibernate-es validációs annotáció is,
ami nem a standard JavaEE része!
-
A validáció aktiválásához az is szükséges, hogy a form post-ot fogadó controller metódusban használjuk a
@Valid annotációt:
-
A validációs annotációknak van egy "message" paramétere, melyben beállítható mi legyen a hibaüzenet
ha az adat hibásnak minősül.
A "message" többféleképpen használható:
-
1. lehetőség: Az annotációnak egy konkrét hibaüzenet van megadva:
-
2. lehetőség: A hibaüzenet a classpath gyökerében lévő ValidationMessages.properties
fájlban van megadva (i18n lehetőség), és az annotációban erre hivatkozunk:
Annotáció:
ValidationMessages.properties:
-
3. lehetőség: A hibaüzenet a classpath gyökerében lévő ValidationMessages.properties fájlban
van megadva (i18n lehetőség), de az annotáció nem hivatkozik rá, hanem a default kulccsal olvassa ki onnan
a hibaüzenetet. A default kulcs az osztály teljes neve + ".message" szöveg.
A default kulcs megtekinthető az annotáció forráskódjában is a "message" résznél.
Annotáció:
ValidationMessages.properties:
-
Lehet olyan bean validatort is készíteni, mely nem a bean egy mezőjére, hanem több mezőre egyszerre vonatkozik,
így az egész bean-re kell tenni az annotációt, lásd google: "bean validation multiple fields"
validation / standard
-
A standard validáció a spring eredeti validációs módszere, használatához a lent leírtak szükségesek.
-
Implementálni kell a "Validator" interface-t.
Az interface supports() metódusában jelezzük, hogy a validátor milyen osztályt képes validálni.
Ez nem azt jelenti, hogy automatikusan hozzá van rendelve a validátor ehhez az osztályhoz.
Azt jelenti hogy amikor pl. egy @InitBinder-rel a validátort egy form bean-hez rendeljük,
ellenőrzésre kerül hogy a supports() metódus által támogatva van-e. Ha nem akkor exception váltódik ki!
-
Be kell jegyezni a validátort a használni kívánt controllerben, annak az @InitBinder annotációval
ellátott metódusánál. Az @InitBinder-ben megadott névnek meg kell egyezni a @ModelAttribute-ban megadott
névvel hogy fusson a validátor.
-
A validáció aktiválásához szükséges, hogy a form post-ot fogadó controller metódusban használjuk a
@Valid annotációt:
-
A standard validációt kényelmesen használni form-ok esetében lehet.
Webservice esetén, ha együtt használjuk a @Valid @RequestBody annotációt, a nehézségek a következők:
-
A különálló osztályba helyezett validátor ugyan bejegyzi a talált hibákat, de a @Valid miatt
MethodArgumentNotValidException keletkezik, amit egy exception handlerben kell lekezelni.
-
Az eredeti validált objektumhoz hozzá lehet férni, de a hibaüzenetek kiolvasása a BindingResult
objektumból nem egyszerű.
A példában csak a legelső globális hibaüzenetet nyerjük vissza:
war file
-
Egy Spring boot alkalmazás normális esetben jar fájl-ként fut embedded server módon,
azonban ha szükséges készíthető belőle war fájl is, és telepíthető egy alkalmazás szerverre.
-
A "create a deployable war file"
dokumentáció részletesen leírja a war készítést, mely a következő lépésekből áll:
-
A pom.xml-ben ki kell cserélni a csomagolás módját "jar"-ról "war"-ra:
-
A pom.xml-ben a "spring-boot-starter-tomcat" bejegyzéshez be kell állítani, hogy a végső fájlba
ne csomagolja be ezt a könyvtárat, mert a futtatókörnyezet fogja biztosítani:
-
A @SpringBootApplication annotációt tartalmazó osztálynak egy SpringBootServletInitializer
osztály leszármazottnak kell lennie, és felül kell definiálni a configure() metódusát.
-
WAR esetén a futtató szervertől függ, hogy json vagy xml kimenetet generál a spring, amikor objektum a
controller metódus visszatérési értéke.
Ez a bizonytalanság megszüntethető, ha például beállítjuk a @RequestMapping produces attribútumát.
-
Amennyiben a szerver biztosítja az adatbázis jndi nevet, és ezt nem tudjuk felvenni az application.yml
fájlból, akkor az egyik lehetséges megoldás a DataSource bean ilyen megírása:
websocket
-
A websocket használatához a pom.xml bővítése szükséges:
-
dokumentáció
xesj standard
-
Használt alap technológiák
- Spring Boot
- Thymeleaf
- JdbcTemplate (DAO, DTO)
-
Projekt struktúra
-
Root-package:
-
A root-package-ben nincsenek java osztályok, csak package-ek.
-
A további java osztályok csak a {root-package} alcsomagjaiban lehetnek.
-
Az alkalmazást a Run.java futtatja, mely a {root-package}.system package-ben van.
A Run.java osztályhoz tartozik a @SpringBootApplication annotáció:
-
Controller
-
Controller osztályok nevei: XxxController
-
A "/" url-t lekezelő controller a MainController mely a {root-package}.system package-ben van.
-
Az egyetlen error controller szintén a {root-package}.system package-ben van, deklarációja:
-
View
-
A thymeleaf view-k helye az "src/main/resources/view" könyvtár, és alkönyvtárai.
Mivel nem ez a default, ezt be kell állítani az application.yml fájlban:
-
A controllereknek a view megadásakor meg kell adniuk a view kiterjesztését is, például: /base/menu.html
Ezért nincs suffix:
-
Minden formnál kötelező kitölteni az action tag-et, például:
-
Validation
Általam készített validátor osztályok használata.
Webservice esetén a spring default validátor és a bean validátor használata, és a hibák kezelése nagyon nehézkes,
lásd: @Valid @RequestBody együtt.
-
Conversion
PropertyEditor használata, a formatter-rel és converter-rel szemben.
A PropertyEditor üres form mezőre is fut, így ennek konvertálása is szabályozható.
-
Static resource
Url-en keresztül, a controllerek kikerülésével elérhető static resource-ok
csak az "src/main/resources/public/static" könyvtárban és alkönyvtáraiban lehetnek.
Mivel nem ez a default, ezt be kell állítani az application.yml fájlban:
Így a rájuk való hivatkozásnál meg kell adni a "/static" könyvtárat is, melyből látszik hogy
statikus resource-ról van szó, továbbá a program sosem tartalmaz "/static" url-t kezelő controllert.
-
Context path
Nem lehet üres.
-
Jar
Csak JAR futtatás lehetséges, WAR nem használható.
-
Spring Security
Nem használt, mivel feleslegesen túlbonyolított.
yaml
-
A program jellemzők beállításához application.yml (yaml) fájlt használjunk az application.properties helyett.
Nem kell külön semmit beállítani, a spring boot be fogja olvasni.
-
A yaml jellemzőit legjobb ha egy @ConfigurationProperties osztályba
olvassuk be, mert az képes a yaml-ból az összetett adatszerkezeteket (List, Map) is beolvasni.
-
A yaml előnyei:
-
UTF-8 kódolású, tehát egyszerűen olvasható a tartalma egy sima editorral is. A properties nem UTF-8 kódolású.
-
A stringeket be lehet tenni idézőjelbe (és aposztróf közé is), így nem fordul elő,
hogy egy nem látható space van a végén.
A properties-nél előfordulhat.
-
Az idézőjeles stringen belül használhatók speciális karakterek is, például: \" \n \t
-
Egymásba ágyazhatók a tagok vagyis struktúrált, így pl. az adatbázis elérésnél nem kell mindig kiírni a prefixet.
A properties-nél ki kell írni.
-
Használhatjuk az egymásba ágyazást, vagy a pont-tal jelölt formát is, a kettő egyenértékű:
VAGY