CS/프레임워크

스프링 빈(Spring Bean)

glorypang 2025. 9. 8. 18:14
728x90
반응형
SMALL

Bean이라는 이름의 유래

스프링이 등장하면서 자바빈의 개념을 확장해
스프링 프레임워크 내부에서 관리되는 객체들을 스프링 빈이라고 부르기 시작했습니다.


자바빈에서 영감을 받아 Bean이라는 단어를 사용한 이유는,
단순하고 가벼운 객체라는 뜻을 함축하기 위해서였습니다.

 

즉, 자바빈처럼 스프링 빈도 재사용 가능한 작은 단위의 객체들이며,

스프링 컨테이너가 이를 관리해준다는 개념입니다.

스프링 빈이란?

스프링 빈은 스프링 컨테이너에서 관리되는 객체입니다.

쉽게 말해, 우리가 만든 객체들을 스프링이라는 "특별한 상자(컨테이너)" 안에 넣어두고,

스프링이 그 객체들의 생성부터 소멸까지 모든 것을 관리해준다고 생각하면 됩니다.

“직접 관리” vs “컨테이너 관리”

기존 방식 (개발자가 직접 관리):

  • 개발자가 `new`로 생성하고, 누가 언제 쓰는지, 언제 회수할지 고민해야 함
public class OrderController {
    public void processOrder() {
        // 개발자가 직접 객체 생성
        UserService userService = new UserService();
        PaymentService paymentService = new PaymentService();
        
        // 언제 이 객체들을 삭제해야 하지?
        // 메모리 관리는 어떻게 하지?
        // 다른 곳에서도 같은 객체가 필요하면 또 만들어야 하나?
    }
}

스프링 빈 방식 (스프링이 관리):

  • “필요한 역할(타입/이름)만 알려줘” → 컨테이너가 생성·보관·주입을 전담
@Controller  // 이 클래스를 스프링 빈으로 등록
public class OrderController {
    
    @Autowired  // 스프링이 자동으로 주입해줌
    private UserService userService;
    
    @Autowired
    private PaymentService paymentService;
    
    public void processOrder() {
        // 그냥 사용하면 됨! 스프링이 다 준비해뒀음
        userService.findUser();
        paymentService.processPayment();
    }
}

@Service  // UserService를 스프링 빈으로 등록
public class UserService {
    public void findUser() {
        System.out.println("사용자 조회");
    }
}

@Service  // PaymentService를 스프링 빈으로 등록
public class PaymentService {
    public void processPayment() {
        System.out.println("결제 처리");
    }
}

스프링 빈의 3가지 핵심 역할

1. 객체 생성 및 관리

  • 직접 `new` 할 필요 X
  • 애플리케이션 시작 시 필요한 빈을 만들고 보관
// 개발자가 할 일: 어노테이션만 붙이면 끝
@Service
public class UserService {
    // 비즈니스 로직에만 집중
}

// 스프링이 하는 일:
// 1. UserService 객체 생성
// 2. 메모리에 저장
// 3. 애플리케이션 종료 시 정리

2. 의존성 관리 (DI의 실제 구현)

  • “누구에게 무엇을 꽂아줄지”를 규칙/설정에 따라 자동으로 결정
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;  // 스프링이 자동 연결
    
    public User findUser(Long id) {
        return userRepository.findById(id);  // 그냥 사용하면 됨
    }
}

@Repository
public class UserRepository {
    public User findById(Long id) {
        // DB에서 사용자 조회
        return new User();
    }
}

앞서 DI 예제에서 본 것처럼,

`UserService`는 `UserRepository`의 구체적인 구현을 모릅니다.

스프링이 알아서 연결해주기 때문입니다.

3. 객체 생애 주기 관리

  • 초기화(`@PostConstruct`)와 종료(`@PreDestroy`) 훅을 통해 자원 정리를 도와줌
@Service
public class EmailService {
    
    @PostConstruct  // 빈 생성 후 실행
    public void init() {
        System.out.println("이메일 서비스 초기화 완료");
    }
    
    @PreDestroy  // 빈 소멸 전 실행
    public void cleanup() {
        System.out.println("이메일 서비스 정리 작업");
    }
}

스프링 컨테이너의 빈 관리 방식

싱글톤(Sigleton) 스코프 - 기본값

  • 애플리케이션 당 1개. 대부분의 서비스/리포지토리는 여기에 해당 → 메모리 효율↑
@Service
public class UserService {
    // 애플리케이션 전체에서 딱 하나만 생성됨
}

// 어디서 주입받아도 같은 객체
@Controller
public class UserController {
    @Autowired
    private UserService userService;  // 같은 객체
}

@Controller
public class AdminController {
    @Autowired
    private UserService userService;  // 위와 동일한 객체
}

프로토타입(Prototype) 스코프

  • 요청할 때마다 새 인스턴스. 상태가 서로 달라야 하는 단기 객체에 유용
@Service
@Scope("prototype")  // 요청할 때마다 새로운 객체 생성
public class ReportGenerator {
    private String reportData;  // 각각 다른 데이터를 가질 수 있음
}

스프링 빈 등록하는 3가지 방법

1. 어노테이션 기반 (가장 많이 사용)

@Component  // 일반적인 빈
@Service    // 비즈니스 로직 담당 빈
@Repository // 데이터 접근 담당 빈
@Controller // 웹 요청 처리 담당 빈

2. Java 설정 클래스 사용

@Configuration
public class AppConfig {
    
    @Bean
    public UserService userService() {
        return new UserService();  // 직접 생성해서 반환
    }
    
    @Bean
    public PaymentService paymentService() {
        return new PaymentService();
    }
}

3. XML설정( 거의 사용 안 함)

<bean id="userService" class="com.example.UserService"/>

스프링 빈의 실제 동작 과정

// 1. 애플리케이션 시작
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        // 이 시점에 스프링이 모든 빈을 생성하고 의존성을 주입함
    }
}

// 2. 스프링이 하는 일 (개발자가 직접 하지 않음)
// ApplicationContext context = new ApplicationContext();
// UserRepository userRepository = new UserRepository();  
// UserService userService = new UserService(userRepository);  // 의존성 주입
// OrderController orderController = new OrderController(userService);

// 3. 개발자는 그냥 사용
@RestController
public class OrderController {
    @Autowired
    private UserService userService;  // 스프링이 준비한 객체가 자동 주입됨
    
    @GetMapping("/order")
    public String order() {
        userService.processOrder();  // 바로 사용 가능
        return "주문 완료";
    }
}

왜 스프링 빈을 사용해야 할까?

1. 코드 의존성 감소

  • 구현 교체가 설정 차원에서 가능 → 결합도
// Before: 강한 결합
public class OrderService {
    private KakaoPayService paymentService = new KakaoPayService();  // 카카오페이에 고정
}

// After: 느슨한 결합 (스프링 빈 + DI)
@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;  // 인터페이스만 의존
    
    // 설정만 바꾸면 토스페이로도 변경 가능!
}

2. 테스트 용이성

  • 테스트 용이: Mock/Stubs 주입으로 빠른 단위 테스트
@Test
public class OrderServiceTest {
    @Mock
    private PaymentService mockPaymentService;  // 가짜 객체 주입
    
    @InjectMocks
    private OrderService orderService;
    
    @Test
    public void 주문_테스트() {
        // 실제 결제 없이 테스트 가능
    }
}

3. 메모리 효율성

  • 싱글톤으로 객체 하나만 생성하여 메모리 절약
  • 스프링이 생명주기를 관리하여 메모리 누수 방지

결론

스프링 빈 = 컨테이너가 생성·연결·수명까지 책임지는 표준 부품.
구현 교체와 테스트를 쉽게 만들어 비즈니스 로직에 집중하게 한다.

  • 객체 생명주기 관리가 왜 중요한가?
    • 자원 낭비 방지: 불필요하게 많은 인스턴스를 만들면 메모리 사용량 증가와 성능 저하가 발생합니다.
    • 일관성 유지: 한 번 만든 객체를 재사용하면 상태나 설정이 통일되어 버그가 줄어듭니다.
    • 자원 정리: DB 커넥션, 스레드풀 같은 외부 자원은 종료 시 명확하게 해제해야 누수와 장애를 막습니다.
    • 복잡도 감소: 생성·소멸 로직을 코드 곳곳에 흩뿌리지 않고, 컨테이너가 한 곳에서 관리합니다.
728x90
반응형
LIST