[SPRING] 스프링을 JAVA (SOLID 원칙, 스프링 IoC , DI , Bean)

wannabeing·2025년 3월 27일
1

SPARTA-TIL

목록 보기
9/22
post-thumbnail

✅ 1. SOLID 원칙

객체 지향 설계의 5가지 기본 원칙이다.

1. SRP (단일 책임 원칙)

  • 하나의 클래스는 하나의 책임만 가져야 한다.
  • 하나의 클래스가 수정될 때, 파급효과가 작아지게 하기 위함이다.

2. OCP (개방-폐쇄 원칙) -> 🌱 스프링이 해결

  • 확장에는 열려있고, 수정에는 닫혀있어야 한다.
  • 새로운 기능을 추가할때, 기존코드에 수정은 없으나 확장할 수 있도록 설계해야 한다.
    ex. 다형성 활용 (인터페이스, Enum 등)

3. LSP (리스코프 치환 원칙)

  • 자식클래스는 항상 부모클래스를 대체할 수 있다.

4. ISP (인터페이스 분리 원칙)

  • 특정 객체를 위한 여러개의 인터페이스가 하나의 범용 인터페이스보다 낫다.

5. DIP (의존관계 역전 원칙) -> 🌱 스프링이 해결

  • 구현체에 의존하지 않고, 인터페이스/추상클래스에 의존하도록 해야 한다.

🌱 2. 스프링이 어떻게 도와줄까?

스프링은 다형성만으로 해결하지 못했던 2번(OCP), 5번(DIP)를
IoC(제어의 역전), DI(의존성 주입) 를 통해 만들어준다.

개발자는 "무엇을 사용할지"에만 집중하고,
"어떻게 생성하고 연결할지"는 스프링이 알아서 처리하게 하는 것이 스프링의 역할!


🌀 3. IoC와 DI

🔄 IoC (Inversion of Controller, 제어의 역전)

객체의 생성,관리를 개발자가 아니라 스프링 컨테이너가 하는 것을 말한다.
객체 간의 결합도를 낮춰서 유연한 코드를 구성하게 도와준다.

👀 일반적인 의존성에 대한 제어권은 아래와 같다.

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;
    }
}
  • 여기서 다른 누군가가 의존성을 주입해주는 것을 DI(Dependency Injection) 라 한다.
  • 그럼 누가 해주냐? 바로 스프링 컨테이너(=IoC 컨테이너)가 그걸 해준다!

💭 이전에 진행했던 키오스크 과제에서도
"필드를 어디에서 초기화할지"에 대해 고민한 적이 있었는데,
지금 설명하고 있는 제어의 역전(IoC) 개념과 같은 고민이었다.
👉 키오스크 과제 회고 보러가기


🌐 DI (Dependency Injection, 의존성 주입)

  • 의존성 주입은 IoC의 구현 방법 중에 하나이다.
  • 스프링 컨테이너가 객체(Bean)를 생성하고 필요한 의존성을 주입해주는 것이다.
  • 스프링은 다양한 방식으로 의존성 주입을 할 수 있다.
    • 생성자 주입 (권장)
    • 필드 주입
    • Setter 주입
  • 상세한 방법은 아래에 @Autowired 사용 부분을 참고하자.

❗️ 의존성 주입(DI)은 빈(Bean)끼리만 할 수 있다.

즉, 스프링 컨테이너 안에 있는 객체(Bean)들끼리만 의존성 주입(DI)이 가능하다!


❓ 제어의 역전(IoC)/의존성 주입(DI)... 그래서 뭐가 좋은데?

1. 결합도 감소
객체 간의 의존성을 외부로 분리해서, 변경에 유연하게 대응할 수 있게함
ex. OwnerRepository → MockOwnerRepository로 쉽게 교체

2. 테스트 용이성
의존성을 주입받으므로, 테스트할 때 가짜(mock) 객체로 교체가 쉬움

3. 유연성, 재사용성 증가
객체 간이 느슨하게 연결되므로 다양한 상황에 재사용 가능하다!

4. 관심사 분리
객체(OwnerController)는 "자신이 할 일" 에만 집중할 수 있다.
생성, 연결 같은 작업은 스프링 컨테이너가 맡는다.


🥡 4. 스프링 컨테이너 (=IoC 컨테이너)

  • 스프링 컨테이너는 빈(Bean)의 생성, 설정, 관리한다.
    또한 BeanFactory/ApplicationContext 인터페이스의 구현체를 통해 동작한다.
  • 스프링은 이 두가지 인터페이스 기반으로 IoC 컨테이너를 구성한다.
    • ✔️ BeanFactory
      가장 기본적인 IoC 컨테이너 인터페이스
    • ✔️ ApplicationContext
      BeanFactory를 상속받은 인터페이스로 더 많은 기능을 갖고 있고,
      실무에서 ApplicationContext를 더 많이 사용한다.

💭 멀티쓰레드 상황에서 싱글톤 패턴을 구현하는 것은 번거롭고 조심스러운 일인데,
이러한 일을 스프링 컨테이너가 빈(Bean)을 등록하여 쉽게 구현해준다.


🫛 5. 스프링 빈(Bean)

스프링 컨테이너가 관리하는 모든 객체(인스턴스)를 의미한다.

❓ 빈(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);
    }
}
  • 빈(Bean)인 객체는 ApplicationContext 안에 있는 객체를 뜻한다.
    스프링 컨테이너 안에 있지 않은 객체는 빈(Bean)이 아닌 객체이다.

❗️ 인텔리제이에서 빈(Bean)에 등록되었는지 쉽게 아는 방법!

클래스 옆에 농구공 모양🏀이 빈(Bean)에 등록되었다는 뜻이라고 한다. ㅎㅎ


💡 빈(Bean)을 등록할 수 있는 방법

1. @ComponentScan으로 빈(Bean)을 자동등록 해보자!

스프링은 특정 패키지 내에서 특정 애너테이션이 붙은 클래스를 자동으로 검색하고,
빈(Bean)으로 등록하는 기능을 제공한다.

👉 스프링이 인식하는 주요 애너테이션:
@Component, @Controller, @Service, @Repository, @Configuration 등

❓ @ComponentScan은 어디에 선언할까?

  • @SpringBootApplication 안에 @ComponentScan 이 존재한다.
    따라서 선언할 필요 없이, 루트(최상위) 패키지 기준으로 자동 스캔이 시작된다!

📌 @ComponentScan의 작동 방식

  1. @ComponentScan이 선언된 위치부터 하위 모든 패키지를 탐색한다.
  2. 특정 애너테이션을 찾는다.
  3. 찾은 클래스를 스프링 컨테이너에 빈(Bean)으로 등록한다.

2. @Configuration으로 빈(Bean)을 수동등록 해보자!

수동등록을 하기 위해서는 설정을 해야 한다.
다음은 @Configuration, @Bean 을 통해 수동등록 하는 방법이다.

<// ✅ SampleController 직접 빈(Bean)에 등록하는 설정 클래스
@Configuration
public class SampleConfig{
	
    @Bean
    public SampleController sampleController(){
    	return new SampleController();
    }
}

📌 중요 포인트

  • @Controller 어노테이션을 붙이지 않아도 SampleController는 빈 객체이다!
  • 항상 @Configuration과 함께 사용해야 Bean이 싱글톤으로 관리된다.
    CGLIB 라이브러리 와 연관이 있다.

❓ 언제 수동등록을 사용하게 될까?

  • 외부 라이브러리같은 우리가 직접 애너테이션을 붙일 수 없는 객체를 등록할 때
  • 설정을 명확하게 제어하고 싶을 때
  • 테스트를 위해 대체 구현체(mock 등)를 직접 지정하고 싶을 때

💡 @Autowired를 통해 필요한 빈(Bean)을 받아오자!

@Autowired를 사용하면, 필요한 빈(Bean)을
다음과 같은 방식으로 의존성 주입(DI)할 수 있다.

❗️ DI(의존성 주입) 방법

  • 생성자 주입 (권장)
  • 필드 주입
  • Setter 주입

❓ 왜 생성자로 의존성 주입 방법이 권장될까?

  • 불변성 보장
    주입받은 의존성을 final로 선언하여 객체가 변경되지 않게 보호할 수 있다.
  • 의존성 누락 방지
    의존성 주입이 제대로 되지 않을 때에 인스턴스를 만들지 못하게 강제할 수 있다.

1. 생성자에서 의존성 주입하기

@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 어노테이션 생략이 가능해졌다.


2. 필드에서 의존성 주입하기

@Controller
public class Controller {
	// ✅ 필드에서 의존성 주입하기
    @Autowired
	private Repository repo;
  	....
}
  • final 키워드를 사용할 수 없다!
    → Controller 인스턴스가 생성되고, 의존성 주입이 되기 때문이다.

3. Setter로 의존성 주입하기

@Controller
public class Controller {

	private Repository repo;
  	....
    
    // ✅ Setter로 Controller가 의존하고 있는 Repository 의존성 주입 (DI)
    @Autowired
    public void setRepository(Repository repo){
    	this.repo = repo;
    }
    
}
  • final 키워드를 사용할 수 없다!
    → Controller 인스턴스가 생성되고, Setter 메서드가 실행되기 때문이다.

✅ @RequiredArgsConstructor를 사용해서 더 쉽게 생성자로 의존성 주입을 해보자!

  • Lombok에서 제공하는 애너테이션이다.
  • final 필드를 모아 자동으로 생성자를 만들어준다.
  • 생성자가 하나뿐이라면, @Autowired를 생략해도 스프링이 자동으로 주입해준다.
  • 따라서, 생성자 주입을 더 간단하게 작성할 수 있다.
@RequiredArgsConstructor
@Controller
public class Controller {

	// ✅ 한줄로 끝!
    private final Repository repo;
}

6. ♻️ 싱글톤 패턴

클래스를 인스턴스화 할 때, 해당 인스턴스를 하나만 생성되도록 보장하는 패턴이다.

싱글톤 패턴 등장 배경

  • 많은 불특정 사용자(클라이언트)가 요청할 때마다, 중복되는 객체를 생성한다.
  • 메모리 낭비가 심했다.

싱글톤 패턴을 적용하면?

  • 중복되는 객체를 하나만 생성한다.
  • 메모리를 획기적으로 절약할 수 있다.
  • 동일한 상태를 유지해야 하는 객체에 적합하다.

👉 싱글톤 패턴 적용 예시 코드

public class Singleton {

    // ✅ static으로 하나의 인스턴스를 생성해놓음
    private static final Singleton instance = new Singleton();

    // ❌ 생성자를 private로 막음 (외부에서 생성 불가!!)
    private Singleton() {}

    // ✅ 외부에서는 이 메서드로만 싱글톤 패턴이 적용된 객체를 사용할 수 있다.
    public static Singleton getInstance() {
        return instance;
    }
}

❓ 싱글톤 패턴의 문제점?

  • 구현하기 위한 코드의 양이 많아진다.
  • 구현 클래스에 의존해야 한다.

❗️ 싱글톤 패턴의 주의점!

  • 싱글톤 패턴이 적용된 객체는 상태를 유지하면 안된다. ➡️ stateless
    데이터 불일치 및 동시성 문제 발생 가능성

🌼 스프링에서의 싱글톤

스프링에서는 모든 빈(Bean)을 싱글톤으로 관리한다.
따라서 무상태(stateless) 설계를 지향해야 한다.


출처

내일배움캠프 스프링 숙련 1주차
인프런 예제로 배우는 스프링 입문 (백기선)

profile
wannabe---ing

0개의 댓글