1. 필드 주입 (Field Injection) - ❌ “옛날 방식”

변수에 @Autowired를 바로 붙이는 방식입니다. 가장 코드가 짧아서 예전에는 많이 썼지만, 지금은 IntelliJ조차 “쓰지 마세요(Not recommended)“라고 경고를 띄웁니다.

Java

@Service
public class UserService {
    @Autowired // 비추천!
    private UserRepository userRepository;
}
  • 단점 1 (테스트 불가): 순수한 자바 코드로 테스트할 때 userRepository에 가짜 객체(Mock)를 넣어줄 방법이 없습니다. (Spring 없이 테스트 불가)

  • 단점 2 (불변성 X): final 키워드를 붙일 수 없습니다. 즉, 실수로 도중에 userRepository가 바뀔 수도 있습니다.


2. 수정자 주입 (Setter Injection) - ⚠️ “거의 안 씀”

Setter 메서드를 열어두고 거기에 @Autowired를 붙이는 방식입니다.

Java

@Service
public class UserService {
    private UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
  • 단점: public으로 열려있어서 누군가 userService.setUserRepository(null) 같이 이상한 짓을 할 수 있습니다.

  • 용도: 진짜 가끔 **“선택적인 의존성”**이 필요할 때만 씁니다. (거의 볼 일 없음)


3. 생성자 주입 (Constructor Injection) - 👑 “현재 표준”

생성자를 통해서 의존성을 넣는 방식입니다. **스프링 4.3 버전부터는 “생성자가 딱 하나만 있으면 @Autowired를 생략해도 자동 주입”**되도록 바뀌었습니다.

Java

@Service
public class UserService {
    // 1. final을 붙일 수 있음 (불변성 보장!)
    private final UserRepository userRepository;

    // 2. 생성자로 주입받음
    // @Autowired (생략 가능!)
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

🌟 왜 이게 최고인가요?

  1. 불변(Immutable): final을 붙여서, 한 번 주입되면 앱이 꺼질 때까지 절대 안 바뀝니다. (안전함)

  2. 누락 방지: new UserService() 처럼 생성할 때 파라미터를 안 넣으면 컴파일 에러가 납니다. “야, Repository 없이 Service 못 만들어!”라고 알려주는 거죠.

  3. 테스트 용이: new UserService(new FakeRepository()) 처럼 테스트 코드 짤 때 내가 원하는 가짜를 쏙 넣어줄 수 있습니다.


🚀 실무 최종 치트키: 롬복(Lombok) 합체

매번 생성자 코드를 타이핑하기 귀찮잖아요? 그래서 롬복의 **@RequiredArgsConstructor**를 씁니다. 이게 바로 김영한 님 스타일이자 실무 국룰입니다.

Java

@Service
@RequiredArgsConstructor // ★ final이 붙은 필드만 모아서 생성자를 자동으로 만들어줌!
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    // (눈에 안 보이지만 아래 생성자가 자동으로 만들어져 있음)
    // public UserService(UserRepository u, PasswordEncoder p) {
    //     this.userRepository = u;
    //     this.passwordEncoder = p;
    // }
}

+++

아, 충분히 헷갈릴 수 있습니다! 😵 방금까지는 UserService 위에 @Service라는 스티커를 딱 붙여서 “스프링아, 이거 내 거니까 가져가서 관리해!” (자동 등록)라고 했었죠.

그런데 갑자기 @Bean을 써서 메서드를 만드는 방식이 튀어나왔으니까요. 이건 **“남의 물건을 등록하는 방식(수동 등록)“**입니다.

차이점을 아주 쉽게 비유로 설명해 드릴게요.


1. 상황: 왜 방식이 다를까? 🤷‍♂️

스프링 컨테이너(가방)에 객체를 담는 방법은 딱 2가지입니다.

① 내 코드는: 스티커 붙이기 (@Component, @Service)

  • 상황: 내가 직접 만든 파일(UserService.java)임.

  • 방법: 클래스 위에 @Service만 붙이면 스프링이 알아서 가져감.

  • 비유: 내 옷에 내 이름을 써서 사물함에 넣음.

② 남의 코드는: 직접 사오기 (@Bean)

  • 상황: BCryptPasswordEncoder는 내가 만든 게 아님. 스프링 시큐리티 라이브러리(남이 만든 파일) 안에 들어있음.

  • 문제: 남이 만든 코드라 내가 그 파일 열어서 @Component를 붙일 수가 없음! (읽기 전용임)

  • 해결: “설정 파일(Config)에서 new로 생성한 다음, 결과물을 스프링한테 넘겨줌.”


2. 코드 해석 돋보기 🔍

Java

@Configuration // 1. 스프링아, 여긴 설정(Config) 파일이야.
public class SecurityConfig {

    @Bean // 2. 이 메서드가 반환(return)하는 객체를 가져가서 관리해!
    public PasswordEncoder passwordEncoder() {
        // 3. 라이브러리 객체를 내가 직접 생성(new)해서 바침
        return new BCryptPasswordEncoder();
    }
}
  1. 스프링이 켜질 때 SecurityConfig를 읽습니다.

  2. @Bean이 붙은 passwordEncoder() 메서드를 실행합니다.

  3. new BCryptPasswordEncoder()가 실행돼서 객체가 생성됩니다.

  4. 스프링이 이 객체를 낚아채서 자기 가방(Container)에 넣습니다.

👉 결과: 이제 UserService 입장에서는 이게 내가 만든 건지, 남이 만든 건지 알 바 아니고, “어쨌든 스프링 가방 안에 있네?” 하고 private final PasswordEncoder로 꺼내 쓸 수 있게 되는 겁니다.


🚀 3줄 요약

  1. @Service, @Controller: 내 코드를 등록할 때 쓴다. (클래스 위에 붙임)

  2. @Bean: **남의 코드(라이브러리)**를 등록할 때 쓴다. (설정 클래스의 메서드 위에 붙임)

  3. 쓰는 사람(UserService) 입장: 등록된 방법이 뭐든 상관없이 똑같이 주입(DI)받아 쓴다.

+++

1. “도대체 조립이 어떻게 가능한 건데?” (DI의 비밀) 🏭

사용자님이 작성하신 코드는 사실 **“설계도(Blueprint)“**일 뿐입니다. 실제 조립은 **스프링(공장장)**이 프로그램 시작할 때 딱 한 번 수행합니다.

1단계: 부품 미리 만들어두기 (Bean 등록)

프로그램이 시작(Run)되면 스프링 공장장이 제일 먼저 하는 일입니다.

  • JwtUtil (@Component): “어? 부품 스티커 붙어있네? 하나 만들어서 선반에 올려놔.”

  • PasswordEncoder (@Bean): “설정 파일에 있네? new BCrypt... 해서 선반에 올려놔.”

  • UserRepository (@Repository): “이것도 만들어서 선반에 올려.”

👉 결과: 스프링 창고(Container)에 이 3가지 **실체(Instance)**가 둥둥 떠다니고 있습니다.

2단계: 조립 요청서 확인 (UserServiceImpl)

이제 UserServiceImpl을 만들 차례입니다. 롬복의 @RequiredArgsConstructor눈에 안 보이는 생성자를 만들어 놨습니다.

Java

// 롬복이 몰래 만든 코드
public UserServiceImpl(UserRepository repo, PasswordEncoder encoder, JwtUtil jwt) {
    this.repo = repo;
    this.encoder = encoder;
    this.jwt = jwt;
}

이 생성자는 스프링에게 이렇게 외치는 겁니다. 📢 “나를 만들고 싶으면, RepoEncoderJwt를 가져와! 안 그러면 난 못 만들어져!“

3단계: 자동 주입 (Auto-wiring)

스프링 공장장이 이 외침을 듣습니다.

  1. “어? UserServiceImpl 만들어야 하네?”

  2. “생성자를 보니까 3개가 필요하네?”

  3. “아까 1단계에서 만들어서 선반에 올려둔 거 있지? 그거 3개 꺼내서 여기 꽂아!”

👉 결론: @Autowired를 안 적어도, 스프링은 “생성자가 딱 하나 있으면, 아 이거 필요한 거구나?” 하고 알아서 창고에서 꺼내와서 조립해 줍니다. 이게 Spring 4.3 버전부터 생긴 스마트한 기능입니다.