Spring Framework/Spring

[Spring] 1. IoC Container

hyomee2 2024. 9. 4. 11:29

IoC와 IoC Container의 정의

1. IoC(Inversion of Control, 제어의 역전)란?

: 일반적인 프로그래밍에서, 프로그램의 제어 흐름 구조가 뒤바뀌는 것

- 객체의 생성, 관리, 객체 간의 의존성 처리 등을 프레임워크에서 대신 처리해주는 것이 IoC의 대표적인 예이다.

  ㄴ> 객체를 관리하는 게 개발자가 아니라 IoC container가 주도권을 갖고 처리하는 것을 제어의 역전이라 한다.

 

2. IoC Container란?

: IoC를 구현한 구체적인 프레임워크(= 제어의 역전이 구현되어 있는 프레임워크)

- IoC Container를 이용하면 객체 생성, 초기화, 의존성 처리 등을 자동으로 수행할 수 있다.

- 대표적인 IoC Container로는 Spring Framework의 ApplicationContext가 있다.


Spring IoC Container

1. Bean이란?

: Spring IoC Container에서 관리되는 객체

- Spring은 bean을 생성하고, 초기화, 의존성 주입, 제거하는 등의 일을 IoC Container를 통해 자동으로 처리할 수 있다.

 

2. Bean Factory란?

: Spring IoC Container의 가장 기본적인 형태로, Bean의 생성, 초기화, 연결, 제거 등의 라이프 사이클을 관리

- 혼자서 관리하는 것은 아니고, 이를 위해 Configuration Metadata를 사용한다.

 

3. Configuration Metadata란?

: Bean Factory가 IoC를 적용하기 위해 사용하는 설정 정보

- IoC Container에 의해 관리되는 Bean 객체를 생성하고 구성할 때 사용된다.

* POJOs란?

- Plain Old Java Object로, 외부적인 기술없이 순수하게 만든 자바 클래스이다.

- POJOs를 Spring Container에 맡기는데,

  어떻게 사용하라는지에 대한 설정 정보를 Configuration Metadata(설정 정보)를 담아서 맡긴다.

  개발자가 짜는 건 이 POJOs(비즈니스 로직)이다.

 

4. Application Context란?

: Bean Factory를 확장한 IoC Container로, Bean을 등록하고 관리하는 기능은 Bean Factory와 동일하지만,

Spring이 제공하는 각종 부가 기능을 추가로 제공한다.

- ListableBeanFactory: Bean Factory가 제공하는 모든 기능 포함

- ApplicationEventPublisher: 이벤트 처리(Event Handling) 기능 제공

- MessageSource: 국제화(i18n)를 지원하는 메세지를 해결하는 부가 기능 제공

- ResourceLoader: 리소스 핸들링(Resource Handling) 기능 제공

- GenericXmlApplictaionContext: ApplicationContext를 구현한 클래스로, XML MetaData Configuration을 읽어 컨테이너 역할 수행

- AnnotationConfigApplicationContext: ApplicationContext를 구현한 클래스로, Java MetaData Configuration을 읽어 컨테이너 역할 수행

- cf. XML로 MetaData를 전달하는 거랑 Java로 MetaData를 전달하는 거랑 Application Context가 다르다.


IoC Container 사용하기

IntelliJ에서 새 프로젝트를 만든 뒤 build.gradle에 아래 라이브러리(Spring Context, Project Lombok)를 추가해준다.

("MVN REPOSITORY" 에서 복사할 수 있다.)

    // https://mvnrepository.com/artifact/org.springframework/spring-context
    implementation 'org.springframework:spring-context:6.1.12'

    // https://mvnrepository.com/artifact/org.projectlombok/lombok
    compileOnly 'org.projectlombok:lombok:1.18.34'
    annotationProcessor 'org.projectlombok:lombok:1.18.34'

 

build.gradle은 아래와 같다.

plugins {
    id 'java'
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    // https://mvnrepository.com/artifact/org.springframework/spring-context
    implementation 'org.springframework:spring-context:6.1.12'

    // https://mvnrepository.com/artifact/org.projectlombok/lombok
    compileOnly 'org.projectlombok:lombok:1.18.34'
    annotationProcessor 'org.projectlombok:lombok:1.18.34'
    testImplementation platform('org.junit:junit-bom:5.10.0')
    testImplementation 'org.junit.jupiter:junit-jupiter'
}

test {
    useJUnitPlatform()
}

 

테스트에 공통적으로 사용할 MemberDTO 클래스이다.

@Getter
@Setter
@ToString
@AllArgsConstructor // 모든 멤버 변수를 파라미터로 받는 생성자 생성
public class MemberDTO {
    private int sequence;
    private String id;
    private String pwd;
    private String name;
}

 

1. XML-based Configuration

(1) GenericApplicationContext

- GenericXmlApplicationContext는 Spring IoC Container 중 하나로, XML 형태의 Configuration Metadata를 사용하여 Bean를 생성한다.

// java\org\example\section01\xmlconfig\Application
public class Application {
    public static void main(String[] args) {

        ApplicationContext applicationContext
                = new GenericXmlApplicationContext("section01/xmlconfig/spring-context.xml");

        MemberDTO member1 = (MemberDTO) applicationContext.getBean("member");    // bean id
        /* MemberDTO 타입의 빈이 하나만 정의되어 있는 경우 그 빈이 member2에 들어가겠지만,
        해당 타입의 빈이 여러 개 정의되어 있는 경우 NoUniqueBeanDefinitionException이 발생한다.*/
        MemberDTO member2 = applicationContext.getBean(MemberDTO.class);             // 클래스 메타 정보
        MemberDTO member3 = applicationContext.getBean("member", MemberDTO.class);

        System.out.println(member3);
    }
}

(2) XML 기반 Configuration Metadata 파일

<!--resources/section01/xmlconfig/spring-context.xml-->
<!--아래 4행은 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">

    <!-- MemberDTO 타입의 bean 등록 -->
    <bean id="member" class="com.ohgiraffers.common.MemberDTO">
    	<!--<constructor-arg> 태그는 생성자를 호출할 때 전달할 인자를 정의한다.-->
        <!--만약 <beans> 태그 내부에 아무것도 작성하지 않으면 기본 생성자를 사용한다는 의미이다.-->
        <constructor-arg index="0" value="1"/>  <!--첫번째 생성자 인자로 1 전달-->
        <constructor-arg name="id" value="user01"/> <!--생성자 인자 중 이름이 id인 인자로 'user01' 전달-->
        <constructor-arg index="2"><value>pass01</value></constructor-arg> <!--세번째 생성자 인자로 'pass01' 전달-->
        <constructor-arg name="name"><value>홍길동</value></constructor-arg> <!--생성자 인자 중 이름이 name인 인자로 '홍길동' 전달-->
    </bean>
</beans>

-

2. Java-based Configuration

(1) AnnotationConfigApplicationContext

- AnnotationConfigApplicationContext는 Spring IoC Container 중 하나로, Java Configuration 형태의 Configuration Metadata를 사용하여 Bean을 생성한다.

// java\org\example\section02\javaconfig\Application
public class Application {
    public static void main(String[] args) {
    	// 생성자에 @Configuration 어노테이션이 달린 설정 클래스(여기선 ConfigurationContext)의 메타 정보 전달 
        ApplicationContext context =
                new AnnotationConfigApplicationContext(ConfigurationContext.class);

        MemberDTO member = context.getBean("member", MemberDTO.class);
        System.out.println(member);
    }
}

 

(2) Java 기반 Configuration Metadata

- Java Configuration 형태의 Configuration Metadata 파일은 아래와 같이 작성할 수 있다. 아래에서는 MemberDTO 클래스의 객체를 Bean으로 등록하고 있다.

// java\org\example\section02\javaconfig\ConfigurationContext
@Configuration("configurationSection02") // 해당 클래스가 빈을 생성하는 설정 클래스임을 표기. 괄호 안의 것은 이름을 붙여준 것.
public class ConfigurationContext {

    @Bean(name = "member")  // Bean의 이름은 자동으로 메소드의 이름을 따라가는데, 괄호 안에서 이름을 지정해줄 수 있다.
    public MemberDTO getMember() { // 반환 타입 중요
        return new MemberDTO(1, "user01", "pass01", "홍길동");
    }
}

 

3. Annotation-based Configuration

* Bean을 등록하는 방법

1) xml 파일에 bean 태그를 통해 등록

2) 메소드 위에 @bean 어노테이션을 붙여 등록

3) 클래스 위에 @Component를 붙이면 IoC container가 얘를 스캔해가는 작업을 통해 등록(component scan)

    -> 이번에 다루는 것

(1) @ComponentScan이란?

: base package로 설정된 하위 경로에 특정 어노테이션을 가지고 있는 클래스를 bean으로 등록하는 기능

- @Componenet 어노테이션이 작성된 클래스를 인식하여 bean으로 등록한다.

/* 
이 base package 하위의 모든 패키지에서 @Component가 붙어있는 클래스들은 다 스캔해서 빈 등록한다.
base package 설정이 별도로 없을 경우엔 현재 패키지 기준으로 스캔이 수행된다.
*/
@ComponentScan(basePackages = "org.example")

- @Component("name") 또는 @Component(value="name") 형식으로 bean의 id를 설정할 수 있다.  이름을 별도로 지정하지 않으면 클래스명의 첫 문자를 소문자로 변경하여 bean의 id로 자동 인식한다.

- 특수 목적에 따라 세부 기능을 제공하는 @Controller, @Service, @Repository, @Configuration 등을 사용한다.

@Component 객체를 나타내는 일반적인 타입으로 <bean> 태그와 동일한 역할
@Controller 프리젠테이션 레이어, 웹 어플리케이션에서 View에서 전달된 웹 요청과 응답을 처리하는 클래스
ex) Controller Class
@Service 서비스 레이어, 비즈니스 로직을 가진 클래스 ex) Service Class
@Repository 퍼시스턴스(persistence) 레이어, 영속성을 가지는 속성(파일, 데이터베이스)을 가진 클래스
ex) Data Access Object Class
@Configuration 빈을 등록하는 설정 클래스

 

(2) @ComponentScan 어노테이션으로 base packages 설정하기

@Configuration("configurationSection03")

@ComponentScan(basePackages = "org.example")
public class ConfigurationContext {
}
public class Application {
    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(ConfigurationContext.class);

        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for(String beanName : beanNames) {
            System.out.println("beanName: " + beanName);
        }
    }
}

실행결과는 위와 같다.

위에서 4개는 context 쪽에서 자동으로 내부에서 등록되는 bean이고,

@Configuration을 통해 등록된 "configurationSection03"와 "configurationSection02",

@Component을 통해 등록된 MemberDAO,

@Bean을 통해 등록된 member,

총 4개의 bean이 추가로 등록되어 있는 것을 확인할 수 있다.

 

(3) XML에서 Component Scan 설정하기

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

     <!--이런 식으로 XML 설정 파일에서 Component Scan의 base package를 설정할 수도 있다.-->
     <context:component-scan base-package="org.example"/>
</beans>
public class Application {
    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new GenericXmlApplicationContext("section03/annotationconfig/spring-context.xml");

        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for(String beanName : beanNames) {
            System.out.println("beanName: " + beanName);
        }
    }
}

정리

- XML 설정은 전통적으로 사용하던 방식으로, 최근에는 Java 설정이 선호된다.

- 개발자가 직접 컨트롤 가능한 클래스의 경우 @Component를 클래스에 사용하여 빈 스캐닝을 통해 자동 빈 등록을 한다.

- 개발자가 직접 제어할 수 없는 외부 라이브러리 @Bean을 메소드에 사용하여 수동 빈 등록을 한다.

- 다형성을 활용하고 싶은 경우에도 @Bean을 사용할 수 있다.

 

 

* 출처

https://docs.spring.io/spring-framework/reference/core/beans/basics.html

 

Container Overview :: Spring Framework

As the preceding diagram shows, the Spring IoC container consumes a form of configuration metadata. This configuration metadata represents how you, as an application developer, tell the Spring container to instantiate, configure, and assemble the component

docs.spring.io