
간단한 Base32인코더와 Base64인코더를 제작한다고 가정해보고, 차근차근 과정을 이해해보자.
// Base32Encoder 클래스
public class Base32Encoder {
public String encode(String message) {
...
return Base32.getEncoder().encodeToString(message.getBytes());
}
}
// Base64Encoder 클래스
public class Base64Encoder {
public String encode(String message) {
...
return Base64.getEncoder().encodeToString(message.getBytes());
}
}
// Main 클래스
public class Main {
public static void main(String[] args) {
String url = "en.dict.naver.com/#/search?query=spring";
// Base32Encode
Base32Encoder base32Encoder = new Base32Encoder();
String base32Result = base32Encoder.encode(url);
// Base64Encode
Base64Encoder base64Encoder = new Base64Encoder();
String base64Result = base64Encoder.encode(url);
}
}
추상화를 적용하지 않고는 위와 같이 작성할 수 있을 것이다.
여기서 인터페이스를 추가하고 DI를 적용하기 전까지 구현하면 다음과 같이 구현할 수 있을 것이다.
// 인터페이스 추가
public interface IEncoder {
String encode(String message);
}
// IEncoder 상속받음
public class Base32Encoder implements IEncoder {
public String encode(String message) {
...
return Base32.getEncoder().encodeToString(message.getBytes());
}
}
// IEncoder 상속받음
public class Base64Encoder implements IEncoder {
public String encode(String message) {
...
return Base64.getEncoder().encodeToString(message.getBytes());
}
}
// Encoder 클래스 수정
public class Encoder {
private IEncoder iEncoder;
// 문제점 : 내부 코드를 수정해야 하는 과정이 필요하다
public Encoder() {
// this.iEncoder = new Base64Encoder();
this.iEncoder = new Base32Encoder();
}
public String encode(String message) {
return iEncoder.encode(message);
}
}
// main 메소드 수정
public class Main {
public static void main(String[] args) {
String url = "en.dict.naver.com/#/search?query=spring";
// 다음과 같이 작성할 수 있음
Encoder encoder = new Encoder();
String result = encoder.encode(url);
}
}
위의 코드를 살펴보면, DI가 적용되어 있지 않아 내가 어떠한 인코더를 사용하느냐에 따라 Encoder의 iEncoder에 Base32Encoder를 생성할 것인지, Base64Encoder를 생성할 것인지 내부 Encoder 클래스를 수정 할 필요성이 생기게 된다.
그렇지만 여기서 DI 개념을 사용해 외부에서 내가 사용하는 객체를 주입시켜보자.
public interface IEncoder {
String encode(String message);
}
public class Base32Encoder implements IEncoder {
public String encode(String message) {
...
return Base32.getEncoder().encodeToString(message.getBytes());
}
}
public class Base64Encoder implements IEncoder {
public String encode(String message) {
...
return Base64.getEncoder().encodeToString(message.getBytes());
}
}
// Encoder 클래스 수정
public class Encoder {
private IEncoder iEncoder;
public Encoder(IEncoder iEncoder) {
this.iEncoder = iEncoder;
}
public String encode(String message) {
return iEncoder.encode(message);
}
}
// main 메소드 수정
public class Main {
public static void main(String[] args) {
String url = "en.dict.naver.com/#/search?query=spring";
// 의존 객체 주입 (DI)
Encoder encoder = new Encoder(new Base64Encoder());
String result = encoder.encode(url);
}
}
위와 같이 외부에서, 사용하는 객체를 주입받는 형태로 작성하게 되면,
Encoder 클래스는 수정할 필요없이 main메서드 내에서만 필요로 하는 인코더만 주입시켜주면 된다.
추가적으로 다른 인코더를 적용할 때도, IEncoder를 상속받고 새로운 Encoder 클래스를 작성시켜, 다른 인코더 클래스를 수정할 필요 없이 main메소드 내에서 객체를 주입시켜주기만 하면 된다.
외부에서 의존 관계를 결정하는 것을 뜻하는 DI(Dependency Injection)는,
스프링에서는 외부의 대상이 Spring Container(IoC Container)가 되고, Spring Container는 Bean(Spring Container가 관리하는 오브젝트)을 대신 주입해준다.

1) 필드 주입
@Controller
public class BookController {
@Autowired
private BookService bookService;
}
2) setter 주입
@Controller
public class BookController {
private BookService bookService;
public void setBookService(BookService bookService) {
this.bookService = bookService;
}
}
3) 생성자 주입
@Controller
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
}
@Component 애너테이션을 클래스에 붙임으로써, Spring Container가 알아서 Spring Bean 객체로 등록하고 생성한다.
이렇게 생성된 Bean은 위에서 소개한 3가지 의존성 주입 방식(생성자 주입, setter 주입, 필드 주입)을 통해 필요한 곳에 객체를 주입하게 된다.