해당 포스트를 작성하려다가 문득..
"도대체 바인더, 바인딩, 바인드 그 의미가 뭘까?"
에서 시작되어 진짜로 그 의미를 파악하고 왔다.
독자도 궁금하다면 대충 스-윽 보고 오면 좋을 것 같다 !
2022.08.04 - [웹개발/JAVA] - [바인딩(Binding)] 바인딩 이란
혹시...

Controller 메소드에 @ModelAttribute UserForm userForm 을 파라미터로 선언하고, jsp 단에 <form:spring modelAttribute="userForm"/> 스프링 폼태그를 사용해 본적이 있는가?
이때 컨트롤러단으로 요청이 들어오면 jsp에서 입력한 값이 modelAttribute 로 지정된 객체의 필드값에 매핑 저장되어 파라미터로 넘어오는 것을 볼 수 있다. 바로 여기서 들어온 요청에 대해 modelAttribute 로 선언된 객체의 필드값이 어떻게 매핑되는지 그 과정을 생각해 본 적이 있는 분!
.
.
.
.
.
메소드에 @ModelAttribute 를 선언했을 때 처리되는 과정은 다음과 같다.
1. 파라미터 타입의 객체를 하나 생성한다.
2. HTTP 요청에서 가져온 객체의 프로퍼티에 바인딩(값을 매긴다) 해준다.
이 과정에서 각 프로퍼티에 맞게 타입을 변환해준다.
만약 타입 변환 오류가 생기면 BindingResult 객체에 error 를 저장하여 컨트롤러로 넘겨준다.
(아~~ 너무 추상적이다..)
파라미터로 넘어온 친구들에 대해 프로퍼티 바인딩을 해준다.
즉, 각 프로퍼티에 맞게 타입을 변환해준다는 거군.
(그럼 '프로퍼티에 바인딩'한다는 건 머선 말이지..)
.
.
.
.
.
프로퍼티에 바인딩한다? - 2가지 방법
프로퍼티 바인딩이란 오브젝트의 프로퍼티(필드값)에 값을 넣는 행위를 말하는 것이다.
각각의 필드에 맞게 타입을 적절히 변환하고 프로퍼티의 커스텀메소드를 호출하는 것이다.
스프링에선 크게 두가지 프로퍼티 바인딩을 지원한다.
1. applicationContext.xml 의 설정파일로 Bean 을 정의할 때 사용한 <property> 태그이다.
해당 태그를 통해 빈의 프로퍼티에 값을 주입했었다.
예시
<!-- Hadler Mapping -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="order" value="1" />
</bean>
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="order" value="0" />
</bean>
2. Http request 파라미터를 모델 객체로 변환하는 경우이다.
.
.
여기서 잘 생각해보면, 저 value 에는 문자열이 아닌 Class 타입이 들어가는 경우도 있다.
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="annotationClass" value="org.mybatis.spring.annotation.MapperScan"/>
<property name="sqlSessionTemplateBeanName" value="sqlSession"/>
</bean>
이런 경우 value 에 문자열로 클래스명을 전달한다. 하지만 잘 바인딩 된다.
이는 스프링에서 제공되는 프로퍼티 바인딩 기능을 사용했기 때문이다.
스프링은 프로퍼티 바인딩을 위해 두 가지 API 를 제공한다고 한다.
PropertyEditor
스프링에서 제공하는 기본적인 프로퍼티 바인딩 API 이다.
PropertyEditor 는 스프링 API가 아니라 자바빈 표준에 정의된 API이다.
GUI 환경에서 비주얼 컴포넌트를 만들 때 사용하도록 설계되었고, 기본적인 기능은 문자열과 자바빈 프로퍼티 사이의 타입 변환이다.
스프링은 이 PropertyEditor 를 문자열-오브젝트 상호변환이 필요한 XML 설정이나 HTTP 파라미터 변환에 유용하게 사용할 수 있다고 판단하여 이를 일찍부터 사용해왔다.
핵심은 이 디폴트 PropertyEditor들은 바인딩 과정에서 파라미터 타입에 맞게 자동으로 선정되어 사용된다는 것이다.
디폴트 프로퍼티 데이터에 등록되지 않은 타입을 파라미터로 사용하고 싶다면 직접 PropertyEditor 를 구현하여 등록하고 사용할 수 있다.
WebDataBinder
직접 구현한 에디터를 구현하기 전에 Controller 에서 메소드 바인딩하는 과정을 먼저 살펴보자.
AnnotationMethodHandlerAdapter 는 @RequestParam, @PathVariable, @ModelAttribute 와 같이 HTTP 요청을 변수에 바인딩하는 어노테이션을 만나면 먼저 WebDataBinder 라는 것을 만든다.
WebDataBinder 는 여기서 HTTP 요청 문자열을 파라미터로 변환하는 기능을 한다.
-> WebDataBinder (이 친구) 때문에 바로 이 글을 시작할 때 언급했던 ModelAttribute 를 선언한 메소드의 객체 필드에 매핑이 되는 것이다.
이때, 직접 구현한 PropertyEditor 를 사용하려면 이 WebDataBinder 에 직접 등록해줘야 한다.
근데 WebDataBinder 의 변환 과정이 외부로 노출되지 않으므로, 직접 등록해줄 방법은 없다.
그래서 스프링이 제공하는 WebDataBinder 초기화 메서드를 사용해야 한다.
.
.
.
.
@InitBinder
Controller 클래스에 아래와 같이 @InitBinder 어노테이션이 부여되고, WebDataBinder 를 매개변수로 받는 메소드를 하나 생성해 봅니다.
@Controller
public class Controller {
// 모든 요청이 들어올때마다 해당 method 를 거친다.
// 모든 컨트롤러 내에서 변환 하려면 ConfigurableWebBindingInitializer 를 설정해서 사용해야 한다.
// 특정 컨트롤러 내에서만 변환 하려면 컨트롤러에 @InitBinder가 붙은 메서드를 작성하여 사용하면 된다.
@InitBinder
private void initBinder(WebDataBinder binder) {
// , 구분자로 배열화하는 것을 방지한다.
binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor(null));
}
.
.
@RequestMapping("json/binder.do")
public String binder(@RequestParam(value = "param", required = false) String[] param) {
//...
}
}
initBinder 메서드는 클래스내의 모든 메서드에 대해서 파라미터를 바인딩하기 전에 자동으로 호출된다.
바인딩 적용 대상은 @RequestParam, @PathVariable, @CookieValue, @RequestHeader, @ModelAttribute의 프로퍼티 이다.
기본적으로 PropertyEditor는 지정한 타입과 일치하면 항상 적용된다.
여기에 프로퍼티 이름을 추가 조건으로 주고, 프로퍼티 이름까지 일치해야만 적용되게 할 수 있다.
이러한 타입의 PropertyEditor는 이미 PropertyEditor가 존재할 경우 사용한다.
WebDataBinder는 바인딩 시 커스텀 PropertyEditor가 있을 경우 이를 선적용하고, 없을 경우 디폴트 PropertyEditor를 적용하기 때문이다.
.
.
.
.
.
+) 추가 예시 설명
binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor(null));
해당 customEditor 를 등록해주지 않은 경우
@RequestParam 에 배열타입으로(String[ ]) 들어오는 파라미터에 쉼표(,) 구분자가 있는 경우 디폴트(PropertyEditor)가 적용되어 무조건 쉼표(,) 구분자(쉼표가 default인 듯)에 의해 배열화 된다.
registerCustomEditor 인자로 String[].class , new StringArrayPropertyEditor(null) 값을 넣으면,
String [] 배열 타입에 대해 어떤 구분자로도 배열화하지 않겠다고 선언하는 것이다. (null 자리의 인자값은 seperator임)
필자가 원하는 값은 파라미터에 쉼표가 존재하더라도 하나의 문자열로 보고 구분하지 않도록 하는 것이었기 때문에 위와 같이 적용해주었다.
[registerCustomEditor 사용전]
@InitBinder
private void initBinder(WebDataBinder binder) {
//binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor(null));
}
@RequestParam(value="to") String[] to
입력 값 | "테, 스트"<test01@ttestt.kr> |
기대 값 | to[0] = ""테, 스트"<test01@ttestt.kr>" |
결과 | to[0]=""테" to[1]="스트"<test01@ttestt.kr>" |
[registerCustomEditor 사용후]
@InitBinder
private void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor(null));
}
입력 값 | "테, 스트"<test01@ttestt.kr> |
기대 값 | to[0] = ""테, 스트"<test01@ttestt.kr>" |
결과 | to[0]=""테, 스트"<test01@ttestt.kr>" |
, 쉼표로 배열화하지 않고 한 덩어리로 넘어온 것을 확인 할 수 있다.
'웹개발 > Spring' 카테고리의 다른 글
[Mybatis] resultMap (0) | 2022.09.04 |
---|---|
[Mybatis] Mybatis 기술의 탄생! (feat.ORM) (1) | 2022.07.30 |