Spring container
스프링 컨테이너는 내부에 존재하는 애플리케이션 Bean(객체)의 생명주기 즉, 생성, 관리, 제거 등의 역할을 담당한다.
스프링 컨테이너는 XML, 애너테이션 기반의 자바 설정 클래스로 만들 수 있다. 과거에는 개발자가 XML을 통해 모두 설정해줬지만 Spring Boot를 사용하면서 XML설정 없이 사용이 가능해 졌다고 한다.
스프링 컨테이너를 사용하는 이유
스프링 컨테이너를 사용하게 되면서 객체들간의 의존관계를 정리하고 의존성을 낮추며 해당 객체들을 스프링에서 관리하여 개발자들은 비즈니스 로직에만 집중하기 더 쉬워 졌다.
스프링 컨테이너를 사용하기 이전에는 객체를 사용하기 위해 new 생성자를 사용하고 무수히 많은 객체들이 서로 참조하게 되어 의존성이 높아 객체지향프로그래밍의 장점을 살리지 못했다. 하지만 스프링 컨테이너를 사용함으로써 구현 클래스에 있는 의존을 제거하고 인테페이스에만 의존하도록 설계하여 유지 및 보수에 더 편리해졌다.
스프링 컨테이너의 생성 과정
1. 스프링 컨테이너 생성
public class MemberTest {
public static void main(String[] args) {
//AnnotationConfigApplicationContext 객체를 생성하여 스프링 컨테이너 생성(ApplicationContext)
ApplicationContext ac = new AnnotationConfigApplicationContext(DependencyConfig.class);
//Configuration metadata(구성정보)로 DependencyConfig클래스를 참조
//이후 구성정보를 토대로 스프링 컨테이너에 Bean들이 생성 및 추가되며 Bean 사용가능
MemberService memberService = ac.getBean("memberService", MemberService.class);
}
}
2. Configuration metadata를 토대로 스프링 컨테이너 안에 Bean 생성 및 관계설정
@Configuration //Configuration metadata
public class DependencyConfig {
@Bean(name="memberService")//보통 메서드명으로 사용하지만 애너테이션에 이름을 주어서도 사용가능
public MemberService sevice() {
return new MemberService(memberRepository());
}
@Bean // sevice()와 의존관계
public MemberRepository memberRepository() {
return new MemberRepository();
}
}
스프링 컨테이너의 종류
앞서 new AnnotationConfigApplicationContext()로 스프링 컨테이너를 생성해 주고 타입으로는 ApplicationContext를 지정해 주었다. 이 ApplicationContext가 스프링 컨테이너의 종류 중 하나이며 Bean의 생명주기 즉, Bean의 생성, 관계 설정등의 제어작업을 총괄한다.
BeanFactory는 스프링 컨테이너의 최상위 인터페이스이며 ApplicationContext는 그의 하위 인터페이스로 BeanFactory보다 더 많은 기능을 가지고 있으며 특별한 이유가 없다면 ApplicationContext를 사용하기를 권장한다고
공식문서에 명시되 있다.
Bean
Configuration Metadata
스프링 컨테이너는 하나의 구성정보(Configuration Metadata)를 제공받으며 이 구성정보는 하나이상의 객체를 개발자가 어떻게 생성 및 제어, 의존할지에 대한 정보를 가지고 있으며 @Configurer 애너테이션을 이용한 클래스로 구성한다.
(출처 : https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-metadata)
Bean
앞서 말한 객체 즉, 스프링 컨테이너에서 사용되는 모든 객체를 Bean이라 하며 Configuration Metadata안에서 Bean들의 제어를 @Bean애너테이션을 이용하여 메서드형태로 설정해주고 스프링 컨테이너에서 해당 Bean을 생성한다.
BeanDefinition
Configration Metadata에서 설정된 Bean들은 BeanDefinition 객체로 스프링 컨테이너로 제공되며 이 BeanDefinition 객체는 해당 Bean에 대한 여러 정보를 담고 있으며 그 정보를 토대로 스프링 컨테이너에 해당 Bean이 생성된다.
(공식사이트에서는 BeanDefinition을 하나의 레시피로 소개하며 스프링 컨테이너는 해당 레시피대로 Bean을 생성한다고 명시되있다.)
Table 1. The bean definition
Property | Explained in |
Class | Instantiating Beans |
Name | Naming Beans |
Scope | Bean Scopes |
Constructor arguments | Dependency Injection |
Properties | Dependency Injection |
Autowiring mode | Autowiring Collaborators |
Lazy initialization mode | Lazy-initialized Beans |
Initialization method | Initialization Callbacks |
Destruction method | Destruction Callbacks |
BeanDefinition 객체로 스프링 컨테이너에 제공하는 이유는 BeanDefinition으로인해 Bean생성시 자바 혹은 XML 어떤 언어가 사용되더라도 스프링 컨테이너에서 Bean을 생성 할수 있게 함이다.
(출처 : https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-definition)
예시
예시1. 다른언어의 Bean 사용
public class MemberTest {
public static void main(String[] args) {
//자바로 설정된 Bean
ApplicationContext javaAC = new AnnotationConfigApplicationContext(DependencyConfig.class);
//XML로 설정된 Bean
ApplicationContext XmlAC = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
//이후 컨테이너에 빈이 생성되며 객체로 사용가능
MemberService javaMemberService = javaAC.getBean("memberService", MemberService.class);
MemberService XmlMemberService = XMLAC.getBean("memberService", MemberService.class);
}
}
예시2. BeanDefinition에 정의된 Bean 정보 출력
public class BeanDefinitionTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(DependencyConfig.class);
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
// @Been 어노테이션을 사용할때만 출력 가능, @Component는 출력안됨
// 스프링 컨테이너에서 빈 생성방식이 달라서 일것으로 추정됨
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
System.out.println("beanDefinitionName = " + beanDefinitionName +
", beanDefinition = " + beanDefinition);
}
}
}
}
Configration metadata에 구성된 Bean들의 정보출력
Bean Scope
@Bean 애노테이션으로(혹은 다른방법으로) BeanDefinition이 만들어 지게 되면 해당 BeanDefinition은 하나의 레시피로써 이후 생성되는 모든 객체가 BeanDefinition에 명시된대로 생성이 된다.
이때 Bean Scope는 BeanDefinition에 의해 생성된 Bean의 범위를 제어할때 사용된다.
Scope | Description |
singleton | (Default) 각 Spring 컨테이너에 대한 단일 객체 인스턴스에 대한 단일 bean definition의 범위를 지정합니다. |
prototype | 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다. |
request | 웹 요청이 들어오고 나갈때 까지 유지되는 스코프이다. |
session | 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프이다. |
application | 웹의 서블릿 컨텍스와 같은 범위로 유지되는 스코프이다. |
websocket | 단일 bean definition 범위를 WebSocket의 라이프사이클까지 확장합니다. Spring ApplicationContext의 컨텍스트에서만 유효합니다. |
모든 Bean은 기본적으로 싱글톤이므로 싱글톤을 사용하기위한 설정은 딱히 필요없다.
예시
예시1. 싱글턴 스코프
@Configuration
public class DependencyConfig {
@Bean(name="memberService")
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemberRepository();
}
}
public class SingletonTest {
static AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(DependencyConfig.class);
static MemberService memberService1 = ac.getBean("memberService", MemberService.class);
static MemberService memberService2 = ac.getBean("memberService", MemberService.class);
public static void main(String[] args){
System.out.println(memberService1);
System.out.println(memberService2);
}
}
위와 같이 Bean에 Bean Scope 범위를 설정해 주지 않을경우 기본값인 싱글톤으로 스코프가 설정이되며 인스턴스화를 두번 진행하더라도 출력결과와 같이 참조변수의 값이 같다는 것을 확인할 수 있다.
예시2. 프로토타입 스코프
@Configuration
public class DependencyConfig {
@Bean(name="memberService")
@Scope("prototype")
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemberRepository();
}
}
특정 bean에 @Scope("prototype")를 설정해주면 다른 참조변수의 값들이 출력되는 것을 볼 수 있다.
(싱글톤 포스트 : https://mason-lee.tistory.com/55)
(스코프 출처 : https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes )
싱글톤 사용시 주의할점
싱글톤 방식은 여러 클라이언트가 하나의 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 무상태로 설계해야한다.
무상태 설계
- 특정 클라이언트가 객체의 값을 변경할 수 있으면 안된다.
- 읽기만 가능해야만 한다.
즉, 상수(final) 혹은 getter를 이용하여 readonly로 사용해야된다.
사전 로딩(Pre-Loading) 지연 로딩(Lazy-Loading)
사전로딩이란 스프링 컨테이너가 생성됨과 동시에 등록된 모든 BeanDefinition의 Bean을 생성하는 것이며 지연로딩은 스프링 컨테이너 생성 이후 특정 객체가 필요할때 스프링 컨테이너에서 Bean을 생성한다.
BeanFactory의 경우 default가 Lazy-Loading되어있으며 ApplicationContext는 Pre-Loading 방식이 default이다.
또한 ApplicationContext는 상위 인터페이스인 BeanFactory를 구현하므로 Lazy-Loading 방식도 사용가능며 이때 @Lazy 애노테이션을 Configuration metadata에서 사용하여 특정객체만을 지연로딩으로 만들 수도 있다.
BeanFactory (Lazy-Loading)
- On-Demand 방식으로 빈을 사용할 때 로딩
- 실제 빈을 사용할 때 로딩하기 때문에 가벼운 경량 컨테이너
ApplicationContext (Pre-Loading)
- 스프링 컨테이너 생성시 모든 빈을 로딩
- 문제가 있는 Bean이 있을 경우 오류를 사전에 확인 할 수 있다.
- 런타임 실행 시점에 등록된 모든 빈을 로딩하기 때문에 부담이 크다.
- @Lazy 애너테이션을 이용하여 BeanFactory의 Lazy Loading 방식을 사용할 수 있다.
그외 애노테이션
@Import
@Component
@Autowired
@ComponentScan
위의 애너테이션은 @Configuration과 @Bean 애너테이션과 관련있거나 비슷한 기능의 애너테이션들이며 Bean등록과 관련되있다.
마무리
Bean 생성시 큰 흐름을 정리하자면 아래와 같다.
- ApplicationContext(스프링 컨테이너)생성, 사전로딩 속성을 가짐 (BeanFactory는 지연로딩)
- Configuration Metadata(@Configuration)에 정의된 Bean 메서드들(@Bean)의 레시피인 BeanDefinition을 스프링 컨테이너로 보냄, 기본적으로 모든 Bean들의 스코프는 싱글톤임
- ApplicationContext는 사전로딩이므로 등록된 Bean들을 BeanDefinition에따라 생성 이후 의존관계 형성
- 이후 비즈니스로직에서 생성된 객체들을 사용
'Java > Spring & Spring Boot' 카테고리의 다른 글
Entity와 Mapper (0) | 2023.04.13 |
---|---|
DTO (Data Transfer Object) 과 Validation (0) | 2023.04.12 |
Servlet과 JSP 그리고 MVC(Model, View, Controller) (0) | 2023.04.11 |
관점 지향 프로그래밍 (Aspect Oriented Programming, AOP) (0) | 2023.04.09 |
POJO (0) | 2023.04.03 |