지금까지 했던 부분입니다
오늘 해볼 부분은 HttpServletBean과 추가적인 부분입니다
HttpServletBean
HttpServlet을 확장하고 있고, bean를 초기화하기 위한 init() 메서드가 정의되어 있습니다
핵심적인 메서드는 총 2가지입니다.
1.init() 메서드
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
이 메서드는 빈을 초기화시키는 역할을 합니다
PropertyValues는 Iterable <PropertyValue>인 구현체입니다.
PropertyValue는 저런 상속 구조를 가지고 있는데요
AttributeAccessor
간단한 Key, Value을 통해서 저장하는 형태의 저장소입니다
public interface AttributeAccessor {
//name과 value 를 세팅합니다
void setAttribute(String name, @Nullable Object value);
//이름으로부터 value를 가져옵니다
@Nullable
Object getAttribute(String name);
//없으면 계산해주는 역할을 합니다
@SuppressWarnings("unchecked")
default <T> T computeAttribute(String name, Function<String, T> computeFunction) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(computeFunction, "Compute function must not be null");
Object value = getAttribute(name);
if (value == null) {
value = computeFunction.apply(name);
Assert.state(value != null,
() -> String.format("Compute function must not return null for attribute named '%s'", name));
setAttribute(name, value);
}
return (T) value;
}
// name 에 해당하는 value 제거
@Nullable
Object removeAttribute(String name);
// name 이 있는지 체크하는 과정 추가
boolean hasAttribute(String name);
//현재 있는 모든 키값 다 가져오기
String[] attributeNames();
}
AttributeAccessorSupport
AttributeAccessor를 implements 하기에, map 형태로 관리를 하고, 추가적인 부분으로는 다른 곳에서 copy를 해오는 것 정도 외에는 전혀 차이가 없습니다
public abstract class AttributeAccessorSupport implements AttributeAccessor, Serializable {
private final Map<String, Object> attributes = new LinkedHashMap<>();
protected void copyAttributesFrom(AttributeAccessor source) {
Assert.notNull(source, "Source must not be null");
String[] attributeNames = source.attributeNames();
for (String attributeName : attributeNames) {
setAttribute(attributeName, source.getAttribute(attributeName));
}
}
}
BeanMetadataElement
Bean에 대한 메타데이터를 관리하는 클래스입니다
딱 하나의 메서드만을 가지고 있기에, 설명은 밑의 코드를 통해 보는 것이 빠를 것 같은데요
Object 타입은 구현체마다, 로직마다 변경될 수 있기에 따로 구체화하지는 않았다고 합니다(feat. BeanMetadataAttribute)
public interface BeanMetadataElement {
//Bean 에 대한 메타데이터를 반환합니다
@Nullable
default Object getSource() {
return null;
}
}
BeanMetadataAttributeAccessor
public class BeanMetadataAttributeAccessor extends AttributeAccessorSupport implements BeanMetadataElement {
@Nullable
private Object source;
// source를 저장한다
public void setSource(@Nullable Object source) {
this.source = source;
}
//저장한 source 를 가져온다
@Override
@Nullable
public Object getSource() {
return this.source;
}
//name 을 바탕으로 key, value 형태로 BeanMetadataAttribute 를 저장한다
public void addMetadataAttribute(BeanMetadataAttribute attribute) {
super.setAttribute(attribute.getName(), attribute);
}
//name 을 바탕으로 저장한다
@Override
public void setAttribute(String name, @Nullable Object value) {
super.setAttribute(name, new BeanMetadataAttribute(name, value));
}
//name 을 바탕으로 BeanMetadataAttribute 를 가져온다
@Nullable
public BeanMetadataAttribute getMetadataAttribute(String name) {
return (BeanMetadataAttribute) super.getAttribute(name);
}
//name 을 바탕으로 가져온다
@Override
@Nullable
public Object getAttribute(String name) {
BeanMetadataAttribute attribute = (BeanMetadataAttribute) super.getAttribute(name);
return (attribute != null ? attribute.getValue() : null);
}
//name 을 바탕으로 제거한다
@Override
@Nullable
public Object removeAttribute(String name) {
BeanMetadataAttribute attribute = (BeanMetadataAttribute) super.removeAttribute(name);
return (attribute != null ? attribute.getValue() : null);
}
}
PropertyValue
public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable {
private final String name;
@Nullable
private final Object value;
private boolean optional = false;
private boolean converted = false;
//밑에 3개의 변수는 내부 로직상에서 변환 필요 여부를 관리합니다
@Nullable
private Object convertedValue;
@Nullable
volatile Boolean conversionNecessary;
@Nullable
transient volatile Object resolvedTokens;
//평범하게 name, value 형태로 생성합니다
public PropertyValue(String name, @Nullable Object value) {
Assert.notNull(name, "Name must not be null");
this.name = name;
this.value = value;
}
//이름을 가져옵니다
public String getName() {
return this.name;
}
//값을 가져옵니다
@Nullable
public Object getValue() {
return this.value;
}
//value holder 대신 원본 propertyValue 를 반환한다고 하네요
public PropertyValue getOriginalPropertyValue() {
PropertyValue original = this;
Object source = getSource();
while (source instanceof PropertyValue && source != original) {
original = (PropertyValue) source;
source = original.getSource();
}
return original;
}
//필요 여부를 관리합니다
public void setOptional(boolean optional) {
this.optional = optional;
}
public boolean isOptional() {
return this.optional;
}
//변환되었는지를 관리하는 내부 로직입니다
public synchronized boolean isConverted() {
return this.converted;
}
//변환후 저장하는 로직입니다
public synchronized void setConvertedValue(@Nullable Object value) {
this.converted = true;
this.convertedValue = value;
}
//변환된 부분을 가져옵니다
@Nullable
public synchronized Object getConvertedValue() {
return this.convertedValue;
}
}
이런 느낌으로 빈의 메타데이터들을 관리합니다
//여기
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
지금까지 이 첫 번째 줄에 PropertyValues에 대해서 알아보았는데요
ServletConfigPropertyValues 쪽을 보시면, 하나하나 PropertyValue를 추가하면서
private static class ServletConfigPropertyValues extends MutablePropertyValues {
/**
* Create new ServletConfigPropertyValues.
* @param config the ServletConfig we'll use to take PropertyValues from
* @param requiredProperties set of property names we need, where
* we can't accept default values
* @throws ServletException if any required properties are missing
*/
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
}
끝까지 requiredProperty 중에 남아있는 것이 있다면 예외를 던집니다
현재 getInitParameter 타입은 String이라는 점을 기억해도 좋아 보입니다
BeanWrapper
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//여기
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
TypeConverter
public interface TypeConverter {
@Nullable
<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException;
@Nullable
<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException;
@Nullable
<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field)
throws TypeMismatchException;
@Nullable
default <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
throw new UnsupportedOperationException("TypeDescriptor resolution not supported");
}
}
이 클래스는 Spring Framework의 인터페이스인 TypeConverter를 정의합니다. 이 인터페이스는 객체의 속성을 다른 타입으로 변환하는 메서드를 정의하며, 주로 PropertyEditorRegistry 인터페이스와 함께 구현됩니다.
TypeConverter의 주요 메서드는 convertIfNecessary()입니다. 이 메서드는 변환할 값을 전달하고 변환할 타입을 지정하여 객체의 속성을 원하는 타입으로 변환합니다. 만약 변환이 실패하면 TypeMismatchException 예외가 발생합니다.
이 인터페이스는 스레드 안전하지 않은 PropertyEditor 클래스를 기반으로 하기 때문에 스레드 안전하지 않다는 점에 유의해야 합니다. 이 인터페이스는 보통 SimpleTypeConverter나 BeanWrapperImpl과 함께 사용됩니다.
TypeConverter 인터페이스의 메서드 중 일부는 MethodParameter나 Field와 같은 추가 매개변수를 지원하며, 이를 통해 제네릭 타입 정보를 분석할 수 있습니다. 또한, TypeDescriptor를 사용하여 메서드 오버로딩을 구현할 수 있습니다.
PropertyEditorRegistry
public interface PropertyEditorRegistry {
void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);
@Nullable
PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
}
이 인터페이스는 JavaBeans의 PropertyEditor를 등록하는 메서드를 캡슐화하는 메서드를 제공합니다. 이 인터페이스는 PropertyEditorRegistrar가 작동하는 중심적인 인터페이스입니다. registerCustomEditor() 메서드는 지정된 유형의 모든 속성에 대해 사용자 정의 PropertyEditor를 등록하고, registerCustomEditor() 메서드는 지정된 유형 및 속성 또는 지정된 유형의 모든 속성에 대해 사용자 정의 PropertyEditor를 등록합니다. findCustomEditor() 메서드는 주어진 유형 및 속성에 대한 사용자 정의 PropertyEditor를 찾습니다. 이 인터페이스는 BeanWrapper에서 확장되며, BeanWrapperImpl 및 org.springframework.validation.DataBinder에서 구현됩니다. 이 인터페이스는 Spring Framework의 데이터 바인딩에 중요한 역할을 합니다.
PropertyEditor는 gui에 활용되는 클래스라고 하네요
A PropertyEditor class provides support for GUIs that want to allow users to edit a property value of a given type.
PropertyAccessor
public interface PropertyAccessor {
//상수들은 저장하는 형태를 관리하기 위해서 사용됩니다. ex)foo.bar
String NESTED_PROPERTY_SEPARATOR = ".";
char NESTED_PROPERTY_SEPARATOR_CHAR = '.';
String PROPERTY_KEY_PREFIX = "[";
char PROPERTY_KEY_PREFIX_CHAR = '[';
String PROPERTY_KEY_SUFFIX = "]";
char PROPERTY_KEY_SUFFIX_CHAR = ']';
boolean isReadableProperty(String propertyName);
boolean isWritableProperty(String propertyName);
@Nullable
Class<?> getPropertyType(String propertyName) throws BeansException;
@Nullable
TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException;
@Nullable
Object getPropertyValue(String propertyName) throws BeansException;
void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException;
void setPropertyValue(PropertyValue pv) throws BeansException;
void setPropertyValues(Map<?, ?> map) throws BeansException;
void setPropertyValues(PropertyValues pvs) throws BeansException;
void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown)
throws BeansException;
void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException;
}
이 인터페이스는 이름이 정해진 property 들을 접근하기 위한 일반적인 인터페이스입니다(이름이 있는 예시로 bean 이 있기에 이 인터페이스를 구현한다고 합니다)
ConfigurablePropertyAccessor
public interface ConfigurablePropertyAccessor extends PropertyAccessor, PropertyEditorRegistry, TypeConverter {
/**
* Specify a Spring 3.0 ConversionService to use for converting
* property values, as an alternative to JavaBeans PropertyEditors.
*/
void setConversionService(@Nullable ConversionService conversionService);
@Nullable
ConversionService getConversionService();
void setExtractOldValueForEditor(boolean extractOldValueForEditor);
boolean isExtractOldValueForEditor();
void setAutoGrowNestedPaths(boolean autoGrowNestedPaths);
boolean isAutoGrowNestedPaths();
}
이 클래스는 PropertyAccessor의 구성 방법을 캡슐화하고, PropertyEditorRegistry 인터페이스를 확장하여 PropertyEditor 관리를 정의하는 인터페이스입니다. BeanWrapper의 기반 인터페이스로 사용됩니다.
ConfigurablePropertyAccessor는 PropertyAccessor, PropertyEditorRegistry 및 TypeConverter 인터페이스를 확장합니다. 이 인터페이스를 구현하면 프로그래밍 방식으로 JavaBeans의 속성을 가져올 수 있으며, 이러한 속성에 대한 변환 서비스 및 PropertyEditor를 구성할 수 있습니다. 또한 AutoGrowNestedPaths와 ExtractOldValueForEditor 등 다양한 옵션을 설정할 수 있습니다.
BeanWrapper는 이 인터페이스를 구현하는 대표적인 클래스입니다. BeanWrapper는 JavaBeans 객체를 래핑 하여 프로그래밍 방식으로 속성 값을 가져올 수 있습니다.
여기서 autoGrow라는 것은 중첩된 객체를 null로 초기화하는 것 대신, 함께 생성해 주는 것인데요
gpt 선생님을 따르면
이런 장점이 있다고 하네요
BeanWrapper
public interface BeanWrapper extends ConfigurablePropertyAccessor {
void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
int getAutoGrowCollectionLimit();
Object getWrappedInstance();
Class<?> getWrappedClass();
PropertyDescriptor[] getPropertyDescriptors();
PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
}
기본적으로 Bean 은 BeanWrapper로 감싸진다는 부분이 여기서 생기는 거더라고요
docs를 번역해 보면
BeanWrapper는 스프링의 JavaBeans 인프라의 핵심 인터페이스입니다. 일반적으로 직접 사용하는 것이 아니라 {@link org.springframework.beans.factory.BeanFactory} 또는 {@link org.springframework.validation.DataBinder}를 통해 암시적으로 사용됩니다.
BeanWrapper는 일반 JavaBeans의 분석 및 조작 작업을 제공합니다. 개별적으로 또는 대량으로 속성 값을 가져오고 설정하는 기능, 속성 기술자를 가져오는 기능, 속성의 읽기/쓰기 가능성을 쿼리 하는 기능이 있습니다.
이 인터페이스는 하위 속성에 대한 설정을 지원하여 속성을 무제한 깊이로 설정할 수 있습니다.
BeanWrapper는 객체 래핑 기능을 제공하며, 래핑 된 빈 인스턴스를 가져오는 getWrappedInstance() 메서드와 해당 인스턴스의 유형을 가져오는 getWrappedClass() 메서드를 제공합니다. BeanWrapper는 또한 JavaBeans 인티로스펙션에 의해 결정된 래핑된 객체의 PropertyDescriptor를 가져오는 getPropertyDescriptors() 및 특정 속성의 속성 기술자를 가져오는 getPropertyDescriptor(String propertyName) 메서드를 제공합니다.
ResourceLoader
@Override
public final void init() throws ServletException {
//
final PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
final BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//여기
final ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (final BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
이제 ResourceLoader 만 남았네요
ResourceLoader는 잘 정리된 글이 많아서
https://velog.io/@yu-jin-song/Spring-ResourceLoader
글을 읽어보시는 것이 좋을 것 같아요
나머지 부분들
@Override
public final void init() throws ServletException {
//
final PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
final BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
final ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//resource 를 직접 읽어올 수 있도록 등록해주는 과정을 거칩니다
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//추상 메서드로 정의되어 있는 initBeanWrapper 메서드를 호출하여 BeanWrapper 를 초기화합니다.
//자식 클래스에서 재정의 할 수 있도록 protected 로 선언되어 있습니다.
initBeanWrapper(bw);
//PropertyValues 를 이용하여 BeanWrapper 에서 프로퍼티 값을 설정합니다.
bw.setPropertyValues(pvs, true);
} catch (final BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 이 부분도 자식 클래스에서 재정의 할 수 있도록 protected 로 선언되어 있습니다.
initServletBean();
}
후기
BeanWrapper를 찾아보면 많이 배울 수 있는 것 같다
Bean 은 Map 형태로 관리되고, 메타데이터는 List <PropertyValue>, Set <String> 형태로 맵과 비슷하게 관리된다!
생각보다 훨씬 더 길어졌네요
진짜 스프링이 계층화가 잘 되어있다는 생각이 들고 다음에는 FrameworkServlet 공부를 해보도록 하겠습니다
'Spring' 카테고리의 다른 글
스프링에서 발생한 에러 로그를 슬랙으로 모니터링하는 방법 (1) | 2023.07.08 |
---|---|
Application Context vs BeanFactory (0) | 2023.05.02 |
Test 의 db 롤백 어디까지 알아보셨나요? (1) | 2023.04.24 |
DispatcherServlet 알아보기 - Servlet 편 (0) | 2023.04.22 |
DispatcherServlet 알아보기 - ServletConfig 편 (0) | 2023.04.19 |