[Spring Core] IoC Container

컨테이너·2일 전
0

SpringFramework

목록 보기
12/15
post-thumbnail

1. IoC와 IoC 컨테이너

1-1. IoC란? (Inversion of Control)

원래 자바 프로그램은 내가 직접:

  • 객체를 new 하고
  • 의존 객체를 직접 생성해서 넣고
  • 언제 버릴지(소멸)까지 결정한다.

제어권이 개발자 코드에 있다.

IoC는 이 흐름을 반대로 뒤집는다고 생각하면 된다.

객체 생성·초기화·연결·소멸, 의존성 관리 같은 걸

“프레임워크(컨테이너)가 대신 제어”하는 것

개발자는 “어떤 객체가 필요하다”만 선언하고, 실제 생성/주입은 스프링이 자동으로 해준다.


1-2. IoC 컨테이너란?

IoC를 실제로 구현한 “상자(컨테이너)”가 IoC 컨테이너다.

  • 객체를 만들고(생성)
  • 함수/필드에 의존성을 넣어주고(주입)
  • 필요 없는 시점에 정리해 주는(소멸)

대표적인 IoC 컨테이너가 스프링의 ApplicationContext이다.


2. Bean, BeanFactory, ApplicationContext

2-1. Bean이란?

스프링 IoC 컨테이너가 관리하는 객체

  • 내가 new 하지 않음
  • 컨테이너가 생성하고, 등록하고, 주입해 주는 객체
  • IoC 컨테이너 안의 객체 = Bean
MemberDTO member = context.getBean(MemberDTO.class);

이렇게 꺼내 쓰는 애들을 Bean이라고 보면 된다.


2-2. BeanFactory란?

스프링 IoC 컨테이너의 가장 기본 형태

  • Bean 생성, 초기화, 의존성 연결, 소멸 등 Bean의 라이프사이클 관리
  • “최소 기능”만 있는 IoC 컨테이너

2-3. ApplicationContext란?

BeanFactory + 각종 편의 기능을 더한 컨테이너

BeanFactory 기능은 그대로 갖고 있으면서 아래의 기능들도 수행할 수 있다.

  • ApplicationEventPublisher : 이벤트 발행/구독
  • MessageSource : 국제화(i18n) 메시지
  • ResourceLoader : 리소스 파일 로딩
  • 그 외 AOP, 환경 설정 등 스프링 부가 기능 사용 가능

구현체 예시:

  • GenericXmlApplicationContext → XML 설정 파일을 읽어서 컨테이너 생성
  • AnnotationConfigApplicationContext@Configuration 자바 클래스를 읽어서 컨테이너 생성

스프링을 쓸 때 가장 기본적으로 사용하는 컨테이너인 것.


3. 스프링 IoC 컨테이너 사용 방법

Bean의 등록 방법에 따른 방법이 아래와 같이 있다.

  1. XML 기반 설정
  2. Java 기반 설정 (@Configuration + @Bean)
  3. 어노테이션 기반 Component Scan (@Component, @Service …)

3-1. XML 기반 설정

3-1-1. 컨테이너 생성

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

여기서 "spring-context.xml" 이 Configuration Metadata(설정 정보) 파일.


3-1-2. XML에서 Bean 등록

<beans ...>
    <bean id="member" class="com.ohgiraffers.common.MemberDTO">
        <constructor-arg index="0" value="1"/>
        <constructor-arg name="id" value="user01"/>
        <constructor-arg index="2"><value>pass01</value></constructor-arg>
        <constructor-arg name="name"><value>홍길동</value></constructor-arg>
    </bean>
</beans>

이를 기존 코드로 해석하면 아래와 같다.

MemberDTO memberDto = new MemberDto(1, "user01", "pass01", "홍길동");

이렇게 객체를 만들어주는거다.

핵심만 보면:

  • <bean> : Bean 객체 하나를 등록
    • id : Bean 이름 (나중에 getBean 할 때 사용) → MemberDto 의 객체 new 대신 사용됨
    • class : 실제 자바 클래스
  • <constructor-arg> : 생성자에 전달할 값(매개변수 생성자의 매개변수 역할을 함)

3-1-3. Bean 꺼내 쓰기

// 1) id로 꺼내기
MemberDTO m1 = (MemberDTO) context.getBean("member");

// 2) 타입으로 꺼내기
MemberDTO m2 = context.getBean(MemberDTO.class);

// 3) id + 타입으로 꺼내기 - 형변환 하지 않아도 된다.
MemberDTO m3 = context.getBean("member", MemberDTO.class);

3-2. Java(어노테이션) 기반 설정 (@Configuration + @Bean)

XML 대신 자바 클래스로 설정을 작성하는 방식.

3-2-1. 컨테이너 생성

ApplicationContext context =
    new AnnotationConfigApplicationContext(ContextConfiguration.class);

여기서 ContextConfiguration.class 가 설정 클래스.

ApplicationContext객체를 생성해서 IOC 컨테이너에 메타데이터를 만들겠다는 것.


3-2-2. 설정 클래스 예시

@Configuration
public class ContextConfiguration {

    @Bean(name = "member")
    public MemberDTO getMember() {
        return new MemberDTO(1, "user01", "pass01", "홍길동");
    }
}

포인트:

  • @Configuration → 이 클래스는 Bean을 등록하는 설정 클래스로 선언하는 어노테이션
  • @Bean → 이 메서드의 반환 객체를 컨테이너에 Bean으로 등록하라는 의미를 가진다. IOC 에서 이 객체가 생성된다는 사실을 알아야 해서 Bean 어노테이션을 붙인다. → 이름을 별도로 붙이지 않으면 bean의 id로 자동인식된다. @Bean("myName") 또는 @Bean(name="myName") 의 형식으로 bean의 이름의 id를 설정할 수 있다.
  • name="member" → Bean 이름 설정 (생략하면 메서드명 getMember가 id가 됨)

3-2-3. Bean 꺼내기

MemberDTO member = context.getBean("member", MemberDTO.class);

XML 방식과 꺼내는 법은 같다.

다른 건 등록의 차이 밖에 없다. (XML vs 자바 코드)

  • 멤버DAO를 IOC 컨테이너에서 BEAN 객체를 통해 들고오기
/*이제 멤버를 들고오는 방법은 IOC 컨테이너 applicationContext 의 .getBean 으로 Bean 객체 이름을 매핑하여 들고온다.*/
MemberDao memberDao =  applicationContext.getBean("memberDao", MemberDao.class);
System.out.println("memberDao = " + memberDao.selectMember(1));

//결과

memberDao = MemberDto(sequence=1, id=user01, pwd=pass01, name=홍길동)

3-3. 어노테이션 기반 Component Scan

이 방식은 개발자가 직접 @Bean으로 하나하나 등록하는 대신, 스프링이 패키지를 스캔하면서 자동으로 Bean 등록하게 만드는 것.

3-3-1. @Component로 Bean 표시

@Component
public class MemberDAO {

    private final Map<Integer, MemberDTO> memberMap = new HashMap<>();

    public MemberDAO() {
        memberMap.put(1, new MemberDTO(1, "user01", "pass01", "홍길동"));
        memberMap.put(2, new MemberDTO(2, "user02", "pass02", "유관순"));
    }

    public MemberDTO selectMember(int sequence) {
        return memberMap.get(sequence);
    }
}
  • @Component → 이 클래스를 자동으로 Bean으로 등록하라는 뜻
  • Bean 이름 미지정 시 → 클래스명 첫 글자를 소문자로 바꾼 이름 (memberDAO)

특수 역할별로 쪼개 놓은 어노테이션도 있다. 계층이 정해져 있는 경우 아래 어노테이션을 사용할 수 있다.

  • @Controller : 웹 요청/응답 처리
  • @Service : 비즈니스 로직
  • @Repository : DB 접근
  • @Configuration : 설정 클래스

실제로는 내부적으로 전부 @Component 의 특수 버전이다. 만약 여기에 해당되지 않는 경우의 것들은 Bean에 전부 알아서 등록해주어야 한다.

Componenet의 기본 범위는 설정 파일이 있는 패키지를 기준으로 사용한다. 따라서 Bean을 어느 범위까지 사용할 것인가에 따라서 Bean의 범위를 등록해야 한다.


3-3-2. @ComponentScan으로 스캔 범위 지정

@ComponentScan(basePackages = "com.ohgiraffers")
public class ContextConfiguration {}
  • @ComponentScan : basePackage로 설정된 하위 경로에 특정 어노테이션을 가지고 있는 클래스를 bean으로 등록하는 기능이다. @Component 어노테이션이 작성된 클래스를 인식해서 bean으로 등록한다.
  • basePackages : Configuration 범위를 얘로 정해줌. 여기 내부를 돌면서 아래를 수행.
  • @Component, @Service, @Repository, @Controller 등이 붙은 클래스를 Bean으로 등록.
  • basePackage를 기입하지 않으면 default는 현재 패키지 기준으로 scan이 수행됨.

테스트:

ApplicationContext context =
    new AnnotationConfigApplicationContext(ContextConfiguration.class);

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

memberDAO 가 찍히면 Bean 등록 성공.

  • getBeanDefinitionNames() : 스프링 컨테이너에서 생성된 bean들의 이름을 배열로 반환

3-3-3. excludeFilters / includeFilters

기본적으로 @Component류는 다 스캔 대상이지만,

원하지 않는 클래스는 제외하거나,

특정 클래스만 포함시키고 싶을 수도 있다.

  1. 제외(exclude)
@ComponentScan(
    basePackages = "com.ohgiraffers",
    excludeFilters = {
        @ComponentScan.Filter(
            type = FilterType.ASSIGNABLE_TYPE,
            classes = { MemberDAO.class }
        )
    }
)
public class ContextConfiguration {}

→ 이렇게 하면 MemberDAO 는 Bean으로 등록되지 않음.

  1. 포함(include)
@ComponentScan(
    basePackages = "com.ohgiraffers",
    useDefaultFilters = false,
    includeFilters = {
        @ComponentScan.Filter(
            type = FilterType.ASSIGNABLE_TYPE,
            classes = { MemberDAO.class }
        )
    }
)
public class ContextConfiguration {}
  • useDefaultFilters = false 로 하면 기본 @Component 스캔을 꺼버리고
  • includeFilters 로 지정한 것만 Bean 등록

3-3-4. XML로 Component Scan

XML 방식에서도 Component Scan 가능:

<beans ... 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">

    <context:component-scan base-package="com.ohgiraffers"/>

</beans>

필요하면 <context:exclude-filter> 로 제외도 가능하다.


4. 정리

  1. XML 설정
    • 예전 스타일
    • 설정이 눈에 한 번에 들어옴
    • 최근에는 주로 레거시 유지보수나 특수 상황에서 사용
  2. Java 설정 (@Configuration + @Bean)
    • 타입 안정성, 리팩토링에 유리
    • 외부 라이브러리처럼 “내가 수정할 수 없는 클래스”를 Bean으로 등록할 때 유용
    • 복잡한 생성 로직, 조건부 생성 등에 좋음
  3. Component Scan (@Component, @Service 등)
    • 프로젝트 내 클래스들을 자동 Bean 등록

profile
백엔드

0개의 댓글