스프링 MVC 활용(19) : 핸들러 메소드 11부 - RedirectAttributes

de_sj_awa·2021년 7월 4일
0
post-custom-banner

19. 핸들러 메소드 11부 - RedirectAttributes

리다이렉트 할 때 기본적으로 Model에 들어있는 primitive type 데이터는 URI 쿼리 매개변수에 추가된다.

  • 스프링 부트에서는 이 기능이 기본적으로 비활성화 되어 있다.
  • Ignore-default-model-on-redirect 프로퍼티를 사용해서 활성화 할 수 있다.

원하는 값만 리다이렉트 할 때 전달하고 싶다면 RedirectAttributes에 명시적으로 추가할 수 있다.

리다이렉트 요청을 처리하는 곳에서 쿼리 매개변수를 @RequestParam 또는 @ModelAttribute로 받을 수 있다.


리다이렉트 할 때 기본적으로 Model에 들어있는 primitive type 데이터는 URI 쿼리 매개변수에 추가된다.

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

    @GetMapping("/events/form/name")
    public String eventsFormName(Model model) {
        model.addAttribute("event", new Event());
        // 자동으로 세션에 넣어줌
        return "events/form-name";
    }

    @PostMapping("/events/form/name")
    public String eventsFormNameSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            return "/events/form-name";
        }
        return "redirect:/events/form/limit";
    }
    
    @GetMapping("/events/form/limit")
    public String eventsFormLimit(@ModelAttribute @Valid Event event, Model model) {
        model.addAttribute("event", event);
        // 자동으로 세션에 넣어줌
        return "events/form-limit";
    }
    
    @PostMapping("/events/form/limit")
    public String eventsFormLimitSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult, SessionStatus sessionStatus, Model model){
        if(bindingResult.hasErrors()){
            return "/events/form-limit";
        }
        sessionStatus.setComplete();
        // 데이터베이스에서 save 했다고 가정
        model.addAttribute("name", event.getname());
        model.addAttribute("limit", event.getLimit());
        // String과 Integer는 redirect할 때 쿼리 파라미터에 붙는다.
        // 스프링 MVC는 이 기능을 지원하나 스프링 부트는 이 기능을 지원하지 않는다.
        return "redirect:/events/list";
    }
    
    @GetMapping("/events/list")
    public String getEvents(Model model, @SessionAttriubte LocalDateTime visitTime){
        System.out.println(visitTime);
        Event event = new Event();
        event.setName("spring");
        event.setLimit(10);
        // 데이터베이스에서 읽어왔다고 가정한다.
        List<Event> eventList = new ArrayList<>();
        eventList.add(event);
        model.addAttribute("eventList", eventList);
        return "/events/list";
    }
}

스프링 부트는 이 기능을 기본으로 지원하지 않는다.

WebMvcAutoConfiguration.java

@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
        @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
        @Qualifier("mvcConversionService") FormattingConversionService conversionService,
        @Qualifier("mvcValidator") Validator validator) {
    RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
					conversionService, validator);
    adapter.setIgnoreDefaultModelOnRedirect(
    this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
    return adapter;
}

isIgnoreDefaultModelOnRedirect()는 기본 값이 True이다. 이를 해제하고 싶으면 application.properties에서 다음과 같이 지정하면 된다.

spring.mvc.Ignore-default-model-on-redirect=false

그러나 Model에 있는 모든 정보를 다 보내고 싶지 않고 일부만 보내고 싶다면 Model 대신에 RedirectAttributes를 사용한다.

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

    @GetMapping("/events/form/name")
    public String eventsFormName(Model model) {
        model.addAttribute("event", new Event());
        // 자동으로 세션에 넣어줌
        return "events/form-name";
    }

    @PostMapping("/events/form/name")
    public String eventsFormNameSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            return "/events/form-name";
        }
        return "redirect:/events/form/limit";
    }
    
    @GetMapping("/events/form/limit")
    public String eventsFormLimit(@ModelAttribute @Valid Event event, Model model) {
        model.addAttribute("event", event);
        // 자동으로 세션에 넣어줌
        return "events/form-limit";
    }
    
    @PostMapping("/events/form/limit")
    public String eventsFormLimitSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult, SessionStatus sessionStatus, RedirectAttributes attributes){
        if(bindingResult.hasErrors()){
            return "/events/form-limit";
        }
        sessionStatus.setComplete();
        // 데이터베이스에서 save 했다고 가정
        attributes.addAttribute("name", event.getName());
        attributes.addAttribute("limit", event.getLimit());
        return "redirect:/events/list";
    }
    
    @GetMapping("/events/list")
    public String getEvents(Model model, @SessionAttriubte LocalDateTime visitTime){
        System.out.println(visitTime);
        Event event = new Event();
        event.setName("spring");
        event.setLimit(10);
        // 데이터베이스에서 읽어왔다고 가정한다.
        List<Event> eventList = new ArrayList<>();
        eventList.add(event);
        model.addAttribute("eventList", eventList);
        return "/events/list";
    }
}

이렇게 하면 RedirectAttributes에 명시한 것들만 URI 쿼리 파라미터에 들어온다.

그럼 이를 받는 쪽에서 다음과 같이 쓸 수 있다.

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

    @GetMapping("/events/form/name")
    public String eventsFormName(Model model) {
        model.addAttribute("event", new Event());
        // 자동으로 세션에 넣어줌
        return "events/form-name";
    }

    @PostMapping("/events/form/name")
    public String eventsFormNameSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            return "/events/form-name";
        }
        return "redirect:/events/form/limit";
    }
    
    @GetMapping("/events/form/limit")
    public String eventsFormLimit(@ModelAttribute @Valid Event event, Model model) {
        model.addAttribute("event", event);
        // 자동으로 세션에 넣어줌
        return "events/form-limit";
    }
    
    @PostMapping("/events/form/limit")
    public String eventsFormLimitSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult, SessionStatus sessionStatus, RedirectAttributes attributes){
        if(bindingResult.hasErrors()){
            return "/events/form-limit";
        }
        sessionStatus.setComplete();
        // 데이터베이스에서 save 했다고 가정
        attributes.addAttribute("name", event.getName());
        attributes.addAttribute("limit", event.getLimit());
        return "redirect:/events/list";
    }
    
    @GetMapping("/events/list")
    public String getEvents(@RequestParam String name,
                            @RequestParam Integer limit,
                            Model model, 
                            @SessionAttriubte LocalDateTime visitTime){
        System.out.println(visitTime);
        
        Event newEvent = new Event();
        newEvent.setName(name);
        newEvent.setLimit(limit);
        
        Event event = new Event();
        event.setName("spring");
        event.setLimit(10);
        
        // 데이터베이스에서 읽어왔다고 가정한다.
        List<Event> eventList = new ArrayList<>();
        eventList.add(event);
        eventList.add(newEvent);
        model.addAttribute("eventList", eventList);
        return "/events/list";
    }
}

혹은 ModelAttribute로 받아올 수도 있는데 유의해야 한다.

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

    @GetMapping("/events/form/name")
    public String eventsFormName(Model model) {
        model.addAttribute("event", new Event());
        // 자동으로 세션에 넣어줌
        return "events/form-name";
    }

    @PostMapping("/events/form/name")
    public String eventsFormNameSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            return "/events/form-name";
        }
        return "redirect:/events/form/limit";
    }
    
    @GetMapping("/events/form/limit")
    public String eventsFormLimit(@ModelAttribute @Valid Event event, Model model) {
        model.addAttribute("event", event);
        // 자동으로 세션에 넣어줌
        return "events/form-limit";
    }
    
    @PostMapping("/events/form/limit")
    public String eventsFormLimitSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult, SessionStatus sessionStatus, RedirectAttributes attributes){
        if(bindingResult.hasErrors()){
            return "/events/form-limit";
        }
        sessionStatus.setComplete();
        // 데이터베이스에서 save 했다고 가정
        attributes.addAttribute("name", event.getName());
        attributes.addAttribute("limit", event.getLimit());
        return "redirect:/events/list";
    }
    
    @GetMapping("/events/list")
    public String getEvents(@ModelAttribute("event") Event event,
                            Model model, 
                            @SessionAttriubte LocalDateTime visitTime){
        System.out.println(visitTime);
        
        Event spring = new Event();
        spring.setName("spring");
        spring.setLimit(10);
        
        // 데이터베이스에서 읽어왔다고 가정한다.
        List<Event> eventList = new ArrayList<>();
        eventList.add(spring);
        eventList.add(event);
        model.addAttribute("eventList", eventList);
        return "/events/list";
    }
}

@SessionAttributes("name")과 같은 이름으로 @ModelAttribute("name")을 사용해서는 안된다. 같은 이름이 있을 때에는 세션에서 찾아보기 때문에 에러가 발생한다.

ModelFactory.java

public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
			throws Exception {

    Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
    container.mergeAttributes(sessionAttributes);
    invokeModelAttributeMethods(request, container);

    for (String name : findSessionAttributeArguments(handlerMethod)) {
        if (!container.containsAttribute(name)) {
            Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
        if (value == null) {
            throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
        }
        container.addAttribute(name, value);
        }
    }
}

sessionStatus.setComplete(); 에서 이미 세션을 제거했기 때문에 에러가 발생하는 것이다. sessionStatus.setComplete();를 @GetMapping("/events/list")에서 수행하고 싶지 않다면, 다음과 같이 @SessionAttributes("name")와 다르게 @ModelAttribute("newName")으로 이름을 바꿔줘야 한다.

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

    @GetMapping("/events/form/name")
    public String eventsFormName(Model model) {
        model.addAttribute("event", new Event());
        // 자동으로 세션에 넣어줌
        return "events/form-name";
    }

    @PostMapping("/events/form/name")
    public String eventsFormNameSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            return "/events/form-name";
        }
        return "redirect:/events/form/limit";
    }
    
    @GetMapping("/events/form/limit")
    public String eventsFormLimit(@ModelAttribute @Valid Event event, Model model) {
        model.addAttribute("event", event);
        // 자동으로 세션에 넣어줌
        return "events/form-limit";
    }
    
    @PostMapping("/events/form/limit")
    public String eventsFormLimitSubmit(@ModelAttribute @Valid Event event, BindingResult bindingResult, SessionStatus sessionStatus, RedirectAttributes attributes){
        if(bindingResult.hasErrors()){
            return "/events/form-limit";
        }
        sessionStatus.setComplete();
        // 데이터베이스에서 save 했다고 가정
        attributes.addAttribute("name", event.getName());
        attributes.addAttribute("limit", event.getLimit());
        return "redirect:/events/list";
    }
    
    @GetMapping("/events/list")
    public String getEvents(@ModelAttribute("newEvent") Event event,
                            Model model, 
                            @SessionAttriubte LocalDateTime visitTime){
        System.out.println(visitTime);
        
        Event spring = new Event();
        spring.setName("spring");
        spring.setLimit(10);
        
        // 데이터베이스에서 읽어왔다고 가정한다.
        List<Event> eventList = new ArrayList<>();
        eventList.add(spring);
        eventList.add(event);
        model.addAttribute("eventList", eventList);
        return "/events/list";
    }
}

이렇게 하면 세션에서 데이터를 가져오는 것이 아니라 쿼리 파라미터에서 데이터를 가져와 바인딩하게 된다.

참고

  • 인프런 : 스프링 웹 MVC(백기선)
profile
이것저것 관심많은 개발자.
post-custom-banner

0개의 댓글