-
[Spring Study] 토비의 스프링 1주차 스터디 회고: 스프링의 핵심 철학Spring 2025. 11. 3. 16:50
스터디를 시작하며
혼자서 스프링을 공부하며 프로젝트를 진행해왔지만, 개념을 깊이 이해하기보다는 일단 작동하게 만드는 데 집중했던 것 같다.
그러다 문득 이런 생각이 들었다.
스프링은 왜 이런 구조를 택했을까? 왜 스프링에서는 이런 기능들을 제공할까?
이런 근본적인 의문을 해소하기 위해, 동기, 선배들과 함께 토비의 스프링 Vol.1을 교재로 하는 스터디를 시작했다.
스터디는 스프링의 바이블로 불리는 토비의 스프링 Vol.1로 진행되며, 각 주차별로 정해진 범위를 읽고, 발표자가 내용을 정리해 발표한 뒤 각자 읽으며 인상 깊었던 부분이나 궁금했던 점을 함께 토론하는 방식으로 진행된다.
발표는 매주 돌아가면서 진행한다.오늘의 목표
오늘 스터디는 토비의 스프링 Vol.1 1장: 오브젝트와 의존관계.
겉보기엔 단순히 UserDao를 리팩토링하는 내용이지만, 그 안에는 스프링의 핵심 개념 (IoC, DI, Bean, ApplicationContext, DataSource)이 모두 숨어 있었다.
초난감 DAO
가장 처음, 책에는 이렇게 생긴 UserDao가 있었다.
public class UserDao { public void add(User user) throws ClassNotFoundException, SQLException { Connection c = DriverManager.getConnection( "jdbc:mysql://localhost:3306/spring", "root", "1234" ); ... } }처음엔 별문제 없어 보였다.
그런데 조금만 생각해보면 문제투성이였다.
- 매번 DB 연결할 때마다 Connection 생성 코드가 중복
- DB 연결정보(URL, ID, PW)가 코드에 하드코딩
- DB 종류가 바뀌면 UserDao 전체를 수정해야 함
즉, 이 코드는 완전히 DB에 종속된 구조였다. 스프링이 강조하는 유연하고 변경에 강한 객체지향과는 정반대였다.
ConnectionMaker의 분리: 의존을 끊어보자
그래서 가장 먼저 처음 리팩토링 한 부분은 DB 연결을 담당하는 부분을 별도의 인터페이스로 분리했다.
public interface ConnectionMaker { Connection makeConnection() throws SQLException, ClassNotFoundException; } public class DConnectionMaker implements ConnectionMaker { public Connection makeConnection() { return DriverManager.getConnection(...); } }그리고 UserDao에서는 더 이상 DB 연결 방법을 알 필요가 없게 만들었다.
public class UserDao { private ConnectionMaker connectionMaker; public UserDao(ConnectionMaker connectionMaker) { this.connectionMaker = connectionMaker; } }이걸로 UserDao는 DB 연결 방식과 독립되었다.
DB가 Oracle이든 MySQL이든 상관없이, ConnectionMaker 구현체만 바꾸면 됐다.
그런데..
현재 구조에서는
UserDao는 DB 연결에서 독립됐는데, 지금은 UserDaoTest가 ConnectionMaker를 직접 만들어버린다.
ConnectionMaker connectionMaker = new DConnectionMaker(); UserDao dao = new UserDao(connectionMaker);그렇다. 이제 의존성이 UserDaoTest로 옮겨간 것뿐이었다.
결국 테스트 코드가 또 다른 책임(객체 생성)을 떠안고 있었다."테스트는 UserDao의 기능만 검증해야지, 객체 조립까지 담당하면 너무 많은 역할을 하는 거 아닌가?"
이 문제의식에서 팩토리(Factory)개념이 나온다.
팩토리의 등장: 객체 생성 책임을 분리하자
그래서 UserDaoTest에서 객체 생성 책임을 분리하기 위해 DaoFactory 클래스를 만들었다.
public class DaoFactory { public UserDao userDao() { return new UserDao(connectionMaker()); } public ConnectionMaker connectionMaker() { return new DConnectionMaker(); } }이제 UserDaoTest는 이렇게 단순해졌다
UserDao dao = new DaoFactory().userDao();테스트는 테스트만, 객체 생성은 팩토리가 담당하는 관심사 분리가 완성됐다.
이 리팩토링은 단순히 코드를 보기 좋게 바꾼 게 아니라, "객체 생성과 사용을 분리한다"는 객체지향의 근본 원칙을 실천한 것이었다.
IoC (Inversion of Control): 제어의 역전
이쯤에서 책은 이렇게 말한다.
"DaoFactory는 스프링의 핵심 원리, IoC(제어의 역전)를 단순한 형태로 구현한 것이다."
기존에는 내 코드가 직접 new를 통해 객체를 생성하고 연결(제어권을 내가 가짐)했다.
하지만 지금은 DaoFactory가 객체 생성과 연결을 대신 제어한다.
기존 방식 IoC 적용 제어 주체 내 코드 외부 팩토리 객체 생성 직접 new 팩토리가 대신 관계 연결 코드 내부 외부에서 주입 즉, "객체 제어의 주체가 개발자 코드에서 외부로 넘어가는 현상" 이것이 바로 IoC (제어의 역전)이다.
ApplicationContext: 스프링의 팩토리 확장
책에서는 이후 이렇게 말한다.
"DaoFactory는 일종의 수동 IoC 컨테이너다. 스프링은 이걸 자동화한 IoC 컨테이너(ApplicationContext)를 제공한다."
DaoFactory가 수동으로 하던 일을 스프링의 ApplicationContext가 대신 처리한다.
ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class); UserDao dao = context.getBean("userDao", UserDao.class);이제 객체 생성, 관계 설정, 관리까지 모두 스프링 컨테이너가 맡는다.
ApplicationContext = IoC 컨테이너, Bean = 그 안에서 관리되는 객체
DI (Dependency Injection): 의존성 주입
IoC를 더 구체적으로 구현하는 방법이 바로 DI다.
UserService가 UserDao를 직접 만들면 둘은 강하게 결합되어 변경에 약하다.
class UserService { private UserDao userDao = new UserDao(); }그래서 스프링에서는 이렇게 한다.
@Service public class UserService { private final UserDao userDao; @Autowired public UserService(UserDao userDao) { this.userDao = userDao; } }이제 UserDao를 직접 생성하지 않고, 스프링 컨테이너가 UserDao를 만들어서 UserService에 주입(Injection) 해준다.
IoC가 "제어권을 넘긴다"라면, DI는 "무엇을, 어떻게 주입할지"를 구체화한 것.
DataSource: DB 연결도 IoC로 관리하자
리팩토링의 마지막은 DataSource이다. UserDao가 DB 연결을 이렇게 직접 만들던 시절이 있었다.
Connection c = DriverManager.getConnection(...);하지만 이 방식은 커넥션 생성 비용이 비싸고, 코드에 DB 정보가 하드코딩되어 있으며, 환경이 바뀌면 직접 수정해야 한다는 단점이 있었다.
그래서 DB 연결을 관리하는 표준 인터페이스 DataSource 를 도입했다.
public class UserDao { private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void add(User user) throws SQLException { Connection c = dataSource.getConnection(); ... } }스프링이 DataSource Bean을 만들어서 UserDao에 주입(DI). 이제 DB 정보가 바뀌어도 application.yml만 수정하면 된다.
즉, DB 연결마저 스프링이 제어하는 완전한 IoC 구조.
느낀 점
스터디를 하기 전까진 "스프링은 그냥 자바를 편하게 써주는 프레임워크"라고만 생각했다. 하지만 오늘 깨달았다.
스프링은 단순한 도구가 아니라, 객체지향 설계를 실천하게 만드는 구조적 장치였다.
- 객체 생성과 사용의 분리
- 관심사의 분리
- 의존성 주입을 통한 유연한 확장성
이 모든 것이 단지 편의성을 위한 것이 아니라,
좋은 객체 설계를 강제하는 프레임워크적 철학이었다.결국 스프링은, 객체를 "잘 만들고, 잘 연결하고, 잘 관리하게" 만드는 프레임워크다.
'Spring' 카테고리의 다른 글
[Spring Study] 토비의 스프링 3주차 스터디 회고: 예외를 던지는 이유를 이해 (0) 2025.11.18 [Spring Study] 토비의 스프링 2주차 스터디 회고: JDBC 리팩토링과 JdbcTemplate의 발전 (0) 2025.11.05 @MemberId 도입기: 스프링 MVC 요청 흐름과 커스ArgumentResolver 활용 (0) 2025.09.08 왜 ResponseEntity를 써야 할까? (2) 2025.08.05 JPA 기반 스프링 애플리케이션에서의 CQRS 패턴 적용 (1) 2025.07.14