스프링 공식 문서 뿌수기(8), Model

2023. 4. 27. 15:25자바/스프링

728x90
반응형

이번에는 지난 포스팅에서 언급한 Model에 대해서 한번 정리해보고자 한다.

 

Model

 

@ModelAttribute 어노테이션을 사용하는 경우에는 WebDataBinder를 통해서 request 받은 값을 객체로 바인딩할 수 있다.  @ControllerAdvier 어노테이션을 사용한 컨트롤러 또한 @ModelAttribute를 사용할 수 있다. @ModelAttribute 어노테이션을 사용한 메서드는 @ModelAttribute나 request body와 연관된 어노테이션을 제외한 @RequestMapping에서 사용하는 메서드들을 지원한다. request body와 관련되었다는 것은 아마 @RequestBody와 같이 Jackson 라이브러리를 사용하는 어노테이션을 의미하는게 아닐까싶다.

 

아래 예시는 공식문서에서 제공한 @ModelAttibute 어노테이션 사용 예시이다.

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountRepository.findAccount(number));
    // add more ...
}

위와 같이 메서드에 @ModelAttribute를 붙이더라도 @RequestParam 어노테이션을 사용할 수 있다. 또한 Model이라는 인자를 전달받아서 attribute를 추가해주는 작업을 하는데, 이는 모델에 attribute를 추가해주는 작업이다. 잠시 후에 살펴보도록 하자.

 

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}

만약 추가하고자하는 attribute가 하나 뿐이라면 위와 같이 객체를 바로 리턴할 수도 있다.

 

@ModelAttribute와 함께 @RequestMapping 어노테이션을 사용할 수 있다. 이 경우에는 return 값을 model attribute로 해석하게 된다. 그런데 사실 @ModelAttribute를 안붙여도 상관없다고 한다. 반환 값이 String인 경우에는 viewResolver가 뷰에서 리턴값과 동일한 뷰 파일을 불러오는 작업을 하지만, 객체를 직접 리턴값에 전달하는 경우에는 model attribute로 해석하게 된다.

 

뭐... 그래서

@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
    // ...
    return account;
}

@GetMapping("/accounts/{id}")
public Account handle() {
    // ...
    return account;
}

위의 두 메서드는 동일하게 동작한다고 생각하면 된다.

 

자... 여기까지가 공식 문서의 내용이다. 그래서 어노테이션을 설명하는 공식 문서로 찾아들어갔다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html

 

ModelAttribute (Spring Framework 6.0.8 API)

The name of the model attribute to bind to. The default model attribute name is inferred from the declared attribute type (i.e. the method parameter type or method return type), based on the non-qualified class name: e.g. "orderAddress" for class "mypackag

docs.spring.io

눈에 띄는 부분은 @Reflective를 사용하고 있다는 점이다. 응? ModelAttribute는 리플렉션을 사용하지 않는 게 아닌가? 궁금해서 구글링해봤다.

 

https://stackoverflow.com/questions/7402581/spring-modelattribute-annotation-uses-reflection-to-create-command-object

 

글을 읽어보니까 어딘가에서 리플렉션을 사용하기는 하는 것 같다. 그래서 코드를 한번 더 까봤다. 이전 포스팅에서 @ModelAttribute에서 기본 생성자가 있는 경우와 기본 생성자가 없는 경우를 모두 설명하면서 코드를 작성했는데, 힌트는 그 곳에 있었다.

어쨌든 생성자를 불러오는 과정에서 getConstructors() 메서드나 getDeclaredConstructors() 메서드를 사용하는데, 둘 다 리플렉션을 사용한다고 적혀있다.

위에 첫 줄만 보자
이것도 위의 첫 줄만 보자

 

결국 객체를 바인딩해서 만들 때에는 적어도 생성자를 만드는 과정에서 리플렉션을 사용하는 것 같다.(다른 곳에서도 사용할 수 있고~)

 

Warning으로 경고를 주고 있는 부분은 Data binding하는 과정에서 감춰야하는 내부의 무언가가 외부로 노출되기 때문에 보안 문제가 있을 수 있다고 한다. 아래에 있는 레퍼런스 메뉴얼을 누르면 자세히 알 수 있다고 하는데, 한번 눌러봤다.

 

우선 Spring Web MVC부터!

 

위의 페이지가 등장했는데, 뭔가 익숙하다...

이전에 data binder에 대해 정리했을 때 참고했던 내용이다. 내가 원한건 이런 자료가 아니었는데...

그래서 페이지를 나오고 Spring WebFlux 페이지를 눌렀다. 그러면...!!

 

 

 

똑같은 내용이 나온다. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

그래도 공식 문서인데, 나름 관계가 있지 않을까?

 

읽어보니까 ModelAttribute를 사용한다는 것은 자바 빈 네이밍 규칙을 지켜야하기 때문에 각 객체들이 어떤 구조로 되어있는지를 외부에서도 어느 정도 유추할 수 있기 때문에 보안이슈가 있다고 말하는 것 같다. 이 정도도 보안 이슈가 있다고 말할 수 있나? 하는 생각이 들기는 하지만 일단 외부에서 알 필요가 없는 내용이 노출되는 것은 맞으니까 일단 어떤 식으로든 위험한 일이 생기지 않을까??

 

다시 돌아와서 마지막 부분에는 다음과 같은 문구가 적혀있다.

Note however that reference data and all other model content are not available to web views when request processing results in an Exception since the exception could be raised at any time making the content of the model unreliable. For this reason @ExceptionHandler methods do not provide access to a Model argument.

 

@ExceptionHandler에서는 model argument를 지원하지 않는다는데, 인자로 model을 전달하는 것을 의미한느 것 같다. 아마 이를 지원하지 않는 것은 예외가 발생했다는 것은 참조하고 있는 model이 올바르지 않을 가능성도 내포하고 있기 때문에 이를 지원하지 않는 것 같다.

 


model.addAttribute() 그래서 뭐야?

 

모델에 대해 공부를 하며 굉장히 헷갈렸던 부분은 '그래서 @ModeAttribute가 뭐하는건데?'라고 하면 조금 설명하기 애매했다는 점이었다. 왜 그런가를 고민해보니까 @ModelAttribute가 인자로 사용되는 경우와 메서드에서 사용되는 경우 두 가지를 분리하지 않아서 그런 것 같다.

 

자, @ModelAttribute가 인자로 사용되는 경우는 충분히 설명한 것 같다. '객체로 값을 바인딩한다.' 그런데 메서드에서 @ModelAttribute가 사용되는 경우는 뭐지?

 

공식문서만 보고 이해하기는 조금 빈약해서 구글링해서 한번 사용 예시와 함께 비교를 해보았다.

 

Model

우선 모델이라는 개념부터 이해하고 넘어가야하는데, 모델에 attribute를 추가하면, 추가한 attribute를 불러와서 뷰에 넘겨줄 수 있다. 한번 예시를 보면 이해가 될 것이다.

 

지금 다음은 웹 장바구니 미션에서 사용하는 코드이다.

위의 컨트롤러에서는 /admin 페이지로 get 요청이 들어오면 displayItemList 메서드가 호출된다. 해당 메서드에서는 model을 인자로 전달받고 있는데, 여기에 model.addAttribute를 호출해서 어트리뷰트를 추가하고 있는 것을 알 수 있다. 이렇게 추가한 어트리뷰트는 일종의 Map 느낌으로 뷰에서 사용할 수 있게 된다.

 

String이 인자값으로 전달되는 경우에는 일반적으로 뷰 리조버가 동작해 문자열에 해당하는 뷰를 랜더링할 수 있다. resources/templates/admin.html 파일을 보자.

 

뷰 리조버를 통해 랜더링할 수 있는 admin.html 파일 내에 이상한 기호가 들어간 것을 확인할 수 있다. 이때 products라는 이름이 사용되고 있는데, 이 products가 바로 model.addAttribute()에서 지정해준 이름이 된다. 

model.addAttribute("products", itemService.findAll());

(여기서의 왼쪽 인자 값이다.)

페이지 모습이다.

 

 

@ModelAttribute

 

@ModelAttribute를 메서드 어노테이션으로 사용하는 경우에는 모델을 등록하는 과정을 미리 선언할 수 있다.

 

위와 같이 @ModelAttribute 어노테이션을 사용해 모델에 어트리뷰트를 하나만 추가할 경우에는 리턴 값에 itemService.findAll()를 넣어줄 수 있다. 이렇게 되면 아래와 같이 key, value 형태가 구성된다.

 

nowItemList : itemService.findAll()

 

따라서 아까 위에서 했던 것처럼 admin.html 페이지를 구성할 때 모델의 키 값으로 추가했던 것을 불러서 사용할 수 있다.

 

이름이 안예쁘면 직접 @ModelAttribute에 명시해줄 수 있다.

 

 

이렇게 @ModelAttribute에 이름을 전달해서 사용하는 경우에는 key값을 변경할 수 있다.

products로 값을 사용할 수 있다.

만약 여러 attribute를 사용하고 싶다면 아래와 같이 model.attribute를 등록해서 사용할 수 있다.

 

 

model에 어트리뷰트를 더 추가할 수 있다.

 

아래에 hi가 추가됐다.

 

오른쪽에 hi가 추가된 것을 확인할 수 있다.

 

 

728x90
반응형