Loading...
Spring Framework Reference Documentation 7.0.2의 Data Binding의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
Data binding은 user input이 property path를 key로 하는 map일 때, 그 user input을 target 객체에 binding하는 데 유용합니다. 이때 property path는 JavaBeans conventions를 따릅니다.
DataBinder는 이를 지원하는 main 클래스이며, user input을 binding하는 두 가지 방식을 제공합니다:
Constructor binding과 property binding 둘 다 적용할 수도 있고, 둘 중 하나만 사용할 수도 있습니다.
Constructor binding을 사용하려면:
null을 사용하여 DataBinder를 생성합니다.targetType을 target 클래스로 설정합니다.construct를 호출합니다.Target 클래스는 argument가 있는 single public constructor 또는 single non-public constructor를 가져야 합니다. 여러 constructor가 있는 경우, default constructor가 존재하면 그것이 사용됩니다.
기본적으로 argument 값은 constructor parameter name을 통해 lookup됩니다. Spring MVC와 WebFlux는 constructor parameter 또는 field에 존재하는 @BindParam 어노테이션을 통해 custom name mapping을 지원합니다. 필요하다면 DataBinder에 NameResolver를 설정하여 사용할 argument name을 customize할 수도 있습니다.
Type conversion은 user input을 변환하기 위해 필요에 따라 적용됩니다. Constructor parameter가 객체인 경우, nested property path를 통해 동일한 방식으로 재귀적으로 construct됩니다. 이는 constructor binding이 target 객체와 그 안에 포함된 모든 객체를 생성한다는 뜻입니다.
Constructor binding은 List, Map, array argument를 지원하며, 이는 single string(예: comma-separated list)에서 변환되거나 accounts[2].name 또는 account[KEY].name과 같은 indexed key를 기반으로 할 수 있습니다.
Binding 및 conversion error는 DataBinder의 BindingResult에 반영됩니다.
Target이 성공적으로 생성되면, construct 호출 이후에 target이 생성된 인스턴스로 설정됩니다.
BeanWrapper를 사용한 Property Bindingorg.springframework.beans 패키지는 JavaBeans standard를 준수합니다.
JavaBean은 default no-argument constructor를 가지며, 예를 들어 bingoMadness라는 property는 setBingoMadness(..) setter 메서드와 getBingoMadness() getter 메서드를 가지는 naming convention을 따르는 클래스입니다. JavaBeans 및 그 명세에 대한 자세한 내용은 javabeans를 참조하십시오.
Beans 패키지에서 꽤 중요한 클래스 중 하나는 BeanWrapper 인터페이스와 그에 대응하는 구현체(BeanWrapperImpl)입니다. Javadoc에서 인용하면, BeanWrapper는 property 값을 (개별적으로 또는 bulk로) 설정하고 가져오며, property descriptor를 가져오고, property가 readable 또는 writable인지 판별하기 위해 property를 query하는 기능을 제공합니다. 또한, BeanWrapper는 nested property를 지원하여, 무제한 깊이의 sub-property에 대해 property를 설정할 수 있게 합니다. BeanWrapper는 또한 target 클래스에 지원 코드가 필요 없이 표준 JavaBeans PropertyChangeListeners와 VetoableChangeListeners를 추가하는 기능을 지원합니다. 마지막으로, BeanWrapper는 indexed property 설정을 지원합니다.
BeanWrapper는 보통 애플리케이션 코드에서 직접 사용되지 않고, DataBinder와 BeanFactory에 의해 사용됩니다.
BeanWrapper의 동작 방식은 그 이름에서 어느 정도 유추할 수 있습니다. Bean을 wrap하여 그 bean에 대해 property 설정 및 조회와 같은 작업을 수행합니다.
Property를 설정하고 조회하는 작업은 BeanWrapper의 setPropertyValue 및 getPropertyValue 오버로드된 메서드 variant를 통해 수행됩니다. 자세한 내용은 해당 Javadoc을 참조하십시오. 아래 표는 이러한 convention의 예를 몇 가지 보여줍니다:
| Expression | Explanation |
|---|---|
name | getName() 또는 isName()<br>및 setName(..) 메서드에 대응하는 name property를 나타냅니다. |
account.name | 예를 들어 getAccount().setName() 또는 getAccount().getName() 메서드에<br>대응하는 property account의 nested property name을 나타냅니다. |
accounts[2] | Indexed property account의 세 번째 element를 나타냅니다. Indexed property는<br>array, list, 또는 그 밖의 자연스럽게 순서가 있는 collection 타입일 수 있습니다. |
accounts[KEY] | KEY 값으로 index된 map entry의 값을 나타냅니다. |
Table 1. Examples of properties
(다음 section은 BeanWrapper를 직접 사용할 계획이 없다면 필수적으로 중요하지는 않습니다. DataBinder와 BeanFactory 및 그 default 구현만 사용하는 경우라면 PropertyEditors에 대한 section으로 건너뛰어도 됩니다.)
다음 두 예제 클래스는 BeanWrapper를 사용하여 property를 get/set합니다:
1public class Company { 2 3 private String name; 4 private Employee managingDirector; 5 6 public String getName() { 7 return this.name; 8 } 9 10 public void setName(String name) { 11 this.name = name; 12 } 13 14 public Employee getManagingDirector() { 15 return this.managingDirector; 16 } 17 18 public void setManagingDirector(Employee managingDirector) { 19 this.managingDirector = managingDirector; 20 } 21}
1class Company { 2 var name: String? = null 3 var managingDirector: Employee? = null 4}
1public class Employee { 2 3 private String name; 4 5 private float salary; 6 7 public String getName() { 8 return this.name; 9 } 10 11 public void setName(String name) { 12 this.name = name; 13 } 14 15 public float getSalary() { 16 return salary; 17 } 18 19 public void setSalary(float salary) { 20 this.salary = salary; 21 } 22}
1class Employee { 2 var name: String? = null 3 var salary: Float? = null 4}
다음 코드 스니펫은 생성된 Company와 Employee의 일부 property를 조회하고 조작하는 예를 보여줍니다:
1BeanWrapper company = new BeanWrapperImpl(new Company()); 2// setting the company name.. 3company.setPropertyValue("name", "Some Company Inc."); 4// ... can also be done like this: 5PropertyValue value = new PropertyValue("name", "Some Company Inc."); 6company.setPropertyValue(value); 7 8// ok, let's create the director and tie it to the company: 9BeanWrapper jim = new BeanWrapperImpl(new Employee()); 10jim.setPropertyValue("name", "Jim Stravinsky"); 11company.setPropertyValue("managingDirector", jim.getWrappedInstance()); 12 13// retrieving the salary of the managingDirector through the company 14Float salary = (Float) company.getPropertyValue("managingDirector.salary");
1val company = BeanWrapperImpl(Company()) 2// setting the company name.. 3company.setPropertyValue("name", "Some Company Inc.") 4// ... can also be done like this: 5val value = PropertyValue("name", "Some Company Inc.") 6company.setPropertyValue(value) 7 8// ok, let's create the director and tie it to the company: 9val jim = BeanWrapperImpl(Employee()) 10jim.setPropertyValue("name", "Jim Stravinsky") 11company.setPropertyValue("managingDirector", jim.wrappedInstance) 12 13// retrieving the salary of the managingDirector through the company 14val salary = company.getPropertyValue("managingDirector.salary") as Float?
PropertyEditorsSpring은 Object와 String 사이의 conversion을 수행하기 위해 PropertyEditor 개념을 사용합니다. 객체 자체와는 다른 방식으로 property를 표현하는 것이 유용할 수 있습니다. 예를 들어, Date는 사람이 읽을 수 있는 방식(String: '2007-14-09')으로 표현될 수 있으며, 이 사람이 읽을 수 있는 형태를 원래 date로 다시 변환할 수 있습니다(또는, 더 나아가 사람이 읽을 수 있는 어떤 date든 다시 Date 객체로 변환할 수 있습니다). 이 동작은 java.beans.PropertyEditor 타입의 custom editor를 등록함으로써 달성할 수 있습니다.
BeanWrapper 또는 (이전 chapter에서 언급한 것처럼) 특정 IoC 컨테이너에 custom editor를 등록하면, 원하는 타입으로 property를 변환하는 방법에 대한 지식을 제공하게 됩니다. PropertyEditor에 대한 자세한 내용은 Oracle의 java.beans 패키지 javadoc을 참조하십시오.
Spring에서 property editing이 사용되는 몇 가지 예는 다음과 같습니다:
PropertyEditor 구현을 사용하여 수행됩니다. XML 파일에서 선언한 어떤 bean의 property 값을 String으로 사용할 때, 해당 property의 setter가 Class parameter를 가지면 Spring은 ClassEditor를 사용하여 parameter를 Class 객체로 resolve하려고 시도합니다.CommandController의 모든 subclass에서 수동으로 binding할 수 있는 다양한 PropertyEditor 구현을 사용하여 수행됩니다.Spring은 life를 편하게 하기 위해 여러 built-in PropertyEditor 구현을 제공합니다. 이들은 모두 org.springframework.beans.propertyeditors 패키지에 위치합니다. 대부분은(그러나 다음 표에 표시된 것처럼 모두는 아님) 기본적으로 BeanWrapperImpl에 의해 등록됩니다. Property editor가 어떤 방식으로든 configurable한 경우, 기본 editor를 override하기 위해 자체 variant를 등록할 수 있습니다. 다음 표는 Spring이 제공하는 다양한 PropertyEditor 구현을 설명합니다:
| Class | Explanation |
|---|---|
ByteArrayPropertyEditor | Byte array를 위한 editor입니다. String을 해당 byte<br>표현으로 변환합니다. 기본적으로 BeanWrapperImpl에 의해 등록됩니다. |
ClassEditor | Class를 나타내는 String을 실제 class로, 그 반대로 parsing합니다. Class를<br>찾을 수 없는 경우 IllegalArgumentException이 발생합니다. 기본적으로<br>BeanWrapperImpl에 의해 등록됩니다. |
CustomBooleanEditor | Boolean property를 위한 customizable property editor입니다. 기본적으로<br>BeanWrapperImpl에 의해 등록되지만, custom 인스턴스를 custom editor로 등록하여<br>override할 수 있습니다. |
CustomCollectionEditor | Source Collection을 주어진 target Collection 타입으로 변환하는 collection을 위한<br>property editor입니다. |
CustomDateEditor | Custom DateFormat을 지원하는 java.util.Date를 위한 customizable property editor입니다.<br>기본적으로 등록되지 않습니다. 필요에 따라 적절한 format과 함께 user가 등록해야 합니다. |
CustomNumberEditor | Integer, Long, Float, Double과 같은 어떤 Number subclass를 위한 customizable<br>property editor입니다. 기본적으로 BeanWrapperImpl에 의해 등록되지만, custom 인스턴스를<br>custom editor로 등록하여 override할 수 있습니다. |
FileEditor | String을 java.io.File 객체로 resolve합니다. 기본적으로<br>BeanWrapperImpl에 의해 등록됩니다. |
InputStreamEditor | String을 받아 (중간에 ResourceEditor와 Resource를 거쳐) InputStream을 생성하는<br>one-way property editor이므로, InputStream property를 String으로 직접 설정할 수 있습니다.<br>기본 사용에서는 InputStream을 자동으로 close하지 않는다는 점에 유의하십시오.<br>기본적으로 BeanWrapperImpl에 의해 등록됩니다. |
LocaleEditor | String을 Locale 객체로 resolve하고 그 반대로도 resolve할 수 있습니다(String format은<br>[language]_[country]_[variant]이며, 이는 Locale의 toString() 메서드와 동일합니다).<br>또한 underscore 대신 separator로 space도 허용합니다.<br>기본적으로 BeanWrapperImpl에 의해 등록됩니다. |
PatternEditor | String을 java.util.regex.Pattern 객체로 resolve하고 그 반대로도 resolve할 수 있습니다. |
PropertiesEditor | (java.util.Properties 클래스의 javadoc에 정의된 format으로) format된 String을<br>Properties 객체로 변환할 수 있습니다. 기본적으로 BeanWrapperImpl에 의해<br>등록됩니다. |
StringTrimmerEditor | String을 trim하는 property editor입니다. 선택적으로 empty string을 null 값으로<br>변환할 수 있습니다. 기본적으로 등록되지 않으며, user가 등록해야 합니다. |
URLEditor | URL의 String 표현을 실제 URL 객체로 resolve할 수 있습니다.<br>기본적으로 BeanWrapperImpl에 의해 등록됩니다. |
Table 2. Built-in PropertyEditor Implementations
Spring은 필요한 property editor를 찾기 위한 search path를 설정하기 위해 java.beans.PropertyEditorManager를 사용합니다. Search path에는 Font, Color, 대부분의 primitive 타입과 같은 타입을 위한 PropertyEditor 구현을 포함하는 sun.bean.editors도 포함됩니다. 또한, 표준 JavaBeans infrastructure는 PropertyEditor 클래스가 처리하는 클래스와 같은 패키지에 있고, 해당 클래스와 동일한 이름에 Editor를 덧붙인 이름을 가지면(명시적으로 등록하지 않아도) 자동으로 PropertyEditor 클래스를 발견한다는 점에 유의하십시오. 예를 들어, 다음과 같은 클래스 및 패키지 구조를 가질 수 있으며, 이는 SomethingEditor 클래스가 Something 타입 property에 대한 PropertyEditor로 인식되고 사용되기에 충분합니다.
1com 2 chank 3 pop 4 Something 5 SomethingEditor // the PropertyEditor for the Something class
또한 여기에서 표준 BeanInfo JavaBeans mechanism도 사용할 수 있습니다 (일부는 여기에 설명되어 있습니다). 다음 예제는 associated 클래스의 property에 하나 이상의 PropertyEditor 인스턴스를 명시적으로 등록하기 위해 BeanInfo mechanism을 사용하는 방법을 보여줍니다:
1com 2 chank 3 pop 4 Something 5 SomethingBeanInfo // the BeanInfo for the Something class
다음은 참조된 SomethingBeanInfo 클래스의 Java source 코드로, Something 클래스의 age property에 CustomNumberEditor를 연결합니다:
1public class SomethingBeanInfo extends SimpleBeanInfo { 2 3 public PropertyDescriptor[] getPropertyDescriptors() { 4 try { 5 final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true); 6 PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) { 7 @Override 8 public PropertyEditor createPropertyEditor(Object bean) { 9 return numberPE; 10 } 11 }; 12 return new PropertyDescriptor[] { ageDescriptor }; 13 } 14 catch (IntrospectionException ex) { 15 throw new Error(ex.toString()); 16 } 17 } 18}
1class SomethingBeanInfo : SimpleBeanInfo() { 2 3 override fun getPropertyDescriptors(): Array<PropertyDescriptor> { 4 try { 5 val numberPE = CustomNumberEditor(Int::class.java, true) 6 val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) { 7 override fun createPropertyEditor(bean: Any): PropertyEditor { 8 return numberPE 9 } 10 } 11 return arrayOf(ageDescriptor) 12 } catch (ex: IntrospectionException) { 13 throw Error(ex.toString()) 14 } 15 } 16}
PropertyEditorsBean property를 string 값으로 설정할 때, Spring IoC 컨테이너는 궁극적으로 표준 JavaBeans PropertyEditor 구현을 사용하여 이러한 string을 property의 complex 타입으로 변환합니다. Spring은 (예를 들어, string으로 표현된 class name을 Class 객체로 변환하기 위해) 여러 custom PropertyEditor 구현을 미리 등록합니다. 추가로, Java 표준 JavaBeans PropertyEditor lookup mechanism은 특정 클래스에 대한 PropertyEditor가 적절히 이름 붙여지고, 지원하는 클래스와 같은 패키지에 위치하도록 함으로써 자동으로 발견될 수 있게 해줍니다.
다른 custom PropertyEditor를 등록해야 하는 경우 사용할 수 있는 여러 mechanism이 있습니다. 가장 manual한 접근 방식(일반적으로 편리하지도, 권장되지도 않음)은 BeanFactory reference가 있다고 가정하고 ConfigurableBeanFactory 인터페이스의 registerCustomEditor() 메서드를 사용하는 것입니다. 또 다른(약간 더 편리한) mechanism은 CustomEditorConfigurer라는 특수 bean factory post-processor를 사용하는 것입니다. Bean factory post-processor를 BeanFactory 구현과 함께 사용할 수 있지만, CustomEditorConfigurer는 nested property 설정을 가지므로, 다른 bean과 비슷한 방식으로 배포할 수 있고 자동으로 감지 및 적용될 수 있는 ApplicationContext와 함께 사용할 것을 강력히 권장합니다.
모든 bean factory와 application context는 property conversion을 처리하기 위해 BeanWrapper를 사용함으로써 여러 built-in property editor를 자동으로 사용한다는 점에 유의하십시오. BeanWrapper가 등록하는 표준 property editor는 이전 section에 나열되어 있습니다. 추가로, ApplicationContext는 특정 application context 타입에 적합한 방식으로 resource lookup을 처리하기 위해 editor를 override하거나 추가로 등록합니다.
표준 JavaBeans PropertyEditor 인스턴스는 string으로 표현된 property 값을 property의 실제 complex 타입으로 변환하는 데 사용됩니다. CustomEditorConfigurer(bean factory post-processor)를 사용하여 ApplicationContext에 추가적인 PropertyEditor 인스턴스 지원을 편리하게 추가할 수 있습니다.
다음 예제는 ExoticType이라는 user 클래스와, ExoticType을 property로 설정해야 하는 DependsOnExoticType이라는 다른 클래스를 정의하는 예입니다:
1package example; 2 3public class ExoticType { 4 5 private String name; 6 7 public ExoticType(String name) { 8 this.name = name; 9 } 10} 11 12public class DependsOnExoticType { 13 14 private ExoticType type; 15 16 public void setType(ExoticType type) { 17 this.type = type; 18 } 19}
1package example 2 3class ExoticType(val name: String) 4 5class DependsOnExoticType { 6 7 var type: ExoticType? = null 8}
구성이 적절히 설정되면, PropertyEditor가 string을 실제 ExoticType 인스턴스로 변환하도록 type property를 string으로 할당할 수 있기를 원합니다. 다음 bean definition은 이 관계를 설정하는 방법을 보여줍니다:
1<bean id="sample" class="example.DependsOnExoticType"> 2 <property name="type" value="aNameForExoticType"/> 3</bean>
PropertyEditor 구현은 다음과 비슷하게 보일 수 있습니다:
1package example; 2 3import java.beans.PropertyEditorSupport; 4 5// converts string representation to ExoticType object 6public class ExoticTypeEditor extends PropertyEditorSupport { 7 8 public void setAsText(String text) { 9 setValue(new ExoticType(text.toUpperCase())); 10 } 11}
1package example 2 3import java.beans.PropertyEditorSupport 4 5// converts string representation to ExoticType object 6class ExoticTypeEditor : PropertyEditorSupport() { 7 8 override fun setAsText(text: String) { 9 value = ExoticType(text.toUpperCase()) 10 } 11}
마지막으로, 다음 예제는 CustomEditorConfigurer를 사용하여 새로운 PropertyEditor를 ApplicationContext에 등록하는 방법을 보여줍니다. 이렇게 하면 ApplicationContext는 필요할 때 이를 사용할 수 있게 됩니다:
1<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> 2 <property name="customEditors"> 3 <map> 4 <entry key="example.ExoticType" value="example.ExoticTypeEditor"/> 5 </map> 6 </property> 7</bean>
PropertyEditorRegistrarSpring 컨테이너에 property editor를 등록하는 또 다른 mechanism은 PropertyEditorRegistrar를 생성하고 사용하는 것입니다. 이 인터페이스는 여러 다른 상황에서 동일한 set의 property editor를 사용해야 할 때 특히 유용합니다. 해당 registrar를 작성하고 각 경우에 재사용할 수 있습니다.
PropertyEditorRegistrar 인스턴스는 Spring BeanWrapper(및 DataBinder)가 구현하는 인터페이스인 PropertyEditorRegistry와 함께 동작합니다. PropertyEditorRegistrar 인스턴스는 CustomEditorConfigurer(설명은 여기 참고)와 함께 사용할 때 특히 편리합니다. CustomEditorConfigurer는 setPropertyEditorRegistrars(..)라는 property를 노출합니다. 이 방식으로 CustomEditorConfigurer에 추가된 PropertyEditorRegistrar 인스턴스는 DataBinder 및 Spring MVC controller와 쉽게 공유할 수 있습니다. 또한 custom editor에 대한 synchronization 필요성을 피할 수 있습니다. PropertyEditorRegistrar는 각 bean 생성 시도마다 새로운 PropertyEditor 인스턴스를 생성할 것으로 기대됩니다.
다음 예제는 자체 PropertyEditorRegistrar 구현을 생성하는 방법을 보여줍니다:
1package com.foo.editors.spring; 2 3public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar { 4 5 public void registerCustomEditors(PropertyEditorRegistry registry) { 6 7 // it is expected that new PropertyEditor instances are created 8 registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor()); 9 10 // you could register as many custom property editors as are required here... 11 } 12}
1package com.foo.editors.spring 2 3import org.springframework.beans.PropertyEditorRegistrar 4import org.springframework.beans.PropertyEditorRegistry 5 6class CustomPropertyEditorRegistrar : PropertyEditorRegistrar { 7 8 override fun registerCustomEditors(registry: PropertyEditorRegistry) { 9 10 // it is expected that new PropertyEditor instances are created 11 registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor()) 12 13 // you could register as many custom property editors as are required here... 14 } 15}
예제로는 org.springframework.beans.support.ResourceEditorRegistrar를 참고하십시오. 이는 PropertyEditorRegistrar 구현 예시입니다. 이 클래스의 registerCustomEditors(..) 메서드 구현에서 각 property editor의 새 인스턴스를 생성하는 방식을 확인하십시오.
다음 예제는 CustomEditorConfigurer를 구성하고, 우리의 CustomPropertyEditorRegistrar 인스턴스를 주입하는 방법을 보여줍니다:
1<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> 2 <property name="propertyEditorRegistrars"> 3 <list> 4 <ref bean="customPropertyEditorRegistrar"/> 5 </list> 6 </property> 7</bean> 8 9<bean id="customPropertyEditorRegistrar" 10 class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
마지막으로(그리고 이 chapter의 초점에서 다소 벗어나지만), Spring의 MVC web framework를 사용하는 경우, data-binding web controller와 함께 PropertyEditorRegistrar를 사용하는 것은 매우 편리할 수 있습니다. 다음 예제는 @InitBinder 메서드 구현에서 PropertyEditorRegistrar를 사용하는 예입니다:
1@Controller 2public class RegisterUserController { 3 4 private final PropertyEditorRegistrar customPropertyEditorRegistrar; 5 6 RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) { 7 this.customPropertyEditorRegistrar = propertyEditorRegistrar; 8 } 9 10 @InitBinder 11 void initBinder(WebDataBinder binder) { 12 this.customPropertyEditorRegistrar.registerCustomEditors(binder); 13 } 14 15 // other methods related to registering a User 16}
1@Controller 2class RegisterUserController( 3 private val customPropertyEditorRegistrar: PropertyEditorRegistrar) { 4 5 @InitBinder 6 fun initBinder(binder: WebDataBinder) { 7 this.customPropertyEditorRegistrar.registerCustomEditors(binder) 8 } 9 10 // other methods related to registering a User 11}
이 스타일의 PropertyEditor registration은 간결한 코드(@InitBinder 메서드 구현이 한 줄에 불과함)를 가능하게 하며, 공통 PropertyEditor registration 코드를 하나의 클래스에 캡슐화하여 필요한 만큼 많은 controller 간에 공유할 수 있게 해줍니다.
Validation Using Spring’s Validator Interface
Resolving Error Codes to Error Messages