본문 바로가기

Spring

Spring Framework 와 Spring Boot (1)

이번 포스팅에서는 Spring FrameworkSpring Boot 에 대한 내용을 포스팅 해보려고 합니다. Spring Framework 의 등장 배경과 진화 과정, 장단점, 철학, 비판 등 Spring Boot 에 도달하기 까지의 과정을 알아보겠습니다.

 

1. Spring Framework 란?

 

Spring Framework 는 자바 플랫폼에서 기업용 애플리케이션을 개발하기 위한 범용 애플리케이션 프레임워크입니다.


이름의 이유는 과거 기업용 어플리케이션 개발을 위해  Java EE(엔터프라이즈 에디션)의 스펙을 구현한 EJB를 통한 개발을 진행할 때의 어려움과 복잡함을 개선하고, EJB 시절의 어려움을 '겨울'에 빗대어 새로운 시작인 '봄'을 생각하며 작명을 했다고 합니다.

저는 공대 출신이기에 처음 Spring Framework 라는 단어를 봤을 때 직관적으로는 용수철을 의미하는 Spring 일까 생각했는데 생각보다 문과적인 감성의 작명 센스를 가지신 선배 개발자분들도 많다는 생각을 했습니다.

 

특징으로는 

1. Java 기반의 모듈형 아키텍쳐 제공
2. 객체지향 설계 원칙에 충실
3. 대표적 특징: 제어의 역전, 의존성 주입, AOP(관점지향 프로그래밍)

 

세가지 정도가 있겠습니다. 여기서 모듈형 아키텍쳐란 애플리케이션을 기능 단위로 나누어서, 각 모듈이 독립적으로 개발, 테스트, 유지보수 될 수 있도록 구성하는 설계방식입니다. 

 

간단히 말하자면, 하나의 거대한 프로젝트를 여러 개의 기능 블록(모듈)로 나누고, 모듈들이 느슨하게 연결되어 협력하도록 설계하는 구조입니다. 물론 이렇게 설명을 들어도 용어의 이해가 어려울 수 있겠습니다.

 

예시를 들어보겠습니다.

 

Spring 에서는 개발 중, 기능에 따라 코드를 작성해 가면서 접하는 다양한 모듈이 있습니다. 기본적이고 대표적인 모듈로는

 

1. Spring Web: DispatcherServlet, 웹 MVC 같은것들 입니다.

2. Spring AOP: AOP 구현에 관한 모듈입니다. 주로 로깅등을 학습하시면 알 수 있습니다.

3. Spring JDBC: 과거 사용되었던 JDBC 템플릿과 같은 DB 와의 연결을 할때 사용되었던 모듈입니다.

4. Spring ORM: JPA 의 구현체인 Hibernate  등의 ORM 을 연동 하는 모듈입니다.

5. Spring Test: 테스트나 mock 객체 지원에 대한 모듈입니다.

 

물론 이외에도 다양한 모듈이 존재하고 위의 다섯가지는 모듈이 무엇인지 이해를 돕기위한 예시입니다. 이러한 모듈들을 의존성 주입을 받아 사용할 수 있습니다. 아래 코드는 자주 보신적 있을 것입니다.

 

dependencies {
    implementation 'org.springframework:spring-web'         // 웹 기능만 사용
    implementation 'org.springframework:spring-aop'          // AOP 기능
    implementation 'org.springframework:spring-context'      // ApplicationContext
    implementation 'org.springframework:spring-jdbc'         // DB 연결
}

 

이렇게 필요한 것만 가져다 쓸 수 있도록 만든것이 Spring 의 모듈형 구조라고 이해하면 되겠습니다.

 

왜 모듈형이 중요한가?

에 대한 내용을 알아보겠습니다. 키워드는 다음 5가지가 있습니다

책임 분리: 각 모듈이 명확한 책임을 가지도록 설계됨 (SRP: 단일 책임 원칙)
독립 개발: 모듈 단위로 개발과 테스트가 가능
테스트 용이: 하나의 기능만 테스트 가능 → 빠르고 정확
배포 유연성: 필요 시 모듈만 따로 배포하거나 교체 가능 (마이크로서비스화도 용이)
재사용성: 같은 기능을 다른 프로젝트에 재사용하기 쉬움

 

그럼 다시 Spring Framework 로 돌아와보겠습니다.

 

2002년까지 J2EE 아키텍쳐라는 것을 활용해 기업에서 사용하는 애플리케이션을 개발해왔다고 합니다. 이후 2003년 Spring Framework 1.0 이 공개 되었고, 당시 J2EE 의 문제점이었던 복잡함을 해결할 수 있었습니다.

 

물론 저는 J2EE 로 개발한 프로젝트에 대한 내용을 자세히 보진 못했지만, 당시에는 복잡한 Enterprise Java Bean 때문에 개발이 무겁고 설정이 번거롭기도 헀고, 트랜잭션 처리가 어렵다는 단점도 있었습니다. 또한 유닛테스트를 진행하는 것에 대한 어려움의존성이 강해서 확장성이 낮다라는 단점이 있었다고 합니다.

 

이러한 모든 문제를 "더 가볍고 유연하게 만들자"라는 철학으로 나온 것이 Spring Framework 입니다.

 

1.x 버전부터 현재 5.x 버전까지 다양한 발전이 있었지만 3.x 버전부터 REST API 를 지원하게 되어 현재는 3.x 부터 필요에 맞는 버전으로 개발을 하고 있는 것 같습니다.

 

핵심 개념은?(철학과 기능)

Spring Framework 시절 부터, 이미 정립된 핵심 개념에 대한 내용을 알아보겠습니다.


 

1. IoC(Inversion of Control) - 제어의 역전

 

용어가 조금 어렵게 느껴집니다. 가벼운 느낌으로 설명해보자면 객체의 생성이나 관리를 개발자가 아니라 컨테이너가 대신 관리하게 하는 것을 말합니다. 즉, 제어의 권한을 프레임워크에게 위임하는 것이죠.

 

이렇게 설명해도 좀 어려울 것 같습니다. 한번 더 풀어보자면, IoC 컨테이너란 애플리케이션에서 사용할 객체(Bean)를 생성, 관리, 주입, 소멸 까지의 모든 Lifecycle 을 책임지는 중앙 관리자 역할을 하는 객체입니다.

개발자 대신 객체를 생성해주고 관리해주는 Spring 의 핵심 엔진이라고 생각하시면 됩니다.

 

이 컨테이너가 바로 ApplicationContext 입니다.

 

ApplicationContext 는 BeanFactory 및 메세지 소스, 이벤트, AOP 등 다양한 부가기능을 하는데 사용됩니다.

 

ApplicationContext 는 다음 네가지의 역할을 개발자 대신 해줍니다.

 

1. 생성: 클래스의 정의를 보고 객체(Bean) 생성
2. 주입: 필요한 의존성(필드, 생성자 등)에 자동 주입
3. 관리: 싱글톤(프로세스 전역에서 유일한 인스턴스) 관리, 요청 시 반환, 라이프사이클 관리
4. 소멸: 애플리케이션 종료 시 Bean 소멸 처리

 

Bean 이 무엇인지에 대해서는 다른 포스팅에서 자세히 다뤄보고자 합니다. 우선 이해하기 쉽게 설명하자면,

 

@Component
@Controller
@Service
@Repository
@Entity

 

같은 어노테이션을 클래스위에 정의하면서 주로 사용하셨을거라 생각합니다. 이 녀석들이 각각 Bean 객체이고(Ctrl + 클릭하여 소스파일을 들어가다 보면 인터페이스로 정의된 것을 확인할 수 있습니다)  ApplicationContext 가 처음 애플리케이션이 실행될때 라이프사이클을 관리해주는 것입니다.

 

이런 ApplicationContext 와 같은 컨테이너가 없던 시절엔 개발자가 직접 객체를 생성하고, 필요한 의존성도 직접 생성자나 필드로 주입해야헀습니다. 다음과 같은 예시로 처럼말이죠.

UserService userService = new UserService(new UserRepository());

 

이런 방식에서 컨테이너를 사용하게된 후로는 다음과 같이 작성하는것을 보셨을겁니다.

 

@Component
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);

 

이렇게 작성하면 객체 생성, 의존성 주입 모두 `ApplicationContext` 라는 컨테이너가 처리해주게 됩니다. 객체를 프레임워크가 생성해주고, 우리는 요청해서 받아만 쓰는 구조가 되는데 이것이 제어의 역전(IoC) 라고 설명할 수 있겠습니다.

 

내부구조의 흐름을 보자면 

 

1. AppConfig 또는 @ComponentScan 으로 Bean 등록

2. 컨테이너가 Bean 정의를 읽음

3. 필요한 객체(Bean)를 생성

4. 의존성 있는 Bean 을 주입

5. getBean() 등으로 필요한 곳에 제공

 

이런 순서를 거치게 됩니다. 이렇게 되면서 결합이 결합이 느슨해지고 확장성도 좋아지게 되는것입니다.


 

2. DI(Dependency Injection) - 의존성 주입

 

여러번 나온 내용입니다. 대체 의존성이란 뭐지?

의존성을 정의해보자면

어떤 객체가 다른 객체의 기능을 사용해야만 동작할 수 있다면, 그 객체는 다른 객체에 '의존'한다

 

라고 할 수 있겠습니다. 다음과 같은 코드 예시를 보고 문장으로 설명하겠습니다.

public class OrderService {
    private final UserRepository userRepository;

    @Autowired
    public OrderService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

 

예를 들어, A 클래스가 B 클래스를 내부에서 사용하고 있다면 A는 B에 의존하고 있다. 라고 할 수 있습니다.

`OrderService` 가 `UserRepository` 를 사용해서 회원 정보를 조회한다고 하면, OrderService 는 UserRepository 에 의존하고 있다 라고 표현할 수 있는 것이지요.

"내가 혼자선 일을 못 해. 너 없으면 나도 못 움직여"

 

이게 의존입니다. 이것을 직접 주입하는 방식과 비교해보자면

public class OrderService {
    private final UserRepository userRepository;

    public OrderService() {
        this.userRepository = new UserRepository(); // 직접 생성 → 강한 결합
    }
}

 

위 처럼 직접 객체를 필드에 선언하고 new UserRepository() 로 생성하면 OrderService 는 UserRepository 의 구현 방식에 강하게 결합되게 됩니다. 이렇게되면 테스트나, 교체, 확장 등이 어려워집니다. 그래서 의존성을 주입해서 사용하는 방식을 사용하게 되는데

 

this.userRepository = new UserRepository();

 

이렇게 직접 생성하는 것이 아닌,

 

@Autowired
public OrderService(UserRepository userRepository) {
    this.userRepository = userRepository;
}

 

외부로 부터 주입 받아서 결합도를 느슨하게 하는것이 의존성 주입을 했다고 볼 수 있습니다. 그래서 일반적으로 사용하는 3-Layered-Architecture 에서 Controller, Service, Repository 의 구조를 보면 ApplicationContext 가 UserRepository 부터 시작해서 OrderService 에 주입해주게 하고, 이렇게 만들어진 OrderService를 OrderController 에게 주입해주게 되어 위에서 말한 제어의 역전이 동작하게 되는 것 입니다.

 

이렇게 결합을 느슨하게 했을때, 장점을 예를 들어 보면 UserRepository 대신 FakeUrserRepository 를 넣어서 테스트를 하게 하거나, MySQL 용 UserRepository 를 MongoDB 등으로 교체하거나 하는 일들이 쉬워집니다. 결합도가 느슨하기 때문이죠. 이런것이 의존성 주입(DI) 이고 제어의 역전(IoC) 와 같이 이해하면 좋습니다.

 

저는 처음 학습할때 여기서 의문이 들었습니다.

OrderController 는 OrderService 에게 의존하고 있기때문에 의존성을 주입받고, OrderService 는 UserRepository 에게 의존하고 있기에 의존성을 주입받고 있는데 UserRepository 는 누구에게 의존성을 주입받는 거지? DB 에게? 아니면 혼자서 태어나나?

 

조금은 이상한 상상일지도 모르겠지만, 위에서 말했던 IoC 컨테이너에 대한 내용을 기억하고 있다면 알 수 있으실 겁니다. 바로 ApplicationContext 가 처음 Bean 객체들에게 라이프사이클을 관리할 때 생성하여 필요에 맞게 의존성을 주입하여 생성해주는 것입니다. 이 2번항목, 1번항목, 3번항목을 번갈아 보시면서 컨테이너가 하는 일을 이해하시면 Spring Framework 에 대한 이해가 좀 더 깊어질 것이라는 생각이 듭니다.


 

3. AOP(Aspect-Oriented Programming) - 관점 지향 프로그래밍

 

AOP 는 공통된 로직을 핵심 비즈니스 로직과 분리하는 것을 말합니다. 이때 공통 로직은 트랜잭션, 로깅, 보안 등을 말합니다.

 

전통적인 OOP 관점에서의 코드를 보겠습니다.

public void createOrder() {
    startTransaction();       // 공통 로직
    doBusinessLogic();        // 핵심 로직
    commitTransaction();      // 공통 로직
}

 

createOrder() 라는 클래스에 핵심은 말그대로 주문을 만드는 것이고, 나머지 `startTransaction()` , `commitTransaction()` 은 공통로직입니다. 공통 로직인 이유는 거의 모든 서비스 로직에 이런 트랜잭션 개념을 이용하여 데이터의 정합성을 보장해야하기 때문이지요.

 

이 방식의 문제점은 중복 코드가 증가하고, 핵심 로직이 공통 코드에 묻히는 가독성 저하를 일으키기도 하고, 재사용성이나 유지보수가 어렵다고 볼 수 있습니다.

 

그래서 우리는 관심사를 분리해버려야합니다. 이때의 관심사는 클래스나 메서드가 책임지고 있는 역할이고 이것을 각각의 목적에 맞게 분리해서 사용해야한다는 의미입니다.

 

핵심 관심사는 비즈니스 로직, 이것은 곧 주문을 생성한다 라는 것이고 부가 관심사는 전반적으로 필요한 공통 기능인 트랜잭션, 로깅, 보안, 인증, 예외처리 등을 말하는 것입니다. 이런 부가 관심사를 모듈화 해놓은 클래스는 Aspect 라고 정의되어있고, 보통 @Aspect 어노테이션을 사용해서 정의합니다.

 

@Aspect
@Component
public class TransactionAspect {

    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("트랜잭션 시작");
        try {
            Object result = joinPoint.proceed(); // 실제 핵심 메서드 실행
            System.out.println("트랜잭션 커밋");
            return result;
        } catch (Exception e) {
            System.out.println("트랜잭션 롤백");
            throw e;
        }
    }
}

 

이런식으로 공통로직인 트랜잭션을 정의해두고, 우리는 핵심 로직에서 @Transactional 이라는 어노테이션만 붙이면 트랜잭션 처리는 AOP 가 가로채서 자동실행하게 됩니다. 이렇게 코드의 중복을 제거하고, 가독성을 좋게 만들고, 유지보수나 테스트에서 이점을 챙길 수 있습니다.


2. Spring Framework 특징 요약

이런 세가지 핵심개념을 가진 Spring Framework, 이제는 좀 내용을 알 것 같습니다.

 

J2EE 와 비교시 가볍고 유연하며,

모듈화를 통해 필요한 모듈만 선택적으로 사용할 수 있고,

MVC(Controller-Service-Repository) 구조를 지원하고,

활발한 커뮤니티와 지속적인 발전으로 다양한 오픈소스를 가져다 사용할 수 있다는 것,

위에서 길게 정의한 제어의 역전, 의존성 주입, 관점 지향 프로그래밍 등의 특징을 가진 것이 

 

Spring Framework 라고 할 수 있겠습니다.

 

설명만 보면 너무 좋은 프레임워크라는 생각이 들겠지만, 당연히 문제점과 비판도 많았습니다.

 

등장 초창기에는 대부분의 설정을 XML 로 해야해서 설정파일만해도 수백줄의 코드가 들어가기도 했고, 다양한 라이브러리를 조합해야 해서 Maven 이나 Gradle 설정이 복잡하기도 했습니다. 또한 Spring 컨텍스트 로딩 속도가 느려서 단위 테스트보다 통합테스트 위주로 할 수 밖에 없었고, 초보자가 접하기엔 조금 어렵고 DI, AOP, 빈 생명주기 등의 추상적인 개념이 많아보니 학습 곡선이 가파른 편이었습니다. 이런 단점을 어느정도 극복하여 유행을 끌고 있는것이 Spring Boot 입니다.

 

설명을 하다보니 글이 너무 길어져서 Spring Boot 에 대한 내용은 다음 포스팅에서 진행하고자 합니다. 천천히 읽어보시면서 모르시는 부분은 댓글로 질문을 달아주시면 고맙겠습니다!!

 

https://wanglan.tistory.com/entry/Spring-Framework-%EC%99%80-Spring-Boot-2

 

Spring Framework 와 Spring Boot (2)

안녕하세요! 지난 포스팅에 Spring Framework 와 Spring Boot 에 대한 내용을 포스팅하다 글이 길어져서 Spring Boot 에 대한 내용은 이번 포스팅에서 진행하도록 하겠습니다. 지난 포스팅과 이어지기 때문

wanglan.tistory.com

 

728x90
반응형

'Spring' 카테고리의 다른 글

Spring - JPA  (0) 2025.04.05
Spring Framework 와 Spring Boot (2)  (0) 2025.04.05
Spring - Redis의 Pub/Sub 및 WebSocket 구현하기  (4) 2024.12.17
Spring - DB 조회의 최적화  (2) 2024.11.27
Spring - Redis 적용 실습  (0) 2024.11.26