Spring

스프링 빈(Bean) 이란?

Chan Dev 2024. 12. 16. 13:47

빈 - Bean

빈( Bean ) 은 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트입니다.

즉, 스프링 컨테이너가 관리하는 자바 객체를 뜻하며, 하나 이상의 빈(Bean)을 관리합니다.

 

빈은 인스턴스화된 객체를 의미하며, 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 합니다.

쉽게 이해하자면 new 키워드를 대신 사용한다고 보시면 됩니다.

IHelloService helloService = new IHelloService()

스프링 빈을 사용하는 이유

정말 간단하게 말하자면 효율적이고 안전한 객체 관리를 위해서입니다.

 

객체의 효율적인 관리

첫 번째로 객체를 효율적으로 관리하기 위해서입니다.

우리가 프로그램을 만들 때, 여러 클래스들이 서로 도와서 일을 합니다. 이때 객체( 클래스를 통해 만들어지는 실제 사용 가능한 데이터 덩어리 ) 들이 생성되고 관리됩니다.

 

만약 우리가 필요할 때마다 새로운 객체를 만들어야 한다면, 메모리를 많이 사용하고, 속도도 느려질 수 있습니다.

그래서 스프링은 객체를 미리 만들어두고( 싱글톤 패턴), 여러 곳에서 공유해서 사용합니다.

싱글톤 패턴
객체의 인스턴스가 오직 1개만 생성되는 패턴
생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이며, 이후에 호출된 생성자는 최초의 생성자가 객체를 리턴하는 것을 말합니다.

 

만약 학생 정보를 저장하는 StudentRepository 가 있다고 가정해 봅시다.

학생 정보가 필요할 때마다 새로운 StudentRepository 객체를 만들면, 같은 데이터를 여러 객체에 중복 저장하거나 메모리가 낭비될 수 있습니다.

StudentRepository repository1 = new StudentRepository(); // A 선생님용
StudentRepository repository2 = new StudentRepository(); // B 선생님용

 

하지만 스프링의 Spring Bean을 사용하면, StudentRepository 객체를 딱 한 번만 생성해서 모든 선생님이 공유할 수 있습니다.

자동 객체 간의 관계 설정

만약 스프링 없이 우리가 직접 클래스를 연결하려면, 아래처럼 직접 객체를 만들어서 연결해야 합니다.

// 객체를 직접 생성하고 연결
StudentRepository studentRepository = new StudentRepository();
StudentService studentService = new StudentService(studentRepository);
StudentController studentController = new StudentController(studentService);

 

하지만 이러한 방식은 객체를 직접 만들어야 하므로 번거롭고, 객체 간의 연결 관계가 복잡해질 가능성이 있습니다.

스프링에서는 위 과정을 우리가 직접 하지 않아도 됩니다.

 

그럼 문득 어떻게 저런 연결들을 자동으로 해주는 걸까?라는 궁금증이 생기실 것입니다.

스프링이 클래스들을 관리하는 방식은 다음과 같습니다.

스프링에게 "이 클래스는 내가 자주 사용할 거야"라고 알려줍니다. 예를 들어, @Controller, @Service, @Repository 같은 어노테이션을 붙여서 스프링에게 알려줍니다.

@Controller // 스프링이 StudentController를 관리하도록 등록
public class StudentController {
    private final StudentService studentService;

    @Autowired // 생략 가능
    public StudentController(StudentService studentService) {
        this.studentService = studentService; // 스프링이 만들어 준 StudentService 객체를 넣어줌
    }
}

 

스프링은 프로그램 시작 시, 위에 코드에서 StudentService, StudentRepository 같은 객체를 미리 만들어 둡니다.

그리고 StudentController 가 StudentService를 필요로 한다는 것을 스프링이 알고, 미리 만들어 둔 StudentService를 자동으로 넣어줍니다.

 

이렇게 Spring Bean 이 도와주면 어떤 객체를 필요할 때마다 new 키워드로 직접 생성하지 않아도 됩니다.

성능과 안전성

웹 프로그램은 여러 명의 사용자가 동시에 접속합니다.

예를 들어, 100명이 동시에 학생 정보를 요청한다고 가정했을 때, 매번 새로운 객체를 생성한다면 서버가 느려지고 충돌이 일어날 수 있습니다. 스프링은 Spring Bean을 사용해 객체를 하나만 생성하고, 모두가 공유해서 사용하게 해 줍니다.

이 방식 덕분에 서버가 더 빠르고 안정적으로 동작하게 됩니다.

 

개발자의 편의성

스프링에서는 Spring Ioc Container라는 특별한 도구가 Spring Bean을 생성하고 관리합니다.

따라서 필요한 객체를 생성하고, 객체 간의 관계를 설정하며 객체의 생명 주기를 관리해 주기 때문에 개발자가 신경 쓸 부분을 대신해서 진행해 줍니다.

 

스프링 빈 등록 방법

Spring Bean을 등록하는 방법은 대표적으로 3가지가 있습니다.

1. XML에 직접 등록

2. @Bean 어노테이션을 이용

3. @Component, @Controller, @Service, @Repository 어노테이션을 이용

 

XML 에 직접 등록

XML 파일을 이용하여 Bean을 등록하는 방법입니다.

스프링에서는 applicationContext.xml과 같은 XML 파일을 설정 파일로 사용하여 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">

    <!-- StudentRepository Bean 등록 -->
    <bean id="studentRepository" class="com.example.StudentRepository" />

    <!-- StudentService Bean 등록 -->
    <bean id="studentService" class="com.example.StudentService">
        <constructor-arg ref="studentRepository" />
    </bean>

    <!-- StudentController Bean 등록 -->
    <bean id="studentController" class="com.example.StudentController">
        <constructor-arg ref="studentService" />
    </bean>
</beans>

 

이렇게 설정을 한 뒤 Main.java 에서 ClassPathXmlApplicationContext로 불러오시면 됩니다.

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        // XML 설정 파일을 로드하여 스프링 컨테이너 생성
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 스프링 컨테이너에서 StudentController Bean 가져오기
        StudentController studentController = (StudentController) context.getBean("studentController");

        // 학생 등록 요청 처리
        studentController.handleStudentRegistration("Chanyoung Park");
    }
}

 

@Bean을 이용한 등록

@Configuration 이 선언된 클래스 내에서 @Bean 어노테이션을 사용하여 Bean을 등록하는 방법입니다.

AppConfig.java 파일을 하나 만들고 미리 만들어둔 Repository, Service.. 등 등록할 빈들을 아래와 같은 코드로 등록하시면 됩니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public StudentRepository studentRepository() {
        return new StudentRepository();
    }

    @Bean
    public StudentService studentService() {
        return new StudentService(studentRepository());
    }

    @Bean
    public StudentController studentController() {
        return new StudentController(studentService());
    }
}

 

XML 과는 다르게 @Bean으로 등록한 것들은 AnnotationConfigApplicationContext를 이용해 꺼내옵니다.

// Main.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        // Java Config 클래스를 통해 스프링 컨테이너 생성
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // 스프링 컨테이너에서 StudentController Bean 가져오기
        StudentController studentController = context.getBean(StudentController.class);

        // 학생 등록 요청 처리
        studentController.handleStudentRegistration("Chanyoung Park");
    }
}

 

@Component... 어노테이션을 이용한 등록

스프링의 어노테이션 기반 방식입니다. 클래스에 어노테이션을 붙여주면 스프링이 자동으로 Bean을 등록하고 관리합니다. ( 가장 많이 사용하는 방법입니다. )

 

StudentRepository.java

import org.springframework.stereotype.Repository;

@Repository
public class StudentRepository {
    public void save(String studentName) {
        System.out.println("Saving student: " + studentName);
    }
}

 

StudentService.java

import org.springframework.stereotype.Service;

@Service
public class StudentService {
    private final StudentRepository studentRepository;

    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    public void registerStudent(String studentName) {
        System.out.println("Registering student: " + studentName);
        studentRepository.save(studentName);
    }
}

 

StudentController.java

import org.springframework.stereotype.Controller;

@Controller
public class StudentController {
    private final StudentService studentService;

    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }

    public void handleStudentRegistration(String studentName) {
        System.out.println("Handling registration for: " + studentName);
        studentService.registerStudent(studentName);
    }
}

 

SpringApp.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringApp {
    public static void main(String[] args) {
        // 스프링 애플리케이션 실행
        ApplicationContext context = SpringApplication.run(SpringApp.class, args);

        // 스프링 컨테이너에서 StudentController Bean 가져오기
        StudentController studentController = context.getBean(StudentController.class);

        // 학생 등록 요청 처리
        studentController.handleStudentRegistration("Chanyoung Park");
    }
}

 

결론

XML 파일을 통해 직접 Bean에 등록을 하면 설정이 분리되어 관리되지만 코드가 길어지고 복잡해 질 수 있으며, @Bean을 사용하여 등록을 하여도 XML 보다는 코드가 간결하지만 여전히 설정을 명시적으로 해주어야 합니다.

가장 흔히 사용되는 @Component, @Service, @Repository, @Controller 등을 사용하여 자동으로 Bean에 등록하고 관리하게 된다면, 코드가 가장 간결하고 유지보수하기 쉽습니다.

실사용 예제

스프링에서 @Component, @Service, @Controller 등을 사용하여 Bean 을 등록한 후, /student 경로로 요청을 받아 @RequestBody로 Json 형태로 객체를 넘겨주는 예시 코드입니다.

Student 클래스 (DTO)

먼저, 클라이언트가 보낼 데이터를 담을 Student 클래스를 정의합니다.

public class Student {
    private String name;
    private int age;

    // 기본 생성자
    public Student() {}

    // 생성자
    public Student(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;
    }
}

 

StudentService 클래스

StudentService는 비즈니스 로직을 담당합니다. 여기서는 Student 객체를 받아서 등록하는 기능만 구현했습니다.

import org.springframework.stereotype.Service;

import com.example.demo.model.Student;

@Service
public class StudentService {

    // Student 객체를 처리하는 메서드
    public void registerStudent(Student student) {
        // 여기서 데이터를 처리하거나, DB에 저장하는 로직을 추가할 수 있습니다.
        System.out.println("Registering student: " + student.getName() + ", Age: " + student.getAge());
    }
}

 

StudentController 클래스

@Controller 어노테이션을 사용하여 REST API의 엔드포인트를 처리합니다.

@RequestBody 어노테이션을 사용하여 HTTP 요청의 본문에 포함된 JSON 데이터를 Student 객체로 매핑합니다.

import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/student")
public class StudentController {

    private final StudentService studentService;

    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }

    // POST 요청을 받을 메서드
    @PostMapping
    @ResponseBody
    public String registerStudent(@RequestBody Student student) {
        // 받은 Student 객체 처리
        studentService.registerStudent(student);
        return "Student " + student.getName() + " has been registered.";
    }
}

 

SpringApp 클래스 ( 애플리케이션 실행 )

@SpringBootApplication 애너테이션으로 스프링 부트 애플리케이션을 실행합니다.

이 클래스는 스프링 부트 애플리케이션을 시작하는 진입점입니다.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringApp {
    public static void main(String[] args) {
        SpringApplication.run(SpringApp.class, args);
    }
}

 

이렇게 설정을 하시고 서버가 실행된 후, Postman이나 CURL을 통해 /student 경로로 POST 요청을 보낼 수 있습니다.

 

Request Body ( JSON )

{
  "name": "Chanyoung Park",
  "age": 25
}

 

Reponse

Student Chanyoung Park has been registered.