본문 바로가기

Java/Spring

Spring - 의존성 주입, Dependency Injection, DI 개념과 예시 코드

Spring dependency injection
Spring Dependency Injection이란?

의존성 주입과 @Autowired 개념

이번 글에서는 의존성 주입(Dependency Injection)에 대하여 알아보겠습니다. 우선 의존성 주입이라는 용어의 의미를 풀어보면서 정확히 이해하는게 중요합니다.

의존성

스프링 컨테이너 안에서 빈의 존재에 대하여 생각해보겠습니다. 하나의 어플리케이션에 포함된 빈들은 독립된 상태로 존재하는 경우도 있지만 다른 빈들과 관계를 맺어야만 그 존재가 유효한 빈이 있습니다. 이렇게 홀로 존재할 수 없고 누군가 필요한 빈들은 의존하는 성격이 있다고 볼 수 있습니다. 예를 들어 컴퓨터 부품인 메인보드만으로는 제 기능을 할 수 없고 CPU와 RAM 등 부속품이 완전하게 갖춰져야 완전한 역할을 할 수 있는 경우와 같습니다.

의존성을 표현하는 방법은 바로 빈에 선언된 필드와 그 레퍼런스 입니다. POJO로 메인보드 클래스를 만들어 예를 들어보겠습니다. 메인보드라는 인스턴스는 cpu와 ram이라는 필드 변수에 의해 또다른 인스턴스에 의존성이 있음을 알 수 있습니다.

class MainBoard() {

    private Cpu cpu;
    private Ram ram;

    public MainBoard() {
        this.cpu = new Cpu();
        this.ram = new Ram();
    }

    public void information() {
        System.out.println("CPU: " + cpu + ", RAM: " + ram);
    }

    public static void main() {
        MainBoard board = new MainBoard();
        board.information();
    }
}

주입

주입은 단순히 넣어 준다는 말입니다. 주입한다는 말에는 한개 이상의 대상 사이에 어떠한 관계가 성립한다는 의미가 포함되어 있습니다. 메인보드와 CPU 혹은 RAM처럼 말이죠. POJO 예시 코드로 돌아가보면, 위 생성자에서 필드 변수에 무언가 인스턴스를 할당하는 행위 자체를 주입한다고 볼 수 있습니다.

this.cpu = new Cpu();
this.ram = new Ram();

@Autowired: 자동으로 스프링 의존성 주입하기

  • 구성에 필요한 빈 만들기
    • CPU 클래스
    • RAM 클래스
  • 필드에 자동으로 연결하기
    • 메인보드 클래스의 cpu, ram 필드에 @Autowired 어노테이션 붙이기

스프링에서 의존성 주입이란 스프링 컨테이너에서 관리되는 여러 빈들의 관계를 맺어주는 일 입니다. Auto wiring의 의미는 선을 이어주듯 자동으로 빈들의 관계를 맺어준다고 이해하니 조금 더 쉬웠습니다. 필요한 빈이 만들어져있다는 가정 하에 레퍼런스를 연결해주고 싶은 필드에 @Autowired 어노테이션을 붙여주면 나머지는 스프링 컨테이너에서 알아서 작업해 줍니다. 그 구체적인 방법은 바로 다음에 이어질 스프링에서 의존성 주입하는 방법을 읽어보시기 바랍니다.

스프링에서 의존성 주입하는 3가지 방식

스프링에서는 다음과 같이 3가지의 의 의존성 주입 방식을 지원하고 있습니다. 차례대로 알아보겠습니다. 이 중 첫번째인 생성자로 의존성 주입하는 방법은 스프링에서 권장하는 스타일이니 조금 더 유의해서 읽어 주세요.

  • 생성자로 의존성 주입하기 - Spring 권장
  • Setter로 의존성 주입하기
  • 필드 선언시 의존성 주입하기

그리고 아래 Cpu와 Ram과 같은 간단한 빈이 이미 선언되어있다고 가정하고 예시 코드를 위주로 설명하겠습니다.

@Commponent
class Cpu {}

@Component
class Ram {}

생성자에서 의존성 주입

생성자로 빈이 만들어질 때 의존성이 설정됩니다. 생성자로 의존성 주입하는 방식은 스프링 측에서 권장하는 방식입니다. 이 방법의 특징은 @Autowired 어노테이션 없이도 자동으로 빈 연결이 이루어진다는 점 입니다.

@Component
class MainBoard {

    private Cpu cpu;
    private Ram ram;

    public MainBoard(Cpu cpu, Ram ram) {
        this.cpu = cpu;
        this.ram = ram;
    }

    public String toString() {
        return "MainBoard - CPU: " + cpu + ", RAM: " + ram;
    }
}

Setter 메서드로 의존성 주입

Setter 메서드가 호출될때 의존성이 설정되는 방식입니다. Setter 메서드에 @Autowired를 붙여줍니다. 빈의 이름은 클래스명에서 앞 글자를 소문자로 바꾸는 식으로 지정됩니다.

@Component
class MainBoard {

    private Cpu cpu;
    private Ram ram;

    @Autowired
    public void setCpu(Cpu cpu) {
        this.cpu = cpu;
    }

    @Autowired
    public void setRam(Ram ram) {
        this.ram = ram;
    }

    public String toString() {
        return "MainBoard - CPU: " + cpu + ", RAM: " + ram;
    }
}

필드 선언시 의존성 주입

생성자와 Setter 없이 reflection을 사용하여 의존성이 설정되는 방식입니다. 필드 선언부에 @Autowired를 붙여줍니다. 마찬가지로 빈의 이름은 클래스명에서 앞 글자를 소문자로 바꾸는 식으로 지정됩니다.

@Component
class MainBoard {

    @Autowired
    private Cpu cpu;
    @Autowired
    private Ram ram;

    public String toString() {
        return "MainBoard - CPU: " + cpu + ", RAM: " + ram;
    }
}

마치며

Application Context에서 빈 사용하기

위에서 생성자 방식 혹은 @Autowired로 만들어진 빈을 호출해 보겠습니다. 별다른 설정 없이도 간단한 방식으로 스프링 컨테이너에서 의존성 주입 작업이 완료되었습니다.

@Configuration
@ComponentScan
public class MyApplication {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyApplication.class);
        System.out.println("CPU: " + context.getBean("cpu"));
        System.out.println("RAM: " + context.getBean("ram"));
        System.out.println(context.getBean("mainBoard"));
    }
}

의존성 주입된 빈 내역을 볼 수 있습니다. mainBoard 빈에 cpu, ram 빈의 hashcode가 동일합니다.

Output:

CPU: com.example.study.Cpu@3246fb96
RAM: com.example.study.Ram@b62d79
MainBoard - CPU: com.example.study.Cpu@3246fb96, RAM: com.example.study.Ram@b62d79

중요 개념 정리

  • 스프링 의존성 주입의 개념
  • 생성자로 스프링 의존성 주입하기
  • Setter 메서드로 스프링 의존성 주입하기
  • 필드 선언시 스프링 의존성 주입하기

Reference