The Spring context: Defining beans

MeteorLee·2023년 1월 18일
0

Manning Publications의 <Spring Start Here>를 요약 정리했습니다.

The Spring context: Defining beans

Spring Context




스프링 컨텍스트가 무엇이고 어떻게 작동하는지 배우는 것이 스프링 사용법을 배우는 첫 번째 단계입니다. 스프링은 프로젝트 안의 모든 인스턴스를 알지 못합니다. 오직 컨텍스트에 등록된 인스턴스만을 스프링은 인지할 수 있기에 스프링 컨텍스트가 필요합니다.

스프링 컨텍스트는 스프링이 정의한 인스턴스를 제어하는 복합적인 메커니즘을 가지고 있습니다. 그리고 이런 제어를 받는 인스턴스 객체를 "bean"이라고 합니다. 간단하게 스프링의 모든 객체 인스턴스를 추가하는 애플리케이션의 메모리 저장소라고 상상합시다.

Maven project


메이븐은 스프링과 직접적으로 연결된 주제는 아니지만 사용하는 프레임워크와 관계없이 앱의 빌드 프로세스를 쉽게 관리해주는 도구입니다. 종속성 관리를 위해서 빌드 도구가 필요하고 책에서는 메이븐을 사용합니다.

  • 앱에 필요한 종속성(dependency) 다운로드
  • 테스트 실행
  • 구문이 사용자가 정의한 규칙인지 확인
  • 보안 취약점 확인
  • 앱 컴파일
  • 앱을 실행 가능한 파일로 패키징

메이븐 프로젝트 생성


  • GroupId : 프로젝트들을 하나의 그룹으로 묶은 이름
  • ArtifactId : 현재 애플리케이션의 이름
  • Version : 현재 구현된 상태의 버전

    실제 앱에서는 이 세 가지 이름은 중요한 세부 정보로 제공해야 하지만 예제에서는 넘어갑니다.
  • "src" 폴더(소스 폴더)에는 맵에 속한 모든 정보를 저장함
  • 새로운 종속성 추가와 같은 메이븐 프로젝트 구성을 작성하는 pom.xml 파일

폴더 구성


  • 애플리케이션 소스 코드를 저장하는 "main"폴더, 이 폴더에는 Java 코드로 된 "java"폴더와 "resources"라는 두 개의 하위 폴더가 있습니다.
  • 단위 테스트를 저장하는 "test"폴더 (단위 테스트)

pom.xml


프로젝트에서 프레임워크의나 라이브러리와 같은 외부 종속성을 많이 사용합니다. pom.xml 파일의 내용을 변경함으로써 종속성을 추가할 수 있습니다.

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
 <modelVersion>4.0.0</modelVersion><groupId>org.example</groupId>  
 <artifactId>ch01</artifactId>  
 <version>1.0-SNAPSHOT</version><properties> <maven.compiler.source>11</maven.compiler.source>  
 <maven.compiler.target>11</maven.compiler.target>  
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
 </properties>  
</project>


초기 pom.xml 의 내용으로 현재의 내용으로는 외부 라이브러리에 JDK만 표시됩니다.

종속성 추가


프로젝트에 외부 종속성을 추가하는 방법은 다음과 같습니다.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0    
         http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId>
  <artifactId>sq_ch2_ex1</artifactId>
  <version>1.0-SNAPSHOT</version><dependencies>                                 --- 1
    <dependency>                                 --- 2
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
  </dependencies></project>


1. 먼저 종속성들을 넣을 태그를 작성합니다.
2. 종속성은 태그로 표현합니다.

이렇게 작성하게 되면 IDE가 외부 라이브러리로 다운로드하여 IDE에서도 찾을 수 있습니다. 이러한 pom.xml파일에서 종속성을 추가하면 jar파일로 다운로드됩니다.

스프링 컨텍스트에 빈 추가


스프링 컨텍스트에 빈을 추가하는 방식은 여러 개가 있습니다.

  • @Bean 어노테이션 사용
  • 스테레오 타입(stereotype) 주석 사용
  • 프로그래밍적으로 구현

    일단 스프링과 메이븐 없이 객체 인스턴스를 만들겠습니다.
public class Parrot {  
	private String name;public void setName(String name) {  
		this.name = name;  
	}  
	public String getName() {  
		return name;  
	}  
}


그리고 메인 메서드를 포함한 클래스 Main을 만들어 Parraot 클래스의 객체를 인스턴스화시키겠습니다.

public class Main {public static void main(String[] args) {
		Parrot p = new Parrot();
	}
}


이제 메이븐을 이용해서 스프링 컨텍스트 dependency를 추가하겠습니다.

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0     
  http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId>
  <artifactId>sq-ch2-ex1</artifactId>
  <version>1.0-SNAPSHOT</version><dependencies>
     <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.6.RELEASE</version>
     </dependency>
  </dependencies></project>


메이븐을 통해 보면 <artifactId>spring-context</artifactId>에서 우리는 spring-context만을 사용합니다. 이것을 통해 가장 중요한 점은 우리는 스프링이 모듈 방식으로 디자인되었다는 것입니다. 우리는 애플리케이션을 만들 때 스프링의 모든 부분을 사용하지 않고 우리가 원하는 부분만 모듈로 사용합니다. 이번에는 spring-context만을 사용하겠습니다.

종속성을 추가함으로써 이제 스프링 컨텍스트를 인스턴스화할 수 있습니다.

public class Main {public static void main(String[] args) {
		var context = 
			new AnnotationConfigApplicationContext();Parrot p = new Parrot();
	}
}

  • 스프링은 다양한 구현 방식을 제공하는데 이 책에서는 AnnotationConfigApplicationContext 클래스의 인스턴스를 사용합니다. 그리고 구현이나 상속과 관련된 부분보다는 컨텍스트가 하는 일에 집중해서 공부하려고 합니다.

    이제 우리가 할 일은 이런 컨텍스트 안에 만들어둔 Parrot 객체를 추가하는 것입니다. 이제 아까 언급한 3가지 방법으로 빈을 등록하는 방법에 대해서 공부할 것입니다. 먼저 @Bean방식입니다.

@Bean 주석을 사용

  • 빈을 컨텍스트에 등록하는 방식 중에 @Bean 어노테이션을 사용하는 방식은 가장 이해하기 쉽습니다.
  • 스프링 컨텍스트에 빈을 추가하는 이유는 스프링은 컨텍스트에 등록된 객체만을 관리할 수 있기 때문입니다.

    @Bean을 이용한 단계
  1. 일단 스프링 Configuration 클래스(@Configuration 주석이 있는)를 생성합니다.
  2. 컨텍스트에 추가하려는 객체 인스턴스를 반환하는 메서드를 추가하고 @Bean주석을 추가합니다.
  3. 스프링이 1단계에서 정의한 Configuration 클래스를 사용하게 합니다.

1. Configuration 클래스 생성

@Configuration                  
public class ProjectConfig {
}


Configuration클래스는 @Configuration이 달려있는 것이 특징이며 Configuration 클래스들을 통해서 다양한 스프링 관련 설정들을 정의할 수 있습니다. 지금 부분에는 스프링 컨텍스트에 인스턴스를 추가하는 것에 집중합니다.

2. 리턴을 가지는 메서드를 만들고 @Bean 어노테이션을 추가합니다.

@Configuration
public class ProjectConfig {@Bean                       
	Parrot parrot() {         // 1
		var p = new Parrot();    // 2
		p.setName("Koko");      // 3
		return p;                 
	}
}


1. @Bean 어노테이션을 추가함으로써, 스프링 컨텍스트가 초기화될 때 스프링이 메서드를 호출하고 반환된 값을 컨텍스트에 추가하도록 지시합니다.
2. 테스트를 위한 parrot의 이름을 지정합니다.
3. 스프링은 return을 통해 메서드에서 반환된 인스턴스를 컨텍스트에 추가합니다.

메서드는 보통 동사의 뜻을 가진 이름을 많이 사용합니다. 하지만 지금 @Bean 어노테이션이 붙은 메서드의 이름은 객체 인스턴스와 같기 때문에 메서드의 이름을 명사로 사용하는 것이 좋습니다.

3. 새로 생성된 Configuration 클래스를 사용해서 스프링 컨텍스트를 초기화합니다.

public class Main {  
	public static void main(String[] args) {  
		var context = new AnnotationConfigApplicationContext(
						ProjectConfig.class);  // 1Parrot p = context.getBean(Parrot.class);  // 2System.out.println(p.getName());  
	}  
}


1. 컨텍스트 인스턴스 생성 시 configuration 클래스를 매개 인자로 스프링에게 보내서 스프링이 사용하도록 지시합니다.
2. Parrot의 인스턴스 p가 현재 컨텍스트에 등록된 Bean과 연결짓기 위해 getBean() 메서드를 통해 reference를 가져옵니다.

이제 컨텍스트 안에서 name의 값을 지정한 Koko를 콘솔에 출력할 수 있습니다.

String 타입, Integer 타입 추가하기

@Configuration  
public class ProjectConfig {  
	@Bean  
	Parrot parrot() {  
		var p = new Parrot();  
		p.setName("Koko");  
		return p;  
	}  
	@Bean  
	String hello(){  
		return "Hello";  
	}  
    @Bean  
	Integer ten() {  
		return 10;  
	}  
}


"Hello" 문자열과 정수 10을 컨텍스트에 추가

  • 지금은 모든 인스턴스를 컨텍스트에 넣어서 관리하지만 실제 앱에서는 필요한 인스턴스만 추가합니다.
public class Main {  
	public static void main(String[] args) {  
		var context = new AnnotationConfigApplicationContext(ProjectConfig.class);Parrot p = context.getBean(Parrot.class);  
		System.out.println(p.getName());String s = context.getBean(String.class);  
		System.out.println(s);Integer n = context.getBean(Integer.class);  
		System.out.println(n);}  
}

  • 우리는 캐스팅(String) (Parrot)할 필요가 없습니다. 스프링이 빈을 찾아서 컨텍스트에 알아서 요청할 것이고 없다면 예외를 던져줄 것입니다.

같은 타입의 빈을 여러 개 추가하기


같은 Parrot타입의 빈을 여러 개 추가하고 참조하는 방법에 대해 설명합니다.

@Configuration  
public class ProjectConfig {@Bean  
	Parrot parrot1() {  
		var p = new Parrot();  
		p.setName("Koko");  
		return p;  
	}  
	@Bean  
	Parrot parrot2() {  
		var p = new Parrot();  
		p.setName("Miki");  
		return p;  
	}  
	@Bean  
	Parrot parrot3() {  
		var p = new Parrot();  
		p.setName("Riki");  
		return p;  
	}  
}


그 후 빈을 컨텍스트에 등록합니다.

public class Main {  
	public static void main(String[] args) {  
		var context = new AnnotationConfigApplicationContext(ProjectConfig.class);Parrot p1 = context.getBean(Parrot.class);  
		System.out.println(p1.getName());  
	}  
}

  • 이렇게 되면 NoUniqueBeanDefinitionException 에러가 발생합니다. 참조된 Parrot.class에 3개의 인스턴스가 존재하기 때문에 어떤 인스턴스를 골라야 할지 모르기 때문에 예외가 발생합니다.
public class Main {  
	public static void main(String[] args) {  
        var context = new AnnotationConfigApplicationContext(ProjectConfig.class);Parrot p1 = context.getBean("parrot2",Parrot.class); // 1 
		System.out.println(p1.getName());  
	}  
}


1. 첫 번째 매개 인자가 우리가 참조하려는 인스턴스의 이름이다.

만약 우리가 bean에 다른 이름을 붙이고자 한다면 @Bean 어노테이션을 이용해서 이름을 바꿀 수 있다. "miki"로 바꾸는 과정을 보자

  • @Bean(name="miki")
  • @Bean(value="miki")
  • @Bean("miki")
@Bean(name = "miki")    // 1
parrot2() {
	var p = new Parrot();
	p.setName("Miki");    // 2
	return p;
}


1. bean의 이름을 성정함
2. parrot의 name 변수의 값을 지정함

stereotype 어노테이션을 사용


스프링은 다양한 stereotype의 어노테이션을 제공합니다. 하지만 여기서는 사용법에 중요성을 두고 설명하고자 하며 컨텍스트에 등록되는 방식을 다룹니다.

스프링에는 컨텍스트에 빈을 등록하는 다양한 방식이 존재합니다. 그리고 이런 방법들은 각각의 상황에서 다른 장단점이 존재합니다. 이번에는 저번 장에서 다루었던 하나의 타입을 가진 여러개의 빈을 등록하는 방식을 보겠습니다.

primary


만약 내가 같은 타입을 가진 여러 개의 빈을 컨텍스트에 등록하려고 했을 때 하나의 빈을 여러 개의 빈을 대표하는 primary를 지정하는 방식을 사용할 수 있습니다. primary 빈은 @Primary 어노테이션을 붙여서 만들 수 있으며 스프링의 default한 선택을 받게 됩니다.

@Bean
@Primary
Parrot parrot2() {
	var p = new Parrot();
	p.setName("Miki");
	return p;
}


여기서는 Parrot 클래스 타입의 빈 중에서 Miki를 이름으로 가진 빈을 디폴트로 결정했습니다. 주의할 점은 하나의 빈만을 primary로 지정할 수 있다는 것입니다.

Componet


주로 애플리케이션이 실행되고 스프링 컨텍스트를 만들 때 인스턴스를 추가하고 싶을 때 사용합니다. 인스턴스로 만들고 싶은 클래스를 마킹하고 스프링이 이를 찾게 만드는 방식입니다.

1. @Conponent 어노테이션을 이용해서 컨텍스트에 추가하고 싶은 인스턴스 클래스들을 마킹합니다. (Parrot 클래스)
2. @ComponentScan을 이용해서 스프링에게 마킹한 클래스들을 찾을 위치를 알려줍니다. (main 패키지)

@Component               // 1
public class Parrot {
	private String name;
    
	public String getName() {
		return name;
	}
    
	public void setName(String name) {
		this.name = name;
	}


1. @Component 어노테이션을 이용해서 스프링에게 인스턴스를 만들어 컨텍스트에 추가하도록 지시합니다.

그러나 스프링에게 인스턴스로 만들라는 지시만을 하더라도 스프링은 작동하지 않습니다. 스프링이 이 어노테이션이 달린 클래스를 찾지 못하기 때문입니다.
따라서 @ComponentScan을 통해서 스프링에게 어떤 위치에서 이런 Componenet 클래스들을 찾을 수 있을지 알려줘야 합니다. 자바의 위치는 패키지를 기본으로 하기에 패키지를 알려줌으로써 스프링이 component class를 알게 합니다.

@Configuration
@ComponentScan(basePackages = "main") // 1
public class ProjectConfig {
}


1. basePackages 속성을 통해 스프링에게 stereotype 어노테이션을 가진 클래스들을 어디서 찾을지 알려줍니다.

메인 메서드에서 스프링이 인스턴스를 만들고 컨텍스트에 추가하는 것을 확인할 수 있습니다.

public class Main {  
    public static void main(String[] args) {  
        var context = new AnnotationConfigApplicationContext(ProjectConfig.class);  
		Parrot p = context.getBean(Parrot.class);  
		System.out.println(p);              // 1
		System.out.println(p.getName());  // 2
	}
}


1. main.Parrot@3e11f9e9으로 인스턴스가 생성되었습니다.
2. null이 출력되는데 parrot 인스턴스에 어떤 이름도 할당하지 않았기 때문입니다.

main 메서드의 출력 결과로 스프링이 parrot의 인스턴스를 만드는 것을 확인할 수 있지만 name과 같은 속성을 붙여주지는 못하는 것을 확인할 수 있습니다. 스프링은 인스턴스를 생성할 뿐이고 인스턴스의 속성 등을 변경하는 것은 우리의 의무입니다.

@PostConstruct 어노테이션


인스턴스 생성 직후 인스턴스를 관리하는 방법입니다. 스테레오 타입 어노테이션은 인스턴스 생성을 제어할 수 없기에 생성된 직후 @PostConstruct를 이용하여 스프링에게 빈을 제어하도록 지시할 수 있습니다.

Java 11 이전에는 종속성을 추가할 이유가 없었으나 11 이후에는 JDK에서 Java EE 종속성을 포함해서 SE와 관련되지 않은 API를 정리하였습니다. 따라서 Maven의 종석성을 관리하는 pom.xml에 종속성을 추가해야 합니다.

<dependency>  
   <groupId>javax.annotation</groupId>  
   <artifactId>javax.annotation-api</artifactId>  
   <version>1.3.2</version>  
</dependency>


이제 Parrot 클래스에 다음과 같은 코드를 사용할 수 있습니다.

@Component  
public class Parrot {  
	private String name;  
    
    @PostConstruct
    public void init() {  
		this.name = "Koko";  
  	}  
    
    public String getName() {
    	return name;  
	}  
    public void setName(String name) {
        this.name = name;  
  	}  
}


이제는 main메서드에의 System.out.println(p.getName());이 콘솔에서 null이 아닌 koko가 출력됩니다.

이와 유사하게 애플리케이션이 스프링 컨텍스트를 종료하거나 비우기 직전의 상황에 특정 메서드가 실행되도록하는 @PreDestroy 어노테이션이 있습니다. 하지만 컨텍스트를 비우거나 종료할 때 이런 메서드를 사용하는 일은 문제가 됩니다. 개발자가 끝내기 전에 어떤 메서드를 호출하거나 빈을 관리할지 모르기에 이런 섬세한 부분을 고려하지 않은 메서드를 호출하는 것은 굉장히 큰 문제를 발생시킬 수 있기 때문입니다.

두 방식 비교

@Bean 어노테이션 사용


1. 스프링 컨텍스트에 인스턴스를 생성하고 추가하는 것을 완전하게 제어할 수 있습니다. @Bean 주석이 달린 메서드를 main에서 인스턴스로 만드는 것은 우리의 책임입니다.
2. 동일한 타입의 인스턴스를 스프링 컨텍스트에 더 추가할 수 있습니다.
3. @Bean 주석을 이용하여 스프링 컨텍스트에 객체 인스턴스를 추가할 수 있습니다. 이를 통해 우리는 Integer나 String과 같은 클래스를 정의하지 않고 컨텍스트에 추가할 수 있습니다.
4. 각각의 빈을 만드는 각각의 코드를 작성해야 하기에 많은 양의 코드를 작성해야 합니다. 따라서 @Bean 어노테이션을 사용하는 방식은 stereotype 어노테이션 방식보다 2번째 옵션으로 사용되는 경우가 많습니다.

stereotype 주석 사용


1. 스프링에서 인스턴스를 만든 후에만 인스턴스를 제어할 수 있습니다.
2. 이 방식으로는 클래스마다 인스턴스 하나 만을 컨텍스트에 추가할 수 있습니다.
3. 애플리케이션이 가지고 있는 클래스, 즉 Integer나 String이 아닌 직접 만든 클래스만을 추가할 수 있습니다.
4. 빈을 컨텍스트에 추가하기 위한 상용 구문이 추가되지 않기에 더욱 적은 코드로 빈을 등록할 수 있습니다. 적은 코드로 작동하기에 주로 사용하는 방식입니다.

Programmatically 빈 등록하기


스프링 5에서는 프로그래밍적으로 빈을 컨텍스트에 등록할 수 있습니다. 이 방식은 @Bean이나 스테레오 타입의 방식이 요구 사항에 충분하지 않을 때 사용합니다. 애플리케이션의 독특한 구성 에 따라서 특정 빈을 등록해야 한다고 가정하겠습니다.

if (condition) {  
	context.registerBean(b1);    // 1
} else {
	context.registerBean(b2);       // 2
}

  • 1~2 조건에 따라서 빈의 등록이 다릅니다.

특정 객체 인스턴스를 컨텍스트에 등록하기 위해 ApplicationContext의 registerBean() 메서드를 사용합니다. 코드를 뜯어보면

<T> void registerBean(
    String beanName,								// 1
    Class<T> beanClass,                             // 2
    Supplier<T> supplier,                         	// 3
    BeanDefinitionCustomizer... customizers			// 4
);     


1. beanName은 컨텍스트에 등록하는 빈의 이름입니다. 특이한 점은 이름이 필요 없다면 null을 값으로 사용할 수 있습니다.
2. 컨텍스트에 등록하고 싶은 클래스를 정의합니다. Parrot 클래스를 입력하고 싶다면 Parrot.class를 입력합니다.
3. 매개 인자로 Supplier의 인스턴스를 입력합니다. Supplier 인터페이스는 자바 유틸에서 지원하는 인터페이스로 매개 인자를 따로 사용하지 않고 값을 반환해주는 인터페이스입니다.
4. 매개 인자로 가변 인자를 사용합니다. 생략이 가능하며 빈의 다른 특성을 구현하기 위한 인터페이스입니다.

POJO 추가하기


주석을 사용하지 않은 POJO의 Parrot클래스를 빈으로 컨텍스트에 등록해 보겠습니다.

public class Parrot {  
    String name;  
 	public String getName() {  
        return name;  
  	}  
    public void setName(String name) {  
        this.name = name;  
  	}  
}

@Configuration  
public class ProjectConfig {  
}


Main클래스에 registerBean() 메서드를 통해서 컨텍스트에 추가해 보겠습니다.

public class Main {  
    public static void main(String[] args) {  
        var context = new AnnotationConfigApplicationContext(ProjectConfig.class);Parrot p = new Parrot();  // 1
  		p.setName("Koko");Supplier<Parrot> parrotSupplier = () -> p;  // 2
​
  		context.registerBean("parrot1", Parrot.class, parrotSupplier);  // 3
  		Parrot x = context.getBean(Parrot.class);  // 4System.out.println(x.getName());  // 4
    	System.out.println(p.getName());  // 4
  	}  
}


1. 컨텍스트에 등록할 인스턴스를 생성합니다.
2. 생성한 인스턴스를 반환하는 Supplier를 정의합니다.
3. 스프링 컨텍스트에 추가하기 위해 registerBean()메서드를 호출합니다.
4. 빈이 컨텍스트에 있는지 확인하기 위한 과정입니다. 컨텍스트에 등록되어 있기에 p, x 모두 같은 빈을 가르기에 같은 Koko를 출력합니다.

특정 조건 추가


BeanDefinitionCustomizer를 추가하여서 등록하는 빈에 조건을 추가하겠습니다. 만약 한 클래스의 빈이 여러 개 있을 때 하나의 빈을 Primary로 만들고자 할 때 사용할 수 있습니다.

public class Main {  
    public static void main(String[] args) {  
        var context = new AnnotationConfigApplicationContext(ProjectConfig.class);Parrot p = new Parrot();  
  		p.setName("Koko");Supplier<Parrot> parrotSupplier = () -> p;  
​
  		context.registerBean("parrot2", Parrot.class, parrotSupplier, bc -> bc.setPrimary(true));  // 1Parrot x = context.getBean(Parrot.class);System.out.println(x.getName());  
  		System.out.println(p.getName());  
  	}  
}


1. 같은 타입의 빈이 여러 개 있더라도 빈을 호출하게 된다면 기본적으로 출력되는 빈이 될 것입니다.

요약

  • 스프링에서 가장 먼저 배워야 할 것은 스프링 컨텍스트에 객체 인스턴스(빈)를 추가하는 것입니다. 스프링 컨텍스트에 등록된 빈만 스프링이 관리할 수 있으므로 매우 중요합니다.
  • @Bean 어노테이션, 스테레오타입 어노테이션 사용, 프로그래밍적으로 추가하는 방식이 있습니다.
  • @Bean 주석을 사용하면 모든 종류(String, Integet...)의 인스턴스를 등록할 수 있으며 또한 동일한 타입의 여러 인스턴스를 컨텍스트에 추가할 수 있습니다. 따라서 스테레오타입보다 유연하게 빈을 등록할 수 있지만 @Configuration에 각 메서드의 코드를 작성해야 하므로 더 많은 코드를 작성해야 합니다.
  • 스테레오타입 어노테이션을 사용하면 특정 어노테이션(@Componet)이 있는 애플리케이션 클래스를 빈으로 만들 수 있습니다. 주석을 넣는 것 만으로 빈으로 등록할 수 있기에 더 적은 코드만을 작성하면 되기에 @Bean 방식보다 선호됩니다.
  • 스프링 5부터 지원된 방식으로 registerBean() 메서드를 사용하여 사용자 지정 로직을 구현할 수 있습니다.

생각

시작


사실 내가 이때까지 배운 책이나 강의에서 여기서 했던 행동들은 전부 설정을 하는 방법을 설명하고 열심히 타이핑을 하고 아무튼 이렇게 되는 거고 반복하면 알게 될 것이라는 내용이 많았다. 하지만 이 책은 달랐다. 많은 원칙이 아닌 스프링의 가장 핵심적인 컨텍스트부터 시작하였다. 이 책을 읽기 전 까지는 빈을 등록한다는 의미를 알지 못했는데 이제는 알게 되었다. 읽으면 읽을수록 이 책의 구성이 마음에 든다.


스프링은 모든 객체 인스턴스를 알지 못하기에 컨텍스트에 등록하고 이런 등록된 인스턴스를 빈으로 부른다. 정말 깔끔한 설명이었다. 나에게 빈이라고 하면 떠오르는 첫번째 개념은 싱글톤이었다. 하지만 이 책에서 바로 빈과 싱글톤은 별로 상관이 없다고 말해주었다. 그저 우리가 빈을 등록하는 default 방법이 싱글톤일 뿐이었다. 컨텍스트에 관리되는 인스턴스가 빈일 뿐이지 싱글톤 패턴을 지키는 어떤 클래스가 스프링에서 작동해서 빈이 되는 것이 아니었다.

등록

@Bean


@Bean으로 빈을 등록하는 방식을 가르쳐 주는데 조금 충격을 받았다. 지금까지 빈과 싱글톤이 땔 수 없는 아주 끈끈한 관계라고 생각했었는데 아니었다. 그저 가장 많이 사용되기에 빈을 등록하는 방식들의 default가 싱글톤일 뿐이었다. 책에서도 바로 같은 클래스의 여러 개의 빈을 등록하는 방법을 보여주고 있고 name을 이용하여 각각의 같은 타입의 빈을 사용하는 방식도 보여주고 있어서 너무 좋았다.

스테레오타입

@Component


@ConfigurationScan을 설명하는 부분이 굉장히 마음에 들었다. 스프링은 모든 인스턴스를 알지도 못하고 모든 프로젝트를 알지 못한다. 나는 스프링이 프로젝트의 모든 것을 알고 있어서 처음 @ComponentScan을 입력했을 때 왜 넣어야 하는 것인지 너무 궁금했었는데 이번 기회를 통해서 알게 되었다. 스프링에게 어떤 클래스를 빈으로 등록할지 알려줘야 하는 것이었다.

@PostConstruct


빈을 등록할 때 사용되는 생성자와 같은 기능을 하는 어노테이션이다. 처음에는 @Bean방식이 메서드 안에 여러 가지 초기 값을 할당할 수 있어서 더 좋은 방식이라고 생각했는데 이 어노테이션을 알게 된 이후로는 Component가 확실히 더 좋다고 느끼게 되었다.

Programmatically 프로그래밍적으로 등록


처음 들어본 방식으로 이런 방식을 굳이 써야 하나?라고 생각했는데 POJO를 바로 빈에 등록하는 것을 보고 사용 이유를 알았다. 내가 현업에서 사용할 일이 없을 것 같아서 엄청 자세하게 보지는 않았지만 정확하게 왜 사용하는지는 짚고 넘어갔다.

마무리


책의 2장인데 엄청 길고 실습도 하고 처음 dependency를 등록하는 과정들을 하다 보니까 조금 시간이 많이 걸렸다. 그래도 첫 단추를 잘 끼운 기분이다. 물론 숙달하는 데에는 시간이 많이 걸리겠지만 지금까지 스프링을 배우려는 시도들을 전부 보상받는 기분이 든다.

도움 받은 곳

Head First Servlets and JSP
https://memo-the-day.tistory.com/57
https://velog.io/@seculoper235/Spring-Core-Context-1%ED%8E%B8
https://groups.google.com/g/ksug/c/77jLQan8Q-E?pli=1

profile
코딩 시작

0개의 댓글