위 Spring 삼각형 하나로 Spring의 핵심 개념들을 모두 표현 하고 있다고 해도 과언이 아니다.
POJO는 Spring에서 사용하는 핵심 개념들에 둘러 싸여져 있는 모습이다. 이는 POJO라는 것을 IoC/DI, AOP,PSA를 통해서 달성할 수 있는 것을 의미한다.
위 조건을 만족하는 나머지가 모두 POJO인 것은 아니다 .
진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다.
Spring은 POJO 프로그래밍을 지향하는 Framework임과 세가지 기술을 지원하고 있다.
IoC는 제어의 역전으로 Spring 삼각형을 이루는 기능 중 하나이다.
Library는 애플리케이션 흐름의 주도권이 개발자에게 있고, Framework은 애플리케이션 흐름의 주도권이 Framework에 있다.
여기서 말하는 애플리케이션 흐름의 주도권이 뒤바뀐 것을 제어의 역전 즉, IoC이다.
IoC를 이해하기 위해 샘플 코드를 보면
public class Example2_10 {
public static void main(String[] args) {
System.out.println("Hello IoC!");
}
}
일반적으로 위와 같은 Java콘솔 애플리케이션을 실행하려면 main() 메서드가 있어야 한다. 코드에서는 main() 메서드가 호출되고 난 다음에 System 클래스를 통해서 static 멤버 변수인 out의 println()을 호출한다.
즉, 개발자가 작성한 코드를 순차적으로 실행하는게 일반적인 애플리케이션 제어 흐름이다.
위 그림은 서블릿 컨테이너의 서블릿 호출 예이다.
웹에서 동작하는 애플리케이션의 경우 클라이언트가 외부에서 접속해서 사용하는 서비스이기 때문에 main() 메서드가 종료되지 않아야 한다.
그런데, 서블릿 컨테이너에는 서블릿 사양에 맞게 작성된 서블릿 클래스만 존재하지 별도의 main() 메서드가 존재하지 않는다.
main() 메서드처럼 애플리케이션이 시작되는 지점을 엔트리 포인트(Entry Point)라고도 부른다
서블릿 컨테이너 경우, 클라이언트의 요청이 들어올 때마다 서블릿 컨테이너 내의 컨테이너 로직인 service()가 서블릿을 직접 실행시켜 주기 때문에 main() 메서드가 필요없다.
이 경우에는 서블릿 컨테이너가 서블릿을 제어하고 있기 때문에 애플리케이션의 주도권은 서블릿 컨테이너에 있다. 바로 서블릿과 웹 애플리케이션 간에 IoC(제어의 역전) 개념이 적용되어 있다.
Spring 에서는 IoC 개념이 DI를 통해 적용되어 있다.
객체지향 프로그래밍에서 의존성이라고 하면 대부분 객체 간의 의존성을 의미한다.
의존성을 예시로 들면, A,B 두 클래스가 있다고 하고, A 클래스가 B 클래스의 메소드 기능을 사용할 때 'A클래스는 B클래스에 의존한다' 라고 할 수 있다.
의존성의 주입은 생성자를 통해서 어떤 클래스의 객체를 전달 받는 것을 '의존성 주입' 이라고 한다. 즉, 생성자의 파라미터로 객체를 전달하는 것을 외부에서 객체를 주입한다라고 표현이 된다.
[그림] 의존성 주입 예시코드
[그림] 느슨한 결합이 이루어지지 않은 경우
목록 조회 API로 Stub을 제공하기 위해 MenuServiceStub 클래스를 사용한다. 자세히 보면 MenuController (MenueServiceStub menuService) 에서 MenuServiceStub을 이용하려 하는데 MenuController 와 CafeClient에서 MenuService를 사용하고 있어 MenuServieSturb 클래스로 바꾸어 사용하고 있음을 알 수 있다.
이 클래스를 사용할 대상이 수십, 수백군데가 되게 되면 불필요하게 바꿔야 하는 상황이 일어나게 된다.
이를 방지하기 위해 느슨한 결합을 이용한다.
느슨한 결합은 인터페이스를 통해서 구현이 가능하다.
MenuServiceStub가 MenuServie 인터페이스를 구현함과 동시에 MenuController은 MenuService를 사용하고 있어 클래스 변경없이 MenuService를 사용할 수 있게 된다.
즉, 인터페이스를 통해서 객체간의 결합을 느슨하게 변경해줄 수 있게 된다.
AOP란 관심지향 프로그래밍이다.
AOP에서 Aspect는 부모들이 가지고 있는 아기의 건강 같은 관심사와 마찬가지로 애플리케이션에 필요한 기능 중에서 공통적으로 적용되는 공통 기능에 대한 관심과 관련이 있다.
PSA는 클라이언트가 추상화 된 상위 클래스를 일관되게 바라보며 하위 클래스의 기능을 사용하는 것이 바로 일관된 서비스 추상화(PSA)의 기본 개념이다
PSA를 이해하기 위해 예시를 들면,
미취학 아동을 관리하는 애플리케이션을 설계하면서 아이 클래스를 일반화(추상화)한다라고 하면
아이의 일반적인 속성으로는 이름,키,몸무게,혈액형,나이 등이 있고,
아이가 동작할 수 있는 동작으로는 웃다,울다,자다,먹다 등이 있다.
이렇게 추출한 아이의 일반적인 특징을 클래스로 작성하면
로 표현이 되고, 아이의 연령대 별로 해당 메소드를 기능들을 사용 다시 말해, 상위 클래스(Child)에서 하위 클래스(연령 별 기능 구현) 로 확장해 기능을 사용하기 위해
하위 클래스는 아래와 같이 나타낼 수 있고,
/ NewBornBaby.java(신생아)
public class NewBornBaby extends Child {
@Override
protected void smile() {
System.out.println("신생아는 가끔 웃어요");
}
@Override
protected void cry() {
System.out.println("신생아는 자주 울어요");
}
@Override
protected void sleep() {
System.out.println("신생아는 거의 하루 종일 자요");
}
@Override
protected void eat() {
System.out.println("신생아는 분유만 먹어요");
}
}
// Infant.java(2개월 ~ 1살)
public class Infant extends Child {
@Override
protected void smile() {
System.out.println("영아는 많이 웃어요");
}
@Override
protected void cry() {
System.out.println("영아는 종종 울어요");
}
@Override
protected void sleep() {
System.out.println("영아부터는 밤에 잠을 자기 시작해요");
}
@Override
protected void eat() {
System.out.println("영아부터는 이유식을 시작해요");
}
}
// Toddler.java(1살 ~ 4살)
public class Toddler extends Child {
@Override
protected void smile() {
System.out.println("유아는 웃길 때 웃어요");
}
@Override
protected void cry() {
System.out.println("유아는 화가나면 울어요");
}
@Override
protected void sleep() {
System.out.println("유아는 낮잠을 건너뛰고 밤잠만 자요");
}
@Override
protected void eat() {
System.out.println("유아는 딱딱한 걸 먹기 시작해요");
}
}
위의 하위 클래스를,
위 코드처럼 연령별로 사용할 수 있게 된다.
즉, 일관된 방식으로 해당 서비스의 기능을 사용할 수 있게 된다.
이처럼 애플리케이션에서 특정 서비스를 이용할 때, 서비스의 기능을 접근하는 방식 자체를 일관되게 유지하면서 기술 자체를 유연하게 사용할 수 있도록 하는 것을 PSA(일관된 서비스 추상화)라고 한다.