JAVAIARY

Bean, Container 본문

lectureNote/SPRING

Bean, Container

shiherlis 2023. 12. 17. 23:01

Bean

어렴풋이 스프링에서 관리하는 객체라고 이해하고 넘어갔지만 명확하게 개념이 정리된 것은 아니었다.
그래서 스프링 빈에 대해 공부를 좀 해보려는 찰나,

....

찬찬히 알아보자...

공식 문서에 따르면 빈의 정의는 이렇다.

Spring에서는 애플리케이션의 백본을 형성하고 Spring IoC 컨테이너에 의해 관리되는 객체를 빈이라고 합니다. Bean은 Spring IoC 컨테이너에 의해 인스턴스화, 조립 및 관리되는 객체입니다. 그렇지 않으면 빈은 단순히 애플리케이션의 많은 객체 중 하나일 뿐입니다. Bean과 이들 간의 종속성은 컨테이너에서 사용하는 구성 메타데이터에 반영됩니다.

여기서 말하는 Bean의 관리란 객체(bean)의 생명주기, Scope(Singleton,Prototype) 등을 말한다.

읽어보면 Container 에 관리되지 않는 Bean은 단순 객체에 지나지 않는다고 한다. 그렇다면 컨테이너는 무엇을 하는가?

스프링의 핵심 기능 

  • 관점지향 컨테이너(IoC Container)
    • 빈 생성 및 관리
    • 관점지향(AOP)

 


예제로 알아보기

1) Java에서 인스턴스 생성

Coffee 클래스가 있다. (Getter/Setter 생략)

public class Coffee {
    private Boolean milk;
    private String syrup;
    private int price;

    public Coffee(Boolean milk, String syrup, int price) {
        this.milk = milk;
        this.syrup = syrup;
        this.price = price;
    }
public class CoffeeExample {
    public static void main(String[] args) {
        Coffee vanillaLatte = new Coffee(true, "vanilla", 6000);

        System.out.println(vanillaLatte.getMilk());
        System.out.println(vanillaLatte.getSyrup());
        System.out.println(vanillaLatte.getPrice());

    }
}

우리는 CoffeeExample 클래스에서 Coffee 객체를 만들 수 있다.
여기서 vanillaLatte는 참조변수이자 사용자가 직접 생성한 인스턴스가 된다.

 

2) Container 방식의 인스턴스 생성

Properties 파일에 coffee 클래스에 대한 classpath를 작성해주고, ApplicationContext라는 클래스를 생성해 주었다.
ApplicationContext 클래스는 getBean(String id)이라는 메서드를 가지고 있으며, id라는 매개변수를 받아서 Properties 설정 파일에 설정해둔 id(=coffee)를 요청한다.
그러면 id(key값) 에 해당하는 값(value)인 클래스를 불러오게 된다. 

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;

public class ApplicationContext {
    Properties props;
    public ApplicationContext() {
        props = new Properties();
        try {
            props.load(new FileInputStream("src/main/resources/Beans.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public Object getBean(String id) throws ClassNotFoundException {
        String className =  props.getProperty(id);

        // class 정보 가져오기 (class name 문자열을 이용해 인스턴스 생성 가능!!)
        Class clazz= Class.forName(className);

        //clazz가 가지고 있는 메서드들을 가져옴
        for (Method method : clazz.getMethods()) {
            System.out.println(method.getName());
        }

        return null;
    }
}

 

public static void main(String[] args) throws ClassNotFoundException {
    ApplicationContext context = new ApplicationContext();
    context.getBean("coffee");
}

Properties 객체를 이용, class name 으로 해당 클래스의 메서드를 출력하는 메서드를 작성해서 실행시키면

생성했던 getter/setter 및 클래스에서 사용할 수 있는 메서드들이 출력되는 것을 확인할 수 있다.
🎇Class.forName(className)은 CLASSPATH로부터 className에 해당되는 클래스를 찾은 후 그 클래스 정보를 반환해준다.

반면, ClassLoader를 통해서 인스턴스를 생성하는 방법도 있는데

public Object getBean2(String id ) throws Exception {
    String className = props.getProperty(id);
    Class clazz = Class.forName(className);

    //ClassLoader를 이용한 인스턴스 생성
    Object o = clazz.newInstance(); //clazz 정보를 이용하여 인스턴스 생성
    return o;
}

JAVA11부터는 Deceprecated 되었지만 newInstance()를 통해서 객체를 생성할 수 있다.

context.getBean2("coffee");

똑같이 호출해 보지만 에러메시지가 뜬다 .

Bean 생성에는 기본 생성자가 필요하기 때문이다.
Coffee 클래스에 기본 생성자를 추가해 주고, Coffee 인스턴스 필드값을 설정하도록 변경하였다.

public Coffee() {	//추가한 기본 생성자
}
        Coffee americano = (Coffee) context.getBean2("coffee");
        americano.setMilk(false);
        americano.setPrice(5000);

        System.out.println("americano : " + americano.getMilk());
        System.out.println("---------------------------");

        Coffee latte = (Coffee) context.getBean2("coffee");
        System.out.println("latte : " + latte.getMilk());

        if (americano==latte){
            System.out.println("same Instance");
        }else {
            System.out.println("different Instance");
        }

여기서 americano와 latte는 다른 인스턴스임을 알 수 있다.
싱글톤으로 객체가 생성되는 Spring의 빈과는 대조적인 모습이다.
같은 인스턴스를 바라보게하고 싶다면 객체안에 map 객체를 생성해 같은 객체임을 보장해 줄 수 있다.

public ApplicationContext() {
        props = new Properties();
        objectMap = new HashMap<String,Object>();
        try {
            props.load(new FileInputStream("src/main/resources/Beans.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
public Object getBean3(String id ) throws Exception {
    Object o1 = objectMap.get(id);
    if (o1 ==null){
        String className = props.getProperty(id);
        Class clazz = Class.forName(className);

        Object o = clazz.newInstance();
        objectMap.put(id,o);
        o1=objectMap.get(id);
    }
    return o1;
}
System.out.println("====================================\n");

Coffee americano2 = (Coffee) context.getBean3("coffee");
americano2.setMilk(false);
americano2.setPrice(5000);

System.out.println("americano2 : " + americano2.getMilk());
System.out.println("---------------------------");

Coffee latte2 = (Coffee) context.getBean3("coffee");
System.out.println("latte2 : " + latte2.getMilk());

if (americano2==latte2){
    System.out.println("same Instance");
}else {
    System.out.println("different Instance");
}

같은 인스턴스임을 알 수 있다.

싱글톤 패턴 적용해보기

//싱글톤 패턴
public class ApplicationContextSingleton {
    // 1.자기 자신을 참조하는 static 필드 선언
    private static ApplicationContextSingleton instance  = new ApplicationContextSingleton();

    Properties props;
    Map objectMap;

    // 2. private 생성자
    private ApplicationContextSingleton() {
        props = new Properties();
        objectMap = new HashMap<String,Object>();
        try {
            props.load(new FileInputStream("src/main/resources/Beans.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    // 3. 1에서 만든 인스턴스를 반환하는 static 메소드 작성
    public static ApplicationContextSingleton getInstance(){
        return instance;
    }
public class ApplicationContextSingletonMain {
    public static void main(String[] args) {
        // 4. 3으로부터 1의 인스턴스를 가져옴
        ApplicationContextSingleton context = ApplicationContextSingleton.getInstance();
        ApplicationContextSingleton context2 = ApplicationContextSingleton.getInstance();

        if(context == context2){
            System.out.println("같음");
        }
    }
}

  1. Instance를 static 으로 설정해 주고
  2. private 생성자를 만들어 둔다.
  3. 1의 인스턴스를 반환하는 메소드를 통해 인스턴스에 접근할 수 있도록 함

그러면 getInstance() 메서드를 통해 만드는 인스턴스는 모두 1이 된다.

이렇게 직접만든 Application Context 는 객체를 Singleton으로 관리해주고, 자기 자신도 Singleton이다.
이 ApplicationContext가 Spring에서의 컨테이너의 역할이 되며 실제로는 훨씬 더 복잡한 일을 한다.


 

https://docs.spring.io/spring-framework/reference/core/beans/introduction.html

https://www.youtube.com/watch?v=ShR5CmEUyRY&t=2386s

https://www.baeldung.com/spring-bean