Spring 입문 (IoC, DI, Bean)

KimGwangmin·2026년 9월 8일

IoC (Inversion Of Control)

제어의 역전

  • 프로그램의 제어 흐름을 개발자가 아닌 외부 프레임워크나 컨테이너가 대신 관리
  • 의존성 감소 (코드의 결합도 감소)
    • 재사용성 좋아짐
    • 유지보수 용이
    • 코드양 감소

의존성이란?
클래스 A가 클래스 B를 사용하고 있다면, A는 B에 의존적이다.

DI (Dependency Injection)

의존성 주입

  • IoC를 구현하기 위한 구체적인 방법
  • 개발자가 스프링이 따로 객체를 생성해서 관리, 필요할 때 그 객체를 가져와서 사용
    • 객체(의존성)를 가져와서 사용(주입)

Bean

  • 스프링 IoC 컨테이너가 관리하는 객체
  • 싱글톤 객체, 애플리케이션 전역에서 사용 가능

이전에 실습했던 Service, Controller 코드를 보자.

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
}

@RestController
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;
}

userRepository나 userService 객체를 직접 만들지 않았다. 그러나 IoC 컨테이너에서 이들을 Bean으로 관리하기에, 각 객체들을 DI 받아 사용할 수 있었던 것!

무엇을 Bean으로 쓰고, 무엇을 new로 만들지?

  • 재사용되거나 교체 가능한 비즈니스 로직/인프라는 Bean으로 관리한다.
    • Service, Controller, Repository, ...
  • 데이터 객체(매번 새로운 상태를 가짐)는 개발자가 직접 관리한다.
    • User, OrderRequest, ...
  • @Component가 붙은 클래스는 Bean으로 등록된다.
    • @ComponentScan이 선언된 패키지를 기준으로 하위 패키지들을 탐색한다.
  • @Component를 직접 붙일 수도 있지만, 다른 어노테이션에 이미 합쳐진 경우가 있다.

어노테이션 합성

  • 여러 어노테이션을 합쳐 새로운 어노테이션을 만들 수 있다.
  • @RestController는 @Controller와 @ResponseBody의 합성이었다.

@Component를 가지는 어노테이션들

  • @Component: 개발자가 직접 Bean으로 등록하려는 경우(클래스 레벨)
  • @Controller(@RestController)
  • @Service
  • @Repository

@ComponentScan 역시 이미 사용 중이었다!

  • @SpringBootApplication에 @ComponentScan이 포함된다.

Bean 수동 등록

  • 클래스에 @Configuration을 붙여준다.
  • 해당 클래스 내부에서, 메소드 레벨 어노테이션 @Bean을 붙여준다.
  • @Bean이 붙은 메서드의 반환 객체가 Bean으로 등록된다.

보통은 자동 등록을 훨씬 많이 사용한다.
주로 소스 코드를 직접 수정할 수 없는 외부 라이브러리 클래스를 Bean으로 등록할 때 수동 등록을 사용한다. (외부 클래스에 @Component를 붙일 수 없으니)

DI 방식

@Autowired 어노테이션을 붙여 사용한다. 크게 다음의 세 가지 방법이 있다.

  1. 필드 주입
  2. 세터 주입
  3. 생성자 주입

이 중 생성자 주입만 사용한다. 이전까지 실습했던 방법과 같다.

@Service
public class MemberService {
    private final MemberRepository memberRepository;
    
    @Autowired  // 생성자가 1개만 있으면 @Autowired 생략 가능
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    // Lombok의 @RequiredArgsConstructor를 사용할 수도 있다.
}

필드 주입과 세터 주입을 쓰면 필드를 final로 선언할 수 없다. 도중에 객체가 바뀔 수 있어 안전하지 않다. (불변성 보장이 깨진다)

안전하지 않은 코드

@Service
public class MemberService {
    private MemberRepository memberRepository;
    
    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    
    public void hello() {
		    // memberRepository에 할당된 Bean 대신 null을 할당
		    memberRepository = null;
    }
}

Bean 우선순위

@Component
public class KakaoPayService implements PayService { ... }

@Component
public class NaverPayService implements PayService { ... }

@Service
@RequiredArgsConstructor
public class OrderService {
		// 카카오페이가 주입될까, 아니면 네이버페이가 주입될까?
		private final PayService payService;
		// ...
}

위의 경우 에러가 발생한다. 다음과 같이 우선순위를 지정해야 한다.

1. @Primary

  • 우선 선택할 클래스를 지정
@Component
public class KakaoPayService implements PayService { ... }

@Primary // PayService 타입의 빈이 여러 개일 때, 우선권을 갖는다!
@Component
public class NaverPayService implements PayService { ... }

@RestCotroller
@RequiredArgsConstructor
public class PayController {

    private final PayService payService; // NaverPayService가 자동으로 주입됨

		// ...
}

2. @Qualifier

  • 구체적으로 선택
@Component("kakaoPay") // "kakaoPay"라는 별명을 붙여줌
public class KakaoPayService implements PayService { ... }

@Component("naverPay")
public class NaverPayService implements PayService { ... }

@Service
public class OrderService {
    private final PayService payService;

    // "kakaoPay"라는 이름을 가진 빈을 주입해줘!
    // 이 경우 생성자를 직접 작성해야 하니, @RequiredArgsConstructor 사용은 못함
    public OrderService(@Qualifier("kakaoPay") PayService payService) {
        this.payService = payService;
    }
}

Bean Validation

  • 검증 로직을 어노테이션으로 표현
  • 검증 규칙을 분리해둠으로써 가독성, 재사용성, 일관성 확보

build.gradle에 다음 의존성을 추가해야 한다.

implementation 'org.springframework.boot:spring-boot-starter-validation'

주로 쓰는 Bean Validation

어노테이션의미
@NotBlank공백이 아닌 문자 1개 이상 (null, "", " " 모두 거부)
@Email@가 포함된 이메일 형식
@Size(min=a, max=b)문자열 길이, 컬렉션 크기 제한
@Min(a)/@Max(b)숫자 최소/최대값
@Pattern(regexp=str)정규표현식 패턴 (전화번호, 주민번호 형식 등)

message를 사용하여 커스텀 에러 메시지 설정 가능

// 직접 작성
@NotBlank(message = "이름을 입력해주세요!")
private String name;
// 다른 옵션값 포함 가능
@Size(min = 8, message = "비밀번호는 {min}자 이상이어야 합니다")
private String password;

DTO에는 Validation 규칙을, Controller에는 @Valid를 붙여주면 자동으로 검증한다.

DTOController
업로드중..업로드중..

이메일 형식을 틀리게 해서 요청을 보내니, 검증에 통과하지 못해 실패했다.
업로드중..

0개의 댓글