Spring MVC: GET @RequestParam olarak karmaşık nesne


192

Bir tablodaki nesneleri listeleyen bir sayfam olduğunu ve tabloyu filtrelemek için bir form koymam gerektiğini varsayalım. Filtre, şu şekilde bir URL'ye Ajax GET olarak gönderilir: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z

Ve Denetleyicimde çok sayıda parametre yerine:

@RequestMapping(value = "/action")
public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    @RequestParam(value = "prop1", required = false) String prop1,
    @RequestParam(value = "prop2", required = false) String prop2,
    @RequestParam(value = "prop3", required = false) String prop3) { ... }

Ve varsayalım ben MyObject olarak:

public class MyObject {
    private String prop1;
    private String prop2;
    private String prop3;

    //Getters and setters
    ...
}

Gibi bir şey yapmak istiyorum:

@RequestMapping(value = "/action")
public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    @RequestParam(value = "myObject", required = false) MyObject myObject,) { ... }

Mümkün mü? Bunu nasıl yapabilirim?


1
MyObject'in modeliniz olacağı '@RequestMapping' ile birlikte '@ModelAttribute' kullanmayı deneyin.
michal

@michal +1. İşte bunun nasıl yapılacağını gösteren birkaç öğretici: Spring 3 MVC: Spring 3.0 MVC'de Formları Kullanma , Nedir ve nasıl kullanılır@ModelAttribute , Spring MVC Form İşleme Örneği . Sadece " Bahar MVC form işleme " google ve bir ton öğreticiler / örnekler alırsınız. Ancak modern form işleme yöntemini kullandığınızdan emin olun, yani Bahar v2.5 +
informatik01

Yanıtlar:


249

Bunu kesinlikle yapabilirsiniz, @RequestParamek açıklamayı kaldırın , Spring istek parametrelerinizi sınıf örneğinize temiz bir şekilde bağlayacaktır:

public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    MyObject myObject)

8
Biju, bunu nasıl arayacağınıza dair bir örnek verebilir misiniz? Rest API temel bir http GET çağrısı yapıyorum ve hiçbir fantezi formları vardır.
bschandramohan

33
Yay varsayılan olarak, MyObject öğesinin otomatik olarak bağlanması için alıcıları / ayarlayıcıları gerektirir. Aksi takdirde myObject öğesini bağlamaz.
aka_sh

20
MyObject'te alanları isteğe bağlı / isteğe bağlı olmayan olarak nasıl ayarlayabilirsiniz? Bunun için uygun belgeleri nasıl bulacağından emin değilim ..
worldsayshi

6
@Biju, @RequestParam ile yapabileceğimiz gibi, varsayılan değerleri kontrol etmenin ve bunun için gerekli MyObjectbir yol var mı?
Alexey

7
@BijuKunjummen MyObjectYılan durumunda sorgu parametrelerini kabul etmek ve bir deve vaka üyesiyle eşleştirmek için nasıl güncelleyebilirim MyObject. Örneğin, ?book_id=4birlikte aktarılması gerektiğini bookIdüyesi MyObject?
Vivek Vardhan

55

Benden kısa bir örnek ekleyeceğim.

DTO sınıfı:

public class SearchDTO {
    private Long id[];

    public Long[] getId() {
        return id;
    }

    public void setId(Long[] id) {
        this.id = id;
    }
    // reflection toString from apache commons
    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

Denetleyici sınıfı içinde eşleme iste:

@RequestMapping(value="/handle", method=RequestMethod.GET)
@ResponseBody
public String handleRequest(SearchDTO search) {
    LOG.info("criteria: {}", search);
    return "OK";
}

Sorgu:

http://localhost:8080/app/handle?id=353,234

Sonuç:

[http-apr-8080-exec-7] INFO  c.g.g.r.f.w.ExampleController.handleRequest:59 - criteria: SearchDTO[id={353,234}]

Umut ediyorum bu yardım eder :)

GÜNCELLEME / KOTLIN

Çünkü şu anda birisi benzer DTO'yu tanımlamak istiyorsa Kotlin ile çok fazla çalışıyorum çünkü Kotlin'deki sınıf aşağıdaki forma sahip olmalıdır:

class SearchDTO {
    var id: Array<Long>? = arrayOf()

    override fun toString(): String {
        // to string implementation
    }
}

İle databunun gibi bir sınıfa:

data class SearchDTO(var id: Array<Long> = arrayOf())

Yay (Önyükleme'de test edilmiştir) yanıtta belirtilen istek için aşağıdaki hatayı döndürür:

"'Java.lang.String []' türünün değeri gerekli 'java.lang.Long []' türüne dönüştürülemedi; iç içe kural dışı durum java.lang.NumberFormatException: Giriş dizesi için: \" 353,234 \ ""

Veri sınıfı yalnızca aşağıdaki istek parametreleri formu için çalışır:

http://localhost:8080/handle?id=353&id=234

Bunun farkında olun!


1
dto alanlarına "zorunlu" ayarlamak mümkün müdür?
Normal

4
Spring MVC doğrulayıcılarıyla denemenizi öneririm. Örnek: codejava.net/frameworks/spring/…
Przemek Nowak

Bunun bir ek açıklama gerektirmediği çok merak ediyor! Acaba, buna rağmen gereksiz bir açıklama var mı?
James Watkins

8

Çok benzer bir sorunum var. Aslında sorun düşündüğüm kadar derin. Varsayılan olarak kullanan jquery $.postkullanıyorum Content-Type:application/x-www-form-urlencoded; charset=UTF-8. Ne yazık ki sistemimi buna dayandırdım ve karmaşık bir nesneye ihtiyaç @RequestParamduyduğumda bunu gerçekleştiremedim.

Benim durumumda, kullanıcı tercihlerini aşağıdaki gibi bir şeyle göndermeye çalışıyorum;

 $.post("/updatePreferences",  
    {id: 'pr', preferences: p}, 
    function (response) {
 ...

İstemci tarafında, sunucuya gönderilen gerçek ham veriler;

...
id=pr&preferences%5BuserId%5D=1005012365&preferences%5Baudio%5D=false&preferences%5Btooltip%5D=true&preferences%5Blanguage%5D=en
...

olarak ayrıştırıldı;

id:pr
preferences[userId]:1005012365
preferences[audio]:false
preferences[tooltip]:true
preferences[language]:en

ve sunucu tarafı;

@RequestMapping(value = "/updatePreferences")
public
@ResponseBody
Object updatePreferences(@RequestParam("id") String id, @RequestParam("preferences") UserPreferences preferences) {

    ...
        return someService.call(preferences);
    ...
}

Denedim @ModelAttribute, setter / getters, tüm olasılıklara sahip yapıcılar ekledim, UserPreferencesancak gönderilen verileri 5 parametre olarak tanıdığı için şansı yoktu, ancak aslında eşlenen yöntemin sadece 2 parametresi var. Biju'nun çözümünü de denedim, ancak olan şey, baharın varsayılan yapıcı ile bir UserPreferences nesnesi oluşturması ve verileri doldurmaması.

Istemci tarafında tercihlerin JSon dizesini göndererek ve sunucu tarafında bir dize gibi işlemek sorunu çözdü;

müşteri:

 $.post("/updatePreferences",  
    {id: 'pr', preferences: JSON.stringify(p)}, 
    function (response) {
 ...

sunucu:

@RequestMapping(value = "/updatePreferences")
public
@ResponseBody
Object updatePreferences(@RequestParam("id") String id, @RequestParam("preferences") String preferencesJSon) {


        String ret = null;
        ObjectMapper mapper = new ObjectMapper();
        try {
            UserPreferences userPreferences = mapper.readValue(preferencesJSon, UserPreferences.class);
            return someService.call(userPreferences);
        } catch (IOException e) {
            e.printStackTrace();
        }
}

kısaca, dönüşümü REST yöntemi içinde elle yaptım. Bence baharın gönderilen verileri tanımamasının nedeni içerik türüdür.


1
Aynı sorunu yaşadım ve aynı şekilde çözdüm. Bu arada, bugün itibariyle daha iyi bir çözüm buldunuz mu?
Shay Elkayam

1
Ben de aynı sorunu yaşıyorum. Ben kullanarak temiz bir geçici çözüm yaptım@RequestMapping(method = POST, path = "/settings/{projectId}") public void settings(@PathVariable String projectId, @RequestBody ProjectSettings settings)
Petr Újezdský

4

Zorunlu alanların nasıl ayarlanacağı sorusu her gönderinin altında belirdiğinden, alanların gerektiği gibi ayarlanmasına ilişkin küçük bir örnek yazdım:

public class ExampleDTO {
    @NotNull
    private String mandatoryParam;

    private String optionalParam;

    @DateTimeFormat(iso = ISO.DATE) //accept Dates only in YYYY-MM-DD
    @NotNull
    private LocalDate testDate;

    public String getMandatoryParam() {
        return mandatoryParam;
    }
    public void setMandatoryParam(String mandatoryParam) {
        this.mandatoryParam = mandatoryParam;
    }
    public String getOptionalParam() {
        return optionalParam;
    }
    public void setOptionalParam(String optionalParam) {
        this.optionalParam = optionalParam;
    }
    public LocalDate getTestDate() {
        return testDate;
    }
    public void setTestDate(LocalDate testDate) {
        this.testDate = testDate;
    }
}

@RequestMapping(value = "/test", method = RequestMethod.GET)
public String testComplexObject (@Valid ExampleDTO e){
    System.out.println(e.getMandatoryParam() + " " + e.getTestDate());
    return "Does this work?";
}

0

Bakın cevaplar iken @ModelAttribute, @RequestParam, @PathParamve seviyor geçerlidir, ben koştum küçük yakaladım yoktur. Ortaya çıkan method parametresi, Spring'in DTO'nuzu saran bir proxy'dir. Dolayısıyla, kendi özel türünüzü gerektiren bir bağlamda kullanmaya çalışırsanız, beklenmedik sonuçlar elde edebilirsiniz.

Aşağıdakiler işe yaramaz:

@GetMapping(produces = APPLICATION_JSON_VALUE)
public ResponseEntity<CustomDto> request(@ModelAttribute CustomDto dto) {
    return ResponseEntity.ok(dto);
}

Benim durumumda, Jackson bağlamada kullanmaya çalışmak a com.fasterxml.jackson.databind.exc.InvalidDefinitionException.

Dto'dan yeni bir nesne oluşturmanız gerekecektir.



0

Kabul edilen cevap bir cazibe gibi çalışır, ancak nesnenin bir nesne listesi varsa beklendiği gibi çalışmaz, bu yüzden bazı kazmalardan sonra benim çözümüm.

Bu konu önerisini takiben , işte nasıl yaptım.

  • Ön uç: Nesnenizi gönderilmek üzere base64 içinde kodlamaktan ziyade dizgeleyin.
  • Arka uç: base64 dizgisinin kodunu çözdükten sonra json dizesini istenen nesneye dönüştürün.

API'nızı postacı ile hata ayıklamak için en iyisi değil, ancak benim için beklendiği gibi çalışıyor.

Orijinal nesne: {sayfa: 1, boyut: 5, filtreler: [{alan: "id", değer: 1, karşılaştırma: "EQ"}

Kodlanmış nesne: eyJwYWdlIjoxLCJzaXplIjo1LCJmaWx0ZXJzIjpbeyJmaWVsZCI6ImlkUGFyZW50IiwiY29tcGFyaXNvbiI6Ik5VTEwifV19

@GetMapping
fun list(@RequestParam search: String?): ResponseEntity<ListDTO> {
    val filter: SearchFilterDTO = decodeSearchFieldDTO(search)
    ...
}

private fun decodeSearchFieldDTO(search: String?): SearchFilterDTO {
    if (search.isNullOrEmpty()) return SearchFilterDTO()
    return Gson().fromJson(String(Base64.getDecoder().decode(search)), SearchFilterDTO::class.java)
}

Ve burada SearchFilterDTO ve FilterDTO

class SearchFilterDTO(
    var currentPage: Int = 1,
    var pageSize: Int = 10,
    var sort: Sort? = null,
    var column: String? = null,
    var filters: List<FilterDTO> = ArrayList<FilterDTO>(),
    var paged: Boolean = true
)

class FilterDTO(
    var field: String,
    var value: Any,
    var comparison: Comparison
)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.