https://sundaland.tistory.com/208
Dependencies and Configuration in Detail
관리되는 다른 빈(협력자)에 대한 참조나 인라인으로 정의된 값으로 빈 속솽과 생성자 아규먼트를 정의할 수 있다. 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>
▼ 자바 기반 구성 메타데이
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(destroyMethod = "close")
public BasicDataSource myDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("root");
dataSource.setPassword("misterkaoli");
return dataSource;
}
}
▼ p-namespace를 사용한 더 간결한 XML 구성
<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/> 요소롤 선호하는 몇 안되는 경우 중 하나이다.
▼ 자바 기반 구성 메타데이터
import org.apache.commons.dbcp.BasicDataSource;
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 java.util.Properties;
@Configuration
public class AppConfig {
@Bean
public PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
Properties properties = new Properties();
properties.setProperty("jdbc.driver.className", "com.mysql.jdbc.Driver");
properties.setProperty("jdbc.url", "jdbc:mysql://localhost:3306/mydb");
configurer.setProperties(properties);
return configurer;
}
@Value("${jdbc.driver.className}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Bean(destroyMethod = "close")
public BasicDataSource myDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername("root");
dataSource.setPassword("misterkaoli");
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 속성에 전달된 값에 대한 검증이 수행되지 않았다. 오타는 클라이언트 빈의 targetName 속성에 전달된 값에 대한 검증이 수행되지 않는다.
오타는 클라이언트 빈이 실제로 인스턴스화될 때만 발견된다. (치명적 결과가 발생할 가능성이 높음). 클라이언트 빈이 프로토타입 빈인 경우 이 오타와 결과 예외는 컨테이너가 배포된 후 오랜 시간이 지난 후에야 발견될 수 있다.
<idref/> 엘리먼트가 값을 가져오는 일반적인 장소는 ProxyFactoryBean 빈 정의의 AOP 인터셉터 구성이다. 인터셉터 이름을 지정할 때 <idref/> 요소를 사용하면 인터셉터 ID를 잘못 철자하는 것을 방지할 수 있다.
자바 기반 구성에서 idref 사용
idref 엘리먼트는 다른 빈의 ID를 안전하게 전달하는 방법으로 사용된다.
▼ 자바 기반 구성으로 변환
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> 욧와 동일한 역할을 하며, 타이핑 오류를 방지할 수 있다. 스프링은 런타임에 지정된 ID가 실제로 존재하는지 확인한다.
References to Other Beans (Collaborators)
ref 엘리먼트는 <constructor-arg/> 또는 <property/> 정의 엘리먼트 내부의 마지막 엘리먼트이다. 여기서 빈의 지정된 속성 값을 컨테이너에서 관리하는 다른 빈 (협력자)에 대한 참조로 설정한다. 참조된 빈은 속성을 설정할 빈의 종속성이며 속성이 설정되기 전에 필요에 따라 필요에 따라 초기화된다. (협력자가 싱글톤 빈인 경우 컨테이너에서 이미 초기화되었을 수 있다.) 모든 참조는 궁극적으로 다른 객체에 대한 참조이다. 스코프 지정 및 검증은 빈 또는 부모 속성을 통해 다른 객체의 ID 또는 이름을 지정하는지 여부에 따라 달라진다.
<ref/> 태그의 bean 속성을 통해 타겟 bean을 지정하는 것이 가장 일반적인 형태이며, 동일한 XML 파일에 있는지 여부에 관계없이 동일한 컨테이너 또는 부모 컨테이너에 있는 모든 빈에 대한 참조를 생성할 수 있다. 빈 속성의 값은 빈의 id 속성과 같거나 대상 빈의 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" <!-- bean name is the same as the parent bean -->
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>
자바 기반 구성에서 다른 빈에 대한 참조 설정
스프링의 자바 기반 구성에서 다른 빈에 대한 참조는 @Bean 메서드를 통해 이루어진다. ref 요소의 역할을 자바 코드로 변환하면 아래와 같다.
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 빈을 참조하여 설정된다. 자바 기반 구성에서 부모 컨텍스트의 빈을 참조할 때, 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 콜백을 수신하는 것이 가능하다. 예를 들어, 싱글톤 빈에 포함된 요청 범위의 내부 빈의 경우이다. 내부 빈 인스턴스의 생성은 포함하는 빈에 연결되어 있디잠 파괴 콜백을 통해 요청 범위의 라이프사이클에 참여할 수 있다. 이는 일반적인 시나리오가 아니다. 내부 빈은 일반적으로 포함하는 빈의 범위를 공유한다.
Inner Bean은 다른 빈의 속성이나 생성자 인수로 사용되며, 독립적으로 접근할 수 없는 익명 빈이다.
▼ 자바 기반 구성으로 변환
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 빈이다.
- Inner 빈은 익명으로 생성되며, 외부에서 참조할 수 없고, 부모 빈의 생명주기를 따른다.
Collections
<list/>, <set/>, <map/>, <props/> 엘리먼트는 자바 컬렉션 타입 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>
키나 값의 값, 혹은 Set 값은 다음 엘리먼트 중 하나가 될 수도 있다.
bean | ref | idref | list | set | map | props | value | null
Collection Merging
스프링 컨테이너는 컬렉션 병합도 지원한다. 애플리케이션 개발자는 부모 <list/>, <map/>, <set/> 또는 <props/> 엘리먼트를 정의하고 자식 <list/>, <map/>, <set/> 또는 <props/> 엘리먼트가 부모 컬렉션에서 값을 상속하고 오버라이드하도록 할 수 있다. 즉 자식 컬렉션의 값은 부모 및 자식 컬렉션 엘리먼트를 병합한 결과이며, 자식 컬렉션 엘리먼트는 부모 컬렉션에 지정된 값을 오버라이드한다.
<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 컬렉션 타입의 경우 정렬이 없다. 따라서 컨테이너가 내부적으로 사용하는 연관된 Mpa, Set 및 Properties 구현 타입의 기반이 되는 컬렉션 타입에는 정렬 의미가 적용되지 않는다.
Limitations of Collection Merging
우리는 다른 컬렉션 타입 (Map 및 List)을 병합할 수 없다. 병합을 시도하면 적절한 예외가 발생한다. 병합 속성은 하위 상속된 자식 정의에 지정해야 한다. 부모 컬렉션 정의에 병합 속성을 지정하는 것은 중복이며 원하는 병합을 수행하지 못한다.
Strongly-typed collection
자바의 제너릭 타입 지원으로 강력한 타입의 컬렉션을 사용할 수 있다. 즉 String 엘리먼트들만 포함할 수 있도록 컬렉션 타입을 선언할 수 잇다. 스프링을 사용하여 강력한 타입의 컬렉션을 빈에 종속성 주입하는 경우 스프링의 타입 변환 지원을 활용하여 강력한 타입의 컬렉션 인스턴스의 엘리먼트가 컬렉션에 추가되지 전에 적절한 타입으로 변환된다.
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>
somethung 빈의 accounts 속성이 주입을 위해 준비되면, 강력한 타입의 Map<String, Float>의 엘리먼트 타입에 대한 제너릭 정보를 리플렉션을 통해 사용할 수 있다. 따라서 스프링의 타입 변환 인프라는 다양한 값 엘리먼트를 Flaot 타입으로 인식하고 문자열 값 (9.99, 2.75, 3.99)은 실제 Float 타입으로 변환된다.
자바 기반 구성에서 컬렉션 다루기
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. 컬렉션 병합 예제
컬렉션 병합을 자바 기반으로 구현하는 예제이다.
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. 강력한 타입의 컬렉션 예제
자바의 제너릭 타입을 사용하는 강력한 타입의 컬렉션을 구성하는 방법이다.
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에서 빈을 구성하고 의존성을 주입하는 방법을 자바 기반으로 보여준다.
Null and Empty String Values
Spring은 속성 등에 대한 빈 아규먼트를 빈 문자열로 처리한다. 다음 XML 기반 구성 메타데이터 스니펫은 email 속성을 빈 문자열 값("")으로 설정한다.
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
exampleBean.setEmail("");
▼ <null/> 요소를 사용한 null 값 처리
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
exampleBean.setEmail(null);
XML Shortcut with the p-namespace
p-namespace를 사용하면 중첩된 <property/> 엘리먼트 대신 빈 엘리먼트의 속성을 사용하여 속상 값 협업 빈 또는 둘다를 설명할 수 있다.
스프링은 XML 스키마 정의를 기반으로 하는 네임스페이스를 사용하여 확장 가능한 구성 형식을 지원한다.
p-네임스페이스는 XSD 파일에 정의되 있지 않으며 스프링 핵심에만 존재한다.
▼ XML 스니펫 (표준 XML 형식)
<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>
▼ XML 스니펫 (p-네임스페이스 사용)
빈 정의에서 email이라는 p-네임스페이스의 속성을 보여준다. 이것은 스프링에 속성 선언을 포함하라고 알려준다. 이전에 언급했듯이, p-네임스페이스에는 스키마 정의가 없으므로 속성 이름을 속성 이름으로 설정할 수 있다.
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-네임스페이스를 사용하는 속성 값뿐만 아니라 속성 참조를 선언하기 위해 특수 형식도 사용된다. 첫 번째 빈 정의는 <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-네임스페이스는 표준 XML 형식만큼 유연하지 않다. 예를 들어 속성 참조를 선언하는 형식은 Ref로 끝나는 속성과 충돌하지만 표준 XML 형식은 그렇지 않다. 세 가지 접근 방식을 모두 동시에 사용하는 XML 문서를 생성하지 않으려면 접근 방식을 신중하게 선택하고 팀원에게 이를 전달하는 것이 좋다.
XML Shortcut with the c-namespace
p-네임스페이스를 사용한 XML, Shortcut과 유사하게 스프링 3.1에 도입된 c-네임스페이스는 중첩된 constructor-arg 엘리먼트 대신 생성자 아규먼트를 구성하기 위한 인라인 속성을 허용한다.
▼ from Constructor-based Dependency Injection과 동일한 작업을 수행하는 c-네임스페이스
<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-네임스페이는 p:와 동일한 규칙을 사용한다. (빈 참조에 대한 후행 -ref). 생성자 아규먼트를 이름으로 설정하는데 사용한다. 마찬가지로 XSD 스키마에 정의되어 있지 않더라고 XML 파일에 선언해야한다. (스프링 코어 내부에 존재)
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 된다.
▼ 자바 기반 구성 메타데이터로 변환
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;
}
}
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가 발생하지 않도록 보장한다.
Using depends-on
스프링의 자바 기반 구성에서는 @DependsOn 어노테이션을 사용하여 빈 간의 초기화 순서를 지정할 수 있다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
@Configuration
public class AppConfig {
@Bean
@DependsOn("manager")
public ExampleBean beanOne() {
ExampleBean exampleBean = new ExampleBean();
exampleBean.setManager(manager());
return exampleBean;
}
@Bean
public ManagerBean manager() {
return new ManagerBean();
}
@Bean
public JdbcAccountDao accountDao() {
return new JdbcAccountDao();
}
@Bean
@DependsOn({"manager", "accountDao"})
public ExampleBean beanOneWithMultipleDependencies() {
ExampleBean exampleBean = new ExampleBean();
exampleBean.setManager(manager());
return exampleBean;
}
}
- @DependsOn("manager")를 사용하여 beanOne이 manager 빈이 먼저 초기화된 후에 초기화되도록 지정한다.
- @DepensOn({"manager", "accountDoa"})를 사용하여 beanOneWithMultipleDependencies가 manager와 accountDao 빈이 모두 초기화된 후에 초기화되도록 지정한다.
Lazy-initialized Beans
@Lazy 어노테이션을 사용하여 lazy-initialized 빈을 정의할 수 있다. 또한 컨테니어 수준에서 기본적으로 모든 빈을 lazy- initialized로 설정하려면 @Configuration 클래스에 @Lazy 어노테이션을 추가할 수 있다.
▼ 개별 빈에 대한 lazy initialized
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@Configuration
public class AppConfig {
@Bean
@Lazy
public ExpensiveToCreateBean lazyBean() {
return new ExpensiveToCreateBean();
}
@Bean
public AnotherBean notLazyBean() {
return new AnotherBean();
}
}
- @Lazy 어노테이션을 lazyBean 메서드에 추가하여 해당 빈이 처음 요청될 때까지 초기화되지 않도록 설정한다.
- notLazyBean은 별도의 어노테이션이 없기 때문에, 기본적으로 즉시 초기화된다.
▼ 컨테이너 수준의 lazy initialized
모든 빈을 lazy initialized로 설정하려면, @Configuration 클래스에 @Lazy 어노테이션을 추가할 수 있다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@Configuration
@Lazy
public class AppConfig {
@Bean
public ExpensiveToCreateBean lazyBean() {
return new ExpensiveToCreateBean();
}
@Bean
public AnotherBean notLazyBean() {
return new AnotherBean();
}
}
위 코드에서 @Lazy 어노테이션이 @ Configuration 클래스 레벨에 적용되어, 이 클래스 내의 모든 빈이 lazy initialized 된다.
Method Injection
대부분의 애플리케이션 시나리오에서 컨테이너 배의 대부분의 빈들은 싱글콘이다. 싱글톤 빈이 다른 싱글톤 빈과 협력해야하거나 비싱글톤 빈이 다른 비싱글톤 빈과 협력해야 할 때, 일반적으로 다른 하나의 속성을 정의함으로써 의존성을 처리한다. 문제는 빈 생명주기가 다를 때 발생한다. 싱글톤 빈 A가 비싱글톤(프로토타입) 빈 B를 사용해야 한다고 가정한다.
컨테이너는 싱글톤 빈 A에 새로운 B 인스턴스를 제공할 수 없다.
해결책은 일부 제어의 역전을 포기하는 것이다. ApplicationContextAware 인터페이스를 구한혀여 빈 A 컨테이너를 인식하게하고, 빈 A가 필요할 때마다 컨테이너에 getBean("B") 호출을 하여 (일반적으로 새로운) 빈 B 인스턴스를 요청할 수 있다.
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* A class that uses a stateful Command-style class to perform
* some processing.
*/
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
package fiona.apple;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
// Command interface
interface Command {
void setState(Map<String, Object> state);
Object execute();
}
// Concrete Command implementation
class ConcreteCommand implements Command {
private Map<String, Object> state;
@Override
public void setState(Map<String, Object> state) {
this.state = state;
}
@Override
public Object execute() {
// Perform some operation based on the state
return "Command executed with state: " + state;
}
}
// CommandManager class
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map<String, Object> commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
return this.applicationContext.getBean("command", Command.class);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
// Main method to run the example
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CommandManager commandManager = context.getBean(CommandManager.class);
// Creating a sample state map
Map<String, Object> state = Map.of("key1", "value1", "key2", "value2");
// Processing the command
Object result = commandManager.process(state);
System.out.println(result);
}
}
// Spring Configuration class
@Configuration
@ComponentScan("fiona.apple")
class AppConfig {
@Bean
public Command command() {
return new ConcreteCommand();
}
@Bean
public CommandManager commandManager() {
return new CommandManager();
}
}
앞서 언급된 방법은 바람직하지 않다. 왜냐하면 비즈니스 코드가 Spring 프레임워크를 인지하고 이에 종속되기 때문이다. Spring IoC 컨테이너의 다소 고급 기능은 메서드 주입은 이러한 사용 사례를 깔끔하게 처리할 수 있게 한다.
Lookup Method Injection
컨테이너가 컨테이너 관리 빈의 메서드를 오버라이드하고 컨테이너 내의 다른 이름이 저장된 빈에 대한 조회 결과를 반환하는 능력이다. 조회는 일반적으로 앞서 설명된 시나리오에서처럼 프로트타입 빈을 포함한다. Spring 프레임워크는 CGLIB 라이브러리의 바이트코드 생성을 사용하여 메서드를 오버라이드 하는 동적 서브 클래스를 생성함으로써 이 메서드 인젝션을 구현한다.
- 이 동적 서크클래싱이 작동하려면, Spring 빈 컨테이너가 서브클래싱하는 클래스는 final이 아니어야하며, 오바라이드될 메서드도 final이어서는 안된다.
- 추상 메서드를 가진 클래스를 단위 테스트하는 것은 해당 클래스를 직접 서브클래싱하고 추상 메서드에 대한 스텁 구현을 제공해야 한다.
- 구체적인 메서드는 컴포넌트 스캐닝에도 필요한데, 이는 구체적인 클래스를 선택하는데 필요하다.
- 더욱 중요한 제한 사항은, 조회 메서드가 팩토리 메서드와 특히 설정 클래스 내의 @Bean 메서드와 작동하지 않는다는 것인데, 즉석에서 런타임 생성된 서브클래스를 생성할 수 없기 때문이다.
이전 코드 스니펫에서 CommandManager 클래스의 경우, Spring 컨테이너는 createCommand() 메서드의 구현을 동적으로 오버라이드한다. CommandManager 클래스는 재작업된 예제에서 보여주듯이 Spring 의존성이 없다.
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
메서드가 주입될 클라이언트 클래스(CommandManager)에서 주입될 메서드는 아래와 같은 형식의 시그니처를 요구한다.
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
메서드가 추상적이면 동적으로 생성된 하위 클래스가 메서드를 구현한다. 그렇지 않으면 동적으로 생성된 하위 클래스가 원래 클래스에 정의된 구체적 메서드를 오버라이드한다.
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandManager uses myCommand prototype bean -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
commandManager로 식별된 빈은 myCommand 빈의 새 인스턴스가 필요할 때마다 자체 createCommand() 메서드를 호출한다. 실제로 필요한 경우 myCommand 빈을 프로토타입으로 배포하는데 주의해야한다.
싱글톤인 경우 매번 동일한 myCommand 빈 인스턴스가 반환된다.
▼ @Lookup 어노테이션을 통한 조회 메서드 선언
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
또는 보다 관용적으로, 조회 메서드의 선언된 리턴 타입에 대해 타겟 빈이 해결되는 것을 기대할 수 있다.
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
일반적으로 이러한 어노테이션이 달린 조회 메서드는 구체적인 스텁 구현으로 선언해야 하며, 이를 통해 추상 클래스가 기본적으로 무시되는 스프링의 구성 엘리먼트 스캐닝 규칙과 호환될 수 있다. 이 제한은 명시적으로 등록되거나 명시적으로 가져온 빈 클래스에는 적용되지 않는다.
Scope가 다르게 지정된 타겟 빈에 액세스하는 또 다른 방법은 ObjectFactory/Provider 주입 지점이다. 종속성으로서의 범위가 지정된 빈을 참조한다.
ServiceLocatorFactoryBean(org.springframework.beans.factory.config 패키지에 있다)도 유용할 수 있다.
Bean Scopes
빈 정의를 생성할 때, 해당 빈 정의에 의해 정의된 클래스의 실제 인스턴스를 생성하기 위한 레시피를 만든다. 빈 정의가 레시피라는 생각은 중요하다. 이는 클래스와 마찬가지로 하나의 레시피에서 많은 객체 인스턴스를 생성할 수 있다는 것을 의미한다.
특정 빈 정의에서 생성된 객체에 주입될 다양한 의존성과 구성 값뿐만 아니라, 특정 빈 정의에서 생성된 객체의 범위를 제어할 수도 있다. 이 접근 방식은 강력하고 유연한데, 이는 자바 클래스 수준에서 객체의 범위를 구체화하는 대신 구성을 통해 생성하는 객체의 범위를 선택할 수 있기 때문이다. 빈은 여러 범위 중 하나로 정의될 수 있다.
Spring 프레임워크는 여섯 가지 범위를 지원하며, 이 중 네가지는 web-ware ApplicationContext를 사용할 경우에만 사용할 수 있다. 또한 custom scope (사용자 정의 범위) 생성할 수도 있다.
singleton | 각 스프링 IoC 컨테이너마다 단일 빈 정의를 단일 객체 인스턴스로 범위 지정한다. |
prototype | 단일 Bean 정의의 범위를 원하는 수의 객체 인스턴스로 지정한다. |
request | 단일 HTTP 요청의 라이프사이클에 대해 단일 Bean 정의의 범위를 지정한다. 각 HTTP 요청에는 단일 Bean 정의 뒤에 생성된 자체 Bean 인스턴스가 있다. 웹 인식 스프링 ApplicationContext의 컨텍스트에서만 유효하다. |
session | 단일 Bean 정의의 범위를 HTTP 세션의 라이프사이클로 지정한다. 웹 인식 스프링 ApplicationContext의 컨텍스트에서만 유효하다. |
application | 단일 Bean 정의의 범위를 ServletContext의 라이프사이클로 지정한다. 웹 인식 스프링 ApplicationContext의 컨텍스트에서만 유효하다. |
websocket | 단일 Bean 정의의 범위를 WebSocket의 라이프사이클로 지정한다. 웹 인식 스프링 ApplicationContext의 컨텍스트에서만 유효하다. |
스레드 범위를 사용할 수 있지만 기본적으로 등록되지 않는다.
The Singleton Scope
단 하나의 공유 인스턴스만 관리되며, 해당 빈 정의와 일치하는 ID 또는 IDs를 가진 빈에 대한 모든 요청은 스프링 컨테이너에 의해 특정한 그 하나의 빈 인스턴스가 반환된다.
빈 정의를 정의하고 그것을 싱글톤으로 범위 지정을 할 때, Spring IoC 컨테이너는 해당 빈 정의에 의해 정의된 객체의 정확히 하나의 인스턴스를 생성한다. 이 단일 인스턴스는 싱글톤 빈의 캐시에 저장되며, 그 명명된 빈에 대한 모든 후속 요청 및 참조는 캐시된 객체를 반환한다.
스프링의 싱글톤 빈 개념은 GoF (Gang of Four) 패턴 책에 정의된 싱글톤 패턴과 다르다. GoF 싱글톤은 ClassLoader당 하나의 특정 클래스 인스턴스만 생성되도록 객체의 범위를 하드코딩 한다. 스프링 싱글톤의 범위는 컨테이너별 및 Bean별 범위로 가장 잘 설명된다. 이는 단일 스프링 컨테이너의 특정 클래스에 대해 하나의 Bean을 정의하면 스프링 컨테이너는 해당 Bean 정의에 의해 정의된 클래스의 인스턴스를 하나만 생성한다는 의미이다.
싱글톤의 범위는 스프링의 기본 범위이다.
▼ 빈을 XML의 싱글톤의 정의
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
The Prototype Scope
비싱글톤 프로토타입 범위의 빈 배포는 해당 특정 빈에 대한 요청이 있을 때마다 새로운 빈 인스턴스를 생성한다. 다른 빈에 빈이 주입되거나 컨테이너의 getBean() 메서드 호출을 통해 요청할 때이다. 원칙적으로 상태를 가진 모든 빈에는 프로토타입 범위를 사용하고, 상태가 없는 빈에는 싱글톤 범위를 사용해야 한다.
# 데이터 액세스 객체 (DAO)는 일반적으로 대화형 상태를 보유하지 않기 때문에 프로토타입으로 설정되지 않는다. 싱글톤 다이어그램의 핵심을 재사용하는 것이 더 쉽다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public DefaultAccountService accountService() {
return new DefaultAccountService();
}
다른 스코프와 달리, 스프링은 프로토타입 빈의 전체 생명주기를 관리하지 않는다. 컨테이너는 프로토타입 객체를 인스턴스화하고, 구성하며, 그 외에 조힙한 후 클라이언트에게 전달하지만, 그 프로토타입 인스턴스의 추가적인 기록은 남기지 않는다. 따라서, 초기화 생명주기 콜백 메서드가 범위에 관계없이 모든 객체에 대해 호출되지만, 프로토타입의 경우에는 구성된 소멸 생명주기 콜백이 호출되지 않는다. 클라이언트 코드는 프로토타입 범위의 객체를 정리하고 프로토타입 빈이 보유한 고가의 자원을 해제해야한다. 프로토타입의 범위의 빈이 보유한 자원을 스프링 컨테이너가 해제하도록 하려면, 정리가 필요한 빈에 대한 참조를 보유하는 bean post-processor를 사용한다.
어떤 면에서, 스프링 컨테이너가 프로토타입 스코프 빈과 관련하여 수행하는 역할은 자바의 new 연산자를 대체하는 것이다. 그 지점 이후의 모든 생명주기 관리는 클라이언트에 의해 처리되어야 한다.
▼ 스프링 빈 라이프사이클 콜백 메서
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class MyBean {
// 빈이 생성되고, 의존성 주입이 완료된 후 호출되는 메소드
@PostConstruct
public void init() {
// 초기화 로직
System.out.println("MyBean is initialized");
}
// 빈이 소멸되기 전에 호출되는 메소드
@PreDestroy
public void destroy() {
// 정리 로직
System.out.println("MyBean is destroyed");
}
}
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
Singleton Beans with Prototype-bean Dependencies
'스프링 프레임워크' 카테고리의 다른 글
스프링 프레임워크에서 의존성 주입 (0) | 2024.08.14 |
---|---|
The IoC Container [6] (0) | 2024.08.14 |
The IoC Container (3) (0) | 2024.08.09 |
The IoC Container (2) (0) | 2024.08.09 |
메이븐 (Maven) Build System (0) | 2024.08.08 |