Dependencies and Configuration in Detail

2023. 12. 10. 20:37Spring Framework/Spring IoC

 

이전 섹션에서 언급했듯이, Spring IoC 컨테이너에 의해 관리되는 다른 빈(협력자)에 대한 참조나 인라인으로 정의된 값으로 빈 속성과 컨스트럭터 아규먼트를 정의할 수 있습니다. Spring의 XML 기반 구성 메타데이터는 이 목적을 위해 <property/> 및 <constructor-arg/> 엘리먼트 내에 하위 엘리먼트 타입을 지원합니다.

 

Straight Values (Primitives, Strings, and so on)

<property/> 엘리먼트의 value 속성은 속성이나 컨스트럭터 아규먼트를 사람이 읽을 수 있는 문자열 표현으로 지정합니다. 스프링의 conversion service 는 이러한 값을 String에서 속성이나 아규먼트의 실제 타입으로 변환하는 데 사용됩니다. 다음 예시는 다양한 값이 설정되는 것을 보여줍니다:

 

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<!-- results in a setDriverClassName(String) call -->
	<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
	<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
	<property name="username" value="root"/>
	<property name="password" value="misterkaoli"/>
</bean>

 

위 xml 코드는 자바 기반 구성 메타데이터에서는 다음과 같이 작성할 수 있습니다.

package com.intheeast.ioc.dependenciesandconfigurationindetail.straightvalues.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

@Configuration
public class AppConfig {	
	
    @Bean
    public DataSource myDataSource() throws ClassNotFoundException {
    	SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
    	
    	dataSource.setDriverClass(com.mysql.cj.jdbc.Driver.class);
        dataSource.setUrl("jdbc:mysql://localhost:3306/sbdt_db");
    	dataSource.setUsername("root");
        dataSource.setPassword("1234");
        return dataSource;
    }
}

 

다음 예제에서는 더 간결한 XML 구성을 위해 p-namespace를 사용합니다.

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	https://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close"
		p:driverClassName="com.mysql.jdbc.Driver"
		p:url="jdbc:mysql://localhost:3306/mydb"
		p:username="root"
		p:password="misterkaoli"/>

</beans>

 

앞서의 XML은 더 간결합니다. 그러나 IntelliJ IDEA나 Spring Tools for Eclipse와 같이 빈 정의를 생성할 때 자동 속성 완성을 지원하는 IDE를 사용하지 않는 한, 오타는 디자인 시간이 아닌 런타임에 발견됩니다. 이러한 IDE 지원은 매우 권장됩니다.

 

다음과 같이 java.util.Properties 인스턴스를 구성할 수도 있습니다.

<bean id="mappings"
	class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

	<!-- typed as a java.util.Properties -->
	<property name="properties">
		<value>
			jdbc.driver.className=com.mysql.jdbc.Driver
			jdbc.url=jdbc:mysql://localhost:3306/mydb
		</value>
	</property>
</bean>

 

Spring 컨테이너는 <value/> 엘리먼트 내부의 텍스트를 JavaBeans PropertyEditor 메커니즘을 사용하여 java.util.Properties 인스턴스로 변환합니다. 이는 편리한 단축키이며, Spring 팀이 value 속성 스타일보다 중첩된 <value/> 엘리먼트를 선호하는 몇 안 되는 경우 중 하나입니다.

 

위 xml 코드는 자바 기반 구성 메타데이터에서는 다음과 같이 작성할 수 있습니다.

package com.intheeast.ioc.dependenciesandconfigurationindetail.straightvalues.config;

import java.util.Properties;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

@Configuration
public class AppConfig {
	
	@Bean
    public static PropertySourcesPlaceholderConfigurer properties() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        Properties properties = new Properties();
        properties.setProperty("jdbc.driver.className", "com.mysql.cj.jdbc.Driver");
        properties.setProperty("jdbc.url", "jdbc:mysql://localhost:3306/sbdt_db");
        configurer.setProperties(properties);
        return configurer;
    }
	
	@Value("${jdbc.driver.className}")
    private String driverClassName;

    @Value("${jdbc.url}")
    private String url;

    @Bean
    public DataSource myDataSource() throws ClassNotFoundException {
    	SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
    	
    	dataSource.setDriverClass((Class<? extends java.sql.Driver>) Class.forName(driverClassName));
    	dataSource.setUrl(url);
        dataSource.setUsername("root");
        dataSource.setPassword("1234");
        return dataSource;
    }
}

 

The idref element

idref 엘리먼트는 컨테이너의 다른 빈의 id(문자열 값 - 참조 아님)를 <constructor-arg/> 또는 <property/> 요소에 전달하는 오류 방지 방식일 뿐입니다. 다음 예는 사용 방법을 보여줍니다.

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
	<property name="targetName">
		<idref bean="theTargetBean"/>
	</property>
</bean>

앞의 빈 정의 조각은 다음 조각과 (런타임에) 정확히 동일합니다.

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
	<property name="targetName" value="theTargetBean"/>
</bean>

 

첫 번째 형태는 두 번째 형태보다 더 바람직한데, idref 태그를 사용하면 컨테이너가 배포 시점에 참조하게 하고 명명된 빈이 실제로 존재하는지 검증할 수 있기 때문입니다. 두 번째 변형에서는 클라이언트 빈의 targetName 속성에 전달된 값에 대한 검증이 수행되지 않습니다. 오타는 클라이언트 빈이 실제로 인스턴스화될 때만 발견됩니다(치명적 결과가 발생할 가능성이 높음). 클라이언트 빈이 prototype 빈인 경우 이 오타와 결과 예외는 컨테이너가 배포된 후 오랜 시간이 지난 후에야 발견될 수 있습니다.

 

<idref/> 엘리먼트가 값을 가져오는 일반적인 장소(적어도 Spring 2.0 이전 버전에서는)는 ProxyFactoryBean 빈 정의의 AOP 인터셉터 구성입니다. 인터셉터 이름을 지정할 때 <idref/> 엘리먼트를 사용하면 인터셉터 ID를 잘못 철자하는 것을 방지할 수 있습니다.

 

Java 기반 구성에서 idref 사용
idref 엘리먼트는 다른 빈의 ID를 안전하게 전달하는 방법으로 사용됩니다. 이를 Java 기반 구성으로 변환하면 다음과 같이 작성할 수 있습니다:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public TargetBean theTargetBean() {
        return new TargetBean();
    }

    @Bean
    public ClientBean theClientBean() {
        ClientBean client = new ClientBean();
        client.setTargetName("theTargetBean");  // 직접 ID를 설정
        return client;
    }
}


이 코드에서 setTargetName("theTargetBean")을 사용해 theTargetBean의 ID를 ClientBean에 전달합니다. 이 방법은 XML의 <idref> 엘리먼트와 동일한 역할을 하며, 타이핑 오류를 방지할 수 있습니다. Spring은 런타임에 지정된 ID가 실제로 존재하는지 확인합니다.

 

References to Other Beans (Collaborators)

ref 엘리먼트는 <constructor-arg/> 또는 <property/> 엘리먼트 내부의 마지막 엘리먼트입니다. 여기서 빈의 지정된 속성 값을 컨테이너에서 관리하는 다른 빈(협력자)에 대한 참조로 설정합니다. 참조된 빈은 속성을 설정할 빈의 종속성이며 속성이 설정되기 전에 필요에 따라 초기화됩니다. (협력자가 싱글톤 빈인 경우 컨테이너에서 이미 초기화되었을 수 있습니다.) 모든 참조는 궁극적으로 다른 객체에 대한 참조입니다. 스코프 지정 및 검증은 빈 또는 부모 속성을 통해 다른 객체의 ID 또는 이름을 지정하는지 여부에 따라 달라집니다.

 

<ref/> 태그의 bean 속성을 통해 타겟 bean을 지정하는 것이 가장 일반적인 형태이며, 동일한 XML 파일에 있는지 여부에 관계없이 동일한 컨테이너 또는 부모 컨테이너에 있는 모든 bean에 대한 참조를 생성할 수 있습니다. bean 속성의 값은 타겟 bean의 id 속성과 같거나 대상 bean의 name 속성에 있는 값 중 하나와 같을 수 있습니다. 다음 예는 ref 요소를 사용하는 방법을 보여줍니다.

<ref bean="someBean"/>

 

parent 속성을 통해 타겟 빈을 지정하면 현재 컨테이너의 부모 컨테이너에 있는 빈에 대한 참조가 생성됩니다. parent 속성의 값은 대상 빈의 id 속성이나 타겟 빈의 name 속성에 있는 값 중 하나와 같을 수 있습니다. 타겟 빈은 현재 빈의 부모 컨테이너에 있어야 합니다. 이 빈 참조 변형은 주로 컨테이너 계층이 있고 부모 빈과 이름이 같은 프록시가 있는 부모 컨테이너에 기존 빈을 래핑하려는 경우에 사용해야 합니다. 다음 쌍의 목록은 parent 속성을 사용하는 방법을 보여줍니다.

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
	<!-- insert dependencies as required here -->
</bean>

 

<!-- in the child (descendant) context -->
<bean id="accountService" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target">
		<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
	</property>
	<!-- insert other configuration and dependencies as required here -->
</bean>

 

ref 엘리먼트의 local 속성은 더 이상 4.0 beans XSD에서 지원되지 않습니다. 더 이상 일반 bean 참조에 대한 값을 제공하지 않기 때문입니다. 4.0 스키마로 업그레이드할 때 기존 ref 로컬 참조를 ref bean으로 변경하세요.

 

Java 기반 구성에서 다른 빈에 대한 참조 설정

 

Spring의 Java 기반 구성에서 다른 빈에 대한 참조는 @Bean 메서드를 통해 이루어집니다. ref 요소의 역할을 Java 코드로 변환하면 다음과 같습니다:

1. 다른 빈에 대한 참조 설정

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public SomeBean someBean() {
        return new SomeBean();
    }

    @Bean
    public AnotherBean anotherBean() {
        AnotherBean anotherBean = new AnotherBean();
        anotherBean.setDependency(someBean()); // ref bean="someBean"
        return anotherBean;
    }
}


이 예제에서 anotherBean의 setDependency 메서드는 someBean 빈에 대한 참조를 설정합니다.

2. 부모 컨테이너의 빈 참조 설정 (예제)
부모 컨테이너의 빈을 참조하는 경우, 일반적으로 Spring Boot 또는 Spring의 여러 ApplicationContext를 사용하는 경우가 해당됩니다. 자바 기반 구성 메타데이터에서 동일한 컨텍스트 구조를 만들려면 부모와 자식 컨텍스트를 별도로 설정하고 구성해야 합니다. 다만, 자바 기반 구성에서 직접적으로 부모 컨테이너의 빈을 참조하는 방법은 특별한 설정이 필요하며, 대부분의 경우 자식 컨텍스트에서 부모의 빈을 자동으로 참조할 수 있습니다. 

여기서는 일반적인 경우를 예시로 보여드리겠습니다:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        ApplicationContext parentContext = new AnnotationConfigApplicationContext(ParentConfig.class);
        AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext(ChildConfig.class);
        childContext.setParent(parentContext);

        AccountService accountService = childContext.getBean("accountService", AccountService.class);
        // childContext에서 accountService 빈 사용
    }
}

@Configuration
public class ParentConfig {

    @Bean
    public AccountService accountService() {
        return new SimpleAccountService();
    }
}

@Configuration
public class ChildConfig {

    @Bean
    public AccountService accountService() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTarget(accountService()); // 부모 컨텍스트의 빈 참조
        return (AccountService) proxyFactoryBean.getObject();
    }
}


이 예제에서 childContext는 parentContext의 accountService 빈을 참조하여 설정됩니다. Java 기반 구성에서 부모 컨텍스트의 빈을 참조할 때, childContext.setParent(parentContext)를 사용하여 부모-자식 컨텍스트 관계를 설정합니다.

 

Inner Beans

다음 예제와 같이 <property/> 또는 <constructor-arg/> 엘리먼트 내의 <bean/> 엘리먼트는 내부 빈을 정의합니다.

<bean id="outer" class="...">
	<!-- instead of using a reference to a target bean, simply define the target bean inline -->
	<property name="target">
		<bean class="com.example.Person"> <!-- this is the inner bean -->
			<property name="name" value="Fiona Apple"/>
			<property name="age" value="25"/>
		</bean>
	</property>
</bean>

내부 빈 정의에는 정의된 ID나 이름이 필요하지 않습니다. 내부 빈이 지정된 경우 컨테이너는 이러한 값을 식별자로 사용하지 않습니다. 컨테이너는 또한 생성 시 범위 플래그를 무시합니다. 내부 빈은 항상 익명이며 항상 외부 빈과 함께 생성되기 때문입니다. 내부 빈에 독립적으로 액세스하거나 둘러싼 빈이 아닌 협력하는 빈에 주입할 수 없습니다.

 

코너 케이스로, 사용자 정의 범위에서 destruction 콜백을 수신하는 것이 가능합니다. 예를 들어, 싱글톤 빈에 포함된 요청 범위의 내부 빈의 경우입니다. 내부 빈 인스턴스의 생성은 포함하는 빈에 연결되어 있지만 파괴 콜백을 통해 요청 범위의 라이프사이클에 참여할 수 있습니다. 이는 일반적인 시나리오가 아닙니다. 내부 빈은 일반적으로 포함하는 빈의 범위를 공유합니다.

위 설명에서 언급된 코너 케이스는 사용자 정의 범위(custom scope)를 사용하는 경우에 발생할 수 있는 비일반적인 상황을 말합니다. 구체적으로, 내부 빈(inner bean)이 일반적인 경우와 달리 요청 범위(request scope)처럼 특정 사용자 정의 범위에 속하고, 해당 범위의 라이프사이클(destruction callback)에 참여할 수 있는 상황을 지칭합니다.
일반적으로 내부 빈은 외부 빈과 동일한 범위를 공유하며, 외부 빈의 라이프사이클에 종속됩니다. 하지만 다음과 같은 경우 코너 케이스가 발생할 수 있습니다:
1. 외부 빈이 싱글톤(singleton) 범위인 경우: 외부 빈은 애플리케이션 시작부터 종료까지 살아있는 반면, 내부 빈이 요청 범위(request scope)처럼 사용자 정의 범위를 갖는 경우, 요청 범위에 따라 내부 빈의 생성 및 소멸 콜백이 작동할 수 있습니다.
2. 내부 빈이 파괴(destruction) 콜백을 지원하는 경우: 내부 빈의 생성은 외부 빈과 연결되어 있지만, 요청 범위의 특성으로 인해 요청이 종료될 때 해당 내부 빈에 대한 파괴 콜백이 호출됩니다. 이로 인해 내부 빈이 외부 빈의 라이프사이클과 독립적으로 특정 작업을 수행할 수 있습니다.
이 코너 케이스는 내부 빈의 사용 목적과는 맞지 않으며, 일반적인 설계에서는 거의 발생하지 않는 비정상적인 상황입니다. 내부 빈은 본래 외부 빈의 일부로 설계되었으며, 외부 빈과의 강한 종속성을 갖는 것이 일반적입니다.
즉, 코너 케이스란 내부 빈이 특정 사용자 정의 범위로 설정되어 해당 범위의 라이프사이클(예: 파괴 콜백)에 참여하는 상황을 말하며, 이는 일반적인 내부 빈 사용 방식에서 벗어난 예외적인 시나리오입니다.

 

Java 기반 구성에서 Inner Bean을 정의하는 방법을 살펴보겠습니다. Inner Bean은 다른 빈의 속성이나 컨스트럭터 아규먼트로 사용되며, 독립적으로 접근할 수 없는 익명 빈입니다.

Java 기반 구성으로 변환

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public OuterBean outer() {
        OuterBean outerBean = new OuterBean();
        outerBean.setTarget(new Person("Fiona Apple", 25)); // Inner Bean 정의
        return outerBean;
    }
}


설명

  • Person 빈은 OuterBean의 속성으로 정의되며, 직접 접근이 불가능한 Inner Bean입니다.
  • Inner Bean은 익명으로 생성되며, 외부에서 참조할 수 없고, 부모 빈의 생명주기를 따릅니다.

 

Collections

<list/>, <set/>, <map/>, <props/> 엘리먼트는 Java Collection 타입 List, Set, Map, Properties의 속성과 아규먼트를 각각 설정합니다. 다음 예제는 이를 사용하는 방법을 보여줍니다.

 
<bean id="moreComplexObject" class="example.ComplexObject">
	<!-- results in a setAdminEmails(java.util.Properties) call -->
	<property name="adminEmails">
		<props>
			<prop key="administrator">administrator@example.org</prop>
			<prop key="support">support@example.org</prop>
			<prop key="development">development@example.org</prop>
		</props>
	</property>
	<!-- results in a setSomeList(java.util.List) call -->
	<property name="someList">
		<list>
			<value>a list element followed by a reference</value>
			<ref bean="myDataSource" />
		</list>
	</property>
	<!-- results in a setSomeMap(java.util.Map) call -->
	<property name="someMap">
		<map>
			<entry key="an entry" value="just some string"/>
			<entry key="a ref" value-ref="myDataSource"/>
		</map>
	</property>
	<!-- results in a setSomeSet(java.util.Set) call -->
	<property name="someSet">
		<set>
			<value>just some string</value>
			<ref bean="myDataSource" />
		</set>
	</property>
</bean>
 

map 키나 값의 값, 혹은 set 값은 다음 엘리먼트 중 하나가 될 수도 있습니다.

bean | ref | idref | list | set | map | props | value | null
 

 

Collection Merging

Spring 컨테이너는 컬렉션 병합도 지원합니다. 애플리케이션 개발자는 부모 <list/>, <map/>, <set/> 또는 <props/> 엘리먼트를 정의하고 자식 <list/>, <map/>, <set/> 또는 <props/> 엘리먼트가 부모 컬렉션에서 값을 상속하고 오버라이드하도록 할 수 있습니다. 즉, 자식 컬렉션의 값은 부모 및 자식 컬렉션의 엘리먼트를 병합한 결과이며, 자식 컬렉션 엘리먼트는 부모 컬렉션에 지정된 값을 오버라이드합니다.

병합에 대한 이 섹션에서는 부모-자식 빈 메커니즘을 설명합니다. 부모 및 자식 빈 정의에 익숙하지 않은 독자는 계속하기 전에 relevant section 을 읽어보는 것이 좋습니다.


다음 예는 컬렉션 병합을 보여줍니다.

<beans>
	<bean id="parent" abstract="true" class="example.ComplexObject">
		<property name="adminEmails">
			<props>
				<prop key="administrator">administrator@example.com</prop>
				<prop key="support">support@example.com</prop>
			</props>
		</property>
	</bean>
	<bean id="child" parent="parent">
		<property name="adminEmails">
			<!-- the merge is specified on the child collection definition -->
			<props merge="true">
				<prop key="sales">sales@example.com</prop>
				<prop key="support">support@example.co.uk</prop>
			</props>
		</property>
	</bean>
<beans>

 

자식 빈 정의의 adminEmails 속성의 <props/> 요소에서 merge=true 속성을 사용하는 것을 주목하세요. 자식 빈이 컨테이너에 의해 해결되고 인스턴스화되면 결과 인스턴스에는 자식의 adminEmails 컬렉션을 부모의 adminEmails 컬렉션과 병합한 결과가 포함된 adminEmails Properties 컬렉션이 있습니다. 다음 목록은 결과를 보여줍니다.

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

자식 Properties 컬렉션의 값 집합은 부모 <props/>에서 모든 속성 엘리먼트를 상속하고, 지원 값에 대한 자식의 값은 부모 컬렉션의 값을 오버라이드합니다.

이 병합 동작은 <list/>, <map/> 및 <set/> 컬렉션 타입에도 유사하게 적용됩니다. <list/> 엘리먼트의 특정한 경우 List 컬렉션 타입과 관련된 시맨틱(즉, 값의 정렬된 컬렉션 개념)이 유지됩니다. 부모의 값은 모든 자식 리스트의 값보다 우선합니다. Map, Set 및 Properties 컬렉션 타입의 경우 정렬이 없습니다. 따라서 컨테이너가 내부적으로 사용하는 연관된 Map, Set 및 Properties 구현 타입의 기반이 되는 컬렉션 타입에는 정렬 의미가 적용되지 않습니다.

 

Limitations of Collection Merging

여러분은 다른 컬렉션 타입(예: Map 및 List)을 병합할 수 없습니다. 병합을 시도하면 적절한 예외가 발생합니다. 병합 속성은 하위 상속된 자식 정의에 지정해야 합니다. 부모 컬렉션 정의에 병합 속성을 지정하는 것은 중복이며 원하는 병합을 수행하지 못합니다.

 

Strongly-typed collection

Java의 제너릭 타입 지원 덕분에 강력한 타입의 컬렉션을 사용할 수 있습니다. 즉, (예를 들어) String 엘리먼트들만 포함할 수 있도록 Collection 타입을 선언할 수 있습니다. Spring을 사용하여 강력한 타입의 Collection을 빈에 종속성 주입하는 경우 Spring의 타입 변환 지원을 활용하여 강력한 타입의 Collection 인스턴스의 엘리먼트가 Collection에 추가되기 전에 적절한 타입으로 변환됩니다. 다음 Java 클래스와 빈 정의는 이를 수행하는 방법을 보여줍니다.

public class SomeClass {

	private Map<String, Float> accounts;

	public void setAccounts(Map<String, Float> accounts) {
		this.accounts = accounts;
	}
}
<beans>
	<bean id="something" class="x.y.SomeClass">
		<property name="accounts">
			<map>
				<entry key="one" value="9.99"/>
				<entry key="two" value="2.75"/>
				<entry key="six" value="3.99"/>
			</map>
		</property>
	</bean>
</beans>

 

something 빈의 accounts 속성이 주입을 위해 준비되면, 강력한 타입의 Map<String, Float>의 엘리먼트 타입에 대한 제네릭 정보를 리플렉션을 통해 사용할 수 있습니다. 따라서 Spring의 타입 변환 인프라는 다양한 값 엘리먼트를 Float 타입으로 인식하고 문자열 값(9.99, 2.75 및 3.99)은 실제 Float 타입으로 변환됩니다.

 

Java 기반 구성에서 컬렉션 다루기

Spring XML에서 컬렉션(list, set, map, props)을 사용하는 방법을 Java 기반으로 변환한 예제입니다.

1. 컬렉션 사용 예제

public class ComplexObject {
    private Properties adminEmails;
    private List<Object> someList;
    private Map<String, Object> someMap;
    private Set<Object> someSet;

    // Getters and Setters for each property
    public void setAdminEmails(Properties adminEmails) {
        this.adminEmails = adminEmails;
    }

    public void setSomeList(List<Object> someList) {
        this.someList = someList;
    }

    public void setSomeMap(Map<String, Object> someMap) {
        this.someMap = someMap;
    }

    public void setSomeSet(Set<Object> someSet) {
        this.someSet = someSet;
    }
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.*;

@Configuration
public class AppConfig {

    @Bean
    public ComplexObject moreComplexObject() {
        ComplexObject complexObject = new ComplexObject();

        Properties adminEmails = new Properties();
        adminEmails.put("administrator", "administrator@example.org");
        adminEmails.put("support", "support@example.org");
        adminEmails.put("development", "development@example.org");
        complexObject.setAdminEmails(adminEmails);

        List<Object> someList = new ArrayList<>();
        someList.add("a list element followed by a reference");
        someList.add(myDataSource());
        complexObject.setSomeList(someList);

        Map<String, Object> someMap = new HashMap<>();
        someMap.put("an entry", "just some string");
        someMap.put("a ref", myDataSource());
        complexObject.setSomeMap(someMap);

        Set<Object> someSet = new HashSet<>();
        someSet.add("just some string");
        someSet.add(myDataSource());
        complexObject.setSomeSet(someSet);

        return complexObject;
    }

    @Bean
    public MyDataSource myDataSource() {
        return new MyDataSource();
    }
}


2. 컬렉션 병합 예제
컬렉션 병합을 Java 기반으로 구현하는 예제를 보여줍니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;

import java.util.Properties;

@Configuration
public class AppConfig {

    @Bean
    public ComplexObject parent() {
        ComplexObject parent = new ComplexObject();

        Properties adminEmails = new Properties();
        adminEmails.put("administrator", "administrator@example.com");
        adminEmails.put("support", "support@example.com");
        parent.setAdminEmails(adminEmails);

        return parent;
    }

    @Bean
    public ComplexObject child() {
        GenericBeanDefinition childDefinition = new GenericBeanDefinition();
        childDefinition.setParentName("parent");
        childDefinition.setBeanClass(ComplexObject.class);

        ComplexObject child = new ComplexObject();

        Properties adminEmails = new Properties();
        adminEmails.put("sales", "sales@example.com");
        adminEmails.put("support", "support@example.co.uk"); // overriding
        child.setAdminEmails(adminEmails);

        return child;
    }
}


3. 강력한 타입의 컬렉션 예제

Java의 제네릭 타입을 사용하는 강력한 타입의 컬렉션을 구성하는 방법입니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class AppConfig {

    @Bean
    public SomeClass something() {
        SomeClass someClass = new SomeClass();

        Map<String, Float> accounts = new HashMap<>();
        accounts.put("one", 9.99f);
        accounts.put("two", 2.75f);
        accounts.put("six", 3.99f);

        someClass.setAccounts(accounts);

        return someClass;
    }
}


이 예제들은 컬렉션을 사용하여 다양한 방법으로 Spring에서 빈을 구성하고 의존성을 주입하는 방법을 Java 기반으로 설명합니다.

 

Null and Empty String Values

Spring은 속성 등에 대한 빈 아규먼트를 빈 문자열로 처리합니다. 다음 XML 기반 구성 메타데이터 스니펫은 email 속성을 빈 문자열 값("")으로 설정합니다.

 
<bean class="ExampleBean">
	<property name="email" value=""/>
</bean>

앞의 예제는 다음 Java 코드와 동일합니다.

exampleBean.setEmail("");

<null/> 요소는 null 값을 처리합니다. 다음 목록은 예를 보여줍니다.

<bean class="ExampleBean">
	<property name="email">
		<null/>
	</property>
</bean>

 

이전 구성은 다음 Java 코드와 동일합니다.

exampleBean.setEmail(null);

 

XML Shortcut with the p-namespace

 

p- namespace 를 사용하면 중첩된 <property/> 엘리먼트 대신 빈 엘리먼트의 속성을 사용하여 속성 값 협업 빈 또는 둘 다를 설명할 수 있습니다.

Spring은 XML 스키마 정의를 기반으로 하는 네임스페이스를 사용하여 확장 가능한 구성 형식을 지원합니다. 이 장에서 설명하는 빈 구성 형식은 XML 스키마 문서에 정의되어 있습니다. 그러나 p-namespace는 XSD 파일에 정의되어 있지 않으며 Spring의 코어에만 존재합니다.

다음 예제는 동일한 결과로 해결되는 두 개의 XML 스니펫(첫 번째는 표준 XML 형식을 사용하고 두 번째는 p-네임스페이스를 사용함)을 보여줍니다.

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean name="classic" class="com.example.ExampleBean">
		<property name="email" value="someone@somewhere.com"/>
	</bean>

	<bean name="p-namespace" class="com.example.ExampleBean"
		p:email="someone@somewhere.com"/>
</beans>

 

이 예에서는 빈 정의에서 email이라는 p-namespace의 속성을 보여줍니다. 이것은 Spring에 속성 선언을 포함하라고 알려줍니다. 이전에 언급했듯이, p-namespace에는 스키마 정의가 없으므로 속성 이름을 속성 이름으로 설정할 수 있습니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public ExampleBean classic() {
        ExampleBean exampleBean = new ExampleBean();
        exampleBean.setEmail("someone@somewhere.com");
        return exampleBean;
    }

    @Bean
    public ExampleBean pNamespace() {
        ExampleBean exampleBean = new ExampleBean();
        exampleBean.setEmail("someone@somewhere.com");
        return exampleBean;
    }
}

 

다음 예제에는 다른 빈에 대한 참조를 갖는 두 개의 빈 정의가 더 포함되어 있습니다.

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean name="john-classic" class="com.example.Person">
		<property name="name" value="John Doe"/>
		<property name="spouse" ref="jane"/>
	</bean>

	<bean name="john-modern"
		class="com.example.Person"
		p:name="John Doe"
		p:spouse-ref="jane"/>

	<bean name="jane" class="com.example.Person">
		<property name="name" value="Jane Doe"/>
	</bean>
</beans>

 

이 예제에는 p: namespace를 사용하는 속성 값뿐만 아니라 속성 참조를 선언하기 위해 특수 형식도 사용됩니다. 첫 번째 빈 정의는 <property name="spouse" ref="jane"/>을 사용하여 빈 john에서 빈 jane으로의 참조를 생성하는 반면, 두 번째 빈 정의는 p:spouse-ref="jane"을 속성으로 사용하여 정확히 동일한 작업을 수행합니다. 이 경우 spouse는 속성 이름이고 -ref 부분은 이것이 직접적인 값이 아니라 다른 빈에 대한 참조임을 나타냅니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public Person jane() {
        Person jane = new Person();
        jane.setName("Jane Doe");
        return jane;
    }

    @Bean
    public Person johnClassic() {
        Person john = new Person();
        john.setName("John Doe");
        john.setSpouse(jane()); // jane 빈에 대한 참조 설정
        return john;
    }

    @Bean
    public Person johnModern() {
        Person john = new Person();
        john.setName("John Doe");
        john.setSpouse(jane()); // jane 빈에 대한 참조 설정
        return john;
    }
}
p-namespace는 표준 XML 형식만큼 유연하지 않습니다. 예를 들어, 속성 참조를 선언하는 형식은 Ref로 끝나는 속성과 충돌하지만 표준 XML 형식은 그렇지 않습니다. 세 가지 접근 방식을 모두 동시에 사용하는 XML 문서를 생성하지 않으려면 접근 방식을 신중하게 선택하고 팀원에게 이를 전달하는 것이 좋습니다.

 

XML Shortcut with the c-namespace

 

p-namespace를 사용한 XML Shortcut과 유사하게, Spring 3.1에 도입된 c: namespace는 중첩된 constructor-arg 엘리먼트 대신 컨스트럭터 아규먼트를 구성하기 위한 인라인 속성을 허용합니다.


다음 예제는 c: namespace 사용하여 Constructor-based Dependency Injection과 동일한 작업을 수행합니다.
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:c="http://www.springframework.org/schema/c"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="beanTwo" class="x.y.ThingTwo"/>
	<bean id="beanThree" class="x.y.ThingThree"/>

	<!-- traditional declaration with optional argument names -->
	<bean id="beanOne" class="x.y.ThingOne">
		<constructor-arg name="thingTwo" ref="beanTwo"/>
		<constructor-arg name="thingThree" ref="beanThree"/>
		<constructor-arg name="email" value="something@somewhere.com"/>
	</bean>

	<!-- c-namespace declaration with argument names -->
	<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
		c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>
 

c: namespace는 p:와 동일한 규칙을 사용합니다(빈 참조에 대한 후행 -ref). 컨스트럭터 아규먼트를 이름으로 설정하는 데 사용합니다. 마찬가지로 XSD 스키마에 정의되어 있지 않더라도 XML 파일에 선언해야 합니다(Spring 코어 내부에 존재함).

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public ThingTwo beanTwo() {
        return new ThingTwo();
    }

    @Bean
    public ThingThree beanThree() {
        return new ThingThree();
    }

    // 생성자 기반 의존성 주입을 사용하는 beanOne 빈 정의
    @Bean
    public ThingOne beanOne() {
        return new ThingOne(beanTwo(), beanThree(), "something@somewhere.com");
    }
}

 

컨스트럭터 아규먼트 이름을 사용할 수 없는 드문 경우(일반적으로 바이트코드가 디버깅 정보 없이 컴파일된 경우) 다음과 같이 아규먼트 인덱스로의 대체 방법을 사용할 수 있습니다.

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
	c:_2="something@somewhere.com"/>

 

XML 문법 때문에 인덱스 표기법은 선행 _가 필요합니다. XML 속성 이름은 숫자로 시작할 수 없기 때문입니다(일부 IDE에서는 허용하지만). 해당 인덱스 표기법은 <constructor-arg> 요소에도 사용할 수 있지만, 선언의 일반 순서로 충분하기 때문에 일반적으로 사용되지 않습니다.

 

실제로, 컨스트럭터 해결 메커니즘은 아규먼트를 일치시키는 데 매우 효율적이므로, 정말로 필요하지 않다면 구성 전체에서 이름 표기법을 사용하는 것이 좋습니다.

 

Compound Property Names

경로의 모든 구성 엘리먼트가 최종 속성 이름을 제외하고 null이 아닌 한, 빈 속성을 설정할 때 복합 또는 중첩된 속성 이름을 사용할 수 있습니다. 다음 빈 정의를 고려하세요.

<bean id="something" class="things.ThingOne">
	<property name="fred.bob.sammy" value="123" />
</bean>

 

something 빈에는 fred 속성이 있고, fred 속성에는 bob 속성이 있고, bob 속성에는 sammy 속성이 있으며, 마지막 sammy 속성은 123 값으로 설정됩니다. 이것이 작동하려면, something의 fred 속성과 fred의 bob 속성은 빈이 생성된 후에 null이 아니어야 합니다. 그렇지 않으면 NullPointerException이 throw됩니다. 

 

위의 XML 기반 빈 정의를 자바 기반 구성 메타데이터로 변환해 보겠습니다. 아래 예제는 ThingOne 클래스가 fred라는 프로퍼티를 가지고 있고, 이 fred가 bob이라는 프로퍼티를, 그리고 bob이 sammy라는 프로퍼티를 가진다고 가정합니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public ThingOne something() {
        ThingOne thingOne = new ThingOne();

        Fred fred = new Fred();
        Bob bob = new Bob();
        bob.setSammy(123); // bob의 sammy 프로퍼티를 123으로 설정
        fred.setBob(bob); // fred의 bob 프로퍼티를 설정

        thingOne.setFred(fred); // thingOne의 fred 프로퍼티를 설정

        return thingOne;
    }
}


위 코드에서 ThingOne, Fred, Bob 클래스는 각각 다음과 같이 정의되어야 합니다:

public class ThingOne {
    private Fred fred;

    public Fred getFred() {
        return fred;
    }

    public void setFred(Fred fred) {
        this.fred = fred;
    }
}

public class Fred {
    private Bob bob;

    public Bob getBob() {
        return bob;
    }

    public void setBob(Bob bob) {
        this.bob = bob;
    }
}

public class Bob {
    private int sammy;

    public int getSammy() {
        return sammy;
    }

    public void setSammy(int sammy) {
        this.sammy = sammy;
    }
}


이 구성 메타데이터는 XML에서 정의한 것과 동일한 효과를 가집니다. ThingOne 빈이 생성되면 fred와 bob 프로퍼티가 올바르게 초기화되고, sammy는 123으로 설정됩니다. 이 설정은 스프링 컨테이너에서 빈을 초기화할 때 NPE가 발생하지 않도록 보장합니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public DefaultBlogService blogService() {
        DefaultBlogService blogService = new DefaultBlogService();
        blogService.setBlogDao(blogDao());
        return blogService;
    }

    @Bean
    public BlogDao blogDao() {
        return new BlogDao();
    }
}

'Spring Framework > Spring IoC' 카테고리의 다른 글

Bean Overview  (0) 2024.06.11
Using @Autowired  (0) 2023.12.10
Fine-tuning Annotation-based Autowiring with @Primary  (0) 2023.07.08
Dependency Injection of Spring Framework  (0) 2023.05.01
Bean Definition Inheritance  (0) 2023.04.28