inflearn logo
강의

Course

Instructor

Spring MVC Part 2 - Backend Web Development Application Techniques

Applying converters to view templates

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

612

Jay

2 asked

2

안녕하세요

/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개 찍혀 있네요

MVC spring

Answer 2

4

xxxxxseed

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

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

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

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

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

image

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

 

0

devrudevico

콘솔에 로그 찍어서 여러 케이스에 대해 확인해 본 결과, 더 정돈된 결론이 나왔다고 생각해서 댓글에 공유합니다.

기본적으로는 {파라미터가 없는 기본 생성자로 바인딩할 객체 생성 -> setter로 바인딩}을 원칙으로 합니다.

하지만 기본 생성자가 없더라도 단 하나의 생성자만 존재하는 경우에는 스프링이 유연하게 허용해줍니다.

이때 생성자를 호출하려면 IpPort가 필수적이기 때문에 어쩔 수 없이 컨버터가 1회 호출되고, 원칙적으로 setter 바인딩을 위해 setter를 호출하기 때문에 여기서 컨버터가 또 1회 호출됩니다.

 

즉, 해당 예제에서 컨버터가 1회만 호출되도록 하려면 다음 두 가지 전략 중 하나를 선택할 수 있습니다.

  1. 파라미터가 없는 빈 생성자를 추가하고, setter를 유지

  2. 생성자는 그대로 유지하고, setter를 제거(또는 @ModelAttribute(binding=false) 옵션 설정)

아래 제 블로그에서 검증을 위한 여러가지 케이스에서 로깅과 함께 설명해뒀으니, 더 자세한 확인을 원하시는 분들은 참고하셔도 좋겠습니다.

https://hyoyoonnam.github.io/posts/@ModelAttribute%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EC%84%B1%EC%9E%90,-setter-%EB%B0%94%EC%9D%B8%EB%94%A9/

0

yh

안녕하세요. Jay님

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

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

감사합니다.

0

corojoon930489

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

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.바인딩을 하는데요. 왜 번거롭게 바인딩 작업을 두 번 거치는지 잘 모르겠습니다. 아시는 분 있으면 답변 부탁드립니다.

0

leehs1590972

@호춘

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

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

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

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

이미지 업로드와 db 트랜잭션 묶는법

0

42

1

Could not resolve org.springframework.boot:spring-boot-starter-validation:2.4.4

0

52

2

MessageSourceTest 코드

0

47

1

인터셉터 에러 설정

0

48

1

resolveArgument()메서드 질문

0

56

1

43강 검증1 에서 실패 로직 관련 질문있습니다.

0

54

2

타임리프 3.X 버전 rendering, serializer 에러 해결 방법

2

133

3

스프링 빈에 등록이 안되는거 같은데 어떻게 하면 좋을까요?ㅠㅠ

0

89

3

pdf 오타 문의

0

57

1

ItemUpdateForm 검증 관련 질문입니다.

0

48

1

22page 링크 주소 변경

0

59

2

특정 데이터와 파일을 함께 저장 시, 테이블 구조 질문

0

53

1

섹션3번 수업에 대한 질문입니다.

0

80

2

@Autowired 보다 더 좋은 방법이 어떤 걸까요?

0

85

2

타입컨버터 가 람다랑 비슷해 보이는데 저의 생각이 맞는지?.

0

65

1

자바스크립트 인라인에서 객체 직렬화 시 오류가 납니다

0

142

3

스프링부트 - 오류페이지2 에서 500.html 에서 쓰인 객체 질문

0

63

1

톰캣 에러 페이지가 안보입니다.

0

100

2

apiEceptionController에서 센드 에러 호출하면 안되는지?

0

81

1

세션 타임아웃시 쿠키 삭제 방법이 없나요?

0

118

2

ApiExceptionController 질문드립니다.

0

63

1

셀렉박스 챕터에서 option value에 ==배송 방식 선택== 이것을 넣은 이유가 궁금함, 이렇게 구상해도 되는지?

0

65

1

MemberRepository 필드의 fianl 선언 유무

0

85

2

혹시 index.html 에서는 fragment 사용이 안되는건가요

0

58

1