객체 지향 설계의 5가지 기본 원칙이다.
스프링은 다형성만으로 해결하지 못했던 2번(OCP), 5번(DIP)를
IoC(제어의 역전)
, DI(의존성 주입)
를 통해 만들어준다.
개발자는 "무엇을 사용할지"에만 집중하고,
"어떻게 생성하고 연결할지"는 스프링이 알아서 처리하게 하는 것이 스프링의 역할!
객체의 생성,관리를 개발자가 아니라 스프링 컨테이너가 하는 것을 말한다.
객체 간의 결합도를 낮춰서 유연한 코드를 구성하게 도와준다.
public class OwnerController{
// ✅ 클래스 내부에서 의존 객체를 new 키워드로 인스턴스화한다.
private final OwnerRepository repo = new OwnerRepository();
}
내가(OwnerController
) 사용하는 의존성(OwnerRepository
)은
내가 생성해서 사용한다.
public class OwnerController{
private final OwnerRepository repo;
// ✅ 생성자에서 의존성 주입(IoC)
public OwnerController(OwnerRepository repo){
this.repo = repo;
}
}
💭 이전에 진행했던 키오스크 과제에서도
"필드를 어디에서 초기화할지"에 대해 고민한 적이 있었는데,
지금 설명하고 있는 제어의 역전(IoC) 개념과 같은 고민이었다.
👉 키오스크 과제 회고 보러가기
@Autowired
사용 부분을 참고하자.즉, 스프링 컨테이너 안에 있는 객체(Bean)들끼리만 의존성 주입(DI)이 가능하다!
1. 결합도 감소
객체 간의 의존성을 외부로 분리해서, 변경에 유연하게 대응할 수 있게함
ex. OwnerRepository → MockOwnerRepository로 쉽게 교체
2. 테스트 용이성
의존성을 주입받으므로, 테스트할 때 가짜(mock) 객체로 교체가 쉬움
3. 유연성, 재사용성 증가
객체 간이 느슨하게 연결되므로 다양한 상황에 재사용 가능하다!
4. 관심사 분리
객체(OwnerController
)는 "자신이 할 일" 에만 집중할 수 있다.
생성, 연결 같은 작업은 스프링 컨테이너가 맡는다.
💭 멀티쓰레드 상황에서 싱글톤 패턴을 구현하는 것은 번거롭고 조심스러운 일인데,
이러한 일을 스프링 컨테이너가 빈(Bean)을 등록하여 쉽게 구현해준다.
스프링 컨테이너가 관리하는 모든 객체(인스턴스)를 의미한다.
public class BeanExample (){
// 스프링 컨테이너 구현체
private final ApplicationContext applicationContext;
public BeanExample(ApplicationContext applicationContext){
this.applicationContext = applicationContext;
}
public void getBean(){
// ❌ Bean이 아닌 객체 (직접 의존성 주입한 객체)
Controller notBean = new Controller();
// ✅ Bean인 객체 (스프링 컨테이너가 관리하는 객체)
Controller bean = applicationContext.getBean(Controller.class);
}
}
ApplicationContext
안에 있는 객체를 뜻한다.클래스 옆에 농구공 모양🏀
이 빈(Bean)에 등록되었다는 뜻이라고 한다. ㅎㅎ
스프링은 특정 패키지 내에서 특정 애너테이션이 붙은 클래스를 자동으로 검색하고,
빈(Bean)으로 등록하는 기능을 제공한다.
👉 스프링이 인식하는 주요 애너테이션:
@Component, @Controller, @Service, @Repository, @Configuration 등
❓ @ComponentScan은 어디에 선언할까?
![]()
@SpringBootApplication
안에@ComponentScan
이 존재한다.
따라서 선언할 필요 없이, 루트(최상위) 패키지 기준으로 자동 스캔이 시작된다!
@ComponentScan
이 선언된 위치부터 하위 모든 패키지를 탐색한다.수동등록을 하기 위해서는 설정을 해야 한다.
다음은 @Configuration
, @Bean
을 통해 수동등록 하는 방법이다.
<// ✅ SampleController 직접 빈(Bean)에 등록하는 설정 클래스
@Configuration
public class SampleConfig{
@Bean
public SampleController sampleController(){
return new SampleController();
}
}
@Controller
어노테이션을 붙이지 않아도 SampleController는 빈 객체이다!@Configuration
과 함께 사용해야 Bean이 싱글톤으로 관리된다.외부 라이브러리
같은 우리가 직접 애너테이션을 붙일 수 없는 객체를 등록할 때
@Autowired
를 사용하면, 필요한 빈(Bean)을
다음과 같은 방식으로 의존성 주입(DI)할 수 있다.
❓ 왜 생성자로 의존성 주입 방법이 권장될까?
- 불변성 보장
주입받은 의존성을final
로 선언하여 객체가 변경되지 않게 보호할 수 있다.- 의존성 누락 방지
의존성 주입이 제대로 되지 않을 때에 인스턴스를 만들지 못하게 강제할 수 있다.
@Controller
public class Controller {
private final Repository repo;
// ✅ 생성자를 통해 Controller가 의존하고 있는 Repository 의존성 주입 (DI)
@Autowired
public Controller(Repository repo) {
this.repo = repo;
}
}
💡 생성자가 하나라면, 어노테이션 생략이 가능하다!
@Controller public class Controller { private final Repository repo; // ✅ 생성자 하나므로 @Autowired 생략 public Controller(Repository repo){ this.repo = repo; } }
스프링 4.3 이후로는 의존성 주입을 받는 클래스의 생성자가 하나일 때,
@Autowired
어노테이션 생략이 가능해졌다.
@Controller
public class Controller {
// ✅ 필드에서 의존성 주입하기
@Autowired
private Repository repo;
....
}
final
키워드를 사용할 수 없다!@Controller
public class Controller {
private Repository repo;
....
// ✅ Setter로 Controller가 의존하고 있는 Repository 의존성 주입 (DI)
@Autowired
public void setRepository(Repository repo){
this.repo = repo;
}
}
final
키워드를 사용할 수 없다!final
필드를 모아 자동으로 생성자를 만들어준다.@Autowired
를 생략해도 스프링이 자동으로 주입해준다.@RequiredArgsConstructor
@Controller
public class Controller {
// ✅ 한줄로 끝!
private final Repository repo;
}
클래스를 인스턴스화 할 때, 해당 인스턴스를 하나만 생성되도록 보장하는 패턴이다.
public class Singleton {
// ✅ static으로 하나의 인스턴스를 생성해놓음
private static final Singleton instance = new Singleton();
// ❌ 생성자를 private로 막음 (외부에서 생성 불가!!)
private Singleton() {}
// ✅ 외부에서는 이 메서드로만 싱글톤 패턴이 적용된 객체를 사용할 수 있다.
public static Singleton getInstance() {
return instance;
}
}
stateless
스프링에서는 모든 빈(Bean)을 싱글톤으로 관리한다.
따라서 무상태(stateless) 설계를 지향해야 한다.
내일배움캠프 스프링 숙련 1주차
인프런 예제로 배우는 스프링 입문 (백기선)