[java] Spring MVC : GET @RequestParam과 같은 복잡한 객체

테이블의 객체를 나열하는 페이지가 있고 테이블을 필터링하기 위해 양식을 넣어야한다고 가정합니다. 필터는 다음과 같은 URL에 Ajax GET으로 전송됩니다. http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z

그리고 내 컨트롤러에 많은 매개 변수가있는 대신 :

@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) { ... }

그리고 내가 MyObject를 다음과 같이 가정합니다.

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

    //Getters and setters
    ...
}

나는 다음과 같은 것을하고 싶다 :

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

가능합니까? 어떻게해야합니까?



답변

@RequestParam주석을 제거하면 Spring이 요청 매개 변수를 클래스 인스턴스에 깔끔하게 바인딩합니다.

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


답변

나는 저에게서 짧은 예를 추가 할 것입니다.

DTO 수업 :

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);
    }
}

컨트롤러 클래스 내부의 요청 매핑 :

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

질문:

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

결과:

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

나는 그것이 도움이되기를 바랍니다 🙂

업데이트 / 코 틀린

현재 Kotlin과 많은 일을하고 있기 때문에 누군가가 비슷한 DTO를 정의하려면 Kotlin의 클래스는 다음과 같은 형식이어야합니다.

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

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

data클래스와 함께 :

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

Spring (부팅에서 테스트 됨)은 응답에서 언급 된 요청에 대해 다음 오류를 반환합니다.

” ‘java.lang.String []’유형의 값을 ‘java.lang.Long []’유형으로 변환하지 못했습니다. 중첩 된 예외는 java.lang.NumberFormatException입니다. 입력 문자열 : \”353,234 \ “”

데이터 클래스는 다음 요청 매개 변수 양식에 대해서만 작동합니다.

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

이것을 명심하십시오!


답변

나는 비슷한 문제가 있습니다. 실제로 내가 생각했던대로 문제는 더 깊다. 기본으로 $.post사용 하는 jquery 를 사용 Content-Type:application/x-www-form-urlencoded; charset=UTF-8하고 있습니다. 불행히도 나는 내 시스템을 기반으로했으며 복잡한 객체가 필요할 때 @RequestParam그것을 달성 할 수 없었습니다.

제 경우에는 다음과 같이 사용자 환경 설정을 보내려고합니다.

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

클라이언트 측에서 서버로 전송 된 실제 원시 데이터는 다음과 같습니다.

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

파싱;

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

서버 측은;

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

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

내가 노력 @ModelAttribute, 모든 가능성에 추가 세터 / 게터, 생성자 UserPreferences가 5 개 매개 변수로하여 전송 데이터를 인식하지만 실제로 매핑 방법은이 매개 변수가로하지만 기회를. 나는 Biju의 솔루션을 시도했지만 스프링은 기본 생성자로 UserPreferences 객체를 만들고 데이터를 채우지 않습니다.

클라이언트 측에서 환경 설정의 JSon 문자열을 보내 서버 측의 문자열 인 것처럼 처리하여 문제를 해결했습니다.

고객:

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

섬기는 사람:

@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();
        }
}

간단히 말해 REST 메서드 내에서 수동으로 변환을 수행했습니다. 내 의견으로는 스프링이 전송 된 데이터를 인식하지 못하는 이유는 내용 유형입니다.


답변

필수 항목을 설정하는 방법에 대한 질문이 각 게시물 아래에 표시되므로 필 요에 따라 필드를 설정하는 방법에 대한 작은 예를 작성했습니다.

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?";
}


답변

참조 답변을하는 동안 @ModelAttribute, @RequestParam, @PathParam및 좋아하는이 유효, 나는 우연히 작은 잡았다가있다. 결과 메소드 매개 변수는 Spring이 DTO를 감싸는 프록시입니다. 따라서 고유 한 사용자 지정 유형이 필요한 컨텍스트에서 사용하려고하면 예기치 않은 결과가 발생할 수 있습니다.

다음은 작동하지 않습니다.

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

필자의 경우 Jackson 바인딩에서 사용하려고하면 com.fasterxml.jackson.databind.exc.InvalidDefinitionException.

dto에서 새 객체를 만들어야합니다.


답변

예, 간단한 방법으로 할 수 있습니다. 아래 코드를 참조하십시오.

URL- http : // localhost : 8080 / get / request / multiple / param / by / map? name = ‘abc’& id = ‘123’

 @GetMapping(path = "/get/request/header/by/map")
    public ResponseEntity<String> getRequestParamInMap(@RequestParam Map<String,String> map){
        // Do your business here 
        return new ResponseEntity<String>(map.toString(),HttpStatus.OK);
    } 


답변

허용 된 답변은 매력처럼 작동하지만 객체에 객체 목록이 있으면 예상대로 작동하지 않으므로 파기 후 내 해결책이 있습니다.

이 스레드 조언에 따라 다음 은 내가 한 일입니다.

  • 프론트 엔드 : 제출을 위해 base64로 인코딩하는 것보다 객체를 문자열 화합니다.
  • 백엔드 : base64 문자열을 디코딩 한 다음 문자열 json을 원하는 객체로 변환하십시오.

postman으로 API를 디버깅하는 데 가장 좋지는 않지만 예상대로 작동합니다.

원본 객체 : {페이지 : 1, 크기 : 5, 필터 : [{필드 : “id”, 값 : 1, 비교 : “EQ”}

인코딩 된 오브젝트 : 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)
}

그리고 여기 SearchFilterDTO와 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
)