
스프링을 이해하는데 필수적인 개념인 빈 (Bean)과 컨테이너 (Container)에 대해서 알아보자.
빈 (Bean)은 스프링 프레임워크에서 관리하는 객체를 말한다. 쉽게 말해, 애플리케이션에서 사용되는 다양한 객체 (예: 서비스 (Service), 리포지토리 (Repository), 컨트롤러 (Controller) 등의 인스턴스)를 스프링이 생성하고 관리 (생명주기 포함)하는 것을 빈이라고 한다.
스프링이 객체를 생성하고, 필요할 때마다 제공하며, 객체의 생명주기를 관리한다. 생명주기에 대해서는 해당 글에서 설명한다.
생성 및 관리를 스프링이 대신 해주기에 원래는 객체를 개발자가 스스로 만들어서 필요한 의존 객체도 직접 생성하고 연결해야 하는데, 그럴 필요가 없어졌다.
그래서 객체간 의존관계가 명확하게 선언되어 있고, 필요하면 다른 구현체로 쉽게 갈아낄 수도 있다.
다른 객체와의 관계 (의존성)를 자동으로 설정해준다. 이로인해 코드가 깔끔해지고, 유지보수가 편하다.
XML, 어노테이션 (@), 자바 코드 등 다양한 방법으로 빈을 설정할 수 있으며 주로 어노테이션을 사용한다.
빈을 처음배우면 스프링이 빈 (Bean)을 어떻게 다루는지 잘 와닿지 않을 수 있다. 다음 예제들을 통해서 이해해보자
예시 프로젝트의 구조는 다음과 같다.
(예시에서 사용되는 어노테이션은 해당 글에서 살펴보기로 하고 일단 그러려니 하고 넘어가자.)
✍️ 작성
src
└─ main
└─ java
└─ com.example.demo
├─ DemoApplication.java (메인 클래스)
├─ repository
│ └─ UserRepository.java
├─ service
│ └─ UserService.java
└─ controller
└─ UserController.java
프로젝트의 구조는 다음과 같다. 이제 다음 각 자바 클래스들을 살펴보자.
✍️ 작성
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 여기가 @ComponentScan, @EnableAutoConfiguration 등을 포함함
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
해당 부분은 메인 클래스 부분인데 클래스에 @SpringBootApplication이 붙으면, 이 패키지 이하(com.example.demo.*)를 쭉 뒤져서
@Component, @Service, @Repository, @Controller 가 붙은 클래스를 찾아 전부 스프링 빈 (Bean)으로 등록한다.
✍️ 작성
package com.example.demo.repository;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public String findUsernameById(Long id) {
// DB에서 유저 정보 조회하는 코드라고 가정
return "SpringUser";
}
}
@Repository가 붙었으니, 스프링이 이 클래스를 빈으로 등록한다.
내부적으로 new UserRepository()를 스프링이 호출해 해당 클래스의 인스턴스 (객체)를 생성하고 컨테이너에 담아둔다.
✍️ 작성
package com.example.demo.service;
import org.springframework.stereotype.Service;
import com.example.demo.repository.UserRepository;
@Service
public class UserService {
private final UserRepository userRepository;
// 생성자 주입 (DI)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public String getUsername(Long id) {
return userRepository.findUsernameById(id);
}
}
@Service가 붙었으니 이 클래스도 스프링이 빈으로 관리한다.
이를 위해 스프링은 UserService를 만들기 위해 생성자를 호출해야 하는데
그 생성자 파라미터가 UserRepository userRepository이다.
그러면 스프링은 <어? 나 UserRepository 빈을 이미 가지고 있네!”> 하고,
그 객체를 가져와서 new UserService(스프링이_관리하는_UserRepository_객체) 로 UserService 인스턴스를 생성한다.
즉, 개발자 입장에서는 <UserService 생성자에 UserRepository가 필요하다!>는 사실만 선언해두면, 스프링이 의존 객체(UserRepository)를 자동으로 주입해주는 셈이다.
✍️ 작성
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.service.UserService;
@RestController
public class UserController {
private final UserService userService;
// 생성자 주입
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/username")
public String getUsername() {
return userService.getUsername(1L);
}
}
마찬가지로 @RestController가 붙어서 빈이 되고, 스프링은 이 컨트롤러 객체를 만들기 위해
new UserController(스프링이_관리하는_UserService_객체) 를 호출하여 UserController 객체를 생성한다.
여기서 UserService도 이미 스프링이 빈으로 가지고 있으니, 그냥 그걸 넘겨주면 되는 것이다.
스프링 컨테이너 (Container)는 스프링 애플리케이션의 중심으로, 스프링 빈을 생성, 관리, 조립하는 역할을 한다. 쉽게 말해, 스프링 컨테이너는 애플리케이션의 객체들을 담고 있는 큰 저장소라고 할 수 있다.
컨테이너의 기능은 다음과 같다.
| 기능 | 설명 |
|---|---|
| 빈 생성 및 초기화 | 애플리케이션 시작 시 필요한 빈들을 생성하고 초기화 |
| 의존성 관리 | 빈 (Bean) 간의 의존성을 설정하고 관리 |
| 생명주기 관리 | 빈의 생성부터 소멸까지의 생명주기를 관리 |
| 설정 관리 | 애플리케이션 설정 정보를 바탕으로 빈을 설정 |
| 종류 | 설명 |
|---|---|
| BeanFactory | 가장 기본적인 컨테이너로, 필요할 때마다 빈을 생성 |
| ApplicationContext | BeanFactory의 기능을 확장한 컨테이너로 모든 스프링 빈(객체)들을 관리 다양한 부가 기능(메시지 소스, 이벤트 전달 등)을 제공 일반적으로 스프링 애플리케이션에서는 ApplicationContext를 많이 사용 내부적으로 AnnotationConfigApplicationContext나 WebApplicationContext 같은 구현체를 사용 그러나 개발자는 굳이 이러한 구현체들을 신경쓸 필요는 없고 ApplicationContext를 통해 사용하면 됨. |
✍️ 작성
package com.example.book;
public class Book {
private String title;
private int price;
public Book(String title, int price) {
this.title = title;
this.price = price;
}
public String getTitle() {
return title;
}
public int getPrice() {
return price;
}
}
✍️ 작성
package com.example.book;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BookConfig {
@Bean
public Book book() {
return new Book("Java", 10000);
}
}
✍️ 작성
package com.example.book;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(MainApplication.class, args);
Book book = context.getBean(Book.class);
System.out.println("Book Title: " + book.getTitle());
System.out.println("Book Price: " + book.getPrice());
}
}
본 코드에서 ApplicationContext는 스프링의 IoC 컨테이너 (= DI 컨테이너) 역할을 하는 인터페이스이다.
스프링 부트 애플리케이션을 실행하면 SpringApplication.run() 메서드가 ApplicationContext를 생성하고, 이 안에 애플리케이션에서 사용할 모든 빈들을 관리한다.
context.getBean(Book.class)를 통해, 스프링이 관리하는 Book 빈을 쉽게 가져올 수 있다.
멋쟁이사자처럼 강의자료