1. 학습 주제
Spring을 배우면서 가장 큰 변화는
“내가 new로 직접 객체를 만들고 연결하던 방식”에서
“Spring이 객체를 만들고, 보관하고, 연결해주는 방식”으로 생각을 바꾸는 것이었다.
이번 학습의 중심은 크게 다음 흐름이었다.
- Spring Container와 Bean이 무엇인지
- Bean을 어떤 방식으로 등록하는지
- @Autowired로 의존 객체를 어떻게 주입받는지
- 생성자 주입, 필드 주입, Setter 주입 차이
- 같은 타입 Bean이 여러 개일 때 어떻게 구분하는지
- Bean Life Cycle과 Scope
- 외부 설정값을 코드 밖으로 분리하는 이유
- AOP가 왜 필요한지
이번 범위는 문법을 외우는 것보다
“Spring이 객체를 관리한다”는 관점을 잡는 것이 더 중요했다.
2. Spring Container란 무엇인가
Spring Container
Spring이 객체를 만들고 보관하고 관리하는 공간
이걸 조금 더 풀면:
- 개발자가 직접 객체를 new로 만들지 않아도 되고
- Spring이 설정을 보고 객체를 대신 생성하고
- 객체끼리 필요한 의존관계도 연결해준다
즉 Spring Container는 단순 보관함이 아니라:
- 객체 생성
- 객체 보관
- 객체 조립
- 객체 제공
까지 담당하는 관리자이다.
3. Bean이란 무엇인가
Bean
Spring Container가 관리하는 객체
중요한 점은 모든 객체가 Bean은 아니라는 것이다.
- 내가 그냥 new로 만든 객체는 일반 객체
- Spring이 생성하고 관리하는 객체는 Bean
즉 Bean은 단순 객체가 아니라
Spring이 생명주기와 의존관계를 책임지는 객체라고 보면 된다.
4. Bean을 등록하는 방식
Bean을 Spring Container에 올리는 방법은 여러 가지가 있다.
4.1 XML 설정
예전 방식이다.
설정 파일에 직접 Bean을 적어 넣는 방식이다.
4.2 Java Config의 @Bean
설정 클래스 안에서 메서드로 Bean을 직접 등록하는 방식이다.
@Bean
public SomeService someService() {
return new SomeService();
}
의미:
- 이 메서드가 반환하는 객체를 Bean으로 등록해줘
즉 @Bean은
“이 객체는 Spring이 관리해야 해”라고 명시적으로 등록하는 방식이다.
4.3 Component Scan + @Component
클래스에 어노테이션을 붙여두고, Spring이 패키지를 스캔하면서 자동 등록하는 방식이다.
즉:
- @Bean은 설정 클래스에서 직접 등록
- @Component 계열은 클래스에 표시해두고 자동 등록
이라고 정리하면 된다.
5. @Component 계열 정리
적어둔 필기가 아주 중요하다.
@Component
Spring이 관리할 일반 Bean
이건 가장 기본적인 등록 어노테이션이다.
이걸 세분화한 것이 다음 어노테이션들이다.
@Repository
데이터 접근 계층 Bean
- DB 조회, 저장, 수정, 삭제 같은 역할
- 저장소 접근을 담당하는 계층에 붙인다
@Service
비즈니스 로직 계층 Bean
- 실제 업무 규칙을 처리한다
- 여러 Repository를 조합하거나 로직을 수행한다
@Controller
웹 요청 처리 계층 Bean
- 사용자의 요청을 가장 먼저 받는다
- 화면 이동, 요청 분기, 응답 처리 역할을 가진다
@Configuration
설정 클래스 Bean
- Bean 등록 설정을 담는 클래스에 사용한다
- 보통 @Bean 메서드와 같이 쓴다
즉 이 어노테이션들은 전부 Bean 등록과 관련되어 있지만,
각 클래스가 어떤 역할의 계층인지 의미를 더 분명하게 표현해준다.
6. @Autowired란 무엇인가
@Autowired
필요한 의존 객체를 자동으로 주입
예를 들어 어떤 Service가 DAO가 필요할 때,
직접 new DAO() 하지 않고 Spring이 넣어주게 하는 것이다.
즉 @Autowired는:
- “이 객체가 필요해”
- “Spring이 컨테이너에서 찾아서 넣어줘”
라는 의미다.
이게 바로 DI, 의존성 주입의 핵심이다.
7. 왜 의존성 주입이 필요한가
예전 방식이라면 이런 구조였다.
A a = new A();
B b = new B(a);
C c = new C(b);
이 방식은 객체가 많아질수록:
- 누가 누구를 만드는지 복잡해지고
- 구현체가 바뀌면 수정이 번거롭고
- 결합도가 높아진다
Spring은 이걸 바꾼다.
- 객체 생성은 Container가 하고
- 필요한 객체는 주입받는다
즉 클래스는 “무엇이 필요한지”만 말하고,
“어떻게 만들지는” Spring이 담당한다.
8. 주입 방식 3가지
여기서 많이 헷갈리기 쉽다.
8.1 생성자 주입
실무에서 가장 많이 쓰인다.
private final BookDAO bookDAO;
public BookService(BookDAO bookDAO) {
this.bookDAO = bookDAO;
}
장점:
- 객체가 생성될 때 필요한 의존성이 반드시 들어온다
- final 사용 가능
- 불변 구조 만들기 좋다
- 의존관계가 코드에서 명확하게 드러난다
- 테스트하기 쉽다
즉 생성자 주입은
필수 의존성을 가장 안전하게 주입하는 방식이다.
그래서 실무에서는 생성자 주입이 가장 선호된다.
8.2 필드 주입
@Autowired
private BookDAO bookDAO;
이건 코드가 짧고 편해 보인다.
그래서 처음 배울 때는 오히려 더 쉬워 보일 수 있다.
하지만 단점이 있다.
- 의존관계가 외부에 잘 안 드러난다
- final 사용이 어렵다
- 테스트에서 직접 넣어주기 불편하다
- 객체가 무엇에 의존하는지 한눈에 덜 보인다
즉 필드 주입은 간단하지만,
실무에서는 생성자 주입보다 덜 선호된다.
한 줄로 비교하면:
- 생성자 주입: 명확하고 안전함
- 필드 주입: 짧지만 구조가 덜 드러남
8.3 Setter 주입
@Autowired
public void setBookDAO(BookDAO bookDAO) {
this.bookDAO = bookDAO;
}
이 방식은:
- 객체가 생성된 뒤 나중에 의존성을 넣을 수 있다
- 선택적 의존성에 사용할 수 있다
하지만 필수 의존성이라면 생성자 주입이 더 자연스럽다.
즉 Setter 주입은
“있어도 되고 없어도 되는 값”이나
나중에 바꿀 가능성이 있는 의존성에 더 잘 맞는다.
9. 주입 방식 차이 정리
생성자 주입
- 필수 의존성에 적합
- 실무에서 가장 많이 사용
- 가장 권장되는 방식
필드 주입
- 코드가 짧음
- 테스트와 유지보수 측면에서 아쉬움
Setter 주입
- 선택적 의존성에 적합
- 생성 후 변경 가능
즉 기본 기준은 이렇게 잡으면 된다.
- 필수면 생성자 주입
- 선택적이면 Setter 주입 고려
- 필드 주입은 학습용으로 이해하되 실무 기본값은 생성자 주입
10. 같은 타입 Bean이 여러 개일 때
이 부분도 Spring에서 자주 헷갈리는 포인트다.
예를 들어 DiscountPolicy 타입 Bean이 두 개 있다고 하면,
Spring은 어떤 Bean을 넣어야 할지 헷갈릴 수 있다.
그럴 때 쓰는 것이:
- @Primary
- @Qualifier
이다.
11. @Primary
@Primary
같은 타입 Bean이 여러 개 있을 때 기본 우선 Bean 지정
즉 Spring이 자동 주입할 때 “특별히 지정이 없으면 이 Bean을 먼저 써라”라고 알려주는 것이다.
한 줄로 보면:
- 여러 후보 중 기본값 지정
12. @Qualifier
@Qualifier
이름으로 특정 Bean 지정
즉 같은 타입 Bean이 여러 개 있어도
이름을 기준으로 정확히 어떤 Bean을 넣을지 지정할 수 있다.
그리고 중요한 점:
@Qualifier는 @Primary보다 우선순위가 높다
즉:
- 기본적으로는 @Primary
- 하지만 특정 위치에서 명시적으로 @Qualifier를 쓰면 그게 더 우선
이라고 이해하면 된다.
13. List<T> 주입
이것도 실무에서 꽤 유용하다.
같은 타입 Bean이 여러 개 있을 때:
List<SomeType>
형태로 주입받으면,
Spring이 그 타입의 Bean들을 한 번에 리스트로 모아서 넣어준다.
즉:
- 하나만 고르는 게 아니라
- 같은 타입 Bean 전체를 다 받아서
- 반복문으로 처리할 수 있다
이건 전략 패턴이나 확장 가능한 구조를 만들 때 유용하다.
14. Bean Scope
Spring Bean은 기본적으로 모두 같은 방식으로 만들어지지 않는다.
어떤 범위로 관리할지도 정할 수 있다.
singleton
Container 안에서 하나만 만들어지고 공유된다.
즉:
- 같은 Bean을 여러 번 요청해도 같은 객체
- Spring 기본 스코프
실무에서 가장 많이 쓰는 기본값이다.
prototype
getBean() 호출 때마다 새로 만들어진다.
즉:
- 매번 새로운 객체가 필요할 때 사용
- 기본값은 아니다
한 줄 정리:
- singleton: 하나 만들어서 계속 공유
- prototype: 요청할 때마다 새로 생성
15. Bean Life Cycle
Bean도 객체이기 때문에 생명주기가 있다.
흐름은 다음과 같다.
- Bean 생성
- 의존성 주입
- 초기화
- 사용
- 소멸
이 흐름을 이해하는 이유는,
“어느 시점에 어떤 코드를 실행시켜야 하는지”를 알기 위해서다.
예를 들어:
- 생성 직후 필요한 초기 설정
- 종료 직전 자원 정리
같은 작업은 적절한 생명주기 시점에 넣어야 한다.
16. 초기화와 소멸 메서드
Bean 생명주기 중 초기화와 소멸 시점에 특정 메서드를 실행할 수 있다.
initMethod
초기화 시점 실행
destroyMethod
소멸 시점 실행
즉 Bean이 생성되고 의존성 주입이 끝난 뒤
필요한 추가 초기 작업을 할 수 있다.
그리고 Bean이 사라질 때 자원 정리도 가능하다.
17. @PostConstruct, @PreDestroy
필기한 내용처럼 참고로 많이 쓰는 방식이다.
- @PostConstruct: 초기화 직후 실행
- @PreDestroy: 소멸 직전 실행
즉 설정에서 initMethod, destroyMethod를 주는 방식 외에도,
어노테이션으로 생명주기 메서드를 표시할 수 있다.
핵심은 문법보다도:
- 초기화 시점이 있다
- 소멸 시점이 있다
- 그 시점에 필요한 작업을 넣을 수 있다
를 이해하는 것이다.
18. 외부 설정값을 분리하는 이유
이 부분도 실무에서 정말 중요하다.
왜 코드에 직접 박아두면 안 좋은가
예를 들어:
- DB 주소
- 비밀번호
- API 키
- 파일 경로
- 할인율
- 기본 메시지
이런 값들은 나중에 바뀔 가능성이 높다.
그런데 자바 코드에 직접 적어두면:
- 수정할 때 코드를 다시 건드려야 하고
- 빌드/배포도 다시 해야 하고
- 유지보수가 어려워진다
그래서 이런 값은 코드가 아니라 외부 설정 파일에 두는 것이 좋다.
19. Properties 파일과 설정값 읽기
필기한 흐름처럼 보통 이렇게 쓴다.
- Properties 파일에 값 작성
- @PropertySource로 읽기
- @Value로 주입받기
즉 구조는:
- 설정 파일에 값 저장
- Spring이 그 파일을 읽음
- 필요한 Bean 필드나 생성자에 값 주입
이 방식의 장점은:
- 코드와 설정 분리
- 환경별 설정 교체 가능
- 유지보수 쉬움
이다.
20. AOP란 무엇인가
AOP
공통 관심사를 핵심 로직에서 분리하고, 필요한 실행 지점에 자동으로 끼워 넣는 방식
이 문장이 핵심이다.
처음 들으면 추상적으로 느껴질 수 있는데,
쉽게 말하면:
- 여러 클래스에서 반복되는 코드는 따로 빼고
- 핵심 비즈니스 코드에는 그걸 직접 쓰지 않고
- 실행 시점에 자동으로 붙여 넣는 방식
이다.
21. 공통 관심사와 핵심 관심사
필기 내용 그대로가 정답에 가깝다.
공통 관심사
여러 곳에서 반복되는 기능
예:
- 로그 출력
- 권한 체크
- 트랜잭션 처리
- 실행 시간 측정
핵심 관심사
실제 업무 기능
예:
- 회원 조회
- 주문 생성
- 메모 저장
- 도서 검색
즉 AOP는
“로그 찍는 코드 때문에 핵심 로직이 지저분해지는 문제”를 해결하려고 나온 개념이다.
22. 왜 AOP가 필요한가
예를 들어 서비스 메서드마다 이런 코드가 반복된다고 해보자.
System.out.println("메서드 시작");
serviceLogic();
System.out.println("메서드 종료");
이런 로그 코드는 중요하긴 하지만,
실제 업무 로직 자체는 아니다.
그런데 이런 코드가 모든 서비스에 반복되면:
- 중복이 많아지고
- 핵심 로직이 묻히고
- 수정할 때 여러 군데를 바꿔야 한다
그래서 AOP는 공통 관심사를 따로 분리해서 관리한다.
23. AOP 코드 흐름 이해
이번 예제의 핵심 클래스는 LoggingAspect였다.
@Aspect
@Component
public class LoggingAspect
여기서 의미는:
- @Component: Spring Bean으로 등록
- @Aspect: 이 Bean은 AOP 용도로 사용된다는 뜻
즉 이 클래스는 일반 서비스가 아니라
“공통 기능을 끼워 넣는 역할”을 하는 Bean이다.
24. Pointcut이란
@Pointcut("execution(* ... *Service.*(..))")
public void logPointcut() { }
Pointcut은
어디에 공통 기능을 적용할지 고르는 조건이다.
즉 AOP에서 아주 중요한 질문은 이것이다.
- “어떤 메서드에 이 로그를 적용할까?”
그 기준을 적는 것이 Pointcut이다.
이번 예제에서는
이름이 Service로 끝나는 클래스의 메서드들을 대상으로 잡았다.
즉 “서비스 계층 메서드들에 공통 로깅을 적용하겠다”는 의미다.
25. Advice란
@Before("LoggingAspect.logPointcut()")
public void logBefore(JoinPoint joinPoint)
이건 공통 기능이 언제 실행될지를 정한다.
이번 예제는 @Before이므로:
- 대상 메서드가 실행되기 전에
- 로그를 찍는다
즉 Pointcut이 “어디에”라면,
Advice는 “언제 무엇을 할지”이다.
26. JoinPoint란
public void logBefore(JoinPoint joinPoint)
JoinPoint는
현재 실행 지점의 정보를 담고 있는 객체이다.
예제에서 찍은 정보:
- joinPoint.getTarget()
- joinPoint.getSignature()
즉 AOP는 단순히 “메서드 전후에 코드 넣기”가 아니라,
현재 어떤 객체의 어떤 메서드가 실행되는지 알 수도 있다.
27. AOP를 가장 쉽게 이해하는 비유
AOP를 공연장으로 비유하면 쉽다.
- 핵심 관심사 = 배우가 연기하는 본 공연
- 공통 관심사 = 조명, 안내 방송, 티켓 검사
관객은 공연이 중요하지만,
공연장 입장에서는 조명이나 안내도 여러 곳에 반복해서 필요하다.
그런데 배우가 직접 조명을 켜고 티켓 검사까지 하면 이상하다.
그래서:
- 공연은 배우가 하고
- 공통 작업은 별도 담당이 맡는다
이게 AOP 감각과 비슷하다.
즉 핵심 로직은 핵심 로직대로 두고,
공통 기능은 옆에서 끼워 넣는 구조다.
28. 이번 범위 전체 흐름 정리
1. Bean 관리
- Spring Container가 객체를 만들고 관리
- 그 객체가 Bean
2. Bean 등록 방식
- XML
- @Bean
- @Component 계열 + 스캔
3. 의존성 주입
- @Autowired
- 생성자 주입
- 필드 주입
- Setter 주입
4. 여러 Bean 처리
- @Primary
- @Qualifier
- List<T> 주입
5. Bean 생명주기와 범위
- 생성, 주입, 초기화, 사용, 소멸
- singleton / prototype
- initMethod / destroyMethod
- @PostConstruct / @PreDestroy
6. 외부 설정값
- 바뀔 수 있는 값은 코드 밖으로 분리
- Properties + @PropertySource + @Value
7. AOP
- 공통 관심사와 핵심 관심사 분리
- Pointcut으로 대상 선택
- Advice로 실행 시점 지정
- Aspect Bean이 공통 기능 담당
29. 핵심 정리
- Spring Container는 객체를 만들고 보관하고 관리하는 공간이다.
- Bean은 Spring Container가 관리하는 객체이다.
- @Bean은 설정 클래스에서 객체를 직접 등록하는 방식이다.
- @Component와 그 하위 어노테이션은 스캔을 통해 자동으로 Bean 등록하는 방식이다.
- @Repository, @Service, @Controller는 역할을 더 명확하게 표현하는 @Component 계열이다.
- @Autowired는 필요한 의존 객체를 Spring이 자동으로 주입하게 하는 어노테이션이다.
- 생성자 주입은 실무에서 가장 많이 쓰이고 가장 권장되는 방식이다.
- 필드 주입은 간단하지만 구조가 덜 드러나고 테스트에 불리할 수 있다.
- Setter 주입은 선택적 의존성에 사용할 수 있다.
- 같은 타입 Bean이 여러 개일 때 @Primary는 기본 우선순위, @Qualifier는 특정 Bean 지정 역할을 한다.
- List<T> 주입을 사용하면 같은 타입 Bean 여러 개를 한 번에 받을 수 있다.
- Bean은 생성, 의존성 주입, 초기화, 사용, 소멸의 생명주기를 가진다.
- Scope는 Bean이 몇 개 만들어지고 어떻게 공유될지를 결정하며, 기본은 singleton이다.
- 변경 가능성이 있는 값은 코드에 직접 쓰지 말고 외부 설정 파일로 분리하는 것이 좋다.
- AOP는 로그, 트랜잭션 같은 공통 관심사를 핵심 로직에서 분리하는 방식이다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]Spring Boot JSON 처리와 예외 흐름 기초 (0) | 2026.05.29 |
|---|---|
| [TIL]Spring Boot 요청 매핑, 핸들러 메서드, 세션 처리, 그리고 AOP (0) | 2026.05.28 |
| [TIL]Spring Core: DI, Spring Container, Bean, 계층 구조, @Autowired와 컴포넌트 스캔 (0) | 2026.05.26 |
| [TIL]쿠키, 세션, 인증/인가, 그리고 Front Controller 흐름 (0) | 2026.05.22 |
| [TIL]Servlet JSON API에서 JDBC 연동까지: 프론트엔드, 서블릿, 서비스, DAO, DB 흐름 이해하기 (0) | 2026.05.21 |