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에 대해 더 세부적으로 다뤄보도록 하겠습니다.
'Java' 카테고리의 다른 글
[SpringFramework] Bean 우선순위 부여(@Primary, @Qualifier) (0) | 2023.11.29 |
---|---|
[SpringFramework] Spring Container, Java Bean vs. POJO vs. Spring Bean (0) | 2023.11.26 |
[SpringFramework] Coupling, 강한 결합과 느슨한 결합 (0) | 2023.11.26 |
[SpringFramework] 스프링프로젝트 생성(spring.io, InteliJ), 클래스 생성 (0) | 2023.11.25 |
SpringSecurity 스프링 시큐리티 웹 애플리케이션에서 시작하기 가이드(공식문서) (0) | 2023.10.31 |