🎯 학습 목표
- 의존성 주입(DI)의 개념과 이점을 이해한다.
- @Component/@Service/@Repository 역할을 구분한다.
- 3계층 구조로 책임을 분리한다.
📖 개념 설명
스프링의 심장은 의존성 주입(DI)입니다. 객체가 필요한 다른 객체를 직접 new로 만들지 않고, 스프링 컨테이너가 만들어서 “주입”해 줍니다. 덕분에 결합도가 낮아지고 테스트가 쉬워집니다.
실무 표준은 3계층 구조입니다. Controller(HTTP 요청/응답 담당) → Service(비즈니스 로직) → Repository(데이터 접근). 각 계층이 한 가지 책임만 지므로 유지보수가 쉽습니다.
💻 실습 — 생성자 주입(권장)
// Service
@Service
public class GreetingService {
public String greet(String name) {
return "안녕하세요, " + name + "님!";
}
}
// Controller — 생성자로 Service 주입
@RestController
@RequestMapping("/api")
public class GreetingController {
private final GreetingService service;
// 생성자가 하나면 @Autowired 생략 가능 (생성자 주입 권장)
public GreetingController(GreetingService service) {
this.service = service;
}
@GetMapping("/greet/{name}")
public String greet(@PathVariable String name) {
return service.greet(name);
}
}
📖 어노테이션 역할
@Component : 스프링이 관리하는 일반 빈
@Service : 비즈니스 로직 계층 (의미상 구분)
@Repository : 데이터 접근 계층 (DB 예외 변환 기능 포함)
@Controller / @RestController : 웹 요청 처리 계층
⚠️ 주의사항
- 필드 주입(
@Autowired필드)보다 생성자 주입을 쓰세요. 불변성·테스트·순환참조 발견에 유리합니다. - 스프링이 빈을 못 찾으면(NoSuchBeanDefinitionException) 클래스에 스테레오타입 어노테이션이 있는지, 스캔 범위 안에 있는지 확인하세요.
💡 팁
final필드 + 생성자 주입은 Lombok@RequiredArgsConstructor로 더 간결하게 쓸 수 있습니다.- 계층을 넘나드는 데이터는 Entity가 아니라 DTO로 옮기면 결합도가 낮아집니다.