'Programming/Spring Framework'에 해당하는 글 12건

동일한 scope 의 bean 에 의존성을 가질 때, 일반적으로 bean 의 속성에 의존성을 추가하여 처리한다. 예를 들어 singleton bean 이 prototype bean 을 의존한다면, 컨테이너는 singleton bean 을 단 한번 생성하므로 필요할 때마다 prototype bean 을 제공할 수 없다.


아래는 ApplicationContextAware 를 구현하여 singleton bean 에서 prototype bean 의 새 인스턴스를 요청하는 예제이다.


package fiona.apple;
 
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
 
public class CommandManager implements ApplicationContextAware {
 
    private ApplicationContext applicationContext;
 
    public Object process(Map commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }
 
    protected Command createCommand() {
        // Spring API getBean() 를 의존하고 있다.
        return this.applicationContext.getBean("command", Command.class);
    }
 
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
cs


위 예제에서 컨테이너가 createCommand() 메서드의 구현을 동적으로 재정의한다는 것을 알 수 있다. Spinrg Framework 와 비지니스 코드와 결합되어 있으므로 바람직하지 않지만, 컨테이너의 lookup 메소드 주입을 사용하면 이를 해결할 수 있다.


lookup 메소드 주입은 bean 의 메소드를 재정의하여, 다른 이름의 bean 으로 대체시키는 컨테이너의 기능이다. Spring Framework 는 메소드를 재정의하는 서브 클래스를 동적으로 생성하기 위해 CGLIB 라이브러리의 바이트 코드 생성을 사용한다. 이 때 서브 클래스화 할 클래스와 재정의 할 메소드는 final 이 될 수 없다. lookup 메소드 주입은 factory 메소드나 @Bean 에서는 작동하지 않는다.


주입될 메소드는 다음의 형식으로 작성한다.


<public|protected> [abstract] <return-type> theMethodName(no-arguments);


추상 메소드라면 동적으로 생성된 서브 클래스는 이 메소드를 구현하고, 그렇지 않으면 동적으로 생성된 서브 클래스가 원래 클래스에 정의된 메서드를 재정의한다. 아래 예제에서는 lookup 메소드 주입으로 Spring 과 결합되어 있지 않다.


package fiona.apple;
 
// no more Spring imports!
 
public abstract class CommandManager {
 
    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }
 
    protected abstract Command createCommand();
}
 
cs


<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- 의존성 주입 -->
</bean>
 
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>
cs


commandManager bean 은 myCommand bean 의 새 인스턴스가 필요할 때마다 createCommand() 를 호출한다.


또는 어노테이션 컴포넌트 모델에서 @Lookup 어노테이션을 통해 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();
 
    // or
    // @Lookup
    // protected abstract MyCommand createCommand();
}
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

컨테이너로부터 bean 을 호출하였을 때, 요청 수 만큼의 bean 인스턴스를 반환할 것인지, 하나의 bean 인스턴스로 계속해서 반환할 것인지 등을 <bean /> 요소의 scope 속성을 설정하거나, 컴포넌트에 @RequestScope 처럼 @*Scope 어노테이션을 사용하여 제어할 수 있다. Spring Framework 는 7가지의 scope 를 지원하며 사용자 정의 scope 를 만들 수도 있다.



singleton scope


scope 를 지정하지 않은 bean 은 singleton scope 이다(default). Spring IoC 컨테이너는 해당 bean 정의의 객체 인스턴스를 정확히 하나 생성한다. 이 인스턴스는 singleton bean 의 캐시에 저장되고, 그 이후의 모든 요청과 해당 이름의 Bean 에 대한 참조는 캐시된 객체를 반환한다.


prototype scope


prototype scope 로 설정된 bean 은 요청이 있을 때마다 새로운 bean 인스턴스를 생성한다. 컨테이너는 prototype 객체를 인스턴스화하여 클라이언트에 전달한 이후에는 해당 객체의 라이프 사이클을 관리하지 않기 때문에, 클라이언트에서 bean post-processor 등을 이용하여 리소스를 해제해야 한다.


singleton bean 이 prototype bean 을 의존성 주입하면 prototype bean 이 인스턴스화 되고 singleton bean 으로 주입되지만, 런타임시 반복적으로 prototype bean 의 새로운 인스턴스를 요청한다면 메소드 주입을 사용해야 한다. 원칙적으로, 모든 stateful bean 에 대해 prototype scope 을 사용하고, stateless beans 에 대해 singleton scope 를 사용해야 한다.



다음 5가지 scope 는 웹 기반 Spring ApplicationContext 를 구현(예: XmlWebApplicationContext) 하는 경우에만 사용할 수 있다. 웹 기반 구성시에 Servelet 2.5 는 ServletRequestListener 를 구현한 org.springframework.web.context.request.RequestContextListener 를 web.xml 파일에 등록해야 하며, Servlet 3.0 이상은 WebApplicationInitializer 인터페이스를 통해 프로그래밍 방식으로 대체할 수 있다. Spring Web MVC 의 Spring DispatcherServlet 이나 DispatcherPortlet 에 의해 처리되는 요청에 대해서는 특별한 설정이 필요없다. 만약 리스너 설정에 문제가 있는 경우 Spring 의 RequestContextFilter 를 사용할 수 있으며, DispatcherServlet, RequestContextListener, RequestContextFilter 는 모두 요청을 처리하는 Thread 에 HTTP 요청 객체를 바인딩하는 동일한 작업을 수행하므로, request 와 session scope bean 을 사용할 수 있다.



Request scope


컨테이너는 request scope 로 설정된 bean 에 HTTP 요청이 들어오면, 각 요청마다 새로운 인스턴스를 생성한다. 인스턴스들은 각 요청 별로 각각 동작하며, 요청 처리가 완료되면 해당 bean 은 버려진다.


Session scope


컨테이너는 단일 HTTP Session 의 라이프 사이클 동안 session bean 의 새로운 인스턴스를 생성한다. HTTP Session 이 폐기되면, 해당 bean 도 삭제된다.


Global session scope


HTTP Session scope 와 비슷하며, portlet 기반의 웹 어플리케이션 컨텍스트에서만 적용된다. portlet 기반의 웹 어플리케이션을 구성하는 모든 portlet 간에 해당 bean 은 공유된다. 표준 Servlet 기반의 웹 어플리케이션에서 사용한다면 오류는 발생하지 않지만, HTTP Session scope 로 사용될 것이다.


Application scope


컨테이너는 application scope 로 설정된 bean 정의를 한 번 사용하여 새로운 인스턴스를 생성한다. singleton scope 과 비슷하지만 singleton bean 는 ApplicationContext 당 하나의 인스턴스로 생성되고, application bean 은 ServletContext 당 하나의 인스턴스로 생성되며 일반적인 ServletContext 속성으로 저장된다.


Websocket scope


websocket scope 는 일반적으로 singleton 이다. websocket bean 은 "clientInboundChannel" 에 등록된 컨트롤러 및 모든 채널 인터셉터에 주입할 수 있으며, 이들은 개별 websocket 세션보다 오래 유지된다. 따라서 websocket bean 은 scope 프록시 모드를 사용해야 한다.




프록시 주입


HTTP request, session, globalSession bean 을 다른 bean 으로 주입하려면, scope 가 지정된 bean 에 AOP 프록시를 주입해야 한다. 그렇지 않으면 singleton bean 이 컨테이너에서 한 번 인스턴스화 되므로, 그에 주입될 scope bean 도 한 번만 주입될 것이다. 프록시를 생성하는 방법은 scope 가 지정된 bean 안에 <aop:scoped-proxy/> 요소를 삽입하면 된다.


<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>
 
<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
cs


위 예를 보면, singleton bean 인 userManager 는 session bean 인 userPreferences 를 의존한다. 이처럼 수명이 긴 userManager bean 이 짧은 userPreferences bean 을 주입할 때 프록시를 사용하면, 컨테이너는 userPreferences 와 완전히 동일한 객체와 public interface 를 만들어 UserPreferences 객체를 가져올 수 있다. 이 때 생성되는 프록시는 CGLIB 기반의 클래스 프록시이며 public 메소드 호출만 인식하는데, <aop:scoped-proxy/> 요소의 proxy-target-class 속성 값에 false 를 지정하여, CGLIB 이 아닌 표준 JDK 인터페이스 기반 프록시를 사용하면 public 이 아닌 다른 메소드의 호출도 인식할 수 있다.


singleton bean 간에 <aop:scoped-proxy/> 를 사용하면, 비직렬화시 중간 프록시를 거쳐 대상 singleton bean 을 다시 얻을 수 있다. prototype bean 에 <aop:scoped-proxy/> 를 선언하면, 공유된 프록시상의 모든 메소드 호출이 새로운 대상 인스턴스를 생성하게 된다. <aop:scoped-proxy/> 말고도 ObjectFactory<MyTargetBean> 로 주입 지점을 정의하면, 인스턴스를 유지하거나 별도로 저장하지 않고도 getObject() 호출로 필요할 때마다 현재 인스턴스를 검색할 수 있다. JSR-330 에서는 Provider<MyTargetBean> 을 정의하고 get() 호출로 사용 가능하다.



사용자 정의 scope


bean scope 를 사용자 정의하여 확장하는 것이 가능하다. 내장된 기존 scope 를 재정의 하는 것은 좋은 방법이 아니며 singleton 과 prototype 범위는 재정의 할 수 없다. 사용자 scope 를 만들어 Spring 컨테이너에 적용시키려면 org.springframework.beans.factory.config.Scope 인터페이스를 구현해야 한다. Scope 인터페이스는 scope 에서 객체를 가져오고, 객체를 제거하는데 사용하는 네 가지 메소드가 있다.


  • Object get(String name, ObjectFactory objectFactory)
    지정된 scope 에서 객체를 반환한다. 존재하지 않으면 메소드는 나중에 참조할 수 있도록 세션에 바인드 한 후 bean 의 새 인스턴스를 반환한다.

  • Object remove(String name)
    지정된 scope 에서 객체를 제거한다. 객체가 반환되지만, 없는 경우 null 을 반환한다.

  • void registerDestructionCallback(String name, Runnable destructionCallback)
    scope 나 scope 내의 객체가 소멸될 때 실행해야 하는 콜백을 등록한다.

  • String getConversationId()
    지정된 scope 에 대한 conversation 식별자를 가져온다.



하나 이상의 사용자 정의 scope 구현을 작성하고 테스트 한 후에는 Spring 컨테이너에 등록해야 사용할 수 있다. 아래 예제는 ConfigurableBeanFactory 인터페이스의 registerScope 메소드로 scope 를 등록하고 bean 을 정의한다.


Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
cs


<bean id="bar" class="x.y.Bar" scope="thread" />
cs


XML 메타데이터에서도 CustomScopeConfigurer 클래스를 사용하여 사용자 정의 scope 를 등록할 수 있다.


<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="thread">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>
cs


FactoryBean 구현에 <aop:scoped-proxy/> 를 배치하면, getObject() 에서 반환된 객체가 아닌 scope 가 지정된 factory bean 자체가 된다.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

특정 객체가 속성에 다른 객체를 필요로 하여 생성자나 setter 방식으로 주입시켜 함께 작동할 때, 다른 객체를 의존성(dependency) 이라 하고 이를 주입시키는 행위를 의존성 주입(DI: Dependency Injection)  이라고 한다. Spring 에서는 컨테이너가 bean 을 생성할 때 의존성을 주입하게 된다. 의존성 주입의 원리를 사용하면 객체는 의존성의 위치나 클래스와 완벽하게 분리될 수 있다. 인터페이스 또는 추상 클래스에 의존성이 있으면, 단위 테스트에서 스텁(stub) 이나 모의(mock) 구현을 쉽게 사용할 수도 있다.


ApplicationContext 는 자신이 관리하는 bean 에 대해 생성자(Constructor) 및 세터(Setter) 기반의 의존성 주입을 지원한다. 필수 의존성에 대해서는 생성자 기반의 의존성 주입을 사용하고, 선택적 의존성에 대해서는 세터 기반의 의존성 주입을 사용하는 것이 좋다. 생성자 방식은 별도로 세터 메소드 같은 코드가 필요하지 않지만, 너무 많은 생성자 인수는 클래스가 너무 많은 책임을 가지게 되므로 좋은 코드로 볼 수 없다. 세터 기반의 의존성 주입은 해당 객체를 재구성 및 재주입 할 수 있으며, @Required 어노테이션을 사용하면 속성을 필수 의존성으로 만들 수도 있다. 이러한 점들을 감안하여 특정 클래스에 대해 가장 적합한 의존성 주입을 사용해야 한다.



생성자(Constructor) 기반 의존성 주입


생성자 기반의 의존성 주입은 컨테이너가 의존성을 나타내는 하나 이상의 객체를 인수로 하여 생성자를 호출함으로써 수행된다. 

아래는 생성자 인수로 의존성이 주입되는 예제이다.


public class SimpleMovieLister {
 
    // SimpleMovieLister 는 MovieFinder 에 의존성이 있다.
    private MovieFinder movieFinder;
 
    // Spring 컨테이너가 MovieFinder 의존성을 주입할 수 있도록 하는 생성자
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    ...
}
cs


<beans>
    <bean id="SimpleMovieLister" class="x.y.SimpleMovieLister">
        <constructor-arg ref="movieFinder"/>
    </bean>
 
    <bean id="movieFinder" class="x.y.MovieFinder"/>
</beans>
cs


위 예제와 달리 여러 생성자 인수를 Java 내장형 타입으로 정의할 경우, <constructor-arg/> 태그의 속성으로 인수와 매칭해 줄 index, name, type 중 하나를 함께 정의해야 한다. 이 때 설정하는 value 값은 기본적으로 String 형태로 전달하지만, 실제 소스에 정의된 int, long, String, boolean 등과 같은 모든 내장 type 으로 자동 변환된다.


<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
cs



세터(Setter) 기반 의존성 주입


세터 기반의 의존성 주입은 인수가 없는 생성자나 인수가 없는 static factory 메소드를 호출한 후, bean 에서 setter 메소드를 호출함으로써 수행된다.

아래는 세터 방식으로 의존성이 주입되는 예제이다.


public class SimpleMovieLister {
 
    // SimpleMovieLister 는 MovieFinder 에 의존성이 있다.
    private MovieFinder movieFinder;
 
    // Spring 컨테이너가 MovieFinder 의존성을 주입할 수 있도록 하는 세터 메소드
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    ...
}
cs


<beans>
    <bean id="SimpleMovieLister" class="x.y.SimpleMovieLister">
        <property name="movieFinder" ref="movieFinder"/>
    </bean>
 
    <bean id="movieFinder" class="x.y.MovieFinder"/>
</beans>
cs


위 예제처럼, Spring 의 XML 기반 설정 메타데이터는 bean 의 속성 및 생성자 인수로서, <property /> 와 <constructor-arg /> 요소 등을 내부에 가질 수 있다. 이 하부 요소에 value 속성을 가질 경우 문자열 표현("")으로 정의하지만, ConversionService API 에 의해 실제 소스에 정의된 type 으로 자동 변환된다. 여러 <property /> 요소 대신 p-namespace 를 사용하여 간략하게 설정할 수 있으며, 마찬가지로 <constructor-arg /> 요소 대신 c-namespace 를 사용할 수 있다.


아래와 같이 <property /> 와 <constructor-arg /> 요소 안에 또 다른 하위 요소를 지정할 수 있다.


  • <ref /> : bean 속성에 참조할 bean 의 id 나 name 을 지정하는 방식으로 가장 일반적인 방식이다.
  • <idref /> : 다른 bean 을 참조할 때 오타 등으로 인한 런타임 에러를 방지할 수 있다.
  • <bean /> :  내부 bean 이라 부르며, 이는 항상 익명 bean 이 된다.
  • List, Set, Map, Properties 타입의 속성과 인수를 각각 설정하기 위해, <list/>, <set/>, <map/>, <props/> 요소를 사용할 수 있다.
  • value 값이 "" 로 설정되었다면 빈 문자열로 취급되며, null 로 설정해야 할 경우 <null/> 요소를 사용한다.


<bean /> 요소에 depends-on 속성으로 의존성을 설정하면 <ref /> 요소로 설정한 의존성 보다 먼저 해당 bean 을 초기화 한다. 예를 들면 데이터베이스 드라이버 등록과 같은 것이 이에 해당한다. ApplicationContext 구현은 singleton bean 이 사전 인스턴스화 하는데, 사전 인스턴스화를 원하지 않는 bean 이 있다면 <bean /> 요소에 lazy-init 속성을 true 로 설정하여 해당 bean 의 요청이 있을 때 bean 인스턴스를 생성하도록 할 수 있다. <beans /> 요소의 default-lazy-init 속성을 사용하여 해당 컨테이너의 모든 bean 의 사전 인스턴스화를 제어할 수 있다.



Properties


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


<bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>
cs


Spring 컨테이너는 JavaBeans 의 PropertyEditor 메커니즘을 사용하여 <value /> 요소 내부의 텍스트를 java.util.Properties 인스턴스로 변환한다.



Autowiring


autowiring 을 사용하여 Spring 컨테이너가 bean 사이의 의존 관계를 자동으로 연결하게 할 수도 있으며, 이를 사용하면 속성이나 생성자의 인자의 설정을 줄일 수 있다. 사용 방법은 <bean /> 요소의 autowire 속성을 사용하여 no, byName, byType, constructor 모드 중 하나를 지정한다. byType 이나 constructor 모드를 사용하면, 배열과 형식화된 콜렉션도 연결할 수 있다.


  • no : autowiring 없음. (default)
  • byName : 속성의 name 과 일치하는 bean 을 찾아 autowiring.
  • byType : 속성의 type 과 일치하는 bean 을 찾아 autowiring. (둘 이상의 bean 이 존재할 경우 치명적인 예외 발생)
  • constructor : byType 과 유사하지만 생성자 인수와 일치하는 bean 을 찾아 autowiring. 컨테이너에 생성자 인수 타입의 bean 이 정확하게 하나가 아니면 치명적인 오류가 발생한다.


<property/> 와 <constructor-arg/> 요소로 설정된 의존성은 항상 autowiring 보다 우선하며, 단순한 속성들(primitives, Strings, Classes) 은 autowire 할 수 없다. autowiring 은 의존성을 명시적으로 지정한 것보다는 정확하지 않으므로, 자동 연결이 모호한 경우를 피하도록 주의해야 한다. 이 경우 autowiring 을 포기할 수도 있고, type 기반의 autowiring 이라면 <bean/> 요소의 autowire-candidate 속성을 false 로 설정하여 해당 bean 은 autowiring 을 사용하지 않게 할 수도 있다. 또는 <bean /> 요소의 primary 속성을 true 로 설정하여 단일 bean 정의를 기본 후보로 지정하거나, 어노테이션 기반의 세밀한 구성으로 해결할 수 있다.



Bean 상속 정의


자식 bean 은 부모 bean 에서 구성된 데이터를 상속하며, scope, 생성자 인수 값, 속성 값 및 메소드 등을 부모로부터 상속, 무시 및 재정의를 할 수 있다. 부모와 자식 bean 정의를 템플릿의 한 형태로 사용하면 많은 타이핑을 줄일 수 있다. 다음 설정은 항상 자식 bean 의 정의에서 가져온다 : depends on, autowire mode, dependency check, singleton, lazy init.


대부분의 사용자는 ClassPathXmlApplicationContext 로 bean 정의를 구성하여 사용하겠지만 프로그래밍 방식의 ApplicationContext 인터페이스로 작업하는 경우, 자식 bean 정의는 ChildBeanDefinition 클래스로 표현된다. XML 메타데이터를 사용할 때, 부모 bean 을 가리키는 parent 속성을 사용하여 자식 bean 을 정의한다.


<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>
 
<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age 속성 값 1 은 parent 에서 상속될 것이다. -->
</bean>
cs


앞의 예는 abstract 속성을 사용하여 명시적으로 부모 bean 을 추상으로 정의하였다. 부모 bean 정의에서 class 가 지정되지 않으면, 반드시 위와 같이 명시적으로 abstract 를 정의해야 한다. 이 경우 부모 bean 은 자체적으로 인스턴스화 될 수 없으며, 순수 템플릿 bean 정의로만 사용할 수 있다. 마찬가지로 컨테이너 내부의 preInstantiateSingletons() 메소드는 추상으로 정의된 bean 정의를 무시한다.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

Bean

Programming/Spring Framework 2017. 12. 1. 23:20

Spring IoC 컨테이너가 관리하는 모든 객체를 bean 이라고 한다. XML 메타데이터에 <bean /> 요소를 사용하거나, @Bean 어노테이션을 사용하여 bean 을 정의할 수 있다. bean 정의에는 패키지를 포함한 클래스 이름, scope, 라이프사이클 콜백, 다른 bean 의 참조(의존성), 해당 bean 의 추가 설정 등이 포함되어야 한다. 이 메타데이터들은 실제로 컨테이너 내부에서 BeanDefinition 객체를 통해 bean 으로 정의된다.



bean 의 속성


id / name 속성


컨테이너 내의 bean 은 중복된 이름을 가질 수 없으며, 유일해야 한다. <bean /> 요소의 id 나 name 속성을 사용하여 bean 의 식별자를 지정할 수 있으며, 주로 camel-case ('myBean', 'fooService' 등) 방식을 사용한다. bean 의 이름은 다른 bean 으로부터 의존성을 가지는 경우에 필요한 것이므로, 의존성을 가지지 않은 bean 은 이름을 지정하지 않아도 된다. 이 경우 컨테이너가 내부적으로 알아서 식별자를 생성할 것이다. 별칭을 사용하고 싶다면 <alias /> 요소를 사용하거나, name 속성에 쉼표 (,), 세미콜론 (;), 공백 등으로 구분하여 나열할 수 있다. @Bean 어노테이션의 name 속성을 사용하여 별칭을 제공할 수도 있다.


class / factory-bean / factory-method 속성


XML 메타데이터에서 <bean /> 요소의 class 속성에 인스턴스화 할 객체를 패키지를 포함한 타입(또는 클래스) 으로 지정한다. 컨테이너는 class 속성으로 해당 bean 의 생성자를 호출하여 bean 을 인스턴스화 하며, 클래스 내부의 static 클래스를 정의할 수도 있다 (예: com.example.Foo$Bar). 또한 factory-method 속성으로 해당 bean 의 static 팩토리 메소드를 호출하여 bean 을 인스턴스화 할 수도 있다. 이 때 static 팩토리 메소드는 객체를 반환할 수 있어야 한다.


<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
cs


public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}
 
    public static ClientService createInstance() {
        return clientService;
    }
}
cs


class 속성 대신 factory-bean 과 factory-method 사용으로 기존 bean 의 non-static 메소드를 호출하여 새 bean 을 인스턴스화 할 수 있다. factory-bean 을 통해 반환되는 객체로부터 여러 개의 bean 을 생성하는 것이 가능하다.


<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    ...
</bean>
 
<!-- factory bean 으로 생성된 bean -->
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
cs


public class DefaultServiceLocator {
 
    private static ClientService clientService = new ClientServiceImpl();
    private DefaultServiceLocator() {}
 
    public ClientService createClientServiceInstance() {
        return clientService;
    }
}
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

대부분의 어플리케이션은 유지보수가 필요하다. 불가피하게 수정을 하는 일이 발생하기도 하고, 추가 기능이 필요하기도 하다. 이 때 조금더 빠르고 간편하게 업데이트를 하기 위해서는 클래스 객체가 서로 분리되어 있어야 한다. 쉽게 말해 하나의 클래스를 수정한다고 해서 다른 클래스도 수정해야 하고, 또 다른 클래스까지 수정하는 일이 발생하지 않도록 개발해야 한다. Spring 에서 이러한 객체간의 결합도를 완화시켜주는 일을 하는 것이 바로 IoC 컨테이너(Inversion of Control) 이다. IoC 컨테이너는 객체와 객체 간의 의존성을 정의해 놓은 메타데이터를 기반으로 작동하기 때문에, 유지보수 시에는 해당 소스와 메타데이터만 수정하는 것으로 많은 노력을 줄여준다. 이 컨테이너를 IoC, 즉 “제어의 역전” 이라고 부르는 이유는 말그대로 제어가 반대로 진행되기 때문이다. 일반적으로 독립 실행형 Java 에서는 main 메소드 실행 > 의존성 생성 > 다른 메소드 실행 순으로 처리가 되지만, IoC 컨테이너를 사용하면 컨테이너가 의존성 생성 > main 메소드로 의존성 주입 > main 메소드 실행 순으로 처리된다. 그래서 Spring 의 이러한 컨테이너를 IoC 컨테이너라고 부른다.


IoC 컨테이너는 객체들로 구성된 보관소이다. 이 객체들은 XML 파일이나 어노테이션(@) 으로부터 메타데이터를 읽어와 초기화 되며, IoC 컨테이너에서 관리하는 이 객체들을 바로 bean 이라고 부른다. IoC 컨테이너는 객체(Beans) 의 생명주기를 관리하고 의존성 주입(Dependency Injection) 을 통해 각 계층이나 서비스들간의 의존성을 맞춰 준다.



컨테이너의 메타데이터


이 메타데이터는 주로 서비스 객체, 데이터 액세스 객체(DAOs), 프레젠테이션 객체, 인프라 객체, JMS Queue 등을 정의한다. 이 메타데이터에 정의된 객체를 기반으로 컨테이너를 생성하며, 컨테이너는 그 bean 이라는 모든 객체들을 관리한다. 메타데이터는 XML 파일, Java 기반, 어노테이션, 이렇게 3가지로 작성할 수 있다. 아래 각각의 장단점을 보고 어떤 방식의 메타데이터를 사용할 것인지를 결정해야 한다.


XML 기반의 메타데이터는 아키텍처의 논리 계층이나 모듈 별로 여러 XML 파일에 bean 을 정의할 수 있으며, 양이 많아질 경우 구조화에 신경을 써야 한다. ApplicationContext 인터페이스를 구현해서 bean 들을 사용할 수 있으며, 메타데이터 수정시 재컴파일 없이 반영시킬 수 있는 장점이 있다. 


Java 기반의 메타데이터는 일반적으로 @Configuration 어노테이션 클래스 내에서 @Bean 어노테이션 메소드를 사용하여 bean 을 구성한다. XML 파일이 필요 없으므로 구성이 간결하며, 에러 검출이 쉽다. 개인적으로 선호하는 방법이기도 하다. 메타데이터 구성을 한 번에 파악하기 힘들며, 소스코드와 밀접하게 결합하는 문제로 사용을 반대하는 사람도 있다.


어노테이션 기반의 메타데이터는 XML 과 Java 를 혼용하여 사용하는 것이다. 어노테이션 주입이 XML 주입 보다 먼저 수행되므로 두 가지에 모두 정의된 속성은 XML 로부터 재정의 될 것이다. 두 가지 방식을 사용하다 보니 모든 구현이 가능한 반면, 구성이 분산되기 때문에 유지보수 면에서 힘들 수도 있다.



ApplicationContext


ApplicationContext 인터페이스가 곧 Spring IoC 컨테이너이며, ClassPathXmlApplicationContext 나 FileSystemXmlApplicationContext 의 인스턴스를 생성하여 구현하는 것이 일반적이다. IoC 컨테이너는 org.springframework.beans 와 org.springframework.context 패키지를 기반으로 구현된다. 메타데이터를 기반으로 이 인터페이스를 구현하면 IoC 컨테이너를 사용할 수 있다. org.springframework.beans.factory.BeanFactory 인터페이스가 모든 타입의 객체를 관리하지만, 이를 확장한 org.springframework.context.ApplicationContext 인터페이스에 메시지 리소스 처리, 이벤트 처리, 웹 어플리케이션 지원 등의 고급 기능이 추가되어 있기 때문에, 딱 BeanFactory 의 기능만 필요한 것이 아니라면 ApplicationContext 를 사용하는 것이 편할 것이다. ApplicationContext 는 모든 bean 을 정의한 메타데이터를 기반으로 각 bean 을 검증, 생성, 초기화 하며 오류가 있을 경우 즉시 발견할 수 있다.



컨테이너의 bean 조회하는 예제


XML, Java, 어노테이션 기반의 메타데이터에서 myService 라는 bean 을 조회하는 간단한 예제를 보자. 매우 간단하므로 대충 보이는 형태만 감상하자.



XML 기반 설정


XML 메타데이터에 myService bean 정의하기.


> application-bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation"http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean name="myService" class="com.oops4u.blog.ex.MyServiceImpl" />
</beans>
cs


XML 파일의 메타데이터를 기반으로 컨테이너를 생성하여 myService bean 조회하기.


> Process.class

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("application-bean.xml");
    MyService myService = context.getBean("myService", MyService.class);
    ...
}
cs



Java 기반 설정


Java Config 코드의 메소드에 bean 정의하기.


> AppConfig.java

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImp();
    }
}
cs


@Configuration 어노테이션 클래스를 기반으로 컨테이너를 생성하여 @Bean 메소드인 myService 조회하기


> Process.class

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = context.getBean("myService", MyService.class);
    ...
}
cs



어노테이션 기반 설정


XML 파일과 @Configuration 을 혼용하는 어노테이션 기반 설정은 XML 기반의 방식과 유사하게 bean 을 정의하고, 해당 XML 파일로 컨테이너를 인스턴스화 한다. 그리고 기존의 XML 파일에 정의하던 각종 의존성을 @Autowired 을 비롯한 많은 어노테이션으로 Java 코드에 정의하는 방식이다. 또는 AnnotationConfigApplicationContext 를 사용하여 @ImportResource 로부터 XML 을 가져오는 방법도 있다.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,