Java

[SpringFramework] Spring Bean 생성, 확인, 의존성 주입

ride-dev 2023. 11. 26. 23:41

Spring이 Bean을 자동으로 생성하고 관리하도록 하면,

프로젝트의 규모가 크더라도 손쉽게 관리할 수 있습니다.

Java Spring Bean을 만들고 자동 연결하기

SpringFramework를 활용하기 위한 설정을 하겠습니다.

1. SpringContext 실행하기

JVM 내에 SpringContext를 만들기 위해

AnnotationConfig 클래스를 사용하여 AnnotationConfigApplicationContext를 만들기 위해

설정(@Configuration) 클래스를 사용합니다.

(@Configuration클래스로 SpringContext를 실행합니다)

public class AppHelloWorldSpring {
	public static void main(String[] args) {
    	var context = new AnnotationConfigApplicationContext( // SpringContext 입니다.
        	HelloWorldConfiguration.class); // 설정(@Configuration)클래스입니다.
    }
}

2. SpringFramework가 관리하도록 할 것(객체)을 설정하기

@Configuration 설정 클래스를 만들고 이름 등 모든 것을 정의할 수 있습니다.

 

이렇게 만들어진 class에서 Spring Bean을 정의할 수 있습니다.

(Spring Bean : Spring에서 관리하는 것)

설정 클래스에서 메서드를 정의하여 Spring Bean을 생성합니다.

public 메서드에 @Bean 어노테이션을 추가하는 것으로

이 메서드가 Spring 컨테이너에서 관리하는 Bean을 생산하도록 합니다.

@Configuration
public class HelloWorldConfiguration {

    @Bean
    public String name() {
        return "MyName";
    }
}

이제 이 메서드는 Bean을 생성하며, 이 Bean은 Spring 컨테이너가 관리합니다.

(스프링에서 관리하는 것은 무엇이든 Bean이 될 수 있습니다)

다음으로, 스프링에서 이렇게 관리하는 것을 확인해 보겠습니다.

 

3. Spring Bean 확인하기

컨텍스트에서 생성된 Bean의 값을 getBean으로 확인할 수 있습니다.

public class AppHelloWorldSpring {
	public static void main(String[] args) {
    	var context = new AnnotationConfigApplicationContext( // SpringContext 입니다.
        	HelloWorldConfiguration.class); // 설정(@Configuration)클래스입니다.
            
        context.getBean("name");
        // -> @Configuration에서 @Bean으로 생성한 메서드의 이름이 name 입니다.
        System.out.println(context.getBean("name"));
        // 출력 결과 -> MyName
    }
}

 

Spring이 사용자 지정 클래스의 객체를 관리하도록 할 수 있을지 record를 통해 확인합니다.

record는 JDK16부터 추가된 기능으로, Java Bean의 생성을 간단하게 해 줍니다.

일반적으로 Java 클래스를 만들 때

getter, setter, constructor, 등호, 해시코드, toString 메서드를 만들어야 하지만

record를 통해 자동화할 수 있습니다.

record Person (String name, int age) {};

@Configuration
public class HelloWorldConfiguration {

    @Bean
    public String name() {
        return "MyName";
    }
    @Bean
    public int age() {
        return 20;
    }

    @Bean
    public Person person() {
        var person = new Person("A", 21);
        person.name();
        person.age();
        person.toString();
        person.equals("B");
        return person;
    }
}
System.out.println(context.getBean("person"));
// 출력 결과 -> Person[name=A, age=21]

@Bean의 이름이 메서드 이름으로 설정되지만,

필요에 따라 변경할 수 있습니다.

...
    @Bean(name = "address2")
    public Address address() {
        var address = new Address("street", "city");
        return address;
    }
}

만약 getBean("address")를 호출한다면(호출한 Bean을 찾을 수 없다면)

아래와 같은 예외를 맞닥뜨리게 됩니다.

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'address' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:893)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1319)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1173)
	at com.ride.learnspringframework.AppHelloWorldSpring.main(AppHelloWorldSpring.java:19)

Bean 이름이 아닌, class로 호출할 수도 있습니다.

System.out.println(context.getBean(Address.class));
// 출력 결과 -> Person[name=A, age=21]

 

이처럼 Spring에서 여러 Bean을 정의할 수 있으며,

Spring에서 관리하는 객체에 접근하는 방법 또한 다양합니다.

의존성 주입의 다양한 유형

Spring Bean들 사이의 연결을 하는 방법(의존성 주입)

의존성 주입에는 3가지 유형이 존재합니다.

생성자 기반(Constructor-based), 수정자 기반(Setter-based), 필드(Field) 주입입니다.

 

실제 코드를 작성하면서 살펴보도록 하겠습니다.

 

먼저 기본적인 런처를 생성합니다.

@Configuration
@ComponentScan
public class DepInjectionLauncherApplication {

    public static void main(String[] args) {
        try (
                var context =
                        new AnnotationConfigApplicationContext
                                (DepInjectionLauncherApplication.class);
        ) {
                Arrays.stream(context.getBeanDefinitionNames())
                        .forEach(System.out::println);
                        // Spring context에서 어떤 Bean이 쓰였는지 확인 가능
        }
    }
}

아래는 그 출력문입니다.

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
depInjectionLauncherApplication

 

1. 생성자 기반(Constructor-based)

의존성은 해당 객체의 생성자를 사용하여 빈(Bean)을 생성함으로써 설정됩니다.

package com.ride.learnspringframework2.example.a1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
class YourBusinessClass {

    Dependency1 dependency1;
    Dependency2 dependency2;

    public YourBusinessClass(Dependency1 dependency1, Dependency2 dependency2) {
        System.out.println("Constructor Injection - YourBusinessClass");
        this.dependency1 = dependency1;
        this.dependency2 = dependency2;
    }

    public String toString() {
        return "Using + " + dependency1 + " and " + dependency2;
    }
}

@Component
class Dependency1 {

}

@Component
class Dependency2 {

}

@Configuration
@ComponentScan
public class DepInjectionLauncherApplication {

    public static void main(String[] args) {
        try (
                var context =
                        new AnnotationConfigApplicationContext
                                (DepInjectionLauncherApplication.class);
        ) {
            Arrays.stream(context.getBeanDefinitionNames())
                    .forEach(System.out::println);
            System.out.println(context.getBean(YourBusinessClass.class));
        }
    }
}

아래는 그 출력문입니다.

Constructor Injection - YourBusinessClass
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
depInjectionLauncherApplication
dependency1
dependency2
yourBusinessClass
Using + com.ride.learnspringframework2.example.a1.Dependency1@4d910fd6 and com.ride.learnspringframework2.example.a1.Dependency2@26275bef

2. 수정자 기반(Setter-based)

의존성은 빈(Bean) 객체에서 세터 메서드를 호출하여 설정됩니다.

@Component
class YourBusinessClass {

    Dependency1 dependency1;
    Dependency2 dependency2;

    @Autowired
    public void setDependency1(Dependency1 dependency1) {
        System.out.println("Setter Injection - setDependency1");
        this.dependency1 = dependency1;
    }

    @Autowired
    public void setDependency2(Dependency2 dependency2) {
        System.out.println("Setter Injection - setDependency2");
        this.dependency2 = dependency2;
    }
    
    public String toString() {
        return "Using + " + dependency1 + " and " + dependency2;
    }
}

@Component
class Dependency1 {

}

@Component
class Dependency2 {

}

@Configuration
@ComponentScan
public class DepInjectionLauncherApplication {

    public static void main(String[] args) {
        try (
                var context =
                        new AnnotationConfigApplicationContext
                                (DepInjectionLauncherApplication.class);
        ) {
            Arrays.stream(context.getBeanDefinitionNames())
                    .forEach(System.out::println);
            System.out.println(context.getBean(YourBusinessClass.class));
        }
    }
}

아래는 그 출력문입니다.

Setter Injection - setDependency1
Setter Injection - setDependency2
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
depInjectionLauncherApplication
dependency1
dependency2
yourBusinessClass
Using + com.ride.learnspringframework2.example.a1.Dependency1@15bbf42f and com.ride.learnspringframework2.example.a1.Dependency2@550ee7e5

3. 필드(Field)

수정자(setter)나 생성자(constructor)가 없습니다.

의존성은 리플렉션(reflection)을 사용하여 주입됩니다.

필드에서 @Autowiring을 통해 Spring이 자동으로 필드 주입을 합니다.

@Component
class YourBusinessClass{
    @Autowired
    Dependency1 dependency1;
    @Autowired
    Dependency2 dependency2;

    public String toString() {
        return "Using + " + dependency1 + " and " + dependency2;
    }
}
@Component
class Dependency1{

}
@Component
class Dependency2{

}
@Configuration
@ComponentScan
public class DepInjectionLauncherApplication {

    public static void main(String[] args) {
        try (
                var context =
                        new AnnotationConfigApplicationContext
                                (DepInjectionLauncherApplication.class);
        ) {
                Arrays.stream(context.getBeanDefinitionNames())
                        .forEach(System.out::println);
            System.out.println(context.getBean(YourBusinessClass.class));
        }
    }
}

아래는 그 출력문입니다.

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
depInjectionLauncherApplication
dependency1
dependency2
yourBusinessClass
Using + com.ride.learnspringframework2.example.a1.Dependency1@3a6bb9bf and com.ride.learnspringframework2.example.a1.Dependency2@34f7cfd9

 

Bean이 정확한 순서로 초기화되어야 할 때가 있습니다.

만약 초기화된 Bean이 다른 Bean에 의존하는데,

의존하는 Bean이 초기화되지 않은 상태에서 호출되면 예상치 못한 동작이 발생할 수 있습니다.

생성자 주입은 Bean이 객체를 생성하는 동시에 의존성을 주입하므로,

Bean이 완전히 초기화된 상태로 사용될 수 있습니다.

또한 생성자 주입은 주입된 의존성을 변경할 수 없도록  합니다.

이는 Bean이 불변성을 가지고 초기화 이후에도 안정적으로 동작할 수 있도록 합니다.

 

Spring에서는 생성자 기반 주입을 권장합니다.

생성자는 Bean의 생성 시점에 호출되므로 빈의 라이프사이클을 더 효과적으로 관리할 수 있습니다

 

다음 게시글에서는 Spring Bean을 관리하는 SpringContainer에 대해 더 세부적으로 다뤄보도록 하겠습니다.

728x90