본문 바로가기

더 나은 엔지니어가 되기 위해/지금은 안쓰는 자바

[부스트코스 웹 프로그래밍] 스프링과 DI 컨테이너

부스트코스 웹 프로그래밍 BE 영상을 보며 공부한 것을 간단히 정리한다.

1. Spring IoC / DI 컨테이너

컨테이너

  • 컨테이너는 인스턴스의 생명주기를 관리한다.
  • 또, 생성된 인스턴스에게 추가적인 기능을 제공한다.
  • 예를 들어, 톰캣(WAS)는 Servlet 컨테이너를 통해 Servlet 요청을 해결한다.

IoC (Inversion of Contorl)

  • 오브젝트의 제어권을 개발자의 코드가 아니라, 컨테이너가 가져가는 것을 의미한다.
  • 즉, 개발자는 오브젝트를 직접 실행하는 것이 아니라, 컨테이너에게 요청하여 받아온다.

DI (Dependency Injection)

  • 클래스 간의 의존 관계를 코드가 아닌, 컨테이너가 직접 주입해주는 것을 의미한다.
  • 클래스 간의 의존 관계는 Bean 설정을 바탕으로 작성한다.

Spring

  • 스프링은 이런 DI container 기반의 프레임워크다.
  • 스프링에서는 다음과 같은 컨테이너를 제공한다.
    • BeanFactory, ApplicationContext, ...

2. Bean

스프링의 DI 컨테이너를 통해 만들어낼 Java 클래스를 Bean 클래스라고 한다.
DI 컨테이너에 우리가 들 임의의 클래스를 등록하는 방법은 다음과 같다.

1) 먼저 임의의 클래스를 만들어 두자.

DI 컨테이너에 등록할 클래스는 다음 3가지 조건을 지켜야 한다.

  1. 기본 생성자가 있음.
  2. 필드는 private 함.
  3. getter, setter 를 가짐.
// UserBean.java

package kr.or.connect.diexam01;

public class UserBean {

    // 필드는 private 하다.
    private String name;
    private int age;
    private boolean male;

    // 기본생성자를 가지고 있다.
    public UserBean() {
    }

    public UserBean(String name, int age, boolean male) {
        this.name = name;
        this.age = age;
        this.male = male;
    }

  // Getter 와 Setter 를 가지고 있어야 한다.
    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
    ...
}

2) applicationContext.xml 에 클래스 등록

// resources/applicationContext.xml
// (resources 는 java 디렉토리와 같은 디렉토리에 있다.)

<?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 클래스로 등록한다.
  // 다만 이렇게할 시, 기본적으로 싱클톤 인스턴스로 등록됨.
    <bean id="userBean" class="kr.or.connect.diexam01.UserBean"></bean>

</beans>

3) Spring framework 를 build 파일의 dependency 에 추가

// pom.xml

...
<dependencies>
  ...
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.14.RELEASE</version>
    </dependency>
    ...

4) 등록된 Bean Class 사용하기

// ApplicationContextExam01.java

package kr.or.connect.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextExam01 {

    public static void main(String[] args) {

        // ApplicationContext 라는 DI 컨테이너를 사용하여 초기화.
        ApplicationContext ac = new ClassPathXmlApplicationContext( 
                "classpath:applicationContext.xml"); 
        System.out.println("초기화 완료.");

        // ApplicationContext 의 getBean("{bean id}") 으로 인스턴스 획득.
        // 다만 이 때, getBean 은 기본적으로 Instance 오브젝트를 반환하므로 타입 변환 필요.
        UserBean userBean = (UserBean)ac.getBean("userBean");
        UserBean userBean2 = (UserBean)ac.getBean("userBean");

        // 참고로 userBean 과 userBean2 는 같음. (싱글톤)

    }
}

3. Bean property

Bean 에서 프로퍼티란 Setter, Getter 를 의미함.
(즉 메쏘드라고 안부르고, 프로퍼티라고 부름)

한 클래스 내에서 다른 클래스를 가져올 때(Set 할 때) 다음과 같이 Bean을 설정할 수 있음.

// Car 라는 클래스가 Engine 이라는 인스턴스를 내부 필드로 가지는 상황임.
// Car.java

package kr.or.connect.diexam01

public class Car {
    Engine v8;
    ...
    public void setEngine(Engine e) {
        this.v8 = e;
    }
  ...
}

이럴 때, applicationContext.xml 에 Bean 정보를 다음과 같이 바꿔주면 됨.

// resources/applicationContext.xml

...
<bean id="e" class="kr.or.connect.diexam01.Engine"></bean>
<bean id="car" class="kr.or.connect.diexam01.Car">
    <property name="engine" ref="e"></property> 
</bean>

이는 다음 코드와 같은 의미임

Engine e = new Engine();
Car c = new Car();
c.setEngine( e );

4. @Bean 을 이용하여 더 쉽게 Bean 설정하기

위의 방법은 새로운 클래스를 만들 때 마다, applicationContext.xml 에 추가해야하는 번거로움이 있음.
따라서, 더 쉬운 방법을 고안하게 되었는데 바로 Annotation(@) 을 고려한 방법임.

1) Config 파일 설정

@Configuration 를 갖는 별도의 Config 클래스 선언하고,
이 안에 사용할 클래스들을 메쏘드로 초기화.

// ApplicationConfig.java

package kr.or.connect.diexam01;
import org.springframework.context.annotation.*;

@Configuration
public class ApplicationConfig {

    @Bean
    public Car car(Engine e) {
        Car c = new Car();
        c.setEngine(e);
        return c;
    }
    // 중요한건, '메쏘드'로 정의한다는 것.
    // 반환 타입이 해당 클래스의 이름이 됨.

    @Bean
    public Engine engine() {
        return new Engine();
    }
}

2) 등록된 Bean Class 사용하기

AnnotationConfigApplicationContext 를 사용하여 Config 파일을 읽어옴.

// ApplicationContextExam03.java

package kr.or.connect.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationContextExam03 {

  public static void main(String[] args) {
    ApplicationContext ac = new AnnotationConfigApplicationContext(
        ApplicationConfig.class);
    // AnnotationConfigApplicationContext({Config 파일.class}) 를 사용하면,
    // 해당 파일을 프로젝트 내에서 검색 후, 설정 파일을 읽어들임.

    Car car = (Car)ac.getBean("car");
    car.run();
  }
}

5. @ComponentScan 을 통한 더더 쉬운 방법

1) Config 에 @ComponentScan 붙이기

이번엔 @ComponentScan 을 클래스 어노테이션으로 추가하여,
직접 직접 내부 메쏘드에 추가하지 않고, 해당 클래스를 서치하여 등록하는 방법.

// ApplicationConfig2.java

package kr.or.connect.diexam01;
import org.springframework.context.annotation.*;

@Configuration
@ComponentScan("kr.or.connect.diexam01")
public class ApplicationConfig2 {
}

@ComponentScan("패키지 명")패키지 명 에 해당하는 패키지 내 @Component 가 붙어있는 클래스들을 모두 찾아, 메모리에 로드시킴.
이제 우리가 사용하려는 클래스에 @Component 를 붙여줘야함.

2) 사용할 클래스에 @Component 붙이기

// Engine.java

package kr.or.connect.diexam01;

import org.springframework.stereotype.Component;

@Component
public class Engine {
  ...
}
// Car.java

package kr.or.connect.diexam01;

import org.springframework.stereotype.Component;

@Component
public class Car {
  ...
}

3) 등록된 Bean Class 사용하기

// ApplicationContextExam04.java

package kr.or.connect.diexam01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationContextExam04 {

    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(
            ApplicationConfig2.class);
        // AnnotationConfigApplicationContext 사용

        Car car = ac.getBean(Car.class);
        car.run();

    }
}