Argument와 Return 타입

뾰족머리삼돌이·2023년 12월 28일
0

Spring

목록 보기
5/14

개요

handlerArgumentReturn에 올 수 있는 타입들을 알아보자

목록

핸들러 메소드 아규먼트설명
WebRequest요청이나 응답에 접근이 가능한 API
NativeWebRequest요청이나 응답에 접근이 가능한 API
ServletRequest(Response)요청이나 응답에 접근이 가능한 API
HttpServletRequest(Response)요청이나 응답에 접근이 가능한 API
InputStream, Reader요청 본문을 읽어올때 사용되는 API
OutputStream, Writer응답 본문에 쓸때 싸용되는 API
PushBuilderHTTP/2 리소스 푸시에 사용
HttpMethod요청 Method에 대한 정보 ( handler가 받는 Method가 여러개일경우 )
Locale, TimeZone, ZoneIdLocale 정보 ( 국가 언어 등 )
@PathVariable요청 URI경로에 있는 템플릿 변수를 읽을 때 사용
@MatrixVariable@PathVariable과 유사하지만 키/값 쌍을 읽어올 때 사용
@RequestParam서블릿 요청 매개변수값 ( /hello?name=ddings ) 을 선언한 메서드 아규먼트 타입으로 변환해준다 단순 타입 ( ex : String, Integer )인 경우 생략가능
@RequestHeader요청 헤더값을 선언한 타입으로 변환해준다 ( Map이나 HttpHeaders 등 )
@RequestBody요청 body에 있는 값을 사용할 때 ( GET에는 body가 없음 )
HttpEntityheader와 body정보 모두를 사용할 때
Map, Model, ModelMap응답 시 Model정보를 담을 때 사용
RedirectAttributesredirect시 URI에 모델정보를 노출시키지 않고 전달
@ModelAttribute복합객체의 데이터 바인딩
@ResponseBodyView가 아닌 데이터를 응답 본문에 담아 보내고 싶을 때 사용
ResponseEntity상태코드, 헤더, 본문을 포함한 상세한 응답을 보내줄 수 있음
HttpHeader본문 없이 헤더만 반환하고 싶을 때
Stringresources/templates 밑의 View를 찾기위한 단서로 사용
ViewViewResolver를 사용하지 않고 View를 매핑
ModelModel 정보만 리턴하고, 요청 URI를 사용해 view이름을 유추…
@Validated@ModelAttribute로 데이터를 바인딩할때 검증을 하기 위함
@SessionAttributes클래스내에서 관리되는 Session 속성 지정
@SessionAttirubte프로그램내의 Session에 저장된 데이터를 꺼내는데 사용

공식문서


@ModelAttribute

form이나 param으로 넘어온 데이터중에 일치하는걸로 복합객체를 만들어주는 애노테이션

@PostMapping("/events")
public String postFormLimit(@ModelAttribute Event event){
    return "/events/list";
}
@Getter @Setter
public class Event {
    private Integer id;
    private String name;
    private Integer limit;
}

만약 /events?name=ddings&limit=10 이라는 요청이 왔을때 이를 받기위해서는 @RequestParam으로 각각 name과 limit을 받아도되지만

@ModelAttribute를 이용하면 알아서 event객체를 생성하고, 값을 넣어준다

이때, 들어가는 값의 검증이 필요하다면 javax.validation의 도움을 받을 수 있다

@Getter @Setter
public class Event {
  private Integer id;
    
  @NotBlank
  private String name;
	
  @Min(0)
  private Integer limit;
}
@PostMapping("/events")
public String postFormLimit(@Validated @ModelAttribute Event event, BindingResult bindingResult){
    if(bindingResult.hasErrors()){
  		// 원하는 값이 들어오지 않았을 때의 작업
    }
  	
  	// 정상처리 작업
  	return "/events/list";
}

@Valid@Validated를 사용하면 값의 검증이 진행되고 BindingResult를 이용하여 제대로된 값이 들어왔는지를 검증할 수 있다

만약 로그인창에서 들어온 데이터가 원하는 조건에 부합하지않으면 다시 입력값을 돌려보내주는 식으로 응용할 수 있다

@Valid와 다르게 Spring에서 제공해주는 @Validated의 경우 그룹으로 묶어서 조건을 따질 수 있게해준다

@PostMapping("/events")
public String postFormLimit(@Validated(Event.myEvent.class) @ModelAttribute Event event, BindingResult bindingResult){
    if(bindingResult.hasErrors()){
  		// 원하는 값이 들어오지 않았을 때의 작업
    }
  	
  	// 정상처리 작업
  	return "/events/list";
}
@Getter @Setter
public class Event {
  private Integer id;
    
  @NotBlank(groups = myEvent.class)
  private String name;
	
  @Min(value = 0, groups = myEvent.class)
  private Integer limit;
}

이런 방식으로 그룹화를 시켜서 값을 검증할 수 있다


@SessionAttributes

클래스 내에서 Session에 있는 데이터를 꺼내쓰는 방법

로그인이나 장바구니처럼 서버의 처리가 끝난 이후에 내려주는 값을 유지해야하는 경우가 있다
이런 경우에 보통 session이 사용되는데 low하게 HttpSession을 이용하는 방법이 있다

@GetMapping("/events")
public @ResponseBody Event getSession(@Validated @ModelAttribute Event event, BindingResult bindingResult, HttpSession session){
    if(bindingResult.hasErrors()){
  		// 원하는 값이 들어오지 않았을 때의 작업
    }
  
    session.setAttribute("event", event);
    return event;
}
@Test
void sessionTest() throws Exception{
    this.mockMvc.perform(get("/events")
                    .param("name", "ddings")
                    .param("limit", "10"))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(request().sessionAttribute("event", notNullValue()));
}

테스트를 통해서 session안에 event가 저장되었는지를 확인할 수 있다

이렇게 저장된 값은 HttpSession을 통해서 꺼내쓸 수 있는데, Spring에서는 좀 더 추상화된 방법으로 세션을 사용하는 방법이 있다

사용법

@Controller
@SessionAttributes("event")
public class SampleController {

@SessionAttributes는 위와 같이 클래스의 선언부에 설정할 수 있는 애노테이션으로 해당 컨트롤러 내에서 동작하며 @ModelAttrivbute로 값을 바인딩할때 session에 있는 데이터를 사용한다

즉, @ModelAttribute로 데이터를 바인딩할때 session에 해당 이름의 attribute가 없다면 에러가 발생한다

@GetMapping("/events")
public @ResponseBody String getSession(Model model){
    model.addAttribute("event", new Event());
    return "hello";
}

애노테이션을 이용하여 설정을 했을 경우, model에 동일한 이름의 attribute를 담는순간 session에도 담게된다

@GetMapping("/events")
public @ResponseBody String getSession(@Validated @ModelAttribute Event myEvent, Model model){
    model.addAttribute("event", myEvent);
    return "/events/list";
}

위 코드를 작성하고 동일 테스트를 돌리게되면 실패한다

그 이유는 인용글에 적어놨는데 @ModelAttribute 를 통해 데이터를 바인딩할때,
@SessionAttributes에 설정한 이름의 데이터를 Session에서 가져올때 존재하지 않기 때문이다

@ModelAttribute("event")
public Event setEvent(){
    return new Event();
}

@GetMapping("/events")
public @ResponseBody String getSession(@Validated @ModelAttribute Event event
											, Model model){
    model.addAttribute("event", myEvent);
    return "/events/list";
}

이를 방지하기 위해서는 크게 두가지의 방법이 있다

  1. 컨트롤러 안에서 사용될 Model을 미리 넣어주기
  2. @ModelAttribute(”다른이름”)으로 해주기

SessionStatus

@SessionAttributes로 관리되는 데이터는 Session을 사용하고나서 더이상 필요하지 않게되는 경우가 생길 수 있다

예를들어 장바구니에 담았던 제품들을 구매한 경우를 들 수 있다

이땐 SessionStatus를 이용해서 관리되는 데이터를 session에서 없애줄 수 있다

@GetMapping("/events")
public @ResponseBody String getSession(@Validated @ModelAttribute Event myEvent, 
                                       HttpSession session, SessionStatus sessionStatus){
    session.setAttribute("event", myEvent);
    sessionStatus.setComplete();
    return "/events/list";
}

유의해야 할 부분은 @SessionAttributes로 관리되는 데이터만 처리된다는 점이다

위 코드에서 HttpSession으로 session에 넣어준 데이터는 사라지지 않는다

@Test
void sessionTest() throws Exception{
    this.mockMvc.perform(get("/events")
                    .param("name", "ddings")
                    .param("limit", "10"))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(request().sessionAttribute("nope", "nope"));

}

따라서 이 테스트코드는 통과한다


@SessionAttribute

@SessionAttributes와 다르다!

프로그램 전체에서 session에 있는 데이터를 꺼내는데 사용된다

@Test
void sessionTest() throws Exception{
    this.mockMvc.perform(get("/events")
                    .param("name", "ddings")
                    .param("limit", "10")
                    .sessionAttr("test", "testValue"))
            .andDo(print())
            .andExpect(status().isOk());
}

session{ test: testValue }라는 데이터가 들어있다

이를 꺼내기위해서는 HttpSession을 이용하면 된다

@GetMapping("/events")
public @ResponseBody String getSession(@Validated @ModelAttribute Event event
        , BindingResult bindingResult, SessionStatus sessionStatus
        , HttpSession session){
    System.out.println(session.getAttribute("test"));
    sessionStatus.setComplete();
    return "/events/list";
}

@SessionAttribute를 쓰냐? 라는 생각을 할 수 있는데 그 이유는 getAttribute()반환값이 Object라는 점이다

@GetMapping("/events")
public @ResponseBody String getSession(@Validated @ModelAttribute Event event
        , BindingResult bindingResult, SessionStatus sessionStatus
        , @SessionAttribute String test){
    System.out.println(test);
    sessionStatus.setComplete();
    return "/events/list";
}

@SessionAttribute를 이용하게 되면 불필요한 타입캐스팅 코드를 Spring에게 위임할 수 있기때문에 좀 더 비즈니스 로직에 집중할 수 있다


RedirectAttributes와 FlashAttributes

@PostMapping("/events")
public String doRedirect(Model model){
    model.addAttribute("test", "test");
    return "redirect:/events";
}
@Test
void redirectTest() throws  Exception{
    this.mockMvc.perform(post("/events"))
            .andDo(print())
            .andExpect(status().is(302));
}

SpringBoot에서 redirect 시, model에 데이터를 담아줘도 데이터가 넘어가지않는다

이는 spring.mvc.ignore-default-model-on-redirect 라는 옵션때문으로, 이를 false로 바꿔주면 query param의 형태로 넘어간다

그런데 만약 기본적으로 이 옵션이 true인 상태로 특정 값만 넘기고싶은 경우에는 RedirectAttributes를 사용하면 된다

@PostMapping("/events")
public String doRedirect(RedirectAttributes redirectAttributes){
    redirectAttributes.addAttribute("test", "test");
    return "redirect:/events";
}

RedirectAttributes는 query param의 형태로 데이터를 넘겨주기 때문에 문자열로 변경이 가능한 기본형 타입의 데이터만 넘겨줄 수 있다

@PostMapping("/events")
public String doRedirect(RedirectAttributes redirectAttributes){
    redirectAttributes.addAttribute("event", new Event());
    return "redirect:/events";
}

따라서 Event 객체를 넘겨주려고하면 500 에러가 발생한다

@PostMapping("/events")
public String doRedirect(RedirectAttributes redirectAttributes){
    redirectAttributes.addFlashAttribute("event", new Event());
    return "redirect:/events";
}

이런 복합데이터를 넘겨주기 위해서는 addFlashAttribute를 이용하면 된다
이는 일회성이기 때문에 한번 처리하고나면 사라진다

해당 방법의 데이터를 꺼내는건

Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();

에서 이루어진다


ResponseEntity

응답 상태코드, 헤더, 본문을 커스텀 가능

@Autowired
ResourceLoader resourceLoader;

@GetMapping("/files/{filename}")
public ResponseEntity<Resource> fileDownload(@PathVariable String filename) throws IOException {
    Resource resource = resourceLoader.getResource("classpath:" + filename);
    File file = resource.getFile();
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachement; filename=\""+ resource.getFilename() + "\"")
            .header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_GIF_VALUE)
            .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(file.length()))
            .body(resource);

}

파일다운로드의 예시를 들어서 ResponseEntity가 사용되는 모습을 확인해볼 수 있다

이 경우에는 직접 body에 어떤값이 담길지를 명시하기 때문에 따로 @ResponseBody를 사용할 필요가 없다

RequestParam, RequestBody, ModelAttribute

이 세가지 애노테이션은 Controller에서 데이터를 받을때 주로 사용되는 애노테이션이다.
따라서 자주 사용되기 때문에 어떤 차이가 있는지 알아보자

RequestParam

먼저 RequestParam의 경우 파라미터 데이터를 받을 때 사용한다

@RestController
@RequestMapping("sample")
public class SampleController {


    @GetMapping("/param/get")
    public ResponseEntity<?> paramGet(@RequestParam String name, @RequestParam Integer age){
        System.out.println(name + " :  " + age);
        return ResponseEntity.ok().build();
    }

    @PostMapping("/param/post")
    public ResponseEntity<?> paramPost(@RequestParam String name, @RequestParam Integer age){
        System.out.println(name + " :  " + age);
        return ResponseEntity.ok().build();
    }

    @GetMapping("/param/model")
    public ResponseEntity<?> requestParam(@RequestParam Sample sample){
        System.out.println(sample.getName() + " : " + sample.getAge());
        return ResponseEntity.ok().build();
    }
}

하나의 단일 파라미터를 받기위해 사용되는 애노테이션으로 해당하는 파라미터가 없으면 400에러를 반환한다
GET, POST 모두에 사용이 가능하며 객체를 받는데 사용하는건 적합하지않다

유의사항으로는 객체에 매핑할 때, ObjectMapper와 Reflection을 이용하기 때문에 Setter가 필요없다
객체의 필드명을 인식하기위한 Getter는 필요하다

@Getter
@NoArgsConstructor
public class Sample {
    private String name;
    private Integer age;
}

따라서 lombok 사용 시, 1. 위의 두 애노테이션만 달아주거나 2. final과 @RequiredArgsConstructor를 사용하자

RequestBody

이름에서 볼 수 있듯이 body에 있는 데이터를 바인딩하는데 사용된다
GET에는 body가 없기때문에 POST, PUT에서 동작한다

  @RestController
  @RequestMapping("sample")
  public class SampleController {

    @PostMapping("/body/post")
    public ResponseEntity<?> bodyPost(@RequestBody Sample sample){
      System.out.println(sample.getName() + " : " + sample.getAge() );
      return ResponseEntity.ok().build();
    }
  
    @PostMapping("/body/native")
    public ResponseEntity<?> bodyNative(@RequestBody String data){
        System.out.println(data);
        /*
        {
            "name": "ddings",
            "age":10
        }
        */

        return ResponseEntity.ok().build();
    }
    @PostMapping("ma/post")
    public ResponseEntity<?> maPost(@ModelAttribute(name="name") String name, @ModelAttribute(name="age") int age){
        System.out.println(name + " : " + age);
        return ResponseEntity.ok().build();
    }
  }

객체매핑이 가능하며 여러개의 @RequestBody를 사용할 수 없다
객체를 매핑할때는 필드명과 form 데이터 이름을 맞춰야 한다

ModelAttribute

form 형태의 데이터를 받는데 사용된다

  @RestController
  @RequestMapping("sample")
  public class SampleController {
    @PostMapping("ma/post")
    public ResponseEntity<?> maPost(@ModelAttribute(name="name") String name, @ModelAttribute(name="age") int age){
        System.out.println(name + " : " + age);
        return ResponseEntity.ok().build();
    }

    @PostMapping("ma/obj")
    public ResponseEntity<?> maPost(@ModelAttribute Sample sample){
        System.out.println(sample.getName() + " : " + sample.getAge());
        return ResponseEntity.ok().build();
    }
  
    @PostMapping("ma/query")
    public ResponseEntity<?> maquery(@ModelAttribute Sample sample){
        System.out.println(sample.getName() + " : " + sample.getAge());
        return ResponseEntity.ok().build();
    }
  }

@RequestBody와 다르게 여러개 사용할 수 있으며 객체매핑, query를 통한 객체매핑 또한 가능하다
객체를 매핑할때는 필드명과 데이터 이름을 맞춰야 한다

0개의 댓글

관련 채용 정보