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;
}
}
🌟 왜 이게 최고인가요?
-
불변(Immutable):
final을 붙여서, 한 번 주입되면 앱이 꺼질 때까지 절대 안 바뀝니다. (안전함) -
누락 방지:
new UserService()처럼 생성할 때 파라미터를 안 넣으면 컴파일 에러가 납니다. “야, Repository 없이 Service 못 만들어!”라고 알려주는 거죠. -
테스트 용이:
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();
}
}
-
스프링이 켜질 때
SecurityConfig를 읽습니다. -
@Bean이 붙은passwordEncoder()메서드를 실행합니다. -
new BCryptPasswordEncoder()가 실행돼서 객체가 생성됩니다. -
스프링이 이 객체를 낚아채서 자기 가방(Container)에 넣습니다.
👉 결과: 이제 UserService 입장에서는 이게 내가 만든 건지, 남이 만든 건지 알 바 아니고, “어쨌든 스프링 가방 안에 있네?” 하고 private final PasswordEncoder로 꺼내 쓸 수 있게 되는 겁니다.
🚀 3줄 요약
-
@Service,@Controller: 내 코드를 등록할 때 쓴다. (클래스 위에 붙임) -
@Bean: **남의 코드(라이브러리)**를 등록할 때 쓴다. (설정 클래스의 메서드 위에 붙임) -
쓰는 사람(
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;
}
이 생성자는 스프링에게 이렇게 외치는 겁니다. 📢 “나를 만들고 싶으면, Repo랑 Encoder랑 Jwt를 가져와! 안 그러면 난 못 만들어져!“
3단계: 자동 주입 (Auto-wiring)
스프링 공장장이 이 외침을 듣습니다.
-
“어?
UserServiceImpl만들어야 하네?” -
“생성자를 보니까 3개가 필요하네?”
-
“아까 1단계에서 만들어서 선반에 올려둔 거 있지? 그거 3개 꺼내서 여기 꽂아!”
👉 결론: @Autowired를 안 적어도, 스프링은 “생성자가 딱 하나 있으면, 아 이거 필요한 거구나?” 하고 알아서 창고에서 꺼내와서 조립해 줍니다. 이게 Spring 4.3 버전부터 생긴 스마트한 기능입니다.