인프런 커뮤니티 질문&답변

Jay님의 프로필 이미지

작성한 질문수

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

뷰 템플릿에 컨버터 적용하기

ConverterController.java 에서 제출 버튼 눌렀을 때

작성

·

489

1

안녕하세요

/converter/edit 에서 제출 버튼을 클릭해서

@PostMapping("/converter/edit")
public String converterEdit(@ModelAttribute Form form, Model model) {
    IpPort ipPort = form.getIpPort();
    model.addAttribute("ipPort", ipPort);
    return "converter-view";
}

 

위 부분이 실행되기 전에 첫줄(IpPort ipPort = form.getIpPort();)에 break 걸고보면

SpringToIpPortConverter : convert source = 127.0.0.1:8080

위 로그가 연속으로 2개 찍힙니다. 

 

하나는 "127.0.0.1:8080" 의 값이 ModelAttribute에 의해 Form 클래스의 IpPort로 컨버전 되어서 찍혔다고 보면

나머지 하나는 왜 찍혔는지 이해가 안 갑니다.

 

강의 12:38 에도 보면 같은 로그가 마지막에 2개 찍혀 있네요

답변 2

4

한참 지났지만 후에 공부하실 분들을 위해 남깁니다.

위에 두 분의 답을 보고 저도 궁금해서 찾아보다 아래의 블로그를 발견했습니다.

https://hyeon9mak.github.io/model-attribute-without-setter/

매개변수가 없는 기본 생성자가 있으면 setter를 통해 바인딩을 먼저 시도하고

그후 매개변수가 있는 생성자를 통해 한번 더 바인딩을 시도한다는 내용입니다.

image

위와 같이 @Data 대신 @Getter 하나만을 사용하면 기본 생성자가 없어서 setter를 통한 바인딩을 시도하지 않아 로그가 한번만 찍히는 것을 확인했습니다.

 

0

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. Jay님

스프링 MVC 내부에서 확인이 필요해서 이런 과정을 거치는 것으로 추정되는데요.

관련해서 자세히 아시는 분 있으면 답변 부탁드려요.

감사합니다.

안녕하세요. 저도 같은 내용이 궁금해서 질문했었는데요.

https://www.inflearn.com/questions/727358/stringtoipconverter-가-2번-호출되는-이유

디버깅해보면서 분석한 내용 올려봅니다. 정확하지 않은 내용이므로 영한님과 서포터즈분들의 검토 부탁드리겠습니다.

 

@ModelAttribute로 선언한 매개변수에 값이 들어가는 과정은 1.객체만들기, 2.바인딩 으로 이루어집니다. 2.바인딩에 의해 컨버터가 동작할 것이고, 호출횟수는 한 번이라 예상했습니다. 하지만 실제로는 두 번 호출되는 것을 확인했습니다. 디버깅을 해본 결과, 1.객체만들기 과정에서도 컨버터가 동작하는 것을 확인할 수 있었습니다.

 

1.객체만들기 과정을 따라가다보면 ModelAttributeMethodProcessor.constructAttribute() 메서드를 확인할 수 있습니다. 단순히 빈 깡통 객체를 만드는 줄 알았지만, 데이터를 집어넣는 동작까지 합니다. 자세한 코드는 아래를 참조하시면 됩니다.

protected Object constructAttribute(Constructor<?> ctor, String attributeName, MethodParameter parameter,
	WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {

    //생성자로부터 멤버변수가 무엇이 있는지 알아냅니다.
    String[] paramNames = BeanUtils.getParameterNames(ctor);
    
    for (int i = 0; i < paramNames.length; i++) {
        //Request에 멤버변수명으로 값을 조회합니다.
        //"ipPort"로 조회한 결과 -> value:"127.0.0.1"
        Object value = webRequest.getParameterValues(paramName);

        //컨버팅이 필요한 멤버변수가 있다면 컨버팅을 수행한 결과를 저장합니다.
        //"127.0.0.1"을 ipPort로 컨버팅
        args[i] = binder.convertIfNecessary(value, paramType, methodParam);
    }
    
    // 최종적으로 컨버팅한 결과를 바탕으로 객체를 생성합니다.
    return BeanUtils.instantiateClass(ctor, args);
}


아래 코드처럼 binding = false 옵션을 주면, 1.객체만들기만 수행하면서 컨버팅 작업이 한번만 일어나는 것을 확인할 수 있습니다.

@PostMapping("/converter/edit")
public String converterEdit(@ModelAttribute(binding = false) Form form, Model model) {
    IpPort ipPort = form.getIpPort();
    model.addAttribute("ipPort", ipPort);
    log.info("post request end");
    return "converter-view";
}

화면에 찍히는 결과도 동일합니다.

image

전체적인 코드의 흐름을 봤을 때, 1.객체만들기 과정에서 바인딩 작업을 먼저 해보고, 바인딩 에러가 발생하지 않았을 경우에 2.바인딩을 하는데요. 왜 번거롭게 바인딩 작업을 두 번 거치는지 잘 모르겠습니다. 아시는 분 있으면 답변 부탁드립니다.

@호춘

저 또한 간단하게 테스트를 해봤습니다.

Form 클래스는 @Data를 통해 모든 필드 접근법을 생성하고 있는데,

여기서 @Data가 아니라 직접 @Getter, @Setter, @NoArgsConstructor, @ToString(@Value, @RequiredArgsConstructor 제외)를 추가하게되면 POST시 한개의 StringToIpPortConverter의 로그가 찍히는것을 알 수 있습니다.

@Data의 사용을 지양하라는 말을 들었는데 이와도 관련된 상황이 아닐까 하여 의견 덧붙입니다

Jay님의 프로필 이미지

작성한 질문수

질문하기