[Spring] 09. @Autowired와 생성자 주입
이전 글에서는 @Controller, @Service, @Repository에 대해 살펴봤습니다. 이 어노테이션들은 Spring이 클래스를 Bean으로 등록할 수 있게 해주고, 각 클래스의 역할을 명확하게 구분해줍니다.
이번 글에서는 등록된 Bean들이 실제로 어떻게 연결되는지 알아보겠습니다. Spring에서 의존성을 주입할 때 자주 등장하는 어노테이션이 @Autowired입니다. 그리고 의존성 주입 방식 중 가장 많이 권장되는 방식이 생성자 주입입니다.
핵심은 간단합니다. @Autowired는 Spring Container 안에 있는 Bean을 찾아서 필요한 곳에 자동으로 넣어주는 역할을 합니다.
1. @Autowired란?
@Autowired는 Spring이 필요한 Bean을 자동으로 주입해주도록 요청하는 어노테이션입니다. 여기서 주입이란 필요한 객체를 외부에서 넣어주는 것을 의미합니다.
예를 들어 BoardController가 BoardService를 필요로 한다고 생각해보겠습니다.
BoardController
→ BoardService 필요
일반 Java 방식이라면 Controller 안에서 Service 객체를 직접 만들 수 있습니다.
public class BoardController {
private BoardService boardService = new BoardService();
}
하지만 Spring에서는 이렇게 직접 객체를 생성하기보다, Spring Container가 관리하는 Bean을 주입받아 사용합니다. 이때 사용할 수 있는 어노테이션이 @Autowired입니다.
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
}
위 코드에서 Spring은 BoardController가 BoardService를 필요로 한다는 것을 확인합니다. 그리고 Spring Container 안에 등록된 BoardService Bean을 찾아서 boardService 필드에 넣어줍니다.
2. @Autowired가 동작하려면 Bean이 필요하다
@Autowired가 동작하려면 주입받을 객체가 Spring Bean으로 등록되어 있어야 합니다. Spring Container 안에 없는 객체는 자동으로 주입할 수 없습니다.
예를 들어 BoardService를 주입받고 싶다면 BoardService가 Bean으로 등록되어 있어야 합니다.
@Service
public class BoardService {
}
위 클래스는 @Service가 붙어 있으므로 Component Scan을 통해 Bean으로 등록될 수 있습니다.
Component Scan
→ @Service 발견
→ BoardService Bean 등록
→ BoardController에 주입 가능
반대로 BoardService에 아무 어노테이션도 없다면 Spring이 Bean으로 관리하지 못할 수 있습니다.
public class BoardService {
}
이 상태에서 BoardService를 주입받으려고 하면 Spring이 해당 Bean을 찾지 못해 오류가 발생할 수 있습니다.
3. 의존성 주입 방식의 종류
Spring에서 의존성을 주입하는 방식은 대표적으로 세 가지가 있습니다.
| 필드 주입 | 필드 위에 @Autowired를 붙여 주입받는 방식 |
| Setter 주입 | setter 메서드를 통해 주입받는 방식 |
| 생성자 주입 | 생성자를 통해 필요한 객체를 주입받는 방식 |
이 중에서 처음 Spring을 공부할 때는 생성자 주입을 기준으로 익히는 것이 좋습니다. 생성자 주입은 필요한 의존성이 명확하게 보이고, 객체가 만들어질 때 반드시 필요한 값을 받을 수 있기 때문입니다.
4. 필드 주입
필드 주입은 필드 위에 @Autowired를 붙이는 방식입니다. 코드가 짧아서 처음에는 편해 보입니다.
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
}
하지만 필드 주입은 의존성이 겉으로 잘 드러나지 않습니다. BoardController를 만들기 위해 BoardService가 반드시 필요하다는 사실이 생성자에 나타나지 않습니다.
또한 테스트 코드를 작성할 때 직접 객체를 생성하고 의존성을 넣기 불편할 수 있습니다. 그래서 실무에서는 필드 주입보다 생성자 주입을 더 많이 사용합니다.
5. Setter 주입
Setter 주입은 setter 메서드를 통해 의존성을 주입받는 방식입니다.
@Controller
public class BoardController {
private BoardService boardService;
@Autowired
public void setBoardService(BoardService boardService) {
this.boardService = boardService;
}
}
Setter 주입은 객체가 생성된 후에 의존성을 넣을 수 있습니다. 필요에 따라 의존성을 나중에 바꿀 수 있다는 장점이 있습니다.
하지만 반드시 필요한 의존성이라면 Setter 주입보다 생성자 주입이 더 안전합니다. Setter 주입은 의존성이 주입되지 않은 상태의 객체가 만들어질 가능성을 열어둘 수 있기 때문입니다.
6. 생성자 주입
생성자 주입은 생성자를 통해 필요한 객체를 전달받는 방식입니다. Spring에서 가장 많이 권장되는 방식입니다.
@Controller
public class BoardController {
private final BoardService boardService;
@Autowired
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
}
위 코드에서 BoardController는 BoardService를 직접 생성하지 않습니다. 생성자를 통해 BoardService를 전달받습니다.
Spring은 BoardController Bean을 만들 때 생성자를 확인합니다. 그리고 생성자에 필요한 BoardService Bean을 찾아 넣어줍니다.
1. BoardService Bean 생성
2. BoardController 생성 시 BoardService 필요 확인
3. Spring Container에서 BoardService Bean 찾기
4. 생성자에 BoardService 주입
5. BoardController Bean 생성 완료
이 흐름이 생성자 주입입니다.
7. 생성자가 하나라면 @Autowired를 생략할 수 있다
Spring에서는 생성자가 하나만 있는 경우 @Autowired를 생략할 수 있습니다. 그래서 실제 코드에서는 다음과 같이 작성하는 경우가 많습니다.
@Controller
public class BoardController {
private final BoardService boardService;
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
}
생성자 위에 @Autowired가 없지만 Spring은 생성자가 하나라는 것을 보고 자동으로 의존성을 주입할 수 있습니다.
처음 공부할 때는 @Autowired를 붙여서 흐름을 이해하고, 익숙해지면 생성자 하나일 때는 생략 가능하다고 정리하면 좋습니다.
8. 생성자 주입을 권장하는 이유
생성자 주입을 권장하는 이유는 의존성이 명확하고, 객체를 더 안전하게 만들 수 있기 때문입니다.
| 의존성 명확 | 생성자만 봐도 어떤 객체가 필요한지 알 수 있습니다. |
| final 사용 가능 | 한 번 주입된 의존성이 중간에 바뀌지 않도록 만들 수 있습니다. |
| 누락 방지 | 필요한 의존성이 없으면 객체 생성 자체가 어렵습니다. |
| 테스트 용이 | 테스트할 때 필요한 객체를 직접 생성자에 넣기 쉽습니다. |
| 순환 참조 파악 | 객체들이 서로 물고 있는 구조를 더 빨리 확인할 수 있습니다. |
특히 final을 사용할 수 있다는 점이 중요합니다.
private final BoardService boardService;
이렇게 작성하면 BoardController가 생성될 때 BoardService가 반드시 들어와야 합니다. 그리고 한 번 들어온 값은 중간에 다른 값으로 바뀌지 않습니다.
9. Controller, Service, Mapper 연결 예시
게시판 목록 조회 기능을 기준으로 생성자 주입 흐름을 보겠습니다.
@Controller
public class BoardController {
private final BoardService boardService;
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
@GetMapping("/board/list")
public String list(Model model) {
List<BoardDto> boardList = boardService.findBoardList();
model.addAttribute("boardList", boardList);
return "board/list";
}
}
@Service
public class BoardService {
private final BoardMapper boardMapper;
public BoardService(BoardMapper boardMapper) {
this.boardMapper = boardMapper;
}
public List<BoardDto> findBoardList() {
return boardMapper.findAll();
}
}
@Mapper
public interface BoardMapper {
List<BoardDto> findAll();
}
이 구조에서 BoardController는 BoardService를 주입받고, BoardService는 BoardMapper를 주입받습니다. 각 클래스는 필요한 객체를 직접 만들지 않습니다. Spring이 Bean을 찾아 생성자에 넣어줍니다.
BoardController
→ BoardService 주입
BoardService
→ BoardMapper 주입
BoardMapper
→ DB 쿼리 실행
이렇게 연결하면 Controller, Service, Mapper가 각자 자기 역할에 집중할 수 있습니다.
10. Lombok의 @RequiredArgsConstructor
생성자 주입을 사용하다 보면 생성자 코드가 반복됩니다. 이때 Lombok을 사용하면 생성자 코드를 줄일 수 있습니다.
@Controller
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
@GetMapping("/board/list")
public String list(Model model) {
List<BoardDto> boardList = boardService.findBoardList();
model.addAttribute("boardList", boardList);
return "board/list";
}
}
@RequiredArgsConstructor는 final이 붙은 필드를 대상으로 생성자를 자동으로 만들어줍니다. 즉, 아래 생성자를 직접 작성하지 않아도 됩니다.
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
그래서 Spring Boot 프로젝트에서는 생성자 주입과 Lombok을 함께 사용하는 경우가 많습니다.
생성자 주입 기본 형태
→ 직접 생성자 작성
실무에서 자주 보는 형태
→ final 필드 + @RequiredArgsConstructor
다만 처음 공부할 때는 Lombok 없이 생성자를 직접 작성해보는 것이 좋습니다. 그래야 Spring이 어떤 방식으로 Bean을 주입하는지 더 정확히 이해할 수 있습니다.
11. @Autowired가 실패하는 경우
@Autowired가 항상 성공하는 것은 아닙니다. Spring이 주입할 Bean을 찾지 못하거나, 반대로 같은 타입의 Bean이 여러 개 있으면 문제가 생길 수 있습니다.
1) Bean이 없는 경우
@Controller
public class BoardController {
private final BoardService boardService;
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
}
위 코드에서 BoardService를 주입받으려면 BoardService가 Bean으로 등록되어 있어야 합니다. 그런데 BoardService에 @Service가 없거나 Component Scan 범위 밖에 있으면 Spring이 Bean을 찾지 못할 수 있습니다.
원인
→ @Service 없음
→ Component Scan 범위 밖
→ 설정 누락
→ Mapper 등록 누락
2) 같은 타입의 Bean이 여러 개인 경우
같은 타입의 Bean이 여러 개 있으면 Spring은 어떤 Bean을 주입해야 할지 판단하지 못할 수 있습니다.
public interface PayService {
}
@Service
public class KakaoPayService implements PayService {
}
@Service
public class NaverPayService implements PayService {
}
이 상태에서 PayService를 주입받으면 Spring 입장에서는 KakaoPayService를 넣어야 할지, NaverPayService를 넣어야 할지 애매할 수 있습니다.
@Service
public class OrderService {
private final PayService payService;
public OrderService(PayService payService) {
this.payService = payService;
}
}
이런 경우에는 @Qualifier나 @Primary 같은 방법으로 어떤 Bean을 사용할지 지정할 수 있습니다. 이 내용은 다음 글에서 더 자세히 정리하겠습니다.
12. 자주 하는 실수
@Autowired를 사용할 때 자주 하는 실수는 Spring이 관리하지 않는 클래스에 주입하려고 하는 것입니다.
public class BoardUtil {
@Autowired
private BoardService boardService;
}
위 클래스가 Bean으로 등록되어 있지 않다면 Spring이 BoardUtil을 관리하지 않습니다. Spring이 관리하지 않는 객체 안에서는 의존성 주입도 정상적으로 동작하지 않을 수 있습니다.
즉, 의존성 주입을 받는 클래스도 Spring Bean이어야 합니다.
주입하는 객체
→ Bean이어야 함
주입받는 객체
→ 보통 Bean이어야 함
또 다른 실수는 DTO나 VO에 @Autowired를 사용하려는 것입니다. DTO와 VO는 보통 데이터를 담는 객체입니다. Spring Container가 계속 관리하는 Bean이 아니므로, 일반적으로 의존성 주입 대상이 아닙니다.
좋은 사용 위치
→ Controller
→ Service
→ Repository
→ Mapper
→ 설정 클래스
피해야 할 사용 위치
→ DTO
→ VO
→ 단순 데이터 객체
13. 정리
@Autowired는 Spring Container 안에 있는 Bean을 찾아 필요한 곳에 자동으로 주입해주는 어노테이션입니다. 의존성 주입 방식에는 필드 주입, Setter 주입, 생성자 주입이 있습니다.
| 필드 주입 | 코드는 짧지만 의존성이 잘 드러나지 않습니다. |
| Setter 주입 | 선택적인 의존성을 주입할 때 사용할 수 있습니다. |
| 생성자 주입 | 필수 의존성을 명확하게 표현할 수 있어 가장 많이 권장됩니다. |
생성자 주입을 사용하면 필요한 의존성이 명확해지고, final을 사용할 수 있으며, 테스트하기 좋은 구조를 만들 수 있습니다. 생성자가 하나뿐이라면 @Autowired를 생략해도 Spring이 자동으로 의존성을 주입할 수 있습니다.
Spring에서 중요한 흐름은 다음과 같습니다.
Component Scan
→ Bean 등록
→ 생성자 확인
→ 필요한 Bean 탐색
→ 의존성 주입
→ 객체 사용
이 흐름을 이해하면 Spring이 객체를 어떻게 연결하는지 훨씬 명확해집니다.
연습해보기
아래 구조를 생성자 주입 방식으로 작성해보세요.
MemberController
→ MemberService
→ MemberMapper
각 클래스의 역할은 다음처럼 나눌 수 있습니다.
| MemberController | 회원 관련 요청을 받습니다. |
| MemberService | 회원가입, 로그인 같은 기능 흐름을 처리합니다. |
| MemberMapper | 회원 정보를 DB에서 조회하거나 저장합니다. |
다음 글에서는 같은 타입의 Bean이 여러 개 있을 때 사용하는 @Qualifier와 @Primary에 대해 정리하겠습니다.

'Tech Stack > Spring Boot' 카테고리의 다른 글
| [Spring] 11. Bean Scope란? (0) | 2026.07.05 |
|---|---|
| [Spring] 10. @Qualifier와 @Primary (0) | 2026.07.04 |
| [Spring] 08. @Controller, @Service, @Repository (0) | 2026.07.02 |
| [Spring] 07. @Component와 Component Scan (0) | 2026.07.01 |
| [Spring] 06. Spring Container와 ApplicationContext (0) | 2026.06.30 |