이 글은 지금까지의 내용을 하나의 이야기로 연결하는 글이다.
실제로 이 글을 이해하면 Spring을 단순히 사용하는 것이 아니라
"Spring이 내부에서 어떻게 움직이는지"
를 머릿속으로 그릴 수 있게 된다.
Reflection + Annotation + Proxy + ThreadLocal
Spring은 이 네 가지 기술로 움직인다.
Spring은 Reflection으로 클래스를 찾고,
Annotation으로 의미를 판단하고,
Proxy로 기능을 추가하며,
ThreadLocal로 요청 상태를 유지한다.
먼저 질문 하나
아래 코드를 보자.
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/users")
public void save() {
userService.save();
}
}
그리고
@Service
public class UserService {
@Transactional
public void save() {
...
}
}
우리는 그냥
POST /users
↓
DB 저장
이라고 생각한다.
하지만 Spring 내부에서는 엄청난 일이 일어난다.
전체 흐름
Application Start
↓
Reflection
↓
Annotation 분석
↓
Bean 생성
↓
Proxy 생성
↓
BeanContainer 저장
========================
HTTP 요청
↓
Proxy 실행
↓
Transaction 시작
↓
ThreadLocal 저장
↓
실제 Service 실행
↓
Repository
↓
ThreadLocal에서 Connection 조회
↓
Commit
↓
ThreadLocal 정리
이 모든 과정이
지금까지 공부한
Reflection
Annotation
Proxy
ThreadLocal
네 가지 기술만으로 이루어진다.
1단계 - Reflection이 시작한다.
Spring Boot가 실행된다.
Application Start
Spring은
프로젝트 전체를 스캔한다.
com.example
↓
UserController.class
UserService.class
UserRepository.class
Spring은 Reflection으로
Class 객체를 얻는다.
Class<?> clazz = UserService.class;
그리고
필드
메서드
생성자
Annotation
모든 정보를 읽는다.
즉,
Reflection은
"프로젝트를 조사하는 눈"
이다.
2단계 - Annotation이 역할을 결정한다.
Reflection으로
Class를 찾았다.
이제 Spring은 묻는다.
@Service인가?
@Controller인가?
@Repository인가?
Reflection
clazz.isAnnotationPresent(Service.class);
결과
true
그러면
Bean 생성 대상
이라고 판단한다.
여기서 Annotation은
"Spring에게 힌트를 주는 표지판"
역할을 한다.
3단계 - Reflection으로 객체 생성
Spring은
new UserService()
를 코드에 적지 않는다.
Reflection을 사용한다.
clazz.getDeclaredConstructor()
.newInstance();
즉,
Reflection
↓
객체 생성
↓
Bean 생성
이다.
그리고
BeanContainer에 저장한다.
BeanContainer
---------------------
UserService
UserRepository
UserController
---------------------
4단계 - @Autowired도 Reflection이다.
@Autowired
private UserService userService;
Spring은
Reflection으로
필드를 찾는다.
Field field = ...
field.setAccessible(true);
field.set(controller, userService);
즉,
@Autowired
↓
Reflection
↓
Field 접근
↓
객체 주입
이다.
5단계 - 그런데 @Transactional은?
@Transactional
public void save() {
}
많은 사람들이
@Transactional
↓
트랜잭션 시작
이라고 생각한다.
실제로는 아니다.
Spring은 Annotation을 발견한다.
Reflection
↓
@Transactional 발견
그리고
생각한다.
이 Bean은 특별하다.
Proxy를 만들어야겠다.
6단계 - Proxy 생성
기존
Client
↓
UserService
Spring
Client
↓
Transaction Proxy
↓
UserService
BeanContainer에는
실제 UserService가 아니라
Proxy가 들어간다.
그래서
@Autowired
UserService userService;
가 받아오는 것은
실제로
Transaction Proxy
이다.
7단계 - HTTP 요청 발생
POST /users
↓
DispatcherServlet
↓
Controller
↓
userService.save()
우리는
UserService가 호출된다고 생각한다.
실제로는
userService
↓
Transaction Proxy
↓
invoke()
이다.
8단계 - Proxy가 Transaction 시작
Proxy는
beginTransaction()
↓
real.save()
↓
commit()
를 수행한다.
그런데
Connection을 어디에 저장할까?
9단계 - ThreadLocal 등장
Proxy
↓
Connection 생성
↓
ThreadLocal 저장
Thread-1
↓
ThreadLocalMap
↓
Connection
그러면
Service
↓
Repository
↓
DAO
모두
같은 Connection을 사용할 수 있다.
Repository에서는
connection =
DataSourceUtils.getConnection();
실제로는
Thread.currentThread()
↓
ThreadLocalMap
↓
Connection 반환
이다.
그래서
Controller가
Connection을
전달하지 않아도 된다.
Spring Security도 동일하다.
로그인
↓
Authentication 생성
↓
ThreadLocal 저장
Controller
SecurityContextHolder
.getContext()
.getAuthentication()
실제로는
현재 Thread
↓
ThreadLocal
↓
Authentication
을 가져오는 것이다.
그림으로 보면
Spring Boot
│
▼
Reflection
│
┌───────────────┼───────────────┐
▼ ▼
Annotation 분석 Reflection 객체 생성
│ │
└───────────────┬───────────────┘
▼
BeanContainer 등록
│
▼
@Transactional 발견
│
▼
Proxy 생성
│
================================================
HTTP Request
│
▼
Proxy.invoke()
│
┌───────────────┴───────────────┐
▼ ▼
Transaction 시작 ThreadLocal 저장
│ │
└───────────────┬───────────────┘
▼
Real Service 실행
│
▼
Repository
│
▼
ThreadLocal에서 Connection 조회
│
▼
Commit / Rollback
│
▼
ThreadLocal.remove()
한 줄로 기억하는 방법
Reflection
무엇이 있는지 찾는다.
Class
Field
Method
Annotation
Annotation
무엇을 해야 하는지 알려준다.
@Service
@Transactional
@Autowired
Proxy
실제 메서드 앞뒤에 기능을 추가한다.
Transaction
AOP
Cache
Async
ThreadLocal
현재 요청의 상태를 안전하게 보관한다.
Connection
Authentication
SqlSession
RequestContext
면접에서 가장 좋아하는 질문
Spring이 @Transactional을 어떻게 구현하는지 설명해 보세요.
이제는 이렇게 답할 수 있다.
Spring은 애플리케이션 시작 시 Reflection으로 클래스를 스캔하고 @Transactional Annotation을 발견하면 Proxy 객체를 생성한다. 실제 메서드 호출은 Proxy를 통해 이루어지며, Proxy는 트랜잭션을 시작한 후 Connection을 ThreadLocal에 저장한다. 이후 Repository 계층은 ThreadLocal에서 동일한 Connection을 조회하여 하나의 트랜잭션으로 동작하며, 작업이 끝나면 Commit 또는 Rollback을 수행하고 마지막에 ThreadLocal을 정리한다.
지금까지의 로드맵
Reflection
↓
Class 객체
↓
Annotation
↓
Meta Annotation
↓
Dynamic Proxy
↓
JDK Proxy vs CGLIB
↓
Serialization / Deserialization
↓
AutoBoxing / Unboxing
↓
Integer Cache
↓
StringBuilder vs StringBuffer
↓
BigDecimal
↓
ThreadLocal
↓
ThreadLocal Memory Leak
↓
★★★★ Reflection + Annotation + Proxy + ThreadLocal ★★★★
이 글은 단순한 개념 설명이 아니라 Spring 내부 동작을 관통하는 연결점이므로, 이번 챕터를 마무리하는 핵심 글이다.
'language > java' 카테고리의 다른 글
| Text Block(Java 15) (0) | 2026.06.17 |
|---|---|
| var 키워드 (0) | 2026.06.17 |
| ThreadLocal Memory Leak (0) | 2026.06.16 |
| ThreadLocal 원리 (0) | 2026.06.16 |
| BigDecimal을 사용하는 이유 (0) | 2026.06.16 |
댓글