프로퍼티 바인딩은 텍스트 형식의 값을 적절한 타입으로 변환해서 객체의 프로퍼티에 넣어주는 것으로, 대표적으로 XML 설정이나 HTTP 요청 파라미터 처리에 필요하다.
여러 개의 포스팅에 걸쳐 Spring에서 프로퍼티 바인딩을 위해 사용할 수 있는 API들을 살펴볼 것이다. 먼저, PropertyEditor을 살펴보자.
PropertyEditor
- Spring이 기본적으로 사용하는 바인딩용 타인 변환 API로, 자바빈 표준에 정의된 인터페이스다.
- XML의 <property value="">에서 정의된 프로퍼티 값을 실제 프로퍼티에 저장할 때 활용된다.
1) 기본 프로퍼티 에디터
- Spring이 기본적으로 제공하는 프로퍼티 에디터들은 아래 문서에서 확인하자.
2) 커스텀 프로퍼티 에디터
- Spring이 지원하지 않는 타입을 파라미터로 사용해야 한다면 커스텀 프로퍼티 에디터를 만들어서 사용해야 한다. 그렇지 않으면 ConversionNotSupportedException 예외로 인해 HTTP 500 에러를 만나게 된다.
- PropertyEditor에서 변환을 위해 사용되는 메소드는 총 4개로, 필요에 따라 메소드들을 알맞게 구현해주면 된다.
- setAsText() : String 타입의 문자열을 지정함
- getValue() : 변환된 오브젝트를 반환함
- setValue() : 변환할 오브젝트를 지정함
- getAsText() : 변환된 String 타입의 문자열을 반환함
- 커스텀 프로퍼티 에디터를 만들 때는 PropertyEditor 인터페이스를 직접 구현하는 것보다는 구현체인 PropertyEditorSupport 클래스를 상속해서 필요한 메소드만 오버라이드해주는 편이 좋다. 아래 문서를 참조하여 PropertyEditorSupport가 가지는 메소드들을 확인해보길 바란다.
https://docs.oracle.com/javase/7/docs/api/java/beans/PropertyEditorSupport.html
예시 - Level Enum 타입을 위한 커스텀 프로퍼티 에디터
public class LevelPropertyEditor extends PropertyEditorSupport {
public void setAsText(String text) throws IllegalArgumentException {
// String 타입의 문자열을 원하는 오브젝트로 변환해서 this.value에 저장
this.setValue(Level.valueOf(Integer.parseInt(text)));
}
public String getAsText() {
// this.value에 저장되어 있는 오브젝트를 String 타입의 문자열로 변환해서 반환
return String.valueOf(((Level)this.getValue()).intValue());
}
}
- 커스텀 프로퍼티 에디터는 디폴트 프로퍼티 에디터보다 높은 우선순위를 갖기 때문에, 디폴트 프로퍼티 에디터에서 지원하는 타입이라도 커스텀 프로퍼티 에디터를 적용해서 부가적인 조건을 부여해줄 수도 있다. 이 경우는 조금 뒤에서 다뤄보겠다.
3) @InitBinder
(1) @InitBinder란?
- @InitBinder가 붙은 메소드는 프로퍼티 바인딩 작업 전에 자동으로 호출된다.
(2) WebDataBinder
- @InitBinder 메소드의 파라미터로 사용할 수 있는 클래스다.
- AnntationMethodHandlerAdapter는 프로퍼티 바인딩 작업이 필요한 @RequestParam, @ModelAttribute, @PathVariable, @CookieValue 또는 @RequestHeader 어노테이션을 만나면 WebDataBinder를 만든다.
- WebDataBinder은 프로퍼티 바인딩 기능을 포함하기 때문에 커스텀 프로퍼티 에디터를 WebDataBinder에 직접 등록해주면 된다. 다음과 같이 스프링이 제공하는 @InitBinder 어노테이션을 붙인 WebDataBinder 초기화 메소드를 만들고 그 안에 등록 코드를 작성해주면 같은 컨트롤러 메소드의 프로퍼티 바인딩 작업에 적용된다.
@InitBinder
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.registerCustomEditor(Level.class, new LevelPropertyEditor());
}
- 앞서, 디폴트 프로퍼티 에디터에서 지원하는 타입에도 커스텀 프로퍼티 에디터를 적용할 수 있다고 언급했다. 이 때, 모든 특정 타입에 대해 커스텀 프로퍼티 에디터를 적용해버리면 곤란한 상황이 발생할 수 있기 때문에 프로퍼티 이름을 지정해서 커스텀 프로퍼티 에디터를 등록하는 것이 좋다. 단, 단일 파라미터 바인딩에는 적용되지 않는다. 아래 예시에서는 int age 프로퍼티에만 커스텀 프로퍼티 에디터를 등록한다.
@InitBinder
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.registerCustomEditor(int.class, "age", new AgePropertyEditor(0, 300));
}
(3) WebRequest
- @InitBinder 메소드의 파라미터로 사용할 수 있는 인터페이스다.
- WebRequest는 HttpServletRequest에 들어 있는 모든 HTTP 요청 정보를 담고 있기 때문에 이를 활용하여 프로퍼티 에디터를 설정할 수 있다.
(4) WebBindingInitializer
- 프로퍼티 에디터의 적용 범위를 하나의 컨트롤러가 아닌 모든 컨트롤러로 적용하고 싶을 때 사용할 수 있다.
- 아래와 같이 WebBindingInitializer 인터페이스를 상속받아 initBinder() 메소드를 구현한 구현체를 만들면 된다.
public class CustomWebBindingInitializer implements WebBindingInitializer {
public void initBinder(WebDataBinder binder, WebRequest request) {
binder.registerCustomEditor(Level.class, new LevelPropertyEditor());
}
}
- WebBindingInitializer의 구현체는 아래와 같이 AnnotationMethodHandlerAdapter의 webBindingInitializer 프로퍼티에 지정해주면 된다.
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="com.example.demo.custom.CustomWebBindingInitializer" />
</property>
</bean>
※ WebBindingInitializer는 AnnotationMethodHandlerAdapter외에는 참조될 일이 없기 때문에 내부 bean으로 설정했다.
4) 프로토타입 빈 프로퍼티 에디터
- 프로퍼티 에디터는 상태를 가지기 때문에 멀티스레드 환경에서 싱글톤으로 만들어 공유하면 안된다.
- 프로퍼티 에디터가 다른 bean을 참조해야 하는 상황이라면 prototype scope를 가지는 bean으로 만들면 된다. 그러면 bean을 요청할 때마다 새로운 오브젝트를 가져올 수 있으면서 DI도 가능해진다.
@Component
@Scope("prototype")
public class LevelPropertyEditor extends PropertyEditorSupport {
@Autowired LevelService levelService; // DI
...
}
@Controller
public class UserController {
@Inject
Provider<LevelPropertyEditor> levelEditorProvider;
@InitBinder
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.registerCustomEditor(Level.class, levelEditorProvider.get());
}
...
}
5) 모조 오브젝트 프로퍼티 에디터
- 오직 id 값만 가진 오브젝트를 모조(fake) 오브젝트라고 하고, 모조 오브젝트를 만드는 프로퍼티 에디터를 모조 프로퍼티 에디터라고 한다.
- 모조 오브젝트 방식은 폼에서 등록하거나 수정한 사용자의 정보를 단순히 저장하는 것이 목적인 경우에 유용하다. 프로퍼티 에디터에서 오브젝트 타입인 프로퍼티를 바인딩 할 때, 그 오브젝트의 모든 프로퍼티들을 채우기 위해 DB에 접근할 필요가 없다. 단순히 요청 파라미터로 전달받은 id만 받아두고 외래키로 저장할 때 사용하면 된다. 아래 예시를 참고하자.
public class User {
...
Code userType; // 오브젝트 타입의 프로퍼티, 모조 오브젝트
// setter, getter 메소드 추가
}
public class Code {
int id;
// setter, getter 메소드 추가
}
public class CodePropertyEditor extends PropertyEditorSupport {
public void setAsText(String text) throws IllegalArgumentException {
Code code = new Code();
code.setId(Integer.parseInt(text));
setValue(code);
}
public String getAsText() {
return String.valueOf(((Code)getValue()).getId());
}
}
User 오브젝트를 DB에 반영할 때, Code 타입인 필드는 아래와 같이 int로 변환해서 외래키로 저장하면 된다.
user.getUserType().getId()
- 폼의 <select> 태그 등을 통해 전달받은 식별자 값만 참조하는 성격의 오브젝트에 유용하게 쓸 수 있다.
- 서비스 계층의 일부 메소드는 자신에게 전달되는 오브젝트 안에 모조 오브젝트가 있다는 사실을 알고 있어야 한다는 단점이 있다. 이러한 이유로 모조 오브젝트를 사용하는 방법은 위험할 수 있다.
● 참고 자료 : 토비의 스프링 3.1 Vol.2
'Spring Series > Spring Framework' 카테고리의 다른 글
[Spring] 자주 사용되는 WebDataBinder 설정 항목 (0) | 2022.09.22 |
---|---|
[Spring] 프로퍼티 바인딩 방식(2) - Converter와 Formatter (0) | 2022.09.21 |
[Spring] @SessionAttributes와 SessionStatus (0) | 2022.09.14 |
[Spring] @Controller의 리턴 방식 (0) | 2022.09.13 |
[Spring] @Controller의 메소드 파라미터의 종류 (0) | 2022.09.11 |
댓글