Keresés tartalomra
Kategóriák
Címkék
- Java
- Spring
- Python
- IoC
- Android
- DI
- Dagger
- Thymeleaf
- Markdown
- JDK11
- AOP
- Aspect
- Captcha
- I18n
- JavaSpark
- Microframework
- Testing
- JUnit
- Security
- JWT
- REST
- Database
- JPA
- Gépház
- WebFlux
- ReactiveProgramming
- Microservices
- Continuous Integration
- CircleCI
- Deployment Pipeline
- Docker
- Mocking
- LogProcessing
- PlantUML
- UML
- Modellezés
- OAuth2
- Node.js
- DevOps
- Websocket
- PyPI
- Cucumber
- Next.js
Migrálás a Spring Boot 4-es verziójára: Tapasztalatok és buktatók
A Spring Framework és a Spring Boot fejlesztői az utóbbi években a Java-hoz hasonló release cycle-re álltak át, így nagyjából fél évente számíthatunk új alverzióra, illetve 3 évente új főverzióra. Emellett fontos megemlíteni, hogy minden főverzió utolsó alverziója (például Boot 2.7.x, 3.5.x) egyben kiterjesztett (7 évre szóló) Enterprise támogatást kap (az open-source támogatás egységesen 1 év minden alverzióra). Ennek megfelelően tavaly novemberben meg is jelent a Spring Boot 4, ami a hivatalos migrációs útmutató szerint is inkább volt egy hatalmas takarítás, egységesítés, illetve áramvonalasítása a frameworknek. (Nevezzük refactornak? Nevezzük annak.) Természetesen ennek megfelelően a standard függésekből megkapta a legfrissebb elérhető (és kompatibilis) verziókat, amik alapvetően több esetben okoznak szignifikáns változásokat, mint maga a framework által bevezetett változtatások. Erre a gondolatra még később visszatérek, előbb nézzük a "jó híreket".
A migrálás az új verzióra viszonylag könnyen ment, persze nagyban függ attól, hogy mi a kiinduló verzió. Én a 3.4.5-ös verzióról indultam, ami nem a legutolsó főverzió, a hivatalos útmutató pedig konkrétan kitér rá, hogy előbb mindenképp lépjünk fel az alkalmazásainkkal a 3.5.x branch legfrissebb verziójára (ez a cikk írásának pillanatában a 3.5.14-es verzió. Ezt mindenképp tudom javasolni, ugyanis abban a verzióban már látszani fognak a deprecated belső API hívások, konfigurációs paraméterek, teljes függések, így könnyebb felkészülni a migrálásra. A korábbi (2-es főverziójú) Boot-ról közvetlenül 4-esre lépést semmiképp sem tudom ajánlani, mivel a v2 -> v3 migrálás önmagában elég fájdalmas volt, lévén akkor állt át a Boot a Javax API csomagról a Jakarta-ra. 3-as főverzión belül, emlékeim szerint a 3.0.x és a 3.1.x között voltak jelentősebb változások (például a Spring Security API-ában), de a 3.1.x-ről 3.4.5-re frissítés triviális volt (lényegében verzióváltás, build, csomagolás, és mehet élesbe). A 3.4.5 -> 3.5.17 váltásban viszont a Spring Data JPA okozott kisebb meglepetéseket, csak röviden, a teljesség igénye nélkül:
- A Criteria API kisebb változtatásokat kapott, például van egy szűrő implementációm, ahol a "most ne szűrj semmit" egy
Specification.where(null)utasítással volt megoldva. Az említettwheremetódus azonban kapott egy második specifikációt, így "ambiguous call" hibát kezdett dobálni fordítás közben. Helyette aSpecification.unrestricted()használható. - Entitások között N:M kapcsolatok kialakítása eddig lehetséges volt úgy is, hogy ha teszem azt a reláció túlsó oldala ugyanaz a kollekció minden entitáshoz, akkor is csak beleírtuk az entitás objektumba, majd mentettük kézzel, vagy a tranzakció lezárása automatikusan mentette. A továbbiakban ezt a Hibernate (a Spring Data JPA mögött használt JPA implementáció) egy "Found shared references to a collection" hibával díjazza. A legegyszerűbb megoldás a kollekció elcsomagolása egy új kollekcióba (például
new LinkedList<>(sharedEntityList)) minden mentés előtt. - A Leaflet integrációs tesztjeinek kontextusa manuálisan volt összerakva, ez valamiért teljesen tönkrevágta a repository interface-ek és az entitások regisztrálását. A legtriviálisabb megoldás a "standard",
@SpringBootTestalapú kontextusra váltás volt (eltávolítva minden fölösleges manuális konfigurációt), ezzel minden további hajtépés nélkül azonnal újra működni kezdtek a tesztek.
Személy szerint én "csupán" ezekbe a problémákba futottam bele, de azt hiszem ezek is jól mutatják, mennyire fontos, hogy tesztekkel fedve legyenek az alkalmazás kritikus kód útvonalai, hiszen ezek annyira nüansznyi hibák, amik jó eséllyel már csak éles környezetben jöttek volna ki - így viszont a tesztek még időben elkapták.
Akkor most már frissíthetek Boot 4-re?
Igen. De előtte azt javaslom, hogy olvassuk át a hivatalos útmutatót, ami meglehetősen fontos gondolatokat tartalmaz, illetve lehetnek sajnos olyan pontjai is a migrálásnak, ami miatt végül az elhalasztás mellett kell majd döntenünk. Az alábbiakban szeretnék pár olyan dolgot kiemelni és kitárgyalni, amibe én magam is belefutottam és/vagy fontosnak érzem.
A minimális Java Runtime (JRE) és Development Kit (JDK) verzió innentől a 17-es
Valószínűleg 2026-ban már senkit nem kell noszogatni, hogy legalább 17-es Java verziót használjon a Java projektjei alatt (ha mégis, akkor van egy rossz hírem). Kotlin-ból a 2.2-es verzió a jelenlegi minimum.
Konfigurációs paraméterek változásai
Számos standard konfigurációs paraméter neve, útvonala, lehetséges értékei megváltoznak Boot 4-ben. A deprekált kulcsokat szerencsére a Spring támogatással bíró IDE-k is jelzik, de a fejlesztők voltak olyan kedvesek, hogy megkönnyítendő a dolgunkat, kiadtak egy Maven/Gradle plugint, ami diagnosztizálja a migrálásra szoruló kapcsolókat. Az alkalmazás indulásakor erről részletes riportot kapunk, illetve az útmutató szerint képes "ideiglenesen, kizárólag futásidőben migrálni a kérdéses kapcsolókat". Hogy 1-2 példával is éljek, nekem a spring.data.mongodb.* kapcsolókat kellett migrálnom a spring.data.* kulcsok alá, illetve a Jackson kapcsolói változtak meg (erre később még külön visszatérek).
Megszűnik a self-executable JAR csomagolás lehetősége
Feltételezem nem sokan használták, de én egyike voltam azoknak akik igen: a Spring Boot Maven Plugin "executable" kapcsolójával korábban a becsomagolt JAR-ba bekerült egy viszonylag vaskos .sh fájl, aminek a célja az volt, hogy a szokásos java -jar ... parancs helyett pusztán a .jar fájlt mint végrehajtható binárist futtatva elindítsuk a csomagolt alkalmazást (például ./myapplication.jar). Ennek a lehetősége megszűnik a Boot 4-es főverziójában, maga a kapcsoló is eltűnik a plugin kapcsolói közül. Az indoklás szerint ez egyébként is specifikus volt Linux (illetve Unix-szerű) rendszerekre (őszintén nem értem a problémát, futtat bárki Spring Boot alkalmazást bármin ami nem Linux..? -- leszámítva persze esetleg a lokális fejlesztői környezeteket), illetve nem kompatibilis az úgynevezett Efficient Deployments koncepcióval.
Adott körülmények között hajlamosak XML-ben válaszolni a REST endpointok
Ez egy egészen furcsa probléma, de észrevettem, hogy amennyiben a kliens nem nyilatkozik egyértelműen az Accept header használatával az elvárt válaszformátumról és a jackson-dataformat-xml csomag is jelen van a classpath-on, akkor a REST endpointok most már nem JSON, hanem XML formátumban válaszolnak alapból. Sajnos be kell ismernem, hogy nem jöttem rá a probléma okára, valószínűsítem, hogy az egész mögött levő Jackson library változásai okozzák (feltételezem az autokonfiguráció priorizálja az XML szerializálást a JSON szerializálás fölött -- lehetségesnek tartom, hogy ez egy bug, de semmi konkrétat nem találtam róla sajnos). Természetesen ez viszonylag könnyen javítható, kliens oldalról (az Accept: application/json header beállításával), illetve szerver oldalról egyaránt (az endpoint mapping regisztrációjában a produces = "application/json" attribútum beállításával).
A standard Boot függések újracsomagolása
Ezentúl egy egységesebb sémát követnek a Boot standard függéscsomagjai. A koncepció a következő:
- A függéscsomagokat támogatott "technológiák" szerint csoportosítják. Ezek lehetnek adatbázismotorok, servlet engine implementációk, messaging technológiák, lényegében bármi ami eddig is elérhető volt a Boot-hoz.
- Az adott technológia specifikus függései a
spring-boot-<technológia>csomagba kerülnek. - A hozzátartozó Boot-specifikus automatikus konfigurációk, konfigurációs paraméterek, illetve az azok modelljei, valamint feldolgozó kódja a
spring-boot-starter-<technológia>csomagban lesz. - Illetve, amennyiben az adott technológia rendelkezik teszteléshez szánt kiegészítőkkel, akkor annak a kódja a
spring-boot-starter-<technológia>-testcsomagba kerül. - Emellett a különböző Boot-specifikus root package-eket is módosították a fenti koncepciónak megfelelően, így azok az
org.springframework.boot.<technológia>illetveorg.springframework.boot.<technológia>.testpackage nevekre változnak. - A fentieknek megfelelően bizonyos csomagok deprekálásra kerültek (később el is lesznek távolítva), így például a
spring-boot-starter-webhelyett aspring-boot-starter-webmvccsomagra lesz szükségünk.
Tehát például ha Tomcat servlet engine-re van szükségünk, a Tomcat-et magát az org.springframework.boot group spring-boot-tomcat artifact-je húzza majd be, a hozzátartozó autokonfigurációt pedig a spring-boot-starter-tomcat, ami persze alapból hivatkozza az előzőleg említett artifact-et.
Nyilván emellett az adott technológia legfrissebb verziója is behozhat különféle változásokat, bár a legjelentősebb változások a Jackson library körül történtek, erre szeretnék kitérni cikkem hátralevő részében.
A Jackson library változásai
Számomra a Jackson 3-as verziójának bevezetése volt a legnagyobb fájdalom az egész migrálásban. Röviden összefoglalva a következő változások történtek:
- Az
ObjectMappermellett bevezeti aJsonMapper,XmlMapperésYamlMapperosztályokat a szerializálási és deszerializálási műveletek végrehajtására, specifikusan az adott formátumokhoz. A Boot maga is már ezeket konfigurálja alapból. - A korábbi
com.fasterxml.jackson.*root package megszűnik, helyét atools.jacksonroot package veszi át. - Kivéve, hogy a Jackson annotációk maradnak a régi csomagban (például
@JsonInclude,@JsonSerialize,@JsonDeserialize, stb.). Ez olyan szinten változatlan marad, hogy ajackson-databindcsomag 3.x főverziója továbbra is ajackson-annotations2.x verzióját húzza be. - Bizonyos class-okat is átnevez, az útmutató szerint ezek konkrétan a
JsonObjectSerializer(most márObjectValueSerializer),JsonValueDeserializer(ObjectValueDeserializer) ésJackson2ObjectMapperBuilderCustomizer(Jackson2ObjectMapperBuilderCustomizer). - Bizonyos "feature" kapcsolókat kivezet vagy megváltoztat, például a
SerializationFeatureosztályban már nincs jelen awrite-dates-as-timestampskapcsoló, mivel alapbólfalsemódban működik innentől (de átkerült máshova, ha mégis be szeretnénk kapcsolni). Illetve már nem szükséges külön regisztrálni aJavaTimeModule-t sem, standard módon támogatja mostantól a Java Time API-t. - Amennyiben Lombokot is használunk, a Lomboknak ezentúl explicit jelezni kell, hogy a Jackson 2-es vagy 3-as főverziójával dolgozik (a
@Jacksonizedannotáció miatt). Ehhez az adott artifactben (ahol@Jacksonizedannotációt használunk bármely package bármely class-án vagy record-ján), el kell helyeznünk egylombok.confignevű fájlt, abba az alábbi sort írva:lombok.jacksonized.jacksonVersion += 3(ezzel aktiválva a Jackson 3 támogatást).
Amennyiben a migrálás során a Jackson 3-ra való áttérés nem opció, a spring-boot-jackson2 csomag használatával átmenetileg visszatérhetünk a 2-es főverzió használatára, de ezt az útmutató nem javasolja (vagy legalábbis nem hosszútávon). További hasznos információk a migrálással kapcsolatban elérhetők ezen a Spring Boot blog bejegyzésen.
Továbbá fontos megemlíteni azt is, hogy amennyiben Jersey REST klienst használunk az alkalmazásunkban, a Jersey jelenlegi 4-es és a Jackson 3-as főverziója között a kompatibilitás még nem megoldott, így a Jersey jelenleg nem tud Jackson segítségével szerializálni és deszerializálni. Egy GitHub issue és egy PR is nyitva van már január óta a probléma orvoslására, de úgy tűnik semmi előrehaladás nem volt vele azóta se. Én személy szerint átálltam Apache HTTP Client alapokra a REST klienseimet illetően, és gyakorlatilag ez vitte el a migrálás során a legtöbb időt.
Összegzés
Igazából a Jackson-Jersey fiaskót leszámítva egyáltalán nem volt vészes a Boot 4-es főverziójára való migrálás. Hamarosan egyébként (tulajdonképpen most pici csúszásban is vannak vele, de) megérkezik a 4.1.0-s verzió, szóval amennyiben tervben van a migrálás, én már megvárnám azt inkább. Egyelőre rövidtávú előnyeit még nem láttam a 4-es főverziónak, egyértelműen látszik mindenesetre, hogy próbálják áramvonalasabbá tenni az egész frameworköt, kigyomlálni az évek során felhalmozódott szemetet és inkonzisztenciákat. Mellette persze a harmadik féltől származó függések is megkapják a legfrissebb verzióikat, ami biztonsági és teljesítmény szempontokból is mindenképp előnyös. Mindenesetre, ahogy korábban is hangoztattam már (ebben és korábbi cikkeimben is), semmiképp sem állnék neki a frissítésnek egy stabil, átfogó tesztkészlet hiányában, mert nagyon apró, egészen nüansznyi hibák jelentkezhetnek, amiket időben elkapni (ha nem okoznak fordítási hibát) közel lehetetlen. Legyünk tehát résen, végezzünk némi kutatómunkát a frissítés előtt, aztán igazából nem lehet probléma (híres utolsó mondatok?).
Komment írásához jelentkezz be
Bejelentkezés

Még senki nem szólt hozzá ehhez a bejegyzéshez.