스프링이해 2 - Spring에서 진짜 헷갈리는 개념 TOP 5

좋아요 👍
Spring Framework에서 가장 많이 헷갈리는 5개 개념을 설계 의도 기준으로 정리해보겠습니다.
(이거 이해하면 Spring 구조가 거의 보입니다)


---

1️⃣ Bean vs Component

Bean

> Spring 컨테이너가 관리하는 모든 객체



즉 결과 개념입니다.

등록 방법은 여러 가지입니다.

@Component

@Service

@Controller

@Repository

@Bean

XML



---

Component

> Bean을 자동 등록하기 위한 어노테이션



@Component
class UserService {}

동작

@ComponentScan
   ↓
@Component 발견
   ↓
Bean 등록

✔ 정리

Component ⊂ Bean


---

2️⃣ @Bean vs @Component

이건 등록 방식 차이입니다.

@Component

@Service
class UserService {}

특징

자동 등록

Component Scan 필요

우리가 만든 클래스에 사용



---

@Bean

@Bean
public ObjectMapper objectMapper() {
    return new ObjectMapper();
}

특징

수동 등록

메서드 기반

외부 라이브러리 등록


예: Jackson ObjectMapper


---

✔ 핵심 차이

구분 Component Bean

방식 자동 수동
단위 클래스 메서드
사용 우리가 만든 클래스 외부 라이브러리



---

3️⃣ @Configuration vs @Component

이거 진짜 많이 틀립니다.

@Component

단순 Bean

@Component
class AppConfig {}

특별한 기능 없음


---

@Configuration

@Configuration
class AppConfig {}

특징

✔ CGLIB 프록시 생성

목적

> @Bean 싱글톤 보장




---

왜 필요한가


@Bean
public A a() {
    return new A(b());
}

@Bean
public B b() {
    return new B();
}

문제

a() 호출
  ↓
b() 호출

그러면 B가 2번 생성될 수도 있음

그래서 Spring이 프록시로 가로채서 싱글톤 유지

이게 CGLIB입니다.


---

4️⃣ @Autowired vs 생성자 주입

필드 주입

@Autowired
UserRepository repo;

문제

테스트 어려움

순환참조 발견 늦음

final 사용 불가



---

생성자 주입 (권장)

private final UserRepository repo;

public UserService(UserRepository repo) {
    this.repo = repo;
}

장점

✔ 불변성
✔ 테스트 쉬움
✔ 순환참조 빨리 발견

그래서 **Spring Boot 공식 권장입니다.


---

5️⃣ Spring이 싱글톤을 관리하는 이유

Spring Bean 기본 스코프

Singleton

이유

서버는 요청이 많음


1초 1000 요청

객체를 계속 생성하면

GC 폭증
메모리 낭비
성능 저하

그래서

> Spring IoC 컨테이너가 싱글톤으로 관리




---

🔥 Spring 구조 한 장 정리

Spring Container
       │
       │ Bean 관리
       │
 ┌───────────────┐
 │ Bean 등록 방법 │
 └───────────────┘
       │
 ┌─────────────┬─────────────┐
 │ │ │
@Component @Bean XML
 │
@ComponentScan


---

💡 진짜 중요한 Spring 설계 철학

Spring의 핵심은 딱 3개입니다.

1️⃣ IoC (Inversion of Control)
→ 객체 생성은 Spring이 한다

2️⃣ DI (Dependency Injection)
→ 의존성 주입

3️⃣ AOP (Aspect Oriented Programming)
→ 공통 로직 분리


---

원하시면 다음도 설명해드릴게요 (이건 Spring 이해도 급상승 구간입니다)

1️⃣ 왜 @Transactional이 같은 클래스에서 호출하면 안 먹는지 (AOP 프록시)
2️⃣ Spring Bean 생명주기 (면접 단골)
3️⃣ @Configuration이 CGLIB 프록시 쓰는 이유
4️⃣ Spring Boot 자동설정 원리 (@EnableAutoConfiguration)

특히 1번은 Spring 사용하는 사람 70%가 정확히 모르는 개념입니다.



좋습니다 👍
이건 Spring Framework에서 가장 유명한 함정입니다.

> 왜 @Transactional이 같은 클래스 내부 호출에서는 동작하지 않는가?



이건 Spring AOP 프록시 구조 때문입니다.


---

1️⃣ 기본 상황

@Service
public class OrderService {

    @Transactional
    public void saveOrder() {
        // DB 저장
    }

    public void processOrder() {
        saveOrder(); // 내부 호출
    }
}

실행

orderService.processOrder()

결과

❌ 트랜잭션 안 걸림


---

2️⃣ 왜 이런 일이 발생할까

Spring의 @Transactional은 Spring AOP 기반입니다.

즉 실제 객체가 아니라

프록시 객체

가 중간에 끼어 있습니다.

구조

Client
  ↓
Proxy
  ↓
Real Object


---

3️⃣ 외부 호출일 때

client
  ↓
proxy
  ↓
@Transactional 확인
  ↓
트랜잭션 시작
  ↓
real method 실행
  ↓
commit


✔ 프록시가 가로채서 트랜잭션 처리


---

4️⃣ 내부 호출일 때

문제 상황

public void processOrder() {
    saveOrder();
}

구조

processOrder()
   ↓
this.saveOrder()

여기서 중요한 점

Proxy 안 거침

그래서

@Transactional 실행 안됨


---

5️⃣ 그림으로 보면

정상 동작

Client
  ↓
OrderService Proxy
  ↓
@Transactional
  ↓
OrderService.saveOrder()


---

내부 호출

Client
  ↓
OrderService Proxy
  ↓
processOrder()
  ↓
this.saveOrder() ← proxy 안거침


---

6️⃣ 해결 방법

방법 1 (가장 일반적)

클래스 분리

@Service
class OrderService {

    private final OrderTxService txService;

    public void processOrder() {
        txService.saveOrder();
    }
}

@Service
class OrderTxService {

    @Transactional
    public void saveOrder() {}
}

구조

Client
 ↓
Proxy(OrderService)
 ↓
Proxy(OrderTxService)
 ↓
@Transactional

✔ 정상 작동


---

방법 2 (잘 안 씀)

프록시 자기 자신 주입

@Autowired
OrderService self;

하지만

❌ 구조가 더러워짐


---

방법 3

ApplicationContext에서 자기 자신 가져오기

하지만 이것도

❌ 권장 안 함


---

7️⃣ Spring이 프록시를 쓰는 이유

Spring AOP는

JDK Dynamic Proxy

CGLIB


기반입니다.

목적

로깅
트랜잭션
보안
캐싱

같은 공통 로직을 분리하기 위해서입니다.


---

🔥 진짜 중요한 포인트

Spring AOP는

Method Call interception

입니다.

그래서

Proxy를 거치는 호출만 AOP 적용

됩니다.


---

💡 실무에서 많이 터지는 버그

이 패턴

@Transactional
public void a() {
    b();
}

@Transactional
public void b() {}

개발자는

b도 트랜잭션 걸릴거라 생각

하지만 실제

안 걸림

그래서 서비스 분리 패턴을 많이 씁니다.


---

원하시면 다음도 설명해드릴게요.
이건 Spring 이해도 레벨을 한 단계 올리는 내용입니다.

1️⃣ Spring Bean 생명주기 (생성 → 초기화 → 사용 → 소멸)
2️⃣ @Configuration이 왜 CGLIB 프록시를 쓰는지 (대부분 정확히 모름)
3️⃣ Spring Boot 자동설정 원리 (@EnableAutoConfiguration)
4️⃣ @SpringBootApplication 안에 뭐가 들어있는지

특히 4번은 Spring Boot 구조를 완전히 이해하게 되는 포인트입니다.



댓글

가장 많이 본 글