5주차 자료의 모든 토픽을 두 개의 큰 흐름으로 정리한 학습 경로.
1) 동시성 마무리 (Atomic/CAS, 스레드 풀의 필요성)
2) 토비의 스프링 시작 (DAO 진화 → 관심사 분리 → 디자인 패턴 → IoC/DI)자바 기초·동시성을 끝내고 Spring 프레임워크의 핵심 원리로 진입하는 다리 주차다.
[Part A — 동시성 마무리]
[Phase 1] 스레드 풀의 필요성 재정리
↓
[Phase 2] 동시성 안전 도구 3종 비교 (synchronized/volatile/Atomic)
↓ (자바 기초 완성)
[Part B — 토비의 스프링: 객체 설계의 진화]
[Phase 3] 전통 DAO의 문제
↓
[Phase 4] 관심사의 분리
↓
[Phase 5] 디자인 패턴 적용 (템플릿 메소드 / 팩토리 메소드)
↓
[Phase 6] 객체지향 설계 원칙 (OCP, 전략 패턴)
↓
[Phase 7] 제어의 역전 (IoC)
↓
[Phase 8] Spring 컨테이너 (ApplicationContext + DI)
총 8 Phase × 26 Unit
| 주차 | 주제 | 핵심 변화 |
|---|---|---|
| 1주차 | OOP·JVM·GC·컬렉션·I/O 개론 | 자바 큰 그림 |
| 2주차 | JVM 내부·바이트코드·G1 GC | "어떻게 돌아가나" |
| 3주차 | 컬렉션 전체 지도·제네릭·함수형 | 자바 표현력 완성 |
| 4주차 | 멀티스레딩·동시성·Executor | 동시성 첫 정복 |
| 5주차 (지금) | Atomic + Spring IoC/DI 입문 | 자바 → Spring 다리 |
| Day | Phase | 학습 목표 |
|---|---|---|
| 1일차 | Phase 1 + 2 | 동시성 마무리 (Atomic/CAS) |
| 2일차 | Phase 3 | 전통 DAO 분석 |
| 3일차 | Phase 4 | 관심사 분리 (1·2단계) |
| 4일차 | Phase 5 | 템플릿 메소드 + 팩토리 메소드 패턴 |
| 5일차 | Phase 6 | OCP + 전략 패턴 |
| 6일차 | Phase 7 | IoC + 프레임워크 vs 라이브러리 |
| 7일차 | Phase 8 | ApplicationContext + DI |
목표: 4주차에서 본 Executor 프레임워크가 "왜" 필요한지를 스레드 자체의 비용 관점에서 다시 한번 못 박는다.
선수 지식: 4주차 Phase 1, 7
핵심 개념
"스레드를 많이 쓸수록 항상 빠를까?" → NO
3가지 함정:
1. 컨텍스트 스위칭 오버헤드: 스레드 N개 ↑ → 스위칭 비용 ↑
2. 데드락: 락이 많을수록 데드락 가능성 ↑
3. 노는 스레드의 자원 낭비: 작업 안 해도 메모리 차지 + 스케줄링 비용
핵심 통찰: 스레드는 "공짜가 아니다". 존재 자체로 비용.
자기 점검
선수 지식: Unit 1.1, 4주차 Phase 1
핵심 개념
스위칭 시 일어나는 일:
1. 현재 스레드의 CPU 레지스터 → 메모리에 백업
2. 다음 스레드의 이전 상태 → 메모리에서 복원
3. CPU 캐시 무효화 (캐시 미스 발생)
4. 스레드 스택, PC 카운터 등 컨텍스트 전환
스위칭 횟수 = 스레드 수 × 시간 비례 → 스레드 너무 많으면 CPU가 작업보다 스위칭에 더 많은 시간 소비
자기 점검
선수 지식: Unit 1.2, 4주차 Phase 7
핵심 개념
문제 → 해결:
이게 4주차에서 본 Executor 프레임워크의 존재 이유다.
실무 결론:
"스레드를 직접 만들지 마라. ExecutorService를 써라. 풀 크기는 신중히 정하라."
자기 점검
목표: 4주차에서 synchronized와 volatile을 본 다음, 이번엔 Atomic까지 더해 3가지 도구의 정확한 차이를 매트릭스로 잡는다.
선수 지식: 4주차 Phase 4
핵심 개념
동시성 문제는 정확히 2가지 다:
| 문제 | 정의 | 예시 |
|---|---|---|
| 가시성(Visibility) | 한 스레드의 변경이 다른 스레드에 안 보임 | runFlag = false 했는데 무한 루프 |
| 동시 접근(Atomicity) | 동시 수정이 서로를 덮어씀 | count++ 가 일부 손실 |
이 둘을 분리해서 보는 것이 핵심. 도구마다 해결 범위가 다르다.
자기 점검
선수 지식: Unit 2.1
핵심 개념
가장 안전하지만 가장 비싼 도구.
자기 점검
선수 지식: Unit 2.2
핵심 개념
volatile int count = 0;
count++; // ⚠️ 여전히 위험! (read → +1 → write 3단계)
적합한 사용처: 한 스레드만 쓰고, 여러 스레드가 읽는 플래그
volatile boolean shutdown = false; // 한 곳에서만 true 설정
자기 점검
count++ 가 volatile로 안전하지 않은 이유를 어셈블리 수준으로?선수 지식: Unit 2.3
핵심 개념
CAS (Compare And Swap) 알고리즘:
1. 현재 값 읽기 → 레지스터에 저장 (A)
2. 새 값 계산 (B)
3. 메모리의 값과 A를 비교
은행 잔액 예시:
AtomicInteger num = new AtomicInteger(0);
num.incrementAndGet(); // 락 없이 원자적
비교 매트릭스:
| 도구 | 가시성 | 원자성 | 방식 | 성능 |
|---|---|---|---|---|
| synchronized | ✅ | ✅ | 락 (블로킹) | 가장 느림 |
| volatile | ✅ | ❌ | 메모리 동기화 | 빠름 |
| Atomic | ✅ | ✅ | CAS (논블로킹) | 빠름 |
자기 점검
이 파트의 핵심 메시지:
"DAO 코드 한 줄이 어떻게 Spring의 IoC/DI까지 진화하는지" — 한 클래스의 리팩토링 여정을 따라가며 객체지향 설계 원칙과 Spring의 본질을 동시에 익힌다.
목표: Spring이 왜 필요한지를, "Spring 없이 짠 코드의 고통" 으로 직접 본다.
선수 지식: 1주차 Phase 7 (JDBC 개념)
핵심 개념
자기 점검
선수 지식: Unit 3.1
핵심 코드 분석
public void add(User user) throws ... {
Class.forName("com.mysql.jdbc.Driver"); // ① 드라이버 로딩
Connection c = DriverManager.getConnection( // ② DB 접속 정보
"jdbc:mysql://localhost/toby", "root", "*****");
PreparedStatement ps = c.prepareStatement( // ③ SQL 작성
"insert into users(id, name, password) values (?,?,?)");
ps.setString(1, user.getId()); // ④ 파라미터 바인딩
// ...
ps.executeUpdate(); // ⑤ 실행
ps.close(); c.close(); // ⑥ 자원 해제
}
자기 점검
원본 자료: 토비의 스프링 vol.1, p.56
선수 지식: Unit 3.2
핵심 문제 진단
이 한 메서드 안에 섞여있는 책임:
1. DB 연결 정보 관리 (URL, user, password)
2. DB 드라이버 로딩
3. SQL 작성과 실행
4. 자원 해제
5. 예외 처리
왜 문제인가:
getConnection) 가 메서드마다 중복→ 변경 1번 = 수정 N곳 = 유지보수 지옥
자기 점검
목표: 리팩토링의 가장 기본 원리를 코드 변화로 직접 본다.
선수 지식: Unit 3.3
핵심 개념
"관심이 같은 것끼리는 하나로 모으고, 관심이 다른 것은 떨어뜨려라"
관심사(Concern)의 의미:
처음엔 한데 모은 게 편하지만, 시스템이 커지면 분리해야만 살아남는다.
자기 점검
원본 자료: 토비의 스프링 vol.1, p.61
선수 지식: Unit 4.1
핵심 변화
Before (각 메서드가 직접 연결):
public void add(...) {
Class.forName(...);
Connection c = DriverManager.getConnection(...);
// ...
}
public User get(...) {
Class.forName(...); // ← 중복
Connection c = DriverManager.getConnection(...); // ← 중복
// ...
}
After (메서드 추출):
private Connection getConnection() throws ... {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(...);
}
public void add(...) {
Connection c = getConnection(); // 깔끔
// ...
}
public User get(...) {
Connection c = getConnection(); // 깔끔
// ...
}
얻은 것:
자기 점검
선수 지식: Unit 4.2
핵심 변화
문제: "고객사마다 다른 DB를 쓰고 싶다. 그런데 UserDao 코드는 공개하고 싶지 않다."
해결: getConnection을 추상 메서드로
public abstract class UserDao {
public void add(...) {
Connection c = getConnection(); // 변하지 않는 흐름
// ...
}
public abstract Connection getConnection(); // 변하는 부분
}
class NUserDao extends UserDao {
public Connection getConnection() {
// N사 DB 연결 코드
}
}
class DUserDao extends UserDao {
public Connection getConnection() {
// D사 DB 연결 코드
}
}
자기 점검
원본 자료: 토비의 스프링 vol.1, p.65
목표: Phase 4의 리팩토링이 사실은 디자인 패턴 2개의 적용 이었음을 알고, 그 한계까지 본다.
선수 지식: Unit 4.3
핵심 개념
"슈퍼클래스에 기본 흐름을 만들고, 변하는 부분만 서브클래스에서 구현하게 하는 패턴"
구조:
[슈퍼클래스]
├─ 템플릿 메서드 (변하지 않는 흐름)
└─ 추상 메서드 (변하는 부분)
↑ 오버라이드
[서브클래스]
Phase 4-3의 UserDao 코드가 정확히 이 패턴.
자기 점검
JdbcTemplate이 이름에 "Template"이 붙은 이유는?원본 자료: 토비의 스프링 vol.1, p.67
선수 지식: Unit 5.1
핵심 개념
"객체 생성을 서브클래스에 위임하는 패턴"
getConnection() 도 사실은:
그래서 같은 코드가 두 패턴으로 동시에 해석된다:
자기 점검
BeanFactory 이름의 "Factory"는 이 패턴과 어떤 관련이 있는가?원본 자료: 토비의 스프링 vol.1, p.67
선수 지식: Unit 5.2
핵심 한계
상속을 이용한 분리의 단점:
→ 상속 대신 다른 방법이 필요하다: 인터페이스 + 합성(composition)
자기 점검
목표: 인터페이스를 통한 결합도 낮추기로 OCP를 달성하고, 전략 패턴으로 더 유연한 구조를 만든다.
선수 지식: Unit 5.3
핵심 변화
public interface ConnectionMaker {
Connection makeConnection();
}
public class UserDao {
private ConnectionMaker connectionMaker; // 인터페이스에 의존
public UserDao(ConnectionMaker cm) {
this.connectionMaker = cm; // 외부에서 주입받음
}
public void add(User user) {
Connection c = connectionMaker.makeConnection();
// ...
}
}
public class NConnectionMaker implements ConnectionMaker { ... }
public class DConnectionMaker implements ConnectionMaker { ... }
핵심 변화:
자기 점검
원본 자료: 토비의 스프링 vol.1, p.74~78
선수 지식: Unit 6.1
핵심 개념
"확장에는 열려있고, 변경에는 닫혀있어야 한다"
UserDao의 변화:
→ OCP 만족
OCP가 깨지는 코드의 신호:
if (type.equals("MySQL")) ... else if (type.equals("Oracle"))switch 문이 새 케이스 추가될 때마다 수정됨자기 점검
원본 자료: 토비의 스프링 vol.1, p.83
선수 지식: Unit 6.2
핵심 개념
"필요에 따라 변경되는 알고리즘을 인터페이스로 분리하고, 구체 구현을 외부에서 주입받는 패턴"
용어 매핑:
전략 패턴은 곧 OCP의 구현 도구:
자기 점검
원본 자료: 토비의 스프링 vol.1, p.87
목표: 객체 생성·연결·관리의 책임이 어디서 어디로 옮겨가는지 추적한다.
선수 지식: Phase 6
핵심 개념
전통 방식:
public class UserDao {
public UserDao() {
this.connectionMaker = new NConnectionMaker(); // 자기가 결정
}
}
→ UserDao가 자신이 사용할 객체를 스스로 결정·생성
IoC 방식:
public class UserDao {
public UserDao(ConnectionMaker cm) {
this.connectionMaker = cm; // 외부에서 주입
}
}
→ "어떤 ConnectionMaker를 쓸지"의 결정 권한이 외부로 넘어감
제어의 역전:
자기 점검
원본 자료: 토비의 스프링 vol.1, p.92
선수 지식: Unit 7.1
핵심 차이:
| 라이브러리 | 프레임워크 | |
|---|---|---|
| 누가 흐름을 제어 | 내 코드 | 프레임워크 |
| 호출 방향 | 내 코드 → 라이브러리 | 프레임워크 → 내 코드 |
| 비유 | 망치 (필요할 때 꺼내 씀) | 공장 (들어가서 일함) |
Hollywood Principle: "Don't call us, we'll call you" (프레임워크가 너를 부른다)
자기 점변
원본 자료: 토비의 스프링 vol.1, p.93
선수 지식: Unit 7.2
핵심 개념
IoC를 실제로 구현하려면 누군가가:
이 역할을 하는 게 IoC 컨테이너 = Spring의 ApplicationContext.
자기 점검
원본 자료: 토비의 스프링 vol.1, p.94
목표: Spring의 핵심 클래스 ApplicationContext와 DI의 동작을 이해한다.
선수 지식: Phase 7
핵심 개념
@Configuration
public class DaoFactory {
@Bean
public UserDao userDao() {
return new UserDao(connectionMaker());
}
@Bean
public ConnectionMaker connectionMaker() {
return new DConnectionMaker();
}
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao = ctx.getBean("userDao", UserDao.class);
자기 점검
@Bean 메서드의 이름이 빈의 ID가 되는 이유는?원본 자료: 토비의 스프링 vol.1, p.95
선수 지식: Unit 8.1
핵심 동작
ctx.getBean("userDao") 호출 시 ApplicationContext는:
1. 빈 목록에서 "userDao" 찾기
2. 없으면 @Bean 메서드 호출 → 객체 생성
3. 의존하는 다른 빈도 자동으로 같이 생성·주입
4. 만들어진 빈 반환
자기 점검
원본 자료: 토비의 스프링 vol.1, p.100
선수 지식: Unit 8.2
핵심 개념
getBean()을 100번 호출해도 객체는 1개.
왜 싱글톤?
주의: 싱글톤 빈은 stateless 여야 안전:
자기 점검
getInstance())과 Spring 싱글톤의 차이는?원본 자료: 토비의 스프링 vol.1, p.104~106
선수 지식: Unit 8.3
핵심 개념
DI (Dependency Injection):
"구체적인 의존 오브젝트와 그것을 사용할 클라이언트를 런타임에 연결해주는 작업"
3가지 핵심:
1. 의존: A가 B를 사용 (A → B)
2. 인터페이스 의존: A는 B의 인터페이스에만 의존 (구체 구현 모름)
3. 외부 주입: 컨테이너가 런타임에 구체 구현을 주입
3가지 주입 방식:
@Autowired 필드)자기 점검
반드시 깊이 파기:
[ ] Phase 1 — 스레드 풀의 필요성 재정리 (Unit 1.1~1.3)
[ ] Phase 2 — 동시성 안전 도구 3종 비교 (Unit 2.1~2.4)
[ ] Phase 3 — 전통 DAO의 문제 (Unit 3.1~3.3)
[ ] Phase 4 — 관심사의 분리 (Unit 4.1~4.3)
[ ] Phase 5 — 디자인 패턴 적용 (Unit 5.1~5.3)
[ ] Phase 6 — OCP & 전략 패턴 (Unit 6.1~6.3)
[ ] Phase 7 — 제어의 역전 (Unit 7.1~7.3)
[ ] Phase 8 — Spring 컨테이너 (Unit 8.1~8.4)
[ ] 종합 자기 점검 20문항 통과
| 동시성 (Part A) | Spring (Part B) |
|---|---|
| 스레드 풀 = "스레드를 누가 관리하는가" | IoC 컨테이너 = "객체를 누가 관리하는가" |
| Atomic/synchronized = "공유 자원 보호" | 싱글톤 빈 = "공유되는 객체" → stateless 필요 |
| ExecutorService = 스레드 추상화 | ApplicationContext = 객체 추상화 |
→ 두 흐름의 본질은 "관리 책임의 위임" 이라는 동일한 사상.