본문 바로가기
Daily Dev Q&A 정리 템플릿

25.12.22 스프링 프레임워크이란?

by teg0 2025. 12. 22.

Spring Framework

자바 언어를 기반으로 엔터프라이즈급 애플리케이션을 개발하기 위해 만들어진 오픈소스 애플리케이션 프레임워크이다.

현재 한국 시작에서 사실상 표준으로 자리 잡고 있는 기술이다.

 

 

스프링의 핵심 철학 : POJO

스프링의 가장 큰 특징은 "옛날 방식의 자바 객체(POJO)로 돌아가자"라는 것이다.

  • POJO: gettet/setter를 가진 단순 자바 오브젝트로 정의를 하고 있다. 이러한 단순 오브젝트는 의존성이 없고 추후 테스트 및 유지보수가 편리한 유연성의 장점을 가진다.

과거 자바 개발이 너무 복잡하고 특정 기술에 종속적이었다. 스프링은 가장 기본적인 자바 객체만으로도 복잡한 엔터프라이즈 기능을 구현할 수 있게 해주는 것을 목표로 한다. 이를 가능하게 하는 3가지 핵심 기술이 있다.

 

스프링의 3대 핵심 기술

IoC (Inversion of Control, 제어의 역전)

기존의 방식 (개발자가 대장)

과거에는 객체의 생성부터 소멸까지 모든 생애주기를 개발자가 직접 관리했다.

  • "A 객체 만들어!" (new A())
  • "A 안에 B 객체 넣어!" (a.setB(b))
  • "A 실행해!" (a.run())

즉, 개발자의 코드가 라이브러리를 호출하는 형태였다.

 

IoC 방식 (스프링이 대장)

스프링을 쓰면 개발자는 객체(Bean)의 설계도(클래스)만 만들어 놓았다.

  • 생성: 스프링이 알아서 만듭니다.
  • 조립: 스프링이 알아서 필요한 것끼리 연결해 줍니다(DI).
  • 실행: 스프링이 알아서 시점을 정해 실행합니다.

즉, 프레임워크가 개발자의 코드를 호출하는 형태가 된 것이며, 제어의 흐름이 뒤집힌 것이다.

 

제어의 역전 개념에 사용된다는 예시: 

헐리우드 원칙 (Hollywood Principle) "Don't call us, we'll call you." (우리에게 전화하지 마세요. 우리가 당신을 부를게요.)

배우(개발자 코드)는 오디션장에 프로필(Bean)만 내고 기다리면, 감독(스프링)이 필요할 때 불러서 쓴다.

 

IoC의 장점

결합도(Coupling)가 낮아진다 (유연성)

  • 설명: 개발자가 코드 안에서 직접 new SamsungTV()라고 적어버리면, 나중에 LgTV로 바꿀 때 코드를 뜯어고쳐야 한다. (강한 결합)
  • IoC 장점: 스프링이 중간에서 객체를 관리해 주기 때문에, 개발자는 TV 인터페이스만 보고 코드를 짜면 된다.
    실제 삼성TV가 들어올지 LGTV가 들어올지는 스프링(설정 파일)이 알아서 결정합니다. 부품 교체가 매우 쉬워진다.

테스트가 쉬워진다 (Testability)

  • 설명: IoC가 없다면 테스트할 때도 진짜 데이터베이스 연결, 진짜 결제 시스템이 필요하다.
  • IoC 장점: 스프링이 객체를 주입해 주기 때문에, 테스트할 때는 "가짜 객체(Mock)"를 슬쩍 넣어줄 수 있다.
    • 예: "결제 테스트할 건데, 진짜 돈 나가는 RealPayment 말고, 돈 안 나가는 FakePayment 넣어줘." -> 이게 가능해진다.

개발 생산성 향상 (집중)

  • 설명: 객체를 어떻게 만들고, 어떤 순서로 연결하고, 언제 없앨지 신경 쓰는 건 매우 피곤한 일이다.
  • IoC 장점: 이런 귀찮은 관리(LifeCycle)는 스프링에게 맡겨버리고, 개발자는 오직 "핵심 비즈니스 로직(회원가입, 주문처리)" 구현에만 집중할 수 있다.

싱글톤 관리 (효율성)

  • 설명: 웹 애플리케이션은 수많은 사용자가 동시에 접속할 수 있다. 수많은 사람이 요청할 때마다 객체를 새로 만들면 서버 메모리가 터진다.
  • IoC 장점: 스프링 컨테이너가 알아서 "객체를 딱 하나만 만들어서(Singleton)" 모든 사용자에게 공유해준다.
    개발자가 일일이 싱글톤 패턴 코드를 짤 필요가 없다.

 

IoC의 단점

코드의 흐름을 눈으로 쫒기 어렵다. (가독성 저하)

  • 전통적인 방식에서는 Ctrl + 클릭만 하면 어떤 객체가 실행될지 바로 알 수 있다.
    하지만 IoC 환경에서는 코드가 "추상화"되어 있어, 실제 어떤 객체가 주입되는지 코드만 봐서는 알 수 없다.
// 인터페이스
public interface PaymentPolicy {
    void pay();
}

// 구현체 1: 카카오페이
@Component
public class KakaoPay implements PaymentPolicy { ... }

// 구현체 2: 네이버페이
@Component
@Primary // 얘가 우선순위
public class NaverPay implements PaymentPolicy { ... }

// 사용하는 곳 (Controller)
public class OrderService {
    
    private final PaymentPolicy paymentPolicy;

    // 생성자 주입
    public OrderService(PaymentPolicy paymentPolicy) {
        this.paymentPolicy = paymentPolicy;
    }

    public void process() {
        // [문제점] 여기서 코드를 읽는 개발자는 혼란에 빠집니다.
        // "도대체 카카오페이가 실행되는 거야, 네이버페이가 실행되는 거야?"
        paymentPolicy.pay(); 
    }
}
  • 단점: OrderService 코드만 봐서는 paymentPolicy가 KakaoPay인지 NaverPay인지 알 수 없다.
  • 해결: 설정을 뒤져보거나, @Primary, @Qualifier 같은 애노테이션을 찾아다녀야 한다.
    코드가 마치 "마법 상자(Black Box)"처럼 변해서 내부 동작을 파악하기 어려워진다.

 

컴파일 에러가 아닌, '런타임 에러'가 발생한다.

  • 가장 치명적인 단점이다.
    직접 new를 하면 객체가 없거나 오타가 났을 때 빨간 줄(컴파일 에러)이 떠서 실행 전에 바로 알 수 있다.
    하지만 스프링에게 맡기면, 일단 실행을 해봐야(서버를 켜봐야) 에러가 터진다.
// 개발자가 깜빡하고 @Component를 안 붙임!
// 스프링은 이 클래스의 존재를 모름 (스프링 빈 등록 안 됨)
public class EmailService {
    public void send() { ... }
}

@Service
public class MemberService {
    
    private final EmailService emailService;

    @Autowired
    public MemberService(EmailService emailService) {
        this.emailService = emailService;
    }
}
  • 결과:
    1. 자바 문법상으로는 완벽합니다. (빨간 줄 없음)
    2. 신나게 빌드하고 서버를 띄우는 순간... 펑!
    3. NoSuchBeanDefinitionException (빈을 찾을 수 없음) 에러가 발생하며 서버가 죽는다.
  • 단점: 실수한 것을 코드를 짜는 도중에 발견하기 어렵고, 실제 배포하거나 테스트를 돌릴 때 발견하게 된다.

 

순환 참조 (Circular Dependency) 문제

  • 내가 직접 관리할 때는 잘 안 생기는 문제인데, 스프링이 알아서 다 연결해 주다 보니 "서로가 서로를 참조하는 꼬리 물기" 상황이 발생할 수 있다.
@Service
public class AService {
    @Autowired
    private BService bService; // A는 B를 원해

    public void doA() { bService.doB(); }
}

@Service
public class BService {
    @Autowired
    private AService aService; // B는 A를 원해 (순환 발생!)

    public void doB() { aService.doA(); }
}
  • 결과: 스프링이 A를 만들려고 보니 B가 필요하고, B를 만들려고 보니 A가 필요하다.
    • "닭이 먼저냐 알이 먼저냐" 딜레마에 빠져서 애플리케이션 실행이 실패한다. (BeanCurrentlyInCreationException)
  • 단점: 설계가 복잡해지면 이런 의존 관계가 꼬이는 것을 파악하기가 매우 어렵다. (최근 스프링 부트는 이걸 실행 시점에 막아버린다.)

 

DI( Dependency Injection, 의존성 주입 )

DI의 핵심은 객체가 의존하는(필요로 하는) 다른 객체를 직접 생성하지 않고, 외부에서 넘겨받는 것이다.

 

  • DI 적용 전 (일체형 배터리 장난감):
    • 장난감 로봇이 배터리를 자기 뱃속에서 직접 만들어낸다 (new Battery()).
    • 문제: 배터리가 다 닳거나, 다른 종류의 배터리로 바꾸려니 로봇을 뜯어고쳐야 한다.
  • DI 적용 후 (교체형 배터리 장난감):
    • 장난감 로봇은 배터리를 넣는 구멍만 있다.
    • 해결: 외부(사람)에서 에너자이저를 넣든, 듀라셀을 넣든 로봇은 "전기만 들어오면" 작동한다.

 

public class Car {
    private Tire tire;

    public Car() {
        // 자동차가 타이어를 직접 만듦 (한국타이어로 고정됨)
        this.tire = new HankookTire(); 
    }
}

DI를 사용하지 않으면 강한 결합이 생긴다. 예시를 보면, 한국타이어가 고정되어, 다른 타이어로 바꾸기 위해서는 Car 코드를 뜯어고쳐야 하기 때문에, 유연성이 낮다.

 

public class Car {
    private Tire tire;

    // 생성자를 통해 외부에서 타이어를 받아옴 (주입)
    public Car(Tire tire) {
        this.tire = tire;
    }
}

// 외부(스프링 컨테이너)에서 조립
Tire hTire = new HankookTire();
Car myCar = new Car(hTire); // 주입!

DI를 적용하면, Car를 건들지 않고도 타이어 교체를 할 수 있다.

 

스프링에서 DI는 3가지 방법이 존재한다.

필드 주입, 수정자 주입, 생성자 주입이 있으며, 생성자 주입을 사용하여, MVC 패턴을 사용했다.

 

생성자 주입

@Service
public class MemberService {
    
    // final 키워드 사용 가능 (불변성)
    private final MemberRepository memberRepository;

    @Autowired // 생성자가 1개면 생략 가능 (스프링 4.3부터)
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

그렇다면, 왜 생성자 주입을 많이 사용할까? 

간단하게 설명하면, IoC의 단점인 런타임 에러와 불명확성을 해결해 주기 때문이다.

 

  • 불변성 (Immutability):
    • private final을 붙일 수 있다.
    • 한번 조립되면 평생 바뀌지 않음을 보장한다. (중간에 누가 해킹하듯 못 바꿈)
  • 누락 방지 (컴파일 에러):
    • 만약 new MemberService()를 할 때 Repository를 안 넣으면?
      빨간 줄(컴파일 에러)이 뜹니다. "야, 이거 만들려면 재료 필요해!"라고 바로 알려준다. ( NullPointerException 방지 )
  • 순수 자바 테스트:
    • 스프링 없이 단위 테스트(Unit Test)를 할 때도, 개발자가 직접 new로 객체를 넣어서 테스트할 수 있다.

 

 

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

대부분 소프트웨어 개발 프로세스에 사용하는 방법은 OOP이다. 

OOP는 객체지향 원칙에 따라 관심사가 같은 데이터를 한 곳으로 모아 분리하여, 낮은 결합도를 갖게 해 주고 독립성과 유연한 모듈로 캡슐화를 지원한다. 하지만, 이러한 과정 중 중복된 코드들이 많아지고 가독성, 확장성, 유지보수성을 떨어 뜨린다. 이러한 문제를 보완하고자 AOP가 등장한 것이다.

 

AOP에서는 핵심기능과 공통 기능을 분리시켜 핵심 로직에 영향을 끼치지 않게 공통 기능을 끼워 넣는 개발 형태이다.

AOP로 개발한다면, 무분별하게 중복되는 코드를 한 곳에 모아 중복되는 코드를 제거할 수 있고 공통 기능을 한 곳에 보관함으로써 공통 기능 하나의 수정으로 모든 핵심 기능들을 수정할 수 있다.

 

이러한 공통 기능을 한 번에 수정할 수 있어 효율적인 유지보수가 가능하며, 재활용성도 극대화한다.

 

OOP만 사용

public class TransferService {
    public void transfer() {
        // 1. 보안 검사 (중복 코드)
        if (!SecurityContext.isLogin()) {
            throw new Exception("로그인 필요");
        }

        System.out.println("--> 계좌 이체 로직 실행"); // 핵심 기능
    }
}

public class BalanceService {
    public void check() {
        // 1. 보안 검사 (중복 코드 - 또 작성해야 함)
        if (!SecurityContext.isLogin()) {
            throw new Exception("로그인 필요");
        }

        System.out.println("--> 잔액 조회 로직 실행"); // 핵심 기능
    }
}

문제점: "보안 검사" 로직이 바뀌면, 모든 클래스(TransferService, BalanceService 등 수십 개)를 다 열어서 수정해야 합니다. 단일 책임 원칙(SRP)이 깨집니다.

 

OOP와 AOP 함께 사용

OOP 핵심 로직 구현

public class TransferService {
    public void transfer() {
        System.out.println("--> 계좌 이체 로직 실행"); // 오직 이체만 신경 씀
    }
}

public class BalanceService {
    public void check() {
        System.out.println("--> 잔액 조회 로직 실행"); // 오직 조회만 신경 씀
    }
}

 

AOP 공통 로직 구현

@Aspect
public class SecurityAspect {
    
    // "모든 Service의 메소드가 실행되기 전에 이 보안 검사를 먼저 실행해라"
    @Before("execution(* ..Service.*(..))") 
    public void checkSecurity() {
        if (!SecurityContext.isLogin()) {
            throw new Exception("로그인 필요");
        }
        // 통과하면 자동으로 원래 가려던 핵심 로직으로 이동
    }
}

 

PSA (Portable Service Abstraction, 서비스 추상화)

  • 개념: 환경이나 구현 기술이 바뀌어도 코드를 거의 변경하지 않고 사용할 수 있도록 인터페이스로 추상화하는 것이다.
  • 예시: 데이터베이스에 접근하는 기술이 JDBC에서 JPA로 바뀌거나, 트랜잭션 관리자가 바뀌어도 스프링이 제공하는 추상화 계층 덕분에 기존 비즈니스 로직 코드를 뜯어고칠 필요가 없다.
// 1. 추상화 계층 (인터페이스) - "저장하는 기능이 있다"는 것만 정의
public interface MemberRepository {
    void save(Member member);
}

// 2. 구현체 A (옛날 기술: JDBC)
@Repository
public class JdbcMemberRepository implements MemberRepository {
    public void save(Member member) { 
        // ... JDBC로 복잡하게 저장하는 코드 ...
    }
}

// 3. 구현체 B (최신 기술: JPA)
@Repository
public class JpaMemberRepository implements MemberRepository {
    public void save(Member member) {
        // ... JPA로 심플하게 저장하는 코드 ...
    }
}

// 4. 사용하는 곳 (Service) - **여기가 핵심!**
@Service
public class MemberService {
    private final MemberRepository repository; // 인터페이스에만 의존!

    // JDBC가 들어오든 JPA가 들어오든 이 코드는 단 한 줄도 안 바뀜 (PSA의 위력)
    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }
}

결과: JdbcMemberRepository를 쓰다가 JpaMemberRepository로 갈아 끼워도, MemberService 코드는 수정할 필요가 없다. 스프링이 알아서 추상화해 주기 때문이다.

 

MVC 패턴

MVC란 (Model View Controller) 구조로 사용자 인터페이스와 비즈니스 로직을 분리하여 개발하는 것이다.

MVC에서는 Model1과 Model2로 나누어져 있으며 일반적인 MVC는 Model2를 지칭한다.

 

왜 Model2를 사용하는가?

Model 1 (과거의 방식)

  • 구조: 하나의 JSP 파일 안에 HTML(화면)Java 코드(비즈니스 로직, DB 접근)가 짬뽕되어 있다.
  • 문제점:
    • 스파게티 코드: 코드가 뒤섞여 있어 읽기가 매우 힘들다.
    • 유지보수 지옥: 디자인을 조금만 고치려고 해도 실수로 Java 코드를 건드려 에러가 날 수 있다.
    • 협업 불가: 디자이너와 개발자가 한 파일을 두고 싸워야 한다.

 

Model 2 (스프링 MVC 방식)

  • 구조: 화면(View)로직(Controller)을 철저하게 분리합니다.
  • 장점 (이유):
    1. 관심사의 분리 (Separation of Concerns): "보여주는 일"과 "처리하는 일"을 나눈다.
    2. 유지보수성: 비즈니스 로직이 바뀌어도 화면 코드는 건드릴 필요가 없고, 반대도 마찬가지다.
    3. 협업 용이: 디자이너는 HTML만 수정하고, 개발자는 Java만 수정하면 된다.
    4. 재사용성: 하나의 로직(Controller)으로 PC 웹, 모바일 웹, 앱(JSON) 등 다양한 화면(View)에 대응할 수 있다.

 

Model (모델)

정의: 뷰(화면)에 전달할 데이터(정보)를 담는 상자입니다. 비즈니스 로직의 결과물이 여기에 담긴다.

비유: 완성된 요리. 손님이 먹고 싶어 하는 실제 내용물이다.

스프링 예시: org.springframework.ui.Model 객체.

// 컨트롤러 내부
// "coffee"라는 이름표를 붙여서 "아메리카노"라는 요리(데이터)를 담음
model.addAttribute("coffee", "아이스 아메리카노");

 

View (뷰)

정의: 모델에 담긴 데이터를 시각적으로 사용자에게 보여주는 화면이다.

비유: 접시와 플레이팅. 요리(Model)를 예쁘게 담아서 손님 눈앞에 내놓는 결과물이다.

스프링 예시: HTML, JSP, Thymeleaf 파일.

<div>
    <h1>주문하신 음료 나왔습니다.</h1>
    <p>메뉴: <span th:text="${coffee}"></span></p> 
</div>

 

 

 

Controller (컨트롤러)

정의: 사용자의 요청(주문)을 가장 먼저 받아서, 무엇을 해야 할지 판단하고 명령을 내리는 역할이다.

비유: 웨이터(또는 지배인). 손님의 주문을 받아 주방(Service)에 전달하고, 요리가 나오면 손님에게 서빙한다.

스프링 예시: @Controller가 붙은 클래스.

@Controller
public class CoffeeController {

    @GetMapping("/order") // "주문 받을게요"
    public String orderCoffee(Model model) {
        // ... (주방에 주문 넣는 로직) ...
        return "coffeePage"; // "coffeePage라는 접시(View)에 담아주세요"
    }
}

 

 

 

스프링 구조

1. 코어 컨테이너 (Core Container) - "심장"

스프링의 가장 밑바닥이자 핵심입니다. 우리가 지금까지 배운 IoC와 DI, Bean이 바로 여기서 동작한다.

  • Core & Beans: 프레임워크의 가장 기본적인 부분이다.
    IoC(제어의 역전)와 DI(의존성 주입) 기능을 제공한다.
  • Context: Beans 모듈을 바탕으로, 국제화(언어 지원), 이벤트 전파 등 엔터프라이즈급 기능을 추가한 것이다.
    우리가 흔히 부르는 '스프링 컨테이너(ApplicationContext)'가 이쪽이다.
  • SpEL (Expression Language): 런타임 중에 객체 그래프를 조회하고 조작하는 언어를 제공한다.

 

Been이란?

자바 프로그래밍에서 객체는 두 가지로 나뉜다.

 

  • 일반 객체: 개발자인 '나'가 필요할 때마다 new 연산자로 직접 생성하는 객체.
    배고플 때마다 직접 마트 가서 재료를 사고 요리해 먹음
  • 스프링 빈(Bean): '스프링'이 미리 만들어서 컨테이너라는 상자에 담아두고 관리하는 객체.
    학교 급식실(스프링 컨테이너)에서 영양사(스프링)가 미리 딱 만들어서 식판에 담아둔 요리. 학생(다른 객체)들은 그냥 받아먹음

앞서 DI를 하기 위해서는 Bean은 필수 조건으로 등록해야 한다.

  • 만약 Bean이 아니라면?
    스프링은 그 객체의 존재조차 모르기 때문에, 의존성을 주입해 줄 수도 없고, 그 객체를 다른 곳에 주입해 줄 수도 없다.

 

등록과 사용 방법

  • MemberService와 MemberRepository가 있다.
  • MemberService가 작동하려면 MemberRepository가 필요하다(의존).
  • 이때, 두 녀석 모두 'Bean'으로 등록되어 있어야만, 스프링이 "어? 서비스가 리포지토리를 원하네?" 하고 샥 끼워 넣어줄(DI) 수 있다.

빈 등록

@Repository // "이건 저장소 역할을 하는 Bean이야" 라고 스프링에게 알려줌
public class MemoryMemberRepository implements MemberRepository {
    // ...
}

클래스 위에 @Component, @Service, @Repository 등을 붙이면 스프링이 시작될 때 이를 보고 "오, 이건 내가 관리해야 할 Bean이구나!" 하고 가져가서 생성해 둡니다.

 

DI 받기

@Service // "이건 서비스 역할을 하는 Bean이야"
public class MemberService {

    private final MemberRepository memberRepository;

    @Autowired // "스프링아, 네가 가지고 있는 Bean 중에서 memberRepository 좀 넣어줘(DI)"
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

생성자에 @Autowired를 붙이거나(생략 가능), 생성자 파라미터로 선언하면, 스프링이 보관하고 있던 Bean을 꺼내서 넣어준다.

 

 

bean을 사용하면 좋은 점은 효율성(싱글톤, Singleton) 때문이다.

 

  • new 사용 시: 고객이 100명 들어와서 요청하면, 객체도 100개가 생성되고 100번 삭제됩니다. (메모리 낭비 심함)
  • Bean 사용 시: 스프링은 기본적으로 Bean을 딱 1개만 생성해서 공유합니다. 고객이 100명이든 10,000명이든 미리 만들어둔 단 하나의 객체를 돌려가며 씁니다. (메모리 절약, 성능 향상)

 

 

2. AOP & Instrumentation - "접착제"

핵심 비즈니스 로직과 공통 기능(보안, 로그 등)을 분리해 주는 영역이다.

  • AOP: 관점 지향 프로그래밍을 지원한다. (메서드 가로채기 등)
  • Aspects: AspectJ라는 강력한 AOP 프레임워크와 통합을 지원한다.

 

3. 데이터 접근/통합 (Data Access/Integration) - "데이터 창고 관리자"

데이터베이스(DB)와 관련된 지루하고 반복적인 작업을 대신 처리해 준다.

  • JDBC: 자바의 복잡한 JDBC 코드(Connection 연결, 해제, 예외 처리 등)를 획기적으로 줄여주는 추상화 계층을 제공한다. (JdbcTemplate)
  • ORM: JPA, Hibernate, MyBatis 같은 인기 있는 ORM 기술들과 스프링을 쉽게 연결해 준다.
  • Transactions: 트랜잭션 관리를 담당. @Transactional 하나로 트랜잭션을 걸 수 있게 해주는 마법이 여기에 있다.

 

4. 웹 (Web) - "소통 창구"

웹 애플리케이션을 만들기 위한 기능들이다.

  • Web: 웹 개발에 필요한 기본적인 기능(파일 업로드 등)을 제공하고, 스프링 컨테이너를 웹 환경에 맞게 초기화한다.
  • Web-MVC: 우리가 배운 Model-View-Controller 패턴 구현체가 여기에 있다.
  • Web-Flux: 비동기 프로그래밍(Reactive Programming)을 지원하는 최신 모듈이다. (Node.js처럼 동작)

 

5. 테스트 (Test) - "품질 보증"

스프링으로 만든 애플리케이션을 쉽게 테스트할 수 있도록 지원한다.

  • Test: JUnit이나 TestNG 같은 테스트 프레임워크를 사용하여 스프링 컴포넌트를 테스트할 수 있게 해 준다. (예: @SpringBootTest)

 

스프링 프레임워크와 스프링 부트

스프링 부트는 스프링을 대체하는 것이 아니라, 스프링을 편하게 쓰게 해주는 도구이다.

 

  • 스프링 (Spring): 최고급 식재료와 조리 도구가 가득한 주방입니다. 하지만 요리를 하려면 재료를 다듬고, 불 조절을 하고, 양념 배합을 직접 다 세팅해야 합니다.
  • 스프링 부트 (Spring Boot): 그 주방에서 만든 **"밀키트(Meal Kit)"**입니다. 뜯어서 끓이기만 하면 바로 완성된 요리가 나옵니다.

 

결정적인 차이점 3가지

비교 항목 스프링 프레임워크 스프링 부트
설정 매우 복잡하다.
XM 파일이나 별도 설정 클래스를 수십 개 만들어야 작동한다.
자동 설정.
복잡한 설정은 스프링이 알아서 다 해주고, 개발자는 application만 조금 건드리면 된다.
웹 서버 외장 톰캣 필수.
톰캣을 따로 설치하고, 코드를 WAR로 빌드해서 넣어줘야 한다.
내장 톰캣.
톰캣이 코드 안에 들어있으므로, JAR 파일 실행만 하면 서버가 바로 켜진다. (main 메서드 실행)
의존성 버전 관리가 지옥.
라이브러리 간 버전이 맞지 않으면 에러 발생하여, 일일이 맞춰야한다.
Starter 제공.
spring-boot-starter-web 하나만 적으면 관련된 수십 개 라이브러리를 호환되는 버전으로 싹 받아준다.

 

간단 요약

IoC/DI: 객체 관리를 스프링에게 맡겨라. (부품 조립)

AOP: 지저분한 반복 코드는 따로 빼라. (공통 기능)

PSA: 기술이 바뀌어도 내 코드는 지켜라. (추상화)

MVC: 역할(화면, 로직, 데이터)을 나눠라.

Bean/Container: 스프링 창고와 그 안에 든 물건들.

Spring Boot: 이 모든 걸 쉽고 빠르게 쓰게 해주는 도구.

 

 

면접 답변식 요약

스프링 프레임워크는 자바 엔터프라이즈 개발을 위한 오픈소스 애플리케이션 프레임워크입니다.

스프링의 가장 큰 핵심은 '개발자가 비즈니스 로직에만 집중할 수 있게 해 준다'는 점입니다.

이를 위해 POJO 기반의 개발을 지향하며, 크게 3가지 핵심 기술을 제공합니다.

첫째, IoC와 DI입니다. 객체의 생명주기와 의존성 관리를 컨테이너에게 맡겨 결합도를 낮추고, 유연하고 테스트하기 쉬운 코드를 만들 수 있게 해 줍니다.

둘째, AOP입니다. 로깅이나 트랜잭션 같은 공통 관심사를 핵심 로직에서 분리하여 코드의 중복을 줄이고 유지보수성을 높여줍니다.

셋째, PSA입니다. JDBC나 JPA 같은 구체적인 기술이 바뀌어도 서비스 코드를 변경하지 않도록 추상화 계층을 제공하여 기술 확장성을 보장합니다.

최근에는 이러한 스프링의 복잡한 설정을 최소화하고 생산성을 높이기 위해, 내장 톰캣과 자동 설정을 지원하는 '스프링 부트'를 표준처럼 사용하고 있습니다.