Spring Framework 소개: IoC, Bean 설정 및 핵심 API

Spring이란?

오늘날 제어의 역전(Inversion of Control) 원칙은 큰 인기를 얻고 있으며, Spring은 Java 또는 J2EE 애플리케이션을 구축하기 위해 이 원칙을 광범위하게 채택한 경량 프레임워크(Light-Weight Framework)입니다. 대부분의 경우 애플리케이션은 무거운 J2EE 컨테이너가 제공하는 모든 서비스에 접근할 필요가 없음에도 불구하고 이를 사용하곤 합니다. 이러한 경우, 애플리케이션은 Spring 프레임워크/컨테이너가 제공하는 경량 서비스에 의존할 수 있습니다. 이 기사에서는 이것이 어떻게 가능한지 간략하게 설명합니다. 하지만 Spring이 J2EE 컨테이너를 완전히 대체하는 것은 아닙니다. 이 기사는 선언적으로 Bean을 선언하고 초기화하는 방법, Bean 간의 의존성 설정 방법 등 Spring 프레임워크의 핵심 기능에 대한 소개를 제공합니다. 기사의 후반부에서는 Bean XML 설정 파일 내에서 사용 가능한 다양한 기능을 풍부한 샘플 코드와 함께 자세히 살펴봅니다.

Spring의 핵심은 비즈니스 객체(Business Objects)와 그 관계를 관리하기 위한 프레임워크이자 컨테이너로 생각할 수 있습니다. 이 프레임워크의 장점은 대부분의 경우 Spring 특정 클래스나 인터페이스에 의존할 필요가 없다는 점입니다. 이는 클라이언트 애플리케이션이 자체 구현에 의존하도록 강제하는 다른 프레임워크와 다릅니다. 예를 들어, 서블릿(Servlet) 또는 EJB와 같은 다양한 J2EE 컴포넌트를 생각해보십시오. 개발자가 서블릿을 작성하려면 클래스가 HttpServlet에 의존해야 하며, 엔터프라이즈 빈을 생성하는 경우도 마찬가지입니다.

Spring의 설계자들은 클라이언트와 Spring 프레임워크 간의 결합도를 최소한으로 유지하기 위해 프레임워크를 설계하는 데 충분한 시간을 투자했습니다. 대부분의 경우 결합도는 종종 0에 가깝습니다. 다시 말해, Spring에서 작성하는 비즈니스 컴포넌트는 POJO(Plain Old Java Object) 또는 POJI(Plain Old Java Interface)에 불과합니다. POJO/POJI는 타사 구현을 특별히 확장하거나 구현하지 않는 클래스 또는 인터페이스를 의미합니다. 애플리케이션에서 대부분의 클래스나 인터페이스를 POJO/POJI로 유지하는 주요 이점은 애플리케이션에서 쉬운 단위 테스트(Unit Testing)를 용이하게 한다는 것입니다.

예를 들어, 다음 Non-POJO 클래스를 고려해보십시오.

class MyServlet extends HttpServlet {
}

위 클래스 정의의 문제점은 HttpServlet 클래스를 확장하고 있기 때문에 POJO가 아니라는 점입니다. 이 클래스가 단위 테스트를 진행하려면, 실제로 배포된 웹/애플리케이션 서버를 구동해야만 이 클래스의 기능을 확인할 수 있습니다. HttpServlet 클래스의 확장은 실행 중인 서버의 맥락에서만 의미가 있기 때문입니다. Spring 프레임워크는 비즈니스 객체 간에 긴밀한 결합을 제공하지 않으므로 단위 테스트가 빨라져 TDD(Test Driven Development)를 쉽게 구현할 수 있습니다.

Spring 모듈

Spring 프로젝트는 단일 프로젝트가 아니라 여러 모듈로 구성됩니다. 모듈은 특정 영역에 매우 특화된 기능으로 정의하거나 생각할 수 있습니다. Spring 배포판은 이러한 여러 모듈로 제공됩니다. Spring 모듈의 이름과 jar 파일 이름(SPRING_HOME\dist\modules에서 사용 가능)은 아래에 나열되어 있습니다.

  • Spring Web MVC (spring-webmvc.jar)
  • Spring Aop (spring-aop.jar)
  • Spring Beans (spring-beans.jar)
  • Spring Context (spring-context.jar)
  • Spring Core (spring-core.jar)
  • Spring Dao (spring-dao.jar)
  • Spring Hibernate (spring-hibernate3.jar)
  • Spring Ibatis (spring-ibatis.jar)
  • Spring Jca (spring-jca.jar)
  • Spring Jdbc (spring-jdbc.jar)
  • Spring Jdo (spring-jdo.jar)
  • Spring Jms (spring-jms.jar)
  • Spring Jms (spring-jpa.jar)
  • Spring Jmx (spring-jmx.jar)
  • Spring Portlet (spring-portlet.jar)
  • Spring Remoting (spring-remoting.jar)
  • Spring Struts (spring-struts.jar)
  • Spring Support (spring-support.jar)
  • Spring Toplink (spring-toplink.jar)
  • Spring Web (spring-web.jar)
  • Spring Aspects (spring-aspects.jar)

위 목록의 각 모듈은 Jar 파일의 이름으로 식별되는 고유한 기능을 가지고 있습니다. 예를 들어, Spring Jmx (spring-jmx.jar)는 Spring Bean 컴포넌트에 계측 및 관리 지원(Instrumentation and Management Support)을 제공합니다. 마찬가지로 Spring Web은 서버 측 웹 애플리케이션 인프라 개발을 제공합니다. 모든 기능이 모듈화되어 별도의 기능(Jar 파일)으로 제공되므로, 애플리케이션이 관점 지향 프로그래밍(spring-aspects.jar)데이터베이스 접근(spring-jdbc.jar) 기능을 사용하려면 이 두 개의 jar 파일을 클래스패스에 포함시키면 됩니다.

하지만 애플리케이션이 모든 다양한 모듈의 기능을 필요로 한다면 어떻게 될까요? 모든 Jar 파일에 대한 항목을 클래스패스에 정의해야 할까요? Spring은 이 필요에 대한 스마트한 솔루션을 제공합니다. 모든 모듈의 조합인 spring.jar라는 Jar 파일과 함께 제공되기 때문입니다.

제어의 역전 (Inversion of Control)

Spring 프레임워크의 기본 원칙인 제어의 역전을 이해하는 것은 매우 중요합니다. 몇 가지 샘플 코드를 통해 이 원칙에 대해 자세히 설명하겠습니다. 다음 Java 클래스가 포함된 샘플 코드를 고려해보십시오.

public class TaskService{
    public Task createTask(){ ... }
    public boolean deleteTask(){ ... }
    public Set<Task> listTasks(){ ... }
    public void update(Task task){ ... }
}

class Task{
    private TaskService taskService;
    public Task(TaskService taskService){
        this.taskService = taskService;
    }
    public void setTaskService(TaskService taskService){
        this.taskService = taskService;
    }
    public TaskService getTaskService(){
        return taskService;
    }
    public void update(){
        taskService.update();
    }
}

위 클래스는 애플리케이션에서 다양한 컴포넌트가 수행할 작업을 나타내는 Task 객체를 나타냅니다. TaskService 클래스는 서비스 컴포넌트로, 다른 컴포넌트에서 Task 객체 생성(createTask()), 삭제(deleteTask()), 목록 조회(listTasks()) 기능을 제공하는 데 사용됩니다. 원래 Task 모델은 Task 클래스로 표현되며 TaskService 객체에 의존하는 일련의 메서드를 가지고 있습니다.

클라이언트 애플리케이션이 Task.update()를 호출하여 Task 객체를 업데이트하려면 다음과 같은 코드를 작성할 것입니다.

class TaskClient{
    public static void main(String args[]){
        TaskService service = new TaskService();
        Task task = new Task(service);
        task.update();
    }
}

의존성 주입(Dependency Injection)으로 돌아가서, 여기에는 TaskTaskService라는 두 개의 컴포넌트가 있습니다. Task는 다양한 기능을 얻기 위해 TaskService 컴포넌트에 의존합니다. 따라서 TaskTaskService 컴포넌트 사이에는 의존성 연관성이 있습니다. 이 예제에서 컴포넌트 간의 연관성 또는 관계는 이를 사용하는 클라이언트에 의해 설정됩니다. 또한 Task 컴포넌트는 TaskService 컴포넌트에 강하게 의존하고 있습니다. 향후 요구 사항이 변경되어 Task 객체가 이제 TaskService 대신 TimerService에 의존해야 한다면, 이는 일반적으로 좋지 않은 설계로 간주되는 애플리케이션 코드의 주요 변경을 초래할 것입니다. 그렇다면 이 문제를 어떻게 해결할 수 있을까요?

제어의 역전이 이에 대한 해결책을 제공합니다. 가장 먼저 해야 할 일은 컴포넌트 간의 느슨한 결합(Loose Coupling)을 유지하는 것입니다. 즉, 컴포넌트 간의 모든 관계나 연관성은 가능한 한 추상화되어야 합니다. 이는 Task 클래스가 TaskService 클래스에 의존해서는 안 된다는 것을 의미합니다. Java에서 추상화는 인터페이스나 추상 클래스의 형태로 캡처되므로 둘 중 하나를 사용하는 것이 좋습니다. 하지만 추상화를 캡처하기 위해 인터페이스를 사용하는 것이 적극 권장됩니다. 이는 Task 클래스가 TaskService 클래스에 직접 의존하지 않고, TaskService 또는 TimerService가 될 수 있는 서비스 클래스에 의존하도록 정의해야 함을 의미합니다. 즉, 이제 다음과 유사한 클래스/인터페이스 구조를 가지게 됩니다.

public interface Service { }
public interface TaskService extends Service { }
public interface TimerService extends Service { }

그리고 이제 이전에 TaskService를 참조하던 Task 클래스의 코드는 다음과 같이 변경될 수 있습니다.

class Task{
    private Service service;
    public Task(Service service){
        this.service = service;
    }
    public void setService(Service service){
        this.service = service;
    }
    public Service getService(){
        return service;
    }
}

이제 런타임에 TaskTaskService에 의존해야 하는지 TimerService에 의존해야 하는지는 최소한의 변경으로 쉽게 연결할 수 있습니다.

Service service = new TaskService(); // 또는 new TimerService();
Task task = new Task(service);

제어의 역전의 두 번째 사항은 클라이언트 애플리케이션이 코드를 통해 컴포넌트 간의 연관성을 만드는 데 관여해서는 안 된다는 것입니다. 대신 컨테이너(Container) 또는 프레임워크(Framework)라고 불리는 무언가가 이러한 종류의 컴포넌트 와이어링(Component Wiring) 작업을 수행해야 합니다. 컴포넌트 와이어링은 다양한 컴포넌트 간의 연관성을 만드는 데 사용되는 멋진 용어입니다. 이제 컴포넌트 와이어링 작업을 수행하는 애플리케이션을 다시 살펴보겠습니다. 이제 TaskService 객체에 의존하며, 다음 코드 조각을 통해 TaskService 객체 간의 관계를 설정하는 것은 클라이언트 코드입니다.

Service service = ...;
Task task = new Task();
task.setService(service);

프레임워크는 비즈니스 객체 간의 연관성이 외부화되어야 하며 클라이언트 애플리케이션이 이러한 종류의 활동을 수행하는 데 관여해서는 안 된다고 주장합니다. 이상적으로는 setService() 메서드가 프레임워크에 의해 호출되어야 합니다. 여기서 애플리케이션 제어가 역전되는 것을 볼 수 있습니다. 클라이언트가 컴포넌트 간의 관계를 설정하는 제어권을 가지는 대신, 이제 프레임워크가 이 작업을 수행합니다. 즉, 제어권이 클라이언트에서 프레임워크로 역전되며, 이것이 바로 이 원칙이 제어의 역전이라고 불리는 이유입니다.

Spring 핵심 API

Spring의 핵심 API는 매우 제한적이며 일반적으로 다양한 비즈니스 컴포넌트 간의 설정(Configuring), 생성(Creating)연관성 만들기(Making Associations)와 관련됩니다. Spring은 이러한 비즈니스 컴포넌트를 Bean이라고 부릅니다. 다음은 이 목표를 달성하기 위해 Spring에서 사용할 수 있는 핵심 클래스 또는 인터페이스입니다.

  • Resource
  • BeanFactory

Resource

Spring에서 Resource파일(File) 또는 스트림(Stream)에서 오는 모든 종류의 정보를 나타냅니다. 예를 들어, 리소스는 Spring 애플리케이션에 필요한 다양한 설정 정보가 포함된 XML 파일을 나타낼 수 있습니다. 또는 Bean 객체를 나타내는 Java 클래스 파일(Java Class File)을 나타낼 수도 있습니다. 어떤 경우든 구현이 투명한 Resource 객체로 표현될 수 있습니다.

MyJavaClass라는 Java 클래스를 나타내는 리소스를 로드하려면 다음과 같이 할 수 있습니다.

Resource classRes = new ClassPathResource("클래스파일경로", MyJavaClass.class);

ClassPathResource는 Java 클래스 파일을 로드하기 위한 구체적인 구현 클래스입니다. 다음 코드는 로컬 파일 시스템에서 XML 파일을 로드합니다.

Resource fileRes = new FileSystemResource("beans.xml");

FileSystemResource는 모든 종류의 파일을 로드하여 Spring 애플리케이션에서 사용할 수 있도록 하는 데 사용할 수 있으며, XML 파일로 제한되지 않습니다. 일반적으로 사용되는 다른 Resource로는 입력 스트림에서 콘텐츠를 로드하는 InputStreamResource가 있습니다. 이 외에도 Spring 프레임워크에는 다양한 Resource의 구체적인 구현이 많이 있습니다.

BeanFactory

언급했듯이, Spring 용어에서 Bean은 고려 중인 비즈니스 컴포넌트를 나타냅니다. 따라서 BeanFactory는 Bean 객체를 생성하기 위한 팩토리 클래스입니다. 흥미로운 점은 비즈니스 컴포넌트를 생성하기 위해 BeanFactory를 어떻게 설정하는지입니다. 즉, BeanFactory 클래스가 Bean 인스턴스를 생성하기 위해 Bean 정의를 어디서 찾아야 하는지입니다. 중요한 점은 Spring 컨테이너 컨텍스트에 있는 모든 Bean은 외부 파일을 통해 매우 설정 가능하다는 것입니다. 즉, Bean 정의는 XML 파일, Java 속성 파일 또는 데이터베이스에 있을 수 있습니다. Bean 정의는 어떤 형식에도 강하게 결합되어 있지 않지만, 대부분의 개발자는 XML 형식으로 Bean 정의를 작성하는 것을 선호합니다.

다음은 XML 파일에서 모든 Bean 정의를 로드하는 코드입니다.

Resource xmlResource = new FileSystemResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(xmlResource);

XmlBeanFactory 클래스는 BeanFactory 클래스의 구체적인 구현 중 하나입니다.

Spring MVC 샘플 애플리케이션

XML 파일을 통해 설정할 수 있는 다양한 기능과 특징에 대한 다른 세부 사항을 살펴보기 전에 샘플 애플리케이션을 살펴보는 것이 좋습니다. 비즈니스 객체의 기능을 최소한으로 유지하겠습니다.

비즈니스 객체

다음은 주어진 이름을 저장하는 데 사용되는 샘플 비즈니스 컴포넌트 Namer의 코드입니다.

package com.example.spring.intro;
public class Namer {
    private String name;
    public Namer() { }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

이 클래스에는 'name'이라는 단일 속성과 적절한 getter/setter 메서드가 있습니다.

XML 설정 파일

이제 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-2.0.xsd">
    <bean id = "namerId" class = "com.example.spring.intro.Namer">
        <property name = "name">
            <value>Javabeat</value>
        </property>
    </bean>
</beans>

가장 먼저 주목할 점은 이 XML이 정의된 표준 스키마를 따른다는 것입니다. XML 파일의 루트 요소는 정의될 비즈니스 객체의 모음을 나타내는 'beans' 요소입니다. 각 비즈니스 객체는 'id''class'라는 두 가지 속성을 가진 'bean' 요소로 식별됩니다. 'id' 속성은 XML 파일 내에서 고유해야 하며 코드에서 Bean 클래스를 참조하는 데 사용되는 반면, 'class' 속성은 Bean 정의의 정규화된 클래스 이름을 나타냅니다. 단일 값 'Javabeat'를 나타내는 property 요소도 확인하십시오.

클라이언트 애플리케이션

다음은 Resource 객체를 사용하여 XML 파일을 참조한 다음 XmlBeanFactory 클래스를 사용하여 XML 파일의 내용(다양한 Bean 정의 포함)을 읽는 클라이언트 코드입니다. BeanFactory.getBean(id) 메서드를 호출하여 Namer 유형의 객체 인스턴스를 검색합니다. 이 id 인수 값은 XML 파일에 정의된 bean 요소의 'id' 속성에 해당합니다.

package com.example.spring.intro;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.FileSystemResource;

public class SimpleSpringApp {
    public static void main(String args[]){
        Resource configFile = new FileSystemResource("src/resources/namer.xml");
        BeanFactory factory = new XmlBeanFactory(configFile);
        Namer namer = (Namer) factory.getBean("namerId");
        System.out.println(namer.getName());
    }
}

Bean 정의 설정 파일 탐구

Bean 클래스의 모든 기본 정의와 설정 정보, 다른 Bean 객체와의 관계는 XML 설정 파일에서 정의할 수 있습니다. Bean XML 설정 파일의 스키마 정의는 정의와 기능 측면에서 매우 방대하며, 이후 섹션에서는 주요 설정 기능만 다루도록 하겠습니다.

Bean 객체 간 연관성 만들기

애플리케이션에서는 많은 컴포넌트가 서로 상호 작용하여 클라이언트 요구를 충족시키는 것을 흔히 볼 수 있습니다. 이를 위해 컴포넌트 간에 관계를 설정(Establish Relation-ship)해야 하며, 이는 설정 파일에서 쉽게 수행할 수 있습니다. PlayerTeam이라는 두 개의 컴포넌트가 있다고 가정해보겠습니다.

public class Player {
    private String name;
    private Team team;
    // getters/setters 생략
}

public class Team {
    private String name;
    // getters/setters 생략
}

위의 경우 PlayerTeam이라는 두 개의 Bean 컴포넌트가 있습니다. Player는 하나의 Team에 속하므로 Player 클래스 내부에 Team 객체에 대한 참조가 있습니다. 이제 TeamPlayer 클래스의 속성이 됩니다. 그러나 이는 문자열이나 정수를 보유하는 단순한 속성이 아니라 데이터 유형이 Team인 복합 속성입니다.

다음은 PlayerTeam 객체 간의 연관성을 만들기 위한 XML 코드 조각입니다.

<bean id="india" class="com.example.spring.complex.Team">
    <property name="name"><value>India</value></property>
</bean>
<bean id="tendulkar" class="com.example.spring.complex.Player">
    <property name="name"><value>Sachin Tendulkar</value></property>
    <property name="team">
        <ref bean="india"/>
    </property>
</bean>

'ref' 요소를 통해 Team(india)이 Player 객체에 참조되며, bean 속성 값은 이전에 정의된 Team 객체의 'id' 속성에 해당합니다.

컬렉션 속성 매핑

이 섹션에서는 TeamPlayer 클래스 간의 관계를 강화하여 다양한 컬렉션 속성(Collection Properties)을 포함시켜 보겠습니다.

package com.example.spring.complex;
import java.util.*;

public class Team {
    private String name;
    private Set<Player> players;
    // 생성자, getters, setters, toString() 생략
}

public class Player {
    private String name;
    private Team team;
    private Map<Team, Integer> runsScored;
    // 생성자, getters, setters, toString() 생략
}

Team에는 여러 명의 선수가 포함되므로 이를 Set<Player> players 형태로 프로그래밍 측면에서 표현했습니다. 설정 파일에서 선수 집합을 매핑하는 방법을 살펴보겠습니다. 또한 Player 클래스는 여러 팀을 상대하여 득점한 점수를 나타내기 위해 Team을 키로 하고 Integer(특정 팀을 상대로 득점한 점수)를 값으로 하는 Map 속성을 가집니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="india" class="com.example.spring.complex.Team">
        <property name="name"><value>India</value></property>
        <property name="players">
            <set>
                <ref bean="tendulkar"/>
                <ref bean="dravid"/>
                <ref bean="ganguly"/>
            </set>
        </property>
    </bean>

    <bean id="australia" class="com.example.spring.complex.Team">
        <property name="name"><value>Australia</value></property>
    </bean>
    <bean id="south-africa" class="com.example.spring.complex.Team">
        <property name="name"><value>South Africa</value></property>
    </bean>

    <bean id="tendulkar" class="com.example.spring.complex.Player">
        <property name="name"><value>Sachin Tendulkar</value></property>
        <property name="team"><ref bean="india"/></property>
        <property name="runsScored">
            <map>
                <entry>
                    <key><ref bean="australia"/></key>
                    <value>5638</value>
                </entry>
                <entry>
                    <key><ref bean="south-africa"/></key>
                    <value>6383</value>
                </entry>
            </map>
        </property>
    </bean>
    
</beans>

Team이 선수 집합을 나타내므로 이 정보는 'set' 요소를 통해 XML 파일에서 설정됩니다. 집합의 요소 자체가 Bean 객체이므로 'ref' 요소를 사용하여 언급됩니다. Player 객체의 경우 'runsScored' 속성이 Team 객체를 키로 하고 득점 수(정수)를 값으로 하는 Map으로 표현되므로, 전체 구조는 키와 값 요소가 있는 map 요소를 사용하여 표현됩니다.

다음은 여러 Bean 객체의 값을 로드하고 출력하는 클라이언트 코드입니다.

Resource resource = new FileSystemResource("./src/resources/player-team.xml");
BeanFactory factory = new XmlBeanFactory(resource);

Team india = (Team) factory.getBean("india");
System.out.println(india);

Player tendulkar = (Player) factory.getBean("tendulkar");
System.out.println(tendulkar);

설정 파일 가져오기

하나의 설정 파일에 너무 많은 Bean 정의 정보를 포함하면, 특히 파일 크기가 커질 경우 지저분해 보일 수 있습니다. Spring은 이 문제에 대한 모듈식 솔루션을 제공합니다. 한 유형에 대한 Bean 정의 객체를 별도의 파일에 정의하고 메인 설정 파일에 포함시킬 수 있습니다. 이전 예제를 고려하여 모든 Player 객체를 player.xml에, Team 객체를 team.xml에 정의하고, player.xmlteam.xml 파일을 모두 포함하는 main.xml 파일을 가질 수 있습니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <import resource="player.xml"/>
    <import resource="team.xml"/>
</beans>

Bean 생명주기

XML 설정 파일에 정의된 모든 Bean 객체는 표준 생명주기 메커니즘(Standard Lifecycle Mechanism)을 거칩니다. InitializingBeanDisposableBean과 같은 생명주기 인터페이스를 사용할 수 있습니다. 이러한 인터페이스를 사용할 때는 주의해야 합니다. 이는 Spring 특정 인터페이스이므로, 애플리케이션 코드가 Spring 구현에 강하게 결합(Coupled with the Spring Implementation)되기 때문입니다. 따라서 정말 필요할 때만 이러한 인터페이스를 정의하고 사용하십시오.

InitializingBean 인터페이스에는 XML 설정 파일에 정의된 모든 속성 값이 설정된 직후에 호출되는 afterPropertiesSet()이라는 단일 메서드가 있습니다. 사용자 정의 초기화 로직이나 필수 검사를 여기서 수행할 수 있습니다. 마찬가지로 DisposableBean에는 Bean 컨테이너가 종료될 때 호출되는 destroy()라는 단일 메서드가 있습니다.

package com.example.spring.complex;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.DisposableBean;

public class Employee implements InitializingBean, DisposableBean {
    private String name;
    private String id;

    public void afterPropertiesSet() {
        System.out.println("Employee -> afterPropertiesSet() 호출됨");
    }

    public void destroy() {
        System.out.println("Employee -> destroy() 호출됨");
    }
}

Bean 생성 순서 제어

컴포넌트 A가 컴포넌트 B보다 먼저 생성되어야 하는 상황이 발생할 수 있습니다. 이러한 경우 이전에 정의된 Bean 정의 식별자 목록을 사용하는 depends-on 속성을 사용할 수 있습니다. 예를 들어, Employee, Department, Organization이라는 세 개의 Bean 클래스가 있고 Bean 객체의 생성 순서를 Organization, Department, Employee 순서로 유지하려는 상황을 가정해보겠습니다.

<bean id="joseph" class="com.example.spring.complex.Employee" depends-on="admin"/>
<bean id="admin" class="com.example.spring.complex.Department" depends-on="oracle"/>
<bean id="oracle" class="com.example.spring.complex.Organisation"/>

팩토리 클래스를 통한 Bean 인스턴스 생성

Bean 객체를 생성하기 위한 팩토리 클래스가 이미 있고 이 팩토리 클래스를 활용하려는 경우 'factory-method''factory-bean' 속성을 사용합니다.

public class Team {
    private String name;
    // getters/setters 생략
}

public class TeamFactory {
    public static Team getTeamUsingStaticMethod(){
        System.out.println("정적 메서드 호출됨");
        return new Team();
    }
    public Team getTeamNormalMethod(){
        System.out.println("인스턴스 메서드 호출됨");
        return new Team();
    }
}

정적 팩토리 메서드 TeamFactory.getTeamUsingStaticMethod()를 사용하려면 설정 파일의 Bean 정의를 다음과 같이 변경해야 합니다.

<bean id="india" class="com.example.spring.complex.TeamFactory"
    factory-method="getTeamUsingStaticMethod">
    <property name="name"><value>India</value></property>
</bean>

이 경우 class 속성은 팩토리 클래스의 이름을 가리켜야 하고 factory-method 속성은 인스턴스를 반환할 정적 메서드의 이름이어야 합니다. 그리고 평소와 같이 property 요소는 인스턴스를 생성하는 동안 Team 객체에 값을 전달하는 데 사용됩니다.

대신 일반 인스턴스 메서드, 즉 TeamFactory.getTeamNormalMethod()를 사용하려면 팩토리 클래스 자체를 Bean으로 정의한 다음 Bean 정의에서 인스턴스 메서드를 참조해야 합니다.

<bean id="teamFactoryId" class="com.example.spring.complex.TeamFactory"/>

<bean id="australia" factory-bean="teamFactoryId"
    factory-method="getTeamNormalMethod">
    <property name="name"><value>Australia</value></property>
</bean>

가장 먼저 주목할 점은 팩토리 클래스 TeamFactory에 대한 Bean 정의가 만들어지고, 그런 다음 Bean 자체의 정의에서 'factory-bean' 속성이 이전에 정의된 식별자를 가리키며 factory-method 속성은 Team 객체를 생성할 인스턴스 메서드의 이름을 가리킨다는 것입니다.

Bean 상속

'bean' 태그 내의 'parent' 속성 형태로 Bean 컴포넌트 간에 상속(Inheritance)의 최소한의 지원이 제공됩니다. 예를 들어, PlanetEarth 클래스를 고려해보겠습니다. Planet 클래스에는 'name''shape'라는 두 개의 속성이 있습니다. 이 속성들의 값은 각각 'Planet'과 'Elliptical'일 수 있습니다. Earth 클래스(Planet이기도 함)는 Planet 클래스를 확장하지만, Earth 클래스의 속성 값은 'Earth'와 'Elliptical'이어야 합니다. 따라서 여기서 두 가지 작업을 수행합니다. 하나는 Earth 클래스가 Planet 클래스를 확장하는 것이고, 다른 하나는 Planet 객체의 값('Planet', 'Elliptical')이 Earth 객체의 값('Earth', 'Elliptical')으로 재정의된다는 것입니다.

public class Planet {
    private String name;
    private String shape;
    // getters/setters 생략
}

public class Earth extends Planet {
}

XML 설정 파일의 변경 사항은 Earth 객체의 Bean 정의에 'parent' 속성을 포함시키는 것입니다. 또한 name 속성에 대해 'Earth' 값을 원하므로 값을 다시 정의하여 재정의했습니다.

<bean id="planet" class="com.example.spring.complex.Planet">
    <property name="name"><value>Planet</value></property>
    <property name="shape"><value>elliptical</value></property>
</bean>

<bean id="earth" class="com.example.spring.complex.Planet" parent="planet">
    <!-- name 속성 값 재정의 -->
    <property name="name"><value>Earth</value></property>
</bean>

클라이언트 애플리케이션 코드:

Resource resource = new FileSystemResource("./src/resources/planet-earth.xml");
BeanFactory factory = new XmlBeanFactory(resource);

Planet planet = (Planet) factory.getBean("planet");
System.out.println(planet);
Planet earth = (Planet) factory.getBean("earth");
System.out.println(earth);

태그: Spring Framework IOC DI BeanFactory XML Configuration

7월 4일 16:10에 게시됨