
DI (의존성 주입)
쉬운 예시로 ‘레고 조립하기’
스프링 X
public class 카페 { private 바리스타 바리스타 = new 신입바리스타(); // 고정됨! public void 커피만들기() { 바리스타.에스프레소추출(); } }만약 신입 바리스타가 퇴사시 → 코드를 직접 고쳐야하는 상황
스프링 O
@Component public class 카페 { private final 바리스타 바리스타; // 스프링이 알아서 바리스타를 찾아서 넣어줌 public 카페(바리스타 바리스타) { this.바리스타 = 바리스타; } public void 커피만들기() { 바리스타.에스프레소추출(); } } @Component class 신입바리스타 implements 바리스타 { } @Component class 경력바리스타 implements 바리스타 { }스프링이 직접 바리스타를 찾아줌
실제 코드에서의 예시
스프링 X
// ======================================== // 1. User 클래스 // ======================================== public class User { private String name; private String email; private String password; public User(String name, String email, String password) { this.name = name; this.email = email; this.password = password; } } // ======================================== // 2. UserRepository (DB 저장) // ======================================== public class UserRepository { public void save(User user) { System.out.println("MySQL DB에 저장: " + user.getName()); // 실제로는 JDBC 코드 수십 줄... } } // ======================================== // 3. EmailService (이메일 발송) // ======================================== public class EmailService { public void sendWelcome(User user) { System.out.println("환영 이메일 발송: " + user.getEmail()); // 실제로는 SMTP 코드 수십 줄... } } // ======================================== // 4. UserService (회원가입 로직) ⭐️ // ======================================== public class UserService { // 💥 문제: 직접 생성 → 완전히 고정됨! -> 핵심 문제! private UserRepository repository = new UserRepository(); private EmailService emailService = new EmailService(); public void register(String name, String email, String password) { User user = new User(name, email, password); repository.save(user); emailService.sendWelcome(user); } } // ======================================== // 5. 실제 사용 // ======================================== public class Main { public static void main(String[] args) { UserService userService = new UserService(); userService.register("김철수", "chulsoo@email.com", "1234"); } } // 출력: // MySQL DB에 저장: 김철수 // 환영 이메일 발송: chulsoo@email.com주요 문제점
UserService에서 직접 객체를 생성하여 고정시켜 놓았으므로
repository 나 emailService의 결합도가 극도로 높음
다른 환경의 repository, emailService가 필요하면
별도의 객체를 생성해서 추가 생성자를 만드는 등의 행위가 필요테스트 또한 힘듬, 해당 파트의 코드를 다 고쳐야하는 상황
스프링 O
// ======================================== // 1. User 클래스 (동일) // ======================================== public class User { private String name; private String email; private String password; public User(String name, String email, String password) { this.name = name; this.email = email; this.password = password; } // getter/setter... } // ======================================== // 2. 인터페이스 정의 (규격) // ======================================== interface UserRepository { void save(User user); } interface NotificationService { void sendWelcome(User user); } // ======================================== // 3. Repository 구현체들 // ======================================== @Repository @Primary // ⭐️ 기본으로 사용할 구현체! class MySqlUserRepository implements UserRepository { @Override public void save(User user) { System.out.println("[MySQL] DB에 저장: " + user.getName()); } } @Repository class PostgresUserRepository implements UserRepository { @Override public void save(User user) { System.out.println("[PostgreSQL] DB에 저장: " + user.getName()); } } // ======================================== // 4. Notification 구현체들 // ======================================== @Service @Primary // ⭐️ 기본으로 사용할 구현체! class EmailNotificationService implements NotificationService { @Override public void sendWelcome(User user) { System.out.println("[이메일] 발송: " + user.getEmail()); } } @Service class SmsNotificationService implements NotificationService { @Override public void sendWelcome(User user) { System.out.println("[SMS] 발송: " + user.getPhone()); } } // ======================================== // 5. UserService (회원가입 로직) ⭐️ // ======================================== @Service public class UserService { private final UserRepository repository; private final NotificationService notificationService; // ✨ 생성자: 스프링이 자동으로 주입! public UserService(UserRepository repository, NotificationService notificationService) { this.repository = repository; this.notificationService = notificationService; // 💡 어떤 구현체가 들어올지 몰라도 됨! // 💡 그냥 규격(인터페이스)만 알면 됨! } public void register(String name, String email, String password) { User user = new User(name, email, password); repository.save(user); // 어떤 DB인지 몰라도 됨! notificationService.sendWelcome(user); // 이메일인지 SMS인지 몰라도 됨! } } // ======================================== // 6. 실제 사용 // ======================================== @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); // 스프링 내부 동작: // 1. MySqlUserRepository 객체 생성 (@Primary) // 2. PostgresUserRepository 객체 생성 // 3. EmailNotificationService 객체 생성 (@Primary) // 4. SmsNotificationService 객체 생성 // 5. UserService 생성 시: // → MySqlUserRepository 주입 // → EmailNotificationService 주입 } } @RestController public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; // 스프링이 주입! } @PostMapping("/register") public String register(@RequestBody RegisterRequest request) { userService.register( request.getName(), request.getEmail(), request.getPassword() ); return "회원가입 성공!"; } } // 출력: // [MySQL] DB에 저장: 김철수 // [이메일] 발송: chulsoo@email.com해결된 문제점
결합도가 극도로 높음
다른 환경의 repository, emailService가 필요하면
별도의 객체를 생성해서 추가 생성자를 만드는 등의 행위→ 아래와 같이 유연함을 지님
1. UserRepository, NotificationService를 Interface화
→ 규격화
2. 규격을 받아 내어 필요한 Repository를 선언,
MySqlUserRepository, PostgreUserRepository 로 구현함
(NotificationService는 Email, SmsNotificationService로)
3. UserService에서 생성자는 스프링이 컴파일할 때
생성자를 자동으로 주입해줌, 즉 코드 전체가 아닌 명시만 고치면 됨
(@Primary or @Profile 등 코드에 명시된 대로)
최종적으로 의존성 주입을 통해 아래와 같은 강점을 지님
객체의 생성을 직접 선언할 필요가 없어짐
의존성 관리를 자동으로 해주고 있어, 핵심 코드 변경이 필요가 없음
(명시만 바꿔주는 것으로 해결, 결합도 하락, 유지보수 용이)테스트 환경의 경우, 따로 Profile을 선언하여 이 Profile의 경우 이런 의존성을 사용한다고 명시 가능 (Annotation을 통해, if의 무한 반복 없어짐)
한마디
객체가 필요한 의존성을 직접 생성하는게 아닌 외부에서 주입받는 것
규격화된 의존성을 외부에서 받아 new 키워드를 통해 고정되지 않으므로
이를 생성자 파라미터로 받아 결합도를 낮추는 스프링의 특징
여기서 객체 생성을 스프링이 해준다는 것 (IoC Container)
은 IoC (제어의 역전) 라고 함