본문 바로가기
Backend/Spring

[Spring] DI/IoC

by 모닥불꽃 2021. 9. 8.
반응형

이번 포스팅에서는 스프링의 특징 중 하나인 DI/IoC에 대해서 글을 작성해보려고 합니다.

 

DI와 IoC에 대한 개념적인 얘기는 이 포스팅을 참고해주시면 됩니다.

https://programforlife.tistory.com/103

 

[Spring] Spring 기초

이번 포스팅에서는 인턴을 하게 된 회사에서 진행해준 신입사원 교육 중, Spring의 기초에 대해 정리해보려 합니다. Spring Boot로 프로젝트를 진행했던 경험이 있어서 Spring의 특징에 대해 어느 정도

programforlife.tistory.com

해당 포스팅에서는 DI가 뭔지, 예제 코드를 가지고 설명을 했지만, 실제로 사용하는 코드와 다른, 개념을 설명하기 위한 코드로  설명했습니다.

 

이번 포스팅에서는 실무와 유사한 정도의 코드를 가지고 설명하려고 합니다.

 

DI(Dependency Injection)

 

예를 들어 다음과 같은 상황이 주어졌다고 가정합시다.

 

"www.naver.com/books/it?page=10&size=20&name=spring" 이과 같은 URL을 Base64로 인코딩해달라는 요구사항이 있습니다.

 

그럼 개발자는 다음과 같이 코드를 작성하겠죠?

 

먼저 Encoder 클래스를 작성하고, 문자열을 받아서 Base64로 인코딩해서 리턴해주는 encode 메서드를 작성합니다.

public class Base64Encoder {

    public String encode(String msg) {
        return Base64.getEncoder().encodeToString(msg.getBytes());
    }
}

그리고 Main 함수에서 다음과 같이 코드를 작성하면 결과가 잘 나옵니다.

Base64Encoder base64Encoder = new Base64Encoder();
String result = base64Encoder.encode(url);
System.out.println(result);

 

그러다가, 요구사항의 변경으로 Base64외에도 Url 인코딩이 필요하다고 하네요.

 

그럼 개발자는 UrlEncoder라는 클래스를 생성하고, UrlEncoder로 인코딩해주는 encode 메서드를 작성합니다.

public class UrlEncoder {

    public String encode(String msg) {
        try {
            return URLEncoder.encode(msg, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }
}

그리고 Main 함수에서 해당 Encoder를 써서 인코딩을 해주면 잘 나옵니다.

UrlEncoder urlEncoder = new UrlEncoder();
String urlResult = urlEncoder.encode(url);
System.out.println(urlResult);

 

하지만 이렇게 개발을 하면, 다른 인코딩 방식으로 요구사항이 증가 하게되면 코드의 양도 늘어나고, 재사용은 전혀 없을뿐더러 코드가 복잡해집니다.

 

그럼 Encoder에 대한 추상화를 다음과 같이 할 수 있습니다.

IEncoder라는 인터페이스를 만들어 줍니다.

public interface IEncoder {
    String encode(String msg);
}

그리고 기존에 작성해줬던 Base64Encoder, UrlEncoder 클래스들은 IEncoder를 상속받습니다.

그럼 Main 함수의 코드는 다음과 같이 바뀝니다.

//Base 64 로 인코딩
IEncoder base64Encoder = new Base64Encoder();
String result = base64Encoder.encode(url);
System.out.println(result);

//Url encoding 으로 인코딩
IEncoder urlEncoder = new UrlEncoder();
String urlResult = urlEncoder.encode(url);
System.out.println(urlResult);

IEncoder 인터페이스의 등장으로 코드가 아주 조금 바뀌었으나 아직 많이 부족해 보이는 코드입니다.

 

그럼 이제 DI(Dependency Injection)을 적용해 보겠습니다.

 

Encoder 클래스가 있는데, IEncoder 인터페이스를 스스로 갖고 있습니다.

public class Encoder {
    
    private IEncoder iEncoder;
    
    public Encoder() {
        this.iEncoder = new Base64Encoder();
    }
    
    public String encode(String msg) {
        return iEncoder.encode(msg);
    }
}

그리고 Main 함수는 다음처럼 작성할 수 있습니다.

Encoder encoder = new Encoder();
String result = encoder.encode(url);
System.out.println(result);

아까보다 확실히 코드가 간결해졌습니다.

 

하지만 현재 Encoder 클래스는 Base64Encoder의 기능을 하고 있어서 UrlEncoder를 사용하려면 Encoder 클래스를 다음과 같이 수정해줘야 합니다.

public class Encoder {
    
    private IEncoder iEncoder;
    
    public Encoder() {
        //this.iEncoder = new Base64Encoder();
        this.iEncoder = new UrlEncoder();
    }
    
    public String encode(String msg) {
        return iEncoder.encode(msg);
    }
}

즉, Main 함수에서 코드를 수정하지 않고, Encoder 클래스만 수정하면 Base64Encoder와 UrlEncoder를 모두 사용할 수 있습니다.

하지만 이 코드는 비효율적이죠.

 

이제 DI를 적용하기 위해 Encoder 클래스의 생성자를 수정해 주겠습니다.

public class Encoder {

    private IEncoder iEncoder;

    public Encoder(IEncoder iEncoder) {
        this.iEncoder = iEncoder;
    }

    public String encode(String msg) {
        return iEncoder.encode(msg);
    }
}

 

그럼 Main 함수의 코드는 다음과 같이 변경됩니다.

Encoder encoder = new Encoder(new Base64Encoder());
String result = encoder.encode(url);
System.out.println(result);

 

즉, Encoder 클래스 내부가 아닌, 클래스 외부에서, Main 함수에서 주입을 시켜주므로 DI(Dependency Injection)입니다.

Encoder 클래스의 입장에서는 외부에서 iEncoder에 대한 것을 주입받은 것이죠?

 

만약에 새로운 Encoder를 사용해야 한다고 하면, iEncoder 인터페이스를 상속받고, 구현한 후에, Main 함수에서 주입만 시켜주면 사용할 수 있습니다.

 

기존 코드는 수정하지 않고, 새로 추가만 하면 사용할 수 있는 상태가 되었습니다.

 

IoC(Inversion of Control)

이제 DI에 대한 개념은 잡았으니, IoC에 대해 이야기해 보려고 합니다.

DI에서의 코드를 보면 new 키워드를 사용하면서 객체를 개발자가 직접 생성하고 사용하고 있습니다.

Encoder encoder = new Encoder(new Base64Encoder());

스프링에서 IoC는 이런 객체의 생성, 생명주기들을 관리해줍니다.

 

DI에서 사용했던 Base64Encoder 클래스에 @Component 어노테이션을 붙여줍니다.

기존 Base64Encoder는 일반 클래스입니다.

@Component 어노테이션을 붙여줍니다.

그럼 스프링에서 Bean으로 관리하겠다는 아이콘이 생깁니다.

 

스프링에서 Bean에 등록되는 컴포넌트들은 @SpringBootApplication 어노테이션이 있는 애플리케이션 파일에서 다음과 같이 확인할 수 있습니다.

@SpringBootApplication 어노테이션 내부에는 @ComponentScan과 @SpringBootConfiguration이라는 어노테이션이 있습니다.

@ComponentScan은 애플리케이션 내에 Component로 등록된 빈들을 찾아줍니다.

@SpringBootConfiguration내부로 들어가면 @Configuration, @Configuration에 들어가면 @Component 어노테이션이 있고, 이 어노테이션이 @Component들을 스프링 Bean으로 관리를 해줍니다.

 

스프링 애플리케이션이 실행되면, @Component 어노테이션이 있는 객체들을 찾아서 스프링 Bean 컨테이너에서 싱글턴 객체로 직접 관리를 해줍니다.

 

이렇게 객체의 관리를 프로그래머가 아닌 스프링 프레임워크가 해준다고 해서 컨트롤의 역전이 일어나서 IoC라고 부릅니다.

반응형

댓글