Itt jársz most: Kezdőlap > Alkalmazásfejlesztés > REST válasz bővítése

Szűrő megjelenítése

REST válasz bővítése

A Leaflet REST alapú backendje egy Angular alapú frontend alkalmazást is ki fog szolgálni. Emiatt a generált JSON formátumú válasznak tartalmaznia kell bizonyos közös részleteket, például SEO paramétereket, vagy a menüt. Első ránézésre is túl körülményesnek tűnt, hogy minden controller minden esetben hozzáadja a közös részleteket a service réteg által visszaadott válaszhoz, mivel ez esetben “kézzel” kellett volna lekérni azokat más service-ektől, majd a válaszokat egy közös nagy válaszba összegyúrni. Megoldható lett volna ugyan facade-ek alkalmazásával, de még ez is viszonylag kényelmetlennek tűnt.

A megoldáshoz végül úgy döntöttem, a Spring interceptorait fogom segítségül hívni, melyeknek azonban van egy óriási hátrányuk: a válasz tartalmához csak akkor tudnak hozzányúlni, ha az ModelAndView típusú (tehát a controller ModelAndView típusú választ ad vissza). Ez felvetett további problémákat, melyeket orvosolni kellett - de szerencsére ezek az alkalmazás ViewResolver konfigurációjának kisebb módosításával megoldhatóak voltak (erről később bővebben írok).

Az első lépés tehát a válasz becsomagolása volt ModelAndView objektumba. Mivel az alkalmazás controllereinek van egy közös őse, abban létrehoztam egy wrapper metódust, melyet csak a leszármaztatott controllerek látnak. A wrapper metódus bármilyen response modelt képes fogadni, a objektumot pedig egy ModelAndView objektum “body” paramétereként tárolja.

class BaseController {
 
	// ...
 
   protected ModelAndView wrap(BaseBodyDataModel answer) {
 
		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addObject(BODY, answer);
 
		return modelAndView;
	}
}

A controllerek ezután a válaszukat a wrap() metódus segítségével tudják könnyen becsomagolni.

@RequestMapping(method = RequestMethod.GET)
public ModelAndView getAllEntries() {
 
	List<EntryVO> entries = entryService.getAll();
	return wrap(entryVOToEntryDataModelListConverter.convert(entries));
}

A válasz ekkor még természetesen nem kerül konvertálásra, előbb még átfut a kontextusban jelen levő interceptorokon - ezt a működést használjuk ki. Az interceptor létrehozásához a Spring HandlerInterceptorAdapter osztályát kell extendelni és a postHandle() metódust override-olni.

@Component
public class ResponseFillerInterceptor extends HandlerInterceptorAdapter {
 
	@Autowired
	private List<ResponseFiller> responseFillers;
	// ...
	@Override
	public void postHandle(HttpServletRequest request, 
			HttpServletResponse response, Object handler, ModelAndView modelAndView) 
			throws Exception {
		if (Objects.nonNull(modelAndView)) {
			responseFillers.stream()
					.filter(ResponseFiller::shouldFill)
					.forEach(responseFiller -> responseFiller.fill(modelAndView));
		}
		super.postHandle(request, response, handler, modelAndView);
	}
}

A mellékelt kódrészletben a kapott ModelAndView instance kibővítése történik meg, melyet “ResponseFiller”-ek intéznek (ezek már konkrét feladatok megoldását célozták, mivel többféle bővítésre is szükségem volt). A ResponseFiller-ek, bizonyos feltételek teljesülése esetén hozzáadnak további mezőket a ModelAndView-hoz. A ResponseFillerek hívhatnak service belépési pontokat is, így megszerezve a hozzáadandó adatokat. A konkrét implementációk megtekinthetőek a cikk végén szokásosan mellékelt Git repository-ban. Természetesen egy nullcheck-et érdemes implementálni, mivel bizonyos controllerek nem feltétlenül kell, hogy visszaadjanak választ.

Az interceptor létezését ekkor még a Spring tudtára kell hoznunk, melyet a WebMvcConfigurerAdapter alapú konfiguráció módosításával tehetünk meg. Boot alapú alkalmazás esetén nem is feltétlenül kellett készítenünk ilyen konfigurációs osztályt, ez esetben terjesszük ki azt. Az osztályt @EnableWebMvc annotációval ellátva engedélyezzük a Spring Web MVC képességeit (erre egyébként is szükség van nem Boot alapú alkalmazás esetén). Az létrehozott interceptort hozzá kell adnunk a létező interceptorok láncához, melyet az addInterceptors() metódus override-olásával tehetünk meg. Hozzuk létre az interceptor beanjét (vagy ha @Component-et használtunk, akkor már csak @Autowired-ölni kell), és adjuk hozzá az InterceptorRegistry-hez az alább látható módon.

@Configuration
@EnableWebMvc
public class WebMVCConfiguration extends WebMvcConfigurerAdapter {
 
	@Autowired
	private ResponseFillerInterceptor responseFillerInterceptor;
 
	// ...
 
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(responseFillerInterceptor);
		super.addInterceptors(registry);
	}
}

Az alkalmazás indítása ekkor azonban még meglepetéssel fog szolgálni: a ModelAndView típusú visszatérés a Spring-ben automatikusan úgy értelmeződik, hogy egy konkrét nézetet akarunk visszaadni, benne a nézetnek továbbítandó paraméterekkel. Ez esetben ez természetesen nem így van, nézetünk nincs, a választ JSON-ba konvertálva szeretnénk megkapni. A Spring mindenesetre közölni fogja, hogy nem talál mappelt nézetet - ráadásul nincs is megadva a nézet kulcsa. A megoldás nagyon egyszerű, manuálisan kell felülbírálni a Spring által automatikusan hozzáadott ViewResolver-t. Mivel JSON “nézeteket” szeretnénk, hozzunk létre egy MappingJackson2JsonView ViewResolver-t, majd override-oljuk a WebMvcConfigurerAdapter configureViewResolvers() metódusát. Hasonlóképp az interceptorhoz, itt is egy registry-ben kell rögzíteni az új ViewResolver-t, az alábbi módon:

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
 
	ViewResolver jsonViewResolver = 
		(viewName, locale) -> new MappingJackson2JsonView();
	registry.viewResolver(jsonViewResolver);
 
	super.configureViewResolvers(registry);
}

Ezzel be is fejeztük a konfiguráció módosítását, az alkalmazás most már képes lesz automatikusan hozzáadni bármilyen extra mezőt a válaszhoz, és a teljes választ JSON-ba konvertálva kapjuk majd meg.

Alkalmazás forrása

Kommentek

Komment írásához jelentkezz be
Bejelentkezés

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