03장 DI(Dependecy Injection)알아보기

박준우·2025년 6월 27일

Spring Boot

목록 보기
3/14

1. 스프링 프레임워크의 핵심기능

의존성 주입이란(DI)?

Dependecy Injection은 의존하는 부분의 함수를 DI컨테이너에서 주입하는 것을 의미한다.

관점지향 프로그래밍(AOP)이란?

Aspect Oriented Programming 은 기능이아닌 예외 처리, 트랜잭션 제어, 출력 기능등 여러곳에서 사용하는 기능들을 프로그램의 여러곳에서 호출 할 수 있도록하고, 사용자는 구현하고자 하는 핵심기능들만 작성할 수 있도록 하는 기능이다. 이로써, 사용자는 여러곳에서 사용하는 기능들을 여러번 작성할 필요가 없다.

2. DI컨테이너 알아보기

DI컨테이너란?

의존성 주입을 할수있게 도와주는 역할을 한다.
주입: new키워드를 통해 다른 클래스의 객체를 자신 클래스 안에서 사용하는 것.

만약 new로 직접 의존성을 생성하지 않고, DI컨테이너에게 다른 클래스의 객체 생성을 맡기면, 사용하는 쪽의 클래스 수정 없이 사용되는 쪽의 클래스만 수정해 기능을 바꿀 수 있다.(만약 그렇지 않다면, 둘 다 바꿔야 한다.)

DI 컨테이너 규칙

DI컨테이너를 사용하려면 아래 규칙을 지켜야한다.

  1. 인터페이스를 이용한 의존관계 사용
  2. 인스턴스(객체)를 명시적으로 선언X // new를 사용하지 않는다.
  3. 사용되는 쪽 클래스에 애너테이션부여 //@component 사용
  4. 스프링 프레임워크가 인스턴스를 생성하게 한다.
  5. 인스턴스를 이용하고자 하는 사용하는 쪽 클래스에 에너테이션을 부여한다. //@Autowired 사용

객체 생성 에너테이션의 계층화

DI컨테이너가 객체를 생성할 때, 각 계층별로 사용하는 에너테이션이 다르다.

애플리케이션 계층: 클라이언트와 애플리케이션간 요청과 결과 반환
도메인 계층: 비지니스 로직 기술
인프라 계층: DB와 외부서비스와 통신

애플리케이션 계층: @Controller
도메인 계층: @Service
인프라 계층: @Repository
기타 객체 생성: @Component

클라이언트-> 애플리케이션 -> 도메인 -> 인프라 -> DB 순으로 통신된다.

DI를 사용하는 프로그램 만들기

사용되는 쪽(기능) 구현하기(@component)

1. 인터페이스 생성
public interface Greet{
    String greeting();
}

2. 아침인사 기능구현
import org.springframework.stereotype.Component;

@Component
public class MorningGreet implements Greet{
    @Override
    public String greeting(){
        return "좋은 아침입니다.";
    }
}

3. 밤 인사 기능구현
import org.springframework.stereotype.Component;

//@Component : 주석처리(Component가 2개 있으면, 어떤쪽을 객체로 구현할지 몰라서 에러)
public class EveningGreet implements Greet{
    @Override
    public String greeting(){
        return "안녕히 주무세요";
    }
}

@Component 애너테이션을 통해, DI컨테이너가 객체를 생성하도록 지정한다.

사용하는 쪽 만들기(@Autowired)

스프링 이니셜라이저에서 프로젝트를 실행하면 아래처럼 Test클래스가 자동으로 생성된다.

이 파일은 아래처럼 애플리케이션이 시작할때 자동실행되도록 애너테이션이 붙어 있기 때문에, "시작 클래스"라고도 부른다.

이를 이용하여, 사용되는 쪽을 구현해보자.

import com.example.demo.used.Greet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 스프링부트를 시작함을 선언한다.(파일이름 + Application조합이다., spring설정 작동)
@SpringBootApplication
public class DiSampleApplication {
    public static void main(String[] args){
        SpringApplication.run(DiSampleApplication.class, args) 
                .getBean(DiSampleApplication.class).execute();
    }
	
//생성된 인스턴스를 이용하고자 하는 사용되는 쪽에 @Autowired를 선언한다. 
    @Autowired
    private Greet g; // 인터페이스 type + 객체 g + DI가 객체생성
    				 // @Component로 지정한 객체를 Greet type의 g객체로 만든다.

    private void execute(){
        String msg = g.greeting();
        System.out.println(msg);
    }
}
main함수의 run함수와 getBean, execute()의 의미를 알아보자.

1. SpringApplication.run(DiSampleApplication.class, args) 
Spring 애플리케이션을 실행한다는 의미이다.
첫번째 인자 = 어떤 클래스부터 실행할 것인가 설정한다.
두번째 인자 = 메인함수의 args를 그대로 넘겨준다.

2. .getBean(DiSampleApplication.class)
Spring에는 DI컨테이너에서 관리하는 객체를 Bean이라고 한다. 
첫번째 인자 = DiSampleApplication이라는 클래스에서 Bean객체를 가져온다.

3. .execute()
가져온 Bean(객체)의 execute()함수를 실행한다.

결과

좋은 아침입니다.

위처럼 객체의 생성 책임을 DI에게 맡김으로써, 사용하는 쪽의 클래스를 전혀 수정하지 않아도, 사용되는 쪽의 클래스만 @Component로 수정하여 변경사항에 대응할 수 있다.

3. DI컨테이너 인스턴스 생성

스프링 프레임워크는 DI컨테이너를 자바 객체를 대신생성하는 기능을 가진다. 이런 인스턴스를 DI컨테이너에 보관하며, DI컨테이너 안에서 관리되는 인스턴스를 bean이라고 부른다. @Bean을 사용할 경우 2개이상의 인스턴스를 DI컨테이너에 보관하는 것이 가능하다.

빈 정의

빈 정의란? 이 클래스를 빈으로 만들겠다고 지시하는 것을 의미한다.

빈 정의 방법
1. 클래스에 에너테이션 부여하기.
2. java Config 클래스에 함수 생성하기

1번방법은 @Component와 @Autowired를 이용하는 것이며, 2번방법을 아래에 기술하고자 한다.

1. 인터페이스 생성
public interface BusinessLogic {
    void doLogic();
}

2. 기능구현
import com.example.demo.service.BusinessLogic;

public class TestLogicImpl implements BusinessLogic {
    @Override
    public void doLogic() {
        System.out.println("테스트 중입니다.");
    }
}

3. 기능구현
import com.example.demo.service.BusinessLogic;

public class SampleLogicImpl implements BusinessLogic {
    @Override
    public void doLogic(){
        System.out.println("샘플입니다.");
    }
}

config 클래스에 함수 생성하기(@Configuration, @Bean)

@Configuration은 아래의 클래스가 java Config 클래스임을 지정한다.
@Bean은 DI컨테이너에 들어갈 객체의 이름을 구분할 수 있도록 지정한다.

import com.example.demo.service.BusinessLogic;
import com.example.demo.service.impl.SampleLogicImpl;
import com.example.demo.service.impl.TestLogicImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration// 해당 클래스가 설정 클래스임을 나타낸다.
public class AppConfig {
    @Bean(name="test") //함수가 Bean을 반환함을 선언한다.
    public BusinessLogic dataLogic(){ // TestLogicImpl을 test라는 이름의
    									 빈으로 등록하여 컨테이너에 넣는다.
        return new TestLogicImpl();
    }

    @Bean(name = "sample") //sample이라는 빈이름으로, DI컨테이너에 SampleLogicImpl을 등록한다.
    public BusinessLogic viewLogic() {
        return new SampleLogicImpl();
    }
}

시작 클래스에서 Bean 사용하기(@Qualifier)

@Autowired는 객체를 주입할 곳을 지정한다.
@Qualifier는 @Bean으로 지정한 이름의 Bean을 DI컨테이너로 부터 불러와서 객체를 생성한다.

import com.example.demo.service.BusinessLogic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class JavaConfigSampleApplication {
	public static void main(String[] args){
		SpringApplication.run(JavaConfigSampleApplication.class, args)
				.getBean(JavaConfigSampleApplication.class).exe();

	}

	@Autowired
	@Qualifier("test") //빈의 이름을 불러온다.
	private BusinessLogic business1; // business1객체가 생성

	@Autowired
	@Qualifier("sample")
	private BusinessLogic business2;

	public void exe(){
		business1.doLogic();
		business2.doLogic();
	}

}

결과

테스트 중입니다.
샘플입니다.

4. 주입 알아보기

주입 방법

객체 주입은 @Autowired를 통해 이루어진다. 이런 주입방법은 3가지로 이루어진다.

  1. 변수(필드) 주입
    클래스의 필드에 @Autowired를 통해 직접 의존성을 주입한다.
    추천 X 방법이다.
@Autowired
private SomeService someService;
  1. setter 주입
    setter 함수에 @Autowired를 사용하여 의존성을 주입한다.
    필요한 의존성만 주입이 가능하다.
private SomeService someService;

@Autowired
public setSomeService(SomeService someService){
	this.somService = someService;
  1. 생성자 주입
    생성자에 @Autowired를 사용하여 의존성을 주입한다.
    테스트시 불변성이 유지되어 moking(더미 객체를 만들어 테스트 하기)이 용이하다.(매우 추천)
  • 불변성: 객체가 생성된 후 데이터가 변경되지 않는 특성
private final SomeService someService; //객체에 불변성 주입

@Autowired //생략가능
public setSomeService(SomeService someService){
	this.somService = someService;
    

각 주입을 사용하는 프로그램 만들어보기

0. 인터페이스 만들기

1. 인터페이스
public interface Example {
    void run();
}

1. 변수(필드)주입

@Component
public class FieldInjectionExample implements Example {

    //변수(필드) 주입
    @Autowired
    private SomeService someService;

    @Override
    public void run() {
        someService.doService();
    }
}

객체 위에 @Autowired를 사용함으로써, 객체변수에 주입한다.

2. setter 주입

//@Component
public class SetterInjectionExample implements Example {
    private SomeService someService;

    //Setter 주입
    @Autowired
    public void setSomeService(SomeService someService){
        this.someService = someService;
    }

    public void run() {
        someService.doService();
    }
}

setter함수 위에 @Autowired를 사용하여, 객체를 주입시킨다.

3. 생성자주입

//@Component
public class ConstructorInjectionExample implements Example {

    private final SomeService someService;

    @Autowired
    public ConstructorInjectionExample(SomeService someService){
        this.someService = someService;
    }

    public void run() {
        someService.doService();
    }
}

생성자를 통해 객체를 주입한다. 이 때 객체선언시 final을 사용해준다.

4. 롬복을 이용한 생성자주입

@RequiredArgsConstructor
public class ConstructOrInjectionOmitLombokExample implements Example {
    private final SomeService someService;

        // 생성자 생략가능
        public void run() {
            someService.doService();
        }
}

@RequiredArgsConstructor 는 롬복이 자동으로 final이 붙은 필드만 인수로 받는 생성자를 생성해준다.

5. 시작 클래스 만들기

@SpringBootApplication
class InjectionSampleApplication {
	public static void main(String[] args) {
		SpringApplication.run(InjectionSampleApplication.class, args)
				.getBean(InjectionSampleApplication.class).exe();

	}

	@Autowired
	private Example example;

	private void exe(){
		example.run();
	}
}

결과

어떤 서비스
profile
DB가 좋아요

0개의 댓글