
Spring에서의 Bean은 핵심 중에 핵심이 되는 요소이다. Bean에 대해 알기 위해서는 먼저 DI / IoC에 대해 알고 있어야 한다. DI와 IoC에 대한 설명을 간략히 하자면 다음과 같다.
객체의 의존성을 외부에서 주입하는 방식
프로그램의 제어 흐름을 개발자가 아니라 프레임워크가 관리하게 하는 디자인 원칙
IoC의 일반적인 구현 방법 중 하나가 바로 DI이다. DI를 사용하여 객체의 생성 및 라이프사이클을 프레임워크가 관리하게 함으로써 제어의 역전을 구현한다.
@Component
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
public void execute() {
serviceB.performTask();
}
}
@Component
public class ServiceB {
public void performTask() {
System.out.println("Task performed");
}
}
예시 코드의 ServiceA는 ServiceB에 의존한다. 스프링 컨테이너는 ServiceB의 인스턴스를 생성하고 ServiceA의 생성자에 주입한다.
여기서 컨테이너는 IoC 컨테이너를 의미한다.
IoC 컨테이너란 의존성 주입(DI)을 관리하고 애플리케이션의 객체 생명 주기와 의존성을 관리하는 프레임워크의 핵심 구성 요소이다.

특별한 규칙이나 제한 없이 순수하게 자바 언어로 작성된 객체인 POJO(Plain Old Java Object)를 Bean으로 등록해두면, IoC 컨테이너 속에서 관리하다가 필요시 의존성이 주입된 객체를 사용할 수 있다. 일단 Bean에 대한 기본적인 설명은 잠시 미루어두겠다.
기본 객체를 Bean으로 등록해두면 컨테이너에 들어가게 되고 개발자가 직접 의존성을 주입을 하지 않아도 된다고 하는데... 이는 어떻게 구현이 될까?
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.example.Person">
<property name="name" value="John"/>
<property name="age" value="30"/>
</bean>
</beans>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = context.getBean(Person.class);
System.out.println(person);
}
}
스프링은 자바 어노테이션을 사용하여 POJO를 빈으로 등록하는 기능을 제공한다. 주요 어노테이션으로는 @Component, @Service, @Repository, @Controller 등이 있다.
import org.springframework.stereotype.Component;
@Component
public class Person {
private String name = "Minsoo";
private int age = 24;
// Getter 및 Setter 메서드
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {}
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Person person = context.getBean(Person.class);
System.out.println(person);
}
}
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter 및 Setter 메서드
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public Person person() {
return new Person("Minsoo", 24);
}
}
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Person person = context.getBean(Person.class);
System.out.println(person);
}
}
(요약) POJO를 Bean으로 등록하기 위한 3가지 방법
- XML 설정 파일 사용
- 어노테이션 사용
- Config 클래스 사용
기본 객체를 Bean이란 것으로 등록해두기 위한 방법들을 알아봤으니 본격적으로 Bean이란 무엇인지 정리해보고자 한다.
빈(Bean)은 스프링 IoC 컨테이너에 의해 생성되고 관리되는 객체이다.
스프링 프레임워크에서는 빈의 생명 주기와 가시 범위를 관리하기 위해 여러 가지 스코프(scope)를 제공한다. 여기서 스코프는 빈이 생성되고 사용되는 범위를 뜻한다.
스코프가 중요한 이유는, 개발자가 적절한 스코프를 선택하여 사용하면 애플리케이션의 성능과 유지보수성을 향상시킬 수 있기 때문이다.
빈 스코프의 종류로는 싱글톤, 프로토타입, request, session, application 등이 있다. 간단히 정리하자면 다음과 같다.
사용 예시 : 주로 공유되어야 하는 서비스 객체에 사용
코드 예시
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
public class SingletonBean {
public SingletonBean() {
System.out.println("SingletonBean 생성");
}
public void doSomething() {
System.out.println("싱글톤 빈 작업 수행");
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SingletonExample {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
SingletonBean bean1 = context.getBean(SingletonBean.class);
SingletonBean bean2 = context.getBean(SingletonBean.class);
bean1.doSomething();
bean2.doSomething();
System.out.println(bean1 == bean2); // true
}
}
사용 예시 : 상태를 가지며 매번 새로운 인스턴스를 필요로 하는 객체에 사용
코드 예시
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class PrototypeBean {
public PrototypeBean() {
System.out.println("PrototypeBean 생성");
}
public void doSomething() {
System.out.println("프로토타입 빈 작업 수행");
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class PrototypeExample {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
PrototypeBean bean1 = context.getBean(PrototypeBean.class);
PrototypeBean bean2 = context.getBean(PrototypeBean.class);
bean1.doSomething();
bean2.doSomething();
System.out.println(bean1 == bean2); // false
}
}
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
@Component
@RequestScope
public class RequestBean {
public RequestBean() {
System.out.println("RequestBean 생성");
}
public void doSomething() {
System.out.println("리퀘스트 빈 작업 수행");
}
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RequestScopeController {
private final RequestBean requestBean;
public RequestScopeController(RequestBean requestBean) {
this.requestBean = requestBean;
}
@GetMapping("/request")
public String handleRequest() {
requestBean.doSomething();
return "Request scope example";
}
}
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;
@Component
@SessionScope
public class SessionBean {
private int counter = 0;
public SessionBean() {
System.out.println("SessionBean 생성");
}
public void incrementCounter() {
counter++;
}
public int getCounter() {
return counter;
}
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SessionScopeController {
private final SessionBean sessionBean;
public SessionScopeController(SessionBean sessionBean) {
this.sessionBean = sessionBean;
}
@GetMapping("/session")
public String handleSession() {
sessionBean.incrementCounter();
return "Session counter: " + sessionBean.getCounter();
}
}
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.ApplicationScope;
@Component
@ApplicationScope
public class ApplicationBean {
public ApplicationBean() {
System.out.println("ApplicationBean 생성");
}
public void doSomething() {
System.out.println("애플리케이션 빈 작업 수행");
}
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ApplicationScopeController {
private final ApplicationBean applicationBean;
public ApplicationScopeController(ApplicationBean applicationBean) {
this.applicationBean = applicationBean;
}
@GetMapping("/application")
public String handleApplication() {
applicationBean.doSomething();
return "Application scope example";
}
}
동일하진 않지만, 각각의 빈은 생명주기를 가진다. 마지막으로 빈의 생명주기에 대해 정리를 해보려 한다.
스프링 빈의 생명 주기(Bean Lifecycle)는 빈이 생성되고 소멸될 때까지 거치는 일련의 단계들을 말한다.

@PostConstruct 어노테이션 사용
import javax.annotation.PostConstruct;
@Component
public class MyBean {
@PostConstruct
public void init() {
System.out.println("MyBean 초기화");
}
}
XML 설정에서 init-method 속성 사용
// XML 설정
<bean id="myBean" class="com.example.MyBean" init-method="init"/>
InitializingBean 인터페이스의 afterPropertiesSet 메서드 구현
import org.springframework.beans.factory.InitializingBean;
@Component
public class MyBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("MyBean 초기화");
}
}
@PreDestroy 어노테이션 사용
import javax.annotation.PreDestroy;
@Component
public class MyBean {
@PreDestroy
public void destroy() {
System.out.println("MyBean 소멸");
}
}
XML 설정에서 destroy-method 속성 사용
// XML 설정
<bean id="myBean" class="com.example.MyBean" destroy-method="destroy"/>
DisposableBean 인터페이스의 destroy 메서드 구현
import org.springframework.beans.factory.DisposableBean;
@Component
public class MyBean implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("MyBean 소멸");
}
}
스프링 빈의 초기화 및 소멸 메서드를 명시적으로 지정하지 않아도 애플리케이션은 정상적으로 동작하겠지만 빈의 상태를 설정하거나 자원을 해제하는 등의 특정 작업을 수행할 때 사용하면 유용하다!!!