스프링의 대표적인 특성 3가지로 IoC/DI
, AOP
, PSA
가 있다. IoC/DI
는 Inversion Of Control/Dependency Injection의 줄임말로 제어의 역전/의존성 주입이라는 뜻이고, AOP
(Aspect Oriented Programming)는 관점지향 프로그래밍, PSA
(Portable Service Abstraction)는 확장이 용이한 서비스 추상화를 의미한다. 직관적으로 봤을 때 꽤나 애매한 말들이라 가볍게 넘어가기 마련인데, 좋은 서비스를 만들기 위해서는 각각이 의미하는 바가 무엇인지 정확히 알아야 하고 또한 이러한 개념들이 스프링 구조에 어떻게 녹아있는지 이해해야하기 때문에 해당 개념에 대해 조금은 깊게 알아볼 필요가 있다고 생각한다.
해당 개념을 함께 이해하기 위해서는 DI
부터 알아야 한다. 왜냐하면 DI, 의존성 주입이라는 개념을 먼저 알고있어야 IoC
, 제어의 역전이라는 개념이 와닿기 때문이다. 그러면 DI는 무엇일까?
객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다. 이런 개념이 어쩌다가 나왔는지 왜 필요한지 어떤 의의를 가지고 있는지에 대해 알아볼 필요가 있다.
간단한 예시를 들어보겠다. 본 상황에서는 여러 종류의 Internet Service Provider가 있고 Client는 Internet Service Provider를 변경할 수 있다고 가정한다.
다음과 같이 여러종류의 Internet Service에 연결할 수 있는 코드가 있다고 가정해보자. 현재 상황에서는 KT, SK, LG 세 가지의 예시만 들것이다.
public class KTInternetService {
public void connectTo() {
System.out.println(">>> Connect to Internet Service Provider KT");
}
}
.
.
public class LGInternetService {
public void connectTo() {
System.out.println(">>> Connect to Internet Service Provider LG U+");
}
}
.
.
public class SKInternetService {
public void connectTo() {
System.out.println(">>> Connect to Internet Service Provider SK Broadband");
}
}
이 중 현재 클라이언트는 KT 인터넷을 사용하고 있으며 연결을 지원하는 코드는 다음과 같다.
public class Client {
private KTInternetService ktInternetService;
public void connectToInternet() {
ktInternetService = new KTInternetService();
ktInternetService.connectTo();
}
}
클라이언트가 인터넷에 연결되었는지 테스트를 해보면 아래 주석과 같은 결과가 나온다.
public class ConnectionTest {
public static void main(String args[]) {
Client client = new Client();
client.connectToInternet();
}
}
// 결과
// >>> Connect to Internet Service Provider KT
위와 같은 상황의 문제점은 바로 무엇일까??
바로 Client가 특정 InternetService에 과하게 의존하고 있다는 것이다.
💡 의존성이란?
의존성이란 코드에서 두 객체간의 연결이나 관계를 의미한다. 쉽게 말해 한 객체가 어떤 기능을 사용하기 위해 다른 객체를 사용하는 것이다. 다른 객체를 통해 기능을 사용하기 때문에 의존한다 라고 표현한다. 위 코드에서는 Client가 특정 Internet Service(KT)에 과하게 의존하는 모습을 보인다.
그러면 과하게 의존해서 발생되는 문제를 두가지 관점에서 살펴보자.
public class Client {
private SKInternetService skInternetService;
// 또는 private LGInternetService lgInternetService;
public void connectToInternet() {
skInternetService = new SKInternetService();
// 또는 LGInternetService = new LGInternetService();
skInternetService.connectTo();
// 또는 lgInternetService.connectTo();
}
}
그러면 문제점을 정리한 후 개선 전략을 세워 리팩토링 해보겠다.
간접적인 형태
로 제공하고, 나머지는 독립적으로 알 필요도 없이 만들어 주는 것이다.인터페이스
를 제공한다.외부에서 의존받도록
만들어 준다. 이는 다형성
이라는 특성때문에 가능하다.생성자
로 주입받는다.정리하자면 문제를 해결하기위해 Client는 인터페이스를 제공 받으며 특정 구현체를 생성자로 주입받도록 만들어 볼 수 있다.
public interface InternetService {
void connectTo();
}
.
.
public class KTInternetService implements InternetService{
// below connect code
.
.
public class LGInternetService implements InternetService{
// below connect code
.
.
public class SKInternetService implements InternetService{
// below connect code
.
.
Client는 InternetService 추상적인 인터페이스에 의존하며 각 서비스가 어떻게 구성되어있는지 알지 못하고 알 필요도 없다.
public class Client {
private InternetService internetService;
public Client(InternetService internetService) {
this.internetService = internetService;
}
public void connectToInternet() {
internetService.connectTo();
}
}
마찬가지로 클라이언트가 인터넷에 연결되었는지 테스트를 해보면 아래 주석과 같은 결과가 나온다.
public class ConnectionTest {
public static void main(String args[]) {
Client client1 = new Client(new KTInternetService());
client1.connectToInternet();
Client client2 = new Client(new LGInternetService());
client2.connectToInternet();
Client client3 = new Client(new SKInternetService());
client3.connectToInternet();
}
}
// 결과
// >>> Connect to Internet Service Provider KT
// >>> Connect to Internet Service Provider LG U+
// >>> Connect to Internet Service Provider SK Broadband
스프링 핵심개념 중 IoC/DI 중 DI는 꽤 많은 철학과 객체지향 원칙이 녹아있다는 것을 알 수 있었다. 그것을 바탕으로 이 글의 핵심개념을 정리하자면 다음과 같다.
https://github.com/waonderboy/spring-triangle/
- 토비의 스프링
- geeksforgeeks