3.3. Bean Manipulation and the BeanWrapper ~3.3.2. Built-in PropertyEditor Implementations
3.3. Bean Manipulation and the BeanWrapper
org.springframework.beans 패키지는 JavaBeans 표준을 준수한다.
JavaBean은 인수가 없는 기본 생성자를 가진 클래스이며 예를 들어 bingoMadness라는 속성이 setBingoMadness(..) setter 메서드와 getBingoMadness() getter 메서드를 갖는 명명 규칙을 따른다.
https://docs.oracle.com/javase/8/docs/api/java/beans/package-summary.html
java.beans (Java Platform SE 8 )
Interface Summary Interface Description AppletInitializer This interface is designed to work in collusion with java.beans.Beans.instantiate. BeanInfo Use the BeanInfo interface to create a BeanInfo class and provide explicit information about the methods
docs.oracle.com
Bean 패키지에서 매우 중요한 클래스 중 하나는 BeanWrapper 인터페이스와 해당 구현(BeanWrapperImpl)이다.
javadoc에서 인용한 것처럼 BeanWrapper는 속성 값(개별적으로 또는 일괄적으로)을 설정 및 가져오고, 속성 설명자를 가져오고, 속성을 읽을 수 있는지 쓸 수 있는지 확인하는 속성을 쿼리하는 기능을 제공한다.
또한 BeanWrapper는 중첩된 속성을 지원하여 하위 속성의 속성을 unlimited depth로 설정할 수 있다.
BeanWrapper는 대상 클래스에서 코드를 지원하지 않고도 표준 JavaBeans PropertyChangeListeners 및 VetoableChangeListeners를 추가하는 기능도 지원한다.
마지막으로 BeanWrapper는 인덱스 속성 설정을 지원한다. BeanWrapper는 일반적으로 애플리케이션 코드에서 직접 사용되지 않고 DataBinder 및 BeanFactory에서 사용된다. BeanWrapper가 작동하는 방식은 부분적으로 그 이름으로 표시된다.
속성 설정 및 검색과 같은 동작을 수행하기 위해 Bean을 래핑한다.
3.3.1. Setting and Getting Basic and Nested Properties
Setting and getting properties is done through the setPropertyValue and getPropertyValue overloaded method variants of BeanWrapper. See their Javadoc for details. The below table shows some examples of these conventions:
속성 예시
name | Indicates the property name that corresponds to the getName() or isName() and setName(..) methods. |
account.name | Indicates the nested property name of the property account that corresponds to (for example) the getAccount().setName() or getAccount().getName() methods. |
account[2] | Indicates the third element of the indexed property account. Indexed properties can be of type array, list, or other naturally ordered collection. |
account[COMPANYNAME] | Indicates the value of the map entry indexed by the COMPANYNAME key of the account Map property. |
(This next section is not vitally important to you if you do not plan to work with the BeanWrapper directly. If you use only the DataBinder and the BeanFactory and their default implementations, you should skip ahead to the section on PropertyEditors.)
Core Technologies
In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do
docs.spring.io
public class Company {
private String name;
private Employee managingDirector;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManagingDirector() {
return this.managingDirector;
}
public void setManagingDirector(Employee managingDirector) {
this.managingDirector = managingDirector;
}
}
public class Employee {
private String name;
private float salary;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
}
how to retrieve and manipulate some of the properties of instantiated Companys and Employees
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
3.3.2. Built-in PropertyEditor Implementations
Spring은 PropertyEditor의 개념을 사용하여 객체와 문자열 사이의 변환에 영향을 준다. 개체 자체와는 다른 방식으로 속성을 나타내는 것이 편리할 수 있다.
예를 들어 날짜는 사람이 읽을 수 있는 방식(문자열: '2007-14-09')으로 표시할 수 있지만 사람이 읽을 수 있는 형식을 다시 원래 날짜로 변환할 수 있다(다른 방법으로 모든 날짜를 변환할 수 있고, 사람이 읽을 수 있는 형식으로 날짜 개체로 다시 입력됨).
이 동작은 java.beans.PropertyEditor 유형의 사용자 정의 편집기를 등록하여 달성할 수 있다.
BeanWrapper 또는 특정 IoC 컨테이너(이전 장에서 언급한 대로)에 사용자 지정 편집기를 등록하면 속성을 원하는 유형으로 변환하는 방법이 있다. PropertyEditor에 대한 자세한 내용은 Oracle에서 제공하는 java.beans 패키지의 javadoc를 참조할 것.
https://docs.oracle.com/javase/8/docs/api/java/beans/package-summary.html
java.beans (Java Platform SE 8 )
Interface Summary Interface Description AppletInitializer This interface is designed to work in collusion with java.beans.Beans.instantiate. BeanInfo Use the BeanInfo interface to create a BeanInfo class and provide explicit information about the methods
docs.oracle.com
Spring에서 속성 편집이 사용되는 몇 가지 예:
- Bean에 대한 속성 설정은 PropertyEditor 구현을 사용하여 수행할 때
- XML 파일에서 선언한 일부 bean의 속성 값으로 String을 사용할 때
Spring(해당 속성의 setter에 Class 매개 변수가 있는 경우)은 ClassEditor를 사용하여 매개 변수를 Class 개체로 해결하려고 시도한다.
Spring의 MVC 프레임워크에서 HTTP 요청 매개변수를 구문 분석하는 것은 CommandController의 모든 하위 클래스에서 수동으로 바인딩할 수 있는 모든 종류의 PropertyEditor 구현을 사용하여 수행된다.
Spring은 편리한 많은 Built-in PropertyEditor 구현을 가지고 있다. 이들은 모두 org.springframework.beans.propertyeditors 패키지에 있다. 대부분(다음 표에 표시된 것처럼 전부는 아님)은 기본적으로 BeanWrapperImpl에 의해 등록된다.
속성 편집기를 어떤 방식으로든 구성할 수 있는 경우 고유한 변형을 등록하여 기본 변형을 재정의할 수 있다.
Spring이 제공하는 다양한 PropertyEditor 구현
Class | Explanation |
ByteArrayPropertyEditor | Editor for byte arrays. Converts strings to their corresponding byte representations. Registered by default by BeanWrapperImpl. 문자열을 해당 바이트 표현으로 변환 |
ClassEditor | Parses Strings that represent classes to actual classes and vice-versa. When a class is not found, an IllegalArgumentException is thrown. By default, registered by BeanWrapperImpl. 클래스를 나타내는 문자열을 실제 클래스 또는 반대로 구문 분석 없으면 IllegalArgumentException |
CustomBooleanEditor | Customizable property editor for Boolean properties. By default, registered by BeanWrapperImpl but can be overridden by registering a custom instance of it as a custom editor. 속성에 대한 커스텀 속성 편집기 재정의 가능 |
CustomCollectionEditor | Property editor for collections, converting any source Collection to a given target Collection type. 커스텀 Collection 편집기 |
CustomDateEditor | Customizable property editor for java.util.Date, supporting a custom DateFormat. NOT registered by default. Must be user-registered with the appropriate format as needed. DateFormat 커스텀(기본적으로 등록되어있지 않으므로 적절한 형식 등록 해야함) |
CustomNumberEditor | Customizable property editor for any Number subclass, such as Integer, Long, Float, or Double. By default, registered by BeanWrapperImpl but can be overridden by registering a custom instance of it as a custom editor. |
FileEditor | Resolves strings to java.io.File objects. By default, registered by BeanWrapperImpl. File 객체를 문자열로 인식 |
InputStreamEditor | One-way property editor that can take a string and produce (through an intermediate ResourceEditor and Resource) an InputStream so that InputStream properties may be directly set as strings. Note that the default usage does not close the InputStream for you. By default, registered by BeanWrapperImpl. |
LocaleEditor | Can resolve strings to Locale objects and vice-versa (the string format is [language]_[country]_[variant], same as the toString() method of Locale). Also accepts spaces as separators, as an alternative to underscores. By default, registered by BeanWrapperImpl. |
PatternEditor | Can resolve strings to java.util.regex.Pattern objects and vice-versa. |
PropertiesEditor | Can convert strings (formatted with the format defined in the javadoc of the java.util.Properties class) to Properties objects. By default, registered by BeanWrapperImpl. |
StringTrimmerEditor | Property editor that trims strings. Optionally allows transforming an empty string into a null value. NOT registered by default — must be user-registered. |
URLEditor | Can resolve a string representation of a URL to an actual URL object. By default, registered by BeanWrapperImpl. |
Spring uses the java.beans.PropertyEditorManager to set the search path for property editors that might be needed. The search path also includes sun.bean.editors, which includes PropertyEditor implementations for types such as Font, Color, and most of the primitive types. Note also that the standard JavaBeans infrastructure automatically discovers PropertyEditor classes (without you having to register them explicitly) if they are in the same package as the class they handle and have the same name as that class, with Editor appended. For example, one could have the following class and package structure, which would be sufficient for the SomethingEditor class to be recognized and used as the PropertyEditor for Something-typed properties.
com
chank
pop
Something
SomethingEditor // the PropertyEditor for the Something class
Note that you can also use the standard BeanInfo JavaBeans mechanism here as well (described to some extent here). The following example uses the BeanInfo mechanism to explicitly register one or more PropertyEditor instances with the properties of an associated class:
com
chank
pop
Something
SomethingBeanInfo // the BeanInfo for the Something class
The following Java source code for the referenced SomethingBeanInfo class associates a CustomNumberEditor with the age property of the Something class:
public class SomethingBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
@Override
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
}
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
Registering Additional Custom PropertyEditor Implementations
bean 속성을 문자열 값으로 설정할 때 Spring IoC 컨테이너는 궁극적으로 표준 JavaBeans PropertyEditor 구현을 사용하여 이러한 문자열을 속성의 복합 유형으로 변환한다. Spring은 많은 사용자 정의 PropertyEditor 구현을 먼저 등록한다(예를 들어, 문자열로 표현된 클래스 이름을 Class 객체로 변환하기 위해). 또한 Java의 표준 JavaBeans PropertyEditor 조회 메커니즘을 사용하면 클래스에 대한 PropertyEditor의 이름을 적절하게 지정하고 지원을 제공하는 클래스와 동일한 패키지에 배치하여 자동으로 찾을 수 있다.
다른 사용자 정의 PropertyEditor를 등록해야 하는 경우 여러 메커니즘을 사용할 수 있다. 일반적으로 편리하거나 권장되지 않는 가장 수동적인 접근 방식은 BeanFactory 참조가 있다고 가정하고 ConfigurableBeanFactory 인터페이스의 registerCustomEditor() 메서드를 사용하는 것이다.
또 다른(조금 더 편리한) 메커니즘은 CustomEditorConfigurer라는 특수 빈 팩토리 후처리 프로세서를 사용하는 것이다. BeanFactory 구현과 함께 bean factory post-processor를 사용할 수 있지만 CustomEditorConfigurer에는 중첩된 속성 설정이 있으므로 ApplicationContext와 함께 사용하는 것이 좋다. 자동으로 감지되어 적용된다.
모든 빈 팩토리와 애플리케이션 컨텍스트는 속성 변환을 처리하기 위해 BeanWrapper를 사용하여 여러 내장 속성 편집기를 자동으로 사용한다. BeanWrapper가 등록하는 표준 속성 편집기는 이전 섹션에 나열되어 있다. 또한 ApplicationContext는 특정 애플리케이션 컨텍스트 유형에 적합한 방식으로 리소스 조회를 처리하기 위해 추가 편집기를 재정의하거나 추가한다.
표준 JavaBeans PropertyEditor 인스턴스는 문자열로 표현된 속성 값을 속성의 실제 복합 유형으로 변환하는 데 사용된다. Bean Factory 포스트 프로세서인 CustomEditorConfigurer를 사용하여 ApplicationContext에 추가 PropertyEditor 인스턴스에 대한 지원을 편리하게 추가할 수 있다.
다음 예제는
ExoticType이라는 사용자 클래스와 ExoticType을 속성으로 설정해야 하는 DependsOnExoticType이라는 또 다른 클래스를 정의한다
package example;
public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
public class DependsOnExoticType {
private ExoticType type;
public void setType(ExoticType type) {
this.type = type;
}
}
When things are properly set up, we want to be able to assign the type property as a string, which a PropertyEditor converts into an actual ExoticType instance. The following bean definition shows how to set up this relationship:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
The PropertyEditor implementation could look similar to the following
package example;
// converts string representation to ExoticType object
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
Finally, the following example shows how to use CustomEditorConfigurer to register the new PropertyEditor with the ApplicationContext, which will then be able to use it as needed:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
Using PropertyEditorRegistrar
property editors를 Spring 컨테이너에 등록하는 또 다른 메커니즘은 PropertyEditorRegistrar를 만들고 사용하는 것이다.
이 인터페이스는 여러 가지 상황에서 동일한 속성 편집기 세트를 사용해야 할 때 특히 유용한데, 해당 레지스트라를 작성하고 각각의 경우에 재사용할 수 있다. PropertyEditorRegistrar 인스턴스는 Spring BeanWrapper(및 DataBinder)에 의해 구현되는 인터페이스인 PropertyEditorRegistry라는 인터페이스와 함께 작동한다.
PropertyEditorRegistrar 인스턴스는 setPropertyEditorRegistrars(..)라는 속성을 노출하는 CustomEditorConfigurer(여기에서 설명)와 함께 사용할 때 특히 편리하다. 이러한 방식으로 CustomEditorConfigurer에 추가된 PropertyEditorRegistrar 인스턴스는 DataBinder 및 Spring MVC 컨트롤러와 쉽게 공유할 수 있다.
게다 사용자 정의 편집기에 대한 동기화가 필요없다: PropertyEditorRegistrar는 각 빈 생성 시도에 대해 새로운 PropertyEditor 인스턴스를 생성할 것으로 예상된다.
자체 PropertyEditorRegistrar 구현을 만드는 방법 예제이다
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// you could register as many custom property editors as are required here...
}
}
See also the org.springframework.beans.support.ResourceEditorRegistrar for an example PropertyEditorRegistrar implementation. Notice how in its implementation of the registerCustomEditors(..) method, it creates new instances of each property editor.
The next example shows how to configure a CustomEditorConfigurer and inject an instance of our CustomPropertyEditorRegistrar into it:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="customPropertyEditorRegistrar"
class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
Spring MVC 에서 컨트롤러에서 사용하는 예제
uses a PropertyEditorRegistrar in the implementation of an @InitBinder method
@Controller
public class RegisterUserController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
@InitBinder
void initBinder(WebDataBinder binder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// other methods related to registering a User
}
이러한 PropertyEditor등록은 간결한 코드(메서드의 구현은 @InitBinder단 한 줄 길이임)로 이어질 수 있고 공통 PropertyEditor 등록 코드를 클래스에 캡슐화한 다음 필요한 만큼 많은 컨트롤러 간에 공유할 수 있다.
'Spring 정리ver2 > Framework' 카테고리의 다른 글
[Spring Framework core] 3. Validation, Data Binding, and Type Conversion (3) (0) | 2023.04.30 |
---|---|
[Spring Framework core] 3. Validation, Data Binding, and Type Conversion (1) (0) | 2023.04.28 |