스프링의 기본 철학, 스프링 삼각형

2021. 7. 3. 20:43개발

기능 구현에만 치중하다보니, 면접을 볼 때 질문 받는 스프링의 기본 철학에 대해 깔끔하게 대답하지 못하는 걸 많이 느껴 백기선님의 "예제로 배우는 스프링 입문(개정판)"을 수강하고 간단하게 정리한 내용.

스프링 삼각형

스프링의 3대 요소는 DI와 IOC, AOP, PSA 세 가지로 나눌 수 있다. 각각 의존성 주입과 제어의 역전, 관점 지향적 프로그래밍, 이식 가능한 서비스의 추상화 정도로 번역이 된다. 다만 이걸 글만 읽고 이해할 수 있는 번역어인진 또 별개의 문제인지라... 대부분 약어로 쓰는듯 하다.

DI와 IOC (Dependency Injection & Inversion of Control)

이 두 개념의 핵심은 객체의 생명주기를 사람이 관리하는 게 아니라 기계에 관리를 시킨다는 것이다.

우리는 스프링으로 서버를 만들 때 @Controller 어노테이션을 붙여서 라우팅을 시킨다. 그러고 나서 잊어버린다. 이 때 이 Controller 클래스는 어디서 생성되는 것일까? 이 질문에 대한 해답을 주는게 IOC이다.

스프링은 ApplicationContext를 가지고 있고 스프링에서 관리하는 객체인 Bean들은 여기에 담기게 된다. 여기서 ApplicationContext가 IOC 컨테이너이고 Bean의 생명 주기는 이 IOC 컨테이너에 의해 관리된다.

이렇게 생성된 Bean들 사이의 참조 관계(다시 말해 의존성)는 간단히 설정할 수 있는데 이를 DI라고 부른다.

PetController에서 CatService를 통해 고양이에게 밥을 준다고 가정하자. 일반적인 웹 구현에서는 CatService를 호출하기 위해 new 키워드를 붙여 인스턴스를 생성하든지, 각종 생성 패턴(팩토리, 빌더, 싱글턴 등)으로 작성된 클래스의 인스턴스를 받아와야 하지만 Spring 프레임워크 안에선 DI를 이용해 간단하게 구현할 수 있다.

  1. 필드 주입
    @Autowired / @Resource 등의 어노테이션을 필드(속성)에 설정해준다면 스프링에서 검색 규칙에 따라 알맞은 클래스를 기반으로 만든 Bean을 주입시켜 준다.
  2. 세터 주입
    어노테이션을 Setter 형태로 만든 메소드에 붙여주면 해당 필드에 Bean을 주입시켜준다.
  3. 생성자 주입
    클래스 생성자에 주입시킬 Bean들을 파라미터로 넣어주고 설정하는 방법이다. 장점으론 필드 주입과 세터 주입에서 사용하지 못하는 final 키워드를 필드에 붙일 수 있게 된다.

위 세가지 방법으로 구현이 가능하며, 내 경우 실무에선 1번 말고 다른 걸 써본 적이 없는데 스프링 프레임워크 단에서 추천하는 방법은 세번째 생성자 주입이다.

final 키워드를 붙일 수 있어서 중간에 필드 인스턴스를 바꾸는 게 불가능해지며, 의존성을 추가할 수록 생성자 파라미터가 늘어나게 되므로 "뭔가 잘못 되어가는 거 같은" 느낌을 줄 수 있어서 추천하는 방법이라고 한다.

AOP (Aspect Oriented Programming)

관점 지향적 프로그래밍이라고 번역이 되곤 하는데 오히려 이렇게 들으면 더 이해가 힘든 느낌이고, 예제 코드나 예제를 보면 더 이해가 쉬운 케이스가 아닌가 싶다.

void feeding() {
  catService.say("meow");
}

위와 같이 Zoo 클래스에서 사용하는 CatService에 고양이에게 밥을 먹이는 함수가 있다고 가정하자. 물론, 동물원엔 동물이 하나가 아니다. 도롱뇽, 호랑이, 사자 등등... 300여개의 동물이 있다고 가정해본다.

그런데 만약 동물에게 밥을 먹이는 시간을 측정하고 싶어진다면? 300여개 함수를 일일히 수정을 해야 되는가? 이 문제를 해결해주는게 AOP이다.

void feeding() {
// AOP
timeCheckStart();

catService.say("meow");

// AOP
timeCheckEndAndPrint();
}

AOP를 사용한다면 코어 로직을 제외한 부분에 코드를 추가하는 게 매우 간단해진다. 아마 가장 명료한 AOP 사용 예는 @Transactional 어노테이션일 것이다. dc.autoCommit(false)로 해준 후 비즈니스 로직을 쭉 적고 dc.commit();을 해주는 형태로 구현이 되어있을 거니까.

PSA (Portable Service Abstractions)

기술에 대해 공통으로 사용할 수 있게 추상화된 레이어를 제공하고 있단 의미이다. 가령 일반적인 MVC 구조로 쓰이는 프로젝트에서 Reactive 기반인 WebFlux로 전환한다고 하더라도 Spring 기반의 인터페이스로 작성하였다면 @RequestMapping 등의 어노테이션을 굳이 바꿔줄 필요는 없다.(물론, 일부 수정이 필요한 부분은 있을 것이다.)

이 외에도 위에 언급한 @Transactional 어노테이션 역시 JPA를 사용하든 JDBC를 사용하든 변경이 필요가 없으며, 캐시 관련된 설정 역시 공통 로직은 스프링이 제공하는 인터페이스로 작성하였다면 세부 구현이 무엇이든 상관이 없어져 변경이 손쉬워진다.

(2021.04.06 작성된 글을 티스토리로 옮김)