Spring Bean (봄 콩) 이해?

SEUNGJUN·2024년 3월 16일
post-thumbnail

Spring을 공부하면서 Bean은 매우 중요하다는 이야기를 듣고 코드작업에 여러가지 이점이 있다길래 정리를 해보려고 한다.

Bean

Bean이라는 말은 개발자들 사이에서 사용되는 용어로, 간다한게 말하면, 애플리케이션에서 사용되는 객체를 일컫는 말이다. 예를 들어 커피숍을 가면, 커피콩으로 만들어지는 커피를 생각할 수 있다. 커피콩은 커피를 만들기 위한 재료로서, 우리가 주문하는 각각의 커피는 하나의 Bean 이라고 생각할수 있다.

웹 애플리케이션에서 Bean은 사용자의 정보를 저장하거나 처리하는 객체, 데이터베이스와 상호 작용을 처리하는 객체, 웹 페이지를 표시하는 객체 등이 모두 Bean이 될수 있는 것이다.

왜 Bean라고 부를까?

Bean이라는 용어는 JAVA 컴퓨터 프로그래밍에서의 자바빈(JavaBean)에서 유래 했다. 자바빈은 Java 언어에서 재사용 가능한 소프트웨어 컴포넌트를 나타낸다. 이 용어는 Java 컴포넌트 모델의 일부로 만들어 졌으며, 기본적으로 특정 규약을 따르는 재사용 가능한 객체를 말한다.

JavaBean(자바빈)의 규칙

1. 기본 생성자 (Default Constructor)

  • Java Bean 클래스는 기본 생성자(매개변수가 없는 생성자)를 가져야 한다.

2. 속성(멤버 변수)및 접근 메서드

  • JavaBean 클래스는 멤버 변수를 가질 수 있다. 일반적으로 이러한 멤버 변수는 private으로 선언된다.
  • 멤버 변수에 접근하기 위한 Getter(접근자)와 Setter(설정자) 메서드를 제공해야한다.
  • Getter 메서드는 멤버 변수와 값을 반환하고, Setter 메서드는 멤버 변수의 값을 설정한다.

3. 직렬화(Serialization)

  • JavaBean 클래스는 직렬화(Serialization) 가능해야 한다. 이를 위해 Serializable 인터페이스를 구현 할수 있다.

JavaBean 코드 예시

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;

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

    // Getter 메서드
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    // Setter 메서드
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

위의 예제 에서 Person이라는 JavaBean 클래스를 정의 했다. 이 클래스는 이름과 나이라는 두 개의 속성을 가지고 있고, 이를 위한 Getter와 Setter 메서드를 제공한다. 또한 Serializable 인터페이스를 구현하여 객체를 직렬화할 수 있도록 한다. 이 처럼 규칙을 따르는 클래스를 JavaBean 클래스 라고 한다. 이렇게 만들면 재사용성이 높아질수가 있다.

Bean의 이점?

1. 의존성 관리 (Dependency Management)

  • Spring에서는 Bean을 사용하여 객체 간의 의존성을 관리한다. 예를 들어, 서비스 객체가 데이터 액세스 객체에 의존 할 때, Spring은 서비스 객체에 데이터 액세스를 객체를 주입하여 서비스가 액세스를 사용할수 있도록 한다.
public class MyService {
    private MyRepository repository;

    // Constructor Injection을 통한 의존성 주입
    public MyService(MyRepository repository) {
        this.repository = repository;
    }

    public void doSomething() {
        // 데이터 액세스 객체 사용
        repository.getData();
    }
}

2. 유연성 (Flexibility)

  • Bean을 사용하면 애플리케이션의 여러 부분을 쉽게 교체하고 변경할수 있다. 예를 들어, 데이터베이스 연동을 위해 JDBC를 사용하는 대신 JPA로 변경하고자 할 때, 이러한 변경을 Bean의 설정에서만 수정하여 전체 애플리케이션을 다시 작성할 필요가 없다.
@Configuration
public class AppConfig {
    
    @Bean
    public MyRepository myRepository() {
        // JPA Repository를 반환하도록 변경
        return new JpaRepository();
    }
}

3. 테스트 용이성 (Testability)

  • Spring은 테스트 환경에서 가짜 객체(Mock 객체) 사용하여 테스트할 때 Bean을 쉽게 대체할 수 있는 기능을 제공합니다. 이를 통해 테스트 코드를 작성하고 실행할 때 특정 Bean을 가짜 객체로 대체하여 테스트를 수행할 수 있다.
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceTest {

    @MockBean
    private MyRepository mockRepository;

    @Autowired
    private MyService service;

    @Test
    public void testDoSomething() {
        // 가짜 Repository로 테스트
        when(mockRepository.getData()).thenReturn("Mock data");
        String result = service.doSomething();
        assertEquals("Mock data", result);
    }
}

4. 싱글톤(Singleton) 관리

  • Spring은 기본적으로 Bean을 싱글톤으로 관리한다. 이는 하나의 Bean 정의가 컨테이너에서 단일 인스턴스의 객체를 생성하도록 보장한다는 것을 의미한다. 이는 애플리케이션 전반에 걸쳐 공유되는 객체를 쉽게 만들수 있도록 도와준다.
@Component
public class MySingletonBean {
    // ...
}

5. AOP(Aspect-Oriented Programming) 등의 기능 지원

  • Spring은 기본적으로 Bean을 싱글톤으로 관리한다. 이는 하나의 Bean 정의가 컨테이너에서 단일 인스턴스의 객체를 생성하도록 보장한다는 것을 의미한다. 이는 애플리케이션 전반에 걸쳐 공유되는 객체를 쉽게 만들수 있도록 도와준다.
@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.MyService.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        // 메서드 실행 전 로깅
        System.out.println("Logging before method: " + joinPoint.getSignature().getName());
    }

    // 다양한 AOP 기능을 추가할 수 있음
}

Bean을 등록하여 의존성 주입을 하는 경우

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// Service 클래스를 빈으로 등록
@Component
public class MyService {
    private MyRepository repository;

    @Autowired
    public MyService(MyRepository repository) {
        this.repository = repository;
    }

    // 서비스에서 리포지토리 사용
    public void doSomething() {
        repository.getData();
    }
}

위 코드에서 MyService 클래스는 @Component 어노테이션을 사용하여 SPring에게 이 클래스가 Bean임을 알려준다. 이 클래스는 빈으로 등록되어 Spring IoC 컨테이너에서 관리가 된다. @Autowired 어노테이션을 사용하여 MyRepository를 주입받고 있다.

Bean을 등록하지 않고 의존성 주입을 하는 경우

// Service 클래스는 빈으로 등록하지 않음
public class MyService {
    private MyRepository repository;

    public MyService(MyRepository repository) {
        this.repository = repository;
    }

    // 서비스에서 리포지토리 사용
    public void doSomething() {
        repository.getData();
    }
}

위 코드에서는 MyService 클래스를 빈으로 등록하지 않고, 단순히 생서자를 통해 MyRepository를 주입받고 있다. MyService 클래스는 Spring Ioc 컨테이너에 의해 관리되지 않으므로 의존성 주입을 받을 수가 없다.

개발자가 직접 의존성 주입을 관리하는 방식

// 의존성을 직접 주입하는 예시

public class MyController {
    private MyService service;

    public MyController() {
        this.service = new MyService(); // 의존성을 직접 생성
    }

    // 메소드 내에서 서비스를 사용
    public void doSomething() {
        service.doSomething();
    }
}

위의 코드에서는 MyController 클래스가 MyService에 의존하고 있으며, 이를 직접 생성하여 의존성을 주입하고 있다.

Spring의 Bean을 통해 의존성 주입을 관리하는 방식

// Spring의 Bean을 통한 의존성 주입 예시

public class MyController {
    private MyService service;

    // Spring의 의존성 주입 어노테이션을 통해 의존성을 주입 받음
    @Autowired
    public MyController(MyService service) {
        this.service = service;
    }

    // 메소드 내에서 서비스를 사용
    public void doSomething() {
        service.doSomething();
    }
}

위 코드에서는 Spring의 @Autowird 어노테이션을 사용하여 의존성을 주입 받고 있다. 이를 통해서 Spring 컨테이너가 MySerive 객체를 생성하고 주입해준다.

여러 구현 클래스가 있는 경우

// MyService 인터페이스
public interface MyService {
    void doSomething();
}

// 구현 클래스 1
@Component("serviceImpl1")
public class MyServiceImpl1 implements MyService {
    public void doSomething() {
        System.out.println("MyServiceImpl1: Doing something...");
    }
}

// 구현 클래스 2
@Component("serviceImpl2")
public class MyServiceImpl2 implements MyService {
    public void doSomething() {
        System.out.println("MyServiceImpl2: Doing something...");
    }
}

// MyController 클래스
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

@Controller
public class MyController {
    private MyService service;

    @Autowired
    public MyController(@Qualifier("serviceImpl1") MyService service) {
        this.service = service;
    }

    public void doSomething() {
        service.doSomething();
    }
}

MyService 인터페이스를 구현한 두 개의 클래스 MyserviceImpl1, MyserviceImpl2가 있을때 @Component 어노테이션을 통해 각각의 클래스를 Bean으로 등록한다. 그리고 MyControll 클래스에세 @Autowired 어노테이션을 사용하여 MyService 타입의 Bean을 주입받는다. @Qualifier 어노테이션을 사용하여 특정한 Bean을 선택할 수 있다.

profile
RECORD DEVELOPER

0개의 댓글