https://velog.io/@sorzzzzy/series/Spring-Boot-RoadMap-1
스프링(Spring)
스프링은 자바 기반의 웹 어플리케이션을 만들 수 있는 프레임워크이다.
스프링 특징
스프링은 경량 컨테이너로서 자바 객체를 직접 관리한다. 각각의 객체 생성, 소멸과 같은 생명주기(Life cycle)을 관리하며 스프링으로부터 필요한 객체를 얻어올 수 있다.
스프링의 가장 큰 특징으로 IoC와 DI가 많이 언급된다고 하는데, 둘의 간단한 개념은 아래와 같다.
- 제어의 역전(IoC, Inversion of Control)
객체의 생성을 특별한 관리 위임 주체에게 맡기는 것. IoC가 적용될 경우 사용자는 객체를 직접 생성하지 않으며 객체의 생명주기를 컨트롤하는 주체는 다른 주체가 된다. - 의존성 주입(DI, Dependency Injection)
사용하려는 객체를 외부(spring)에서 생성해서 주입시켜주는 방식. DI를 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아진다.
스프링부트(Spring boot)
스프링 부트는 스프링을 더 쉽게 이용하기 위한 도구라고 볼 수 있다. 스프링은 세팅 해야할 요소들이 많고 복잡했지만, 스프링 부트는 이를 간단하게 하여 스프링 개발을 조금 더 쉽게 만들어주는 역할을 하고있다.
📌 스프링과 스프링부트의 차이점을 자세히 알아보자!
https://dzone.com/articles/spring-vs-spring-boot
스프링 빈(Bean)
스프링 빈(Bean)이란 스프링 IoC컨테이너에 의해서 만들어지고, 관리되는 자바 객체를 말한다.
직접 new를 해서 만든 객체는 빈이 아니다.
빈의 생명주기 (Life cycle)
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료
스프링 컨테이너(Spring Container) / 스프링 IoC 컨테이너
기존 자바 프로그래밍에서는 class를 생성하고 new을 입력하여 원하는 객체를 직접 생성하여 사용했었다.
Spring에서는 직접 new를 이용하여 생성한 객체가 아니라, 스프링 컨테이너가 관리하는 자바 객체를 사용한다. 여기서 말하는 자바 객체가 바로 빈(Bean)이다. 스프링 컨테이너는 자바 객체의 생명주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공하는 역할을 한다. 그리고 위에서 배운 IoC와 DI의 원리가 이 스프링 컨테이너에 적용된다.
스프링 컨테이너의 종류
- 빈 팩토리(BeanFactory)
빈 팩토리(BeanFactory)는 빈을 등록하고 생성하고 조회하고 돌려주는 등 빈을 관리하는 역할을 한다. getBean() 메소드를 통해 빈을 인스턴스화 할 수 있다.
빈 팩토리가 빈의 정의는 즉시 로딩하는 반면, 빈 자체가 필요하게 되기 전까지는 인스턴스화를 하지 않는다.
+
커스텀 초기화 메소드(initialization method)와 소멸 메소드(destruction method)를 호출함으로써 빈의 생명주기에 개입할 수 있다.
org .springframework.beans.factory.xml.XmlBeanFactory 가 가장 유용한데, XML 파일에 기술돼있는 정의를 바탕으로 빈을 로딩한다.
BeanFactory factory = new XmlBeanFactory(new FileInputStream( “beans.xml ”));
MyBean myBean = (MyBean) factory.getBean( “myBean ”);
빈 팩토리에게 XML 파일로부터 빈에 대한 정의를 읽어오라고 알려준다 . 빈 팩토리가 빈을 인스턴스화하는 것은 아니고 빈은 빈 팩토리에 “늦게 ”로딩되는데, 이 말은 빈 팩토리가 빈의 정의는 즉시 로딩하는 반면, 빈 자체가 필요하게 되기 전까지는 인스턴스화 하지 않는다.
getBean()이 호출되면, 팩토리는 의존성 주입(DI)을 이용해 빈을 인스턴스화하고 빈의 특성을 설정 시작. 여기서 빈의 일생이 시작된다.
- 애플리케이션 컨텍스트(ApplicationContext)
BeanFactory와 유사한 기능을 제공하지만 좀 더 많은 기능을 제공한다.
Bean을 등록, 생성, 조회, 반환, 관리하는 기능은 BeanFactory와 같으나 차이점이 있다.
BeanFactory는 처음으로 getBean()이 호출된 시점에서야 해당 빈을 생성하고 (Lazy Loading, 게으른 호출), ApplicationContext는 컨텍스트 초기화 시점에 모든 싱글톤 빈을 미리 로드하여 애플리케이션 가동 후에는 Bean을 지연없이 얻을 수 있다. (미리 Bean을 생성해 놓아 빈이 필요할 때 즉시 사용할 수 있도록 보장한다.)
그 외에도 ApplicationContext는 국제화가 지원되는 텍스트 메시지 관리, 이미지 같은 파일 자원을 로드, 리너스로 등록된 빈에게 이벤트 발생 알림 등 부가적인 기능을 갖고 있다.
스프링 빈 등록
빈을 등록하는 방법에는 xml파일에 직접 등록하는 방법과, 어노테이션으로 등록하는 방법이 있다.
❓ 어노테이션(Annotation)이란? 사전적 의미로는 주석이라는 뜻이다. 자바 공부를 하면서 오버라이딩을 할 때 @override 어노테이션을 이용하여 부모 클래스에 있는 메서드를 오버라이드 했다는 걸 명시적으로 선언했었다. 이렇게 특별한 의미를 부여하는 것 이외에도 특별한 기능을 수행하도록 할 수 있다.
- Compenent Scanning
@CompenentScan 어노테이션과 @Component 어노테이션을 사용해서 빈을 등록하는 방법이다.
간단히 말하면 @ComponentScan 어노테이션은 어느 지점부터 컴포넌트를 찾으라고 알려주는 역할을 하고 @Component는 실제로 찾아서 빈으로 등록할 클래스를 의미한다.
스프링 IoC 컨테이너가 IoC 컨테이너를 만들고 그 안에 빈을 등록할 때 사용하는 인터페이스들을 라이프 사이클 콜백 이라고 부른다.
라이프 사이클 콜백 중에는 @Component 어노테이션을 찾아서 이 어노테이션이 붙어있는 모든 클래스의 인스턴스를 생성해 빈으로 등록하는 작업을 수행하는 어노테이션 프로세서가 등록되어 있다.
Spring Boot 프로젝트에서 @ComonentScan 애노테이션이 붙어있는 클래스가 이에 해당한다.
@Component 이외에도 @Controller, @Service, @Repository, @Configuration들 역시 자동으로 스프링 컨테이너에 등록된다.
- 빈 설정 파일에 직접 빈을 등록하는 방법
자바 설정파일은 자바 클래스를 생성해서 작성할 수 있으며 일반적으로 xxxxConfiguration와 같이 명명한다.
그리고 클래스에 @Configuration 애노테이션을 붙인다.
그 안에 @Bean 애노테이션을 사용해 직접 빈을 정의한다.
@Configuration
public class SampleConfiguration {
@Bean
public SampleController SampleController() {
return new SampleController;
}
}
sampleController()에서 리턴되는 객체가 IoC 컨테이너 안에 빈으로 등록된다.
물론 이렇게 빈을 직접 정의해서 등록하면 @Component 애노테이션을 붙이지 않아도 된다.
@Configuration 애노테이션을 보면 이 애노테이션도 @Component를 사용하기 때문에 @ComponentScan의 스캔 대상이 되고 그에 따라 빈 설정파일이 읽힐때 그 안에 정의한 빈들이 IoC 컨테이너에 등록되는 것이다.
의존성 주입(DI, Dependency Injection)
위에서 잠깐 언급되었던 의존성 주입.
컴포넌트 스캔으로 빈을 등록하고 나면 의존 관계를 만들어주어야한다. 설정 파일에서 수동으로 의존관계를 주입할 수도 있지만, 자동으로 빈을 등록할 때에는 @Autowired라는 어노테이션을 사용해야 한다.
@Autowired 어노테이션을 이용한 의존성 주입 방법에는 3가지가 있다.
- 생성자 주입
이름 그대로 생성자를 통해서 의존 관계를 주입받는 방법이다.
@Component
public class OrderServiceImpl implements OrderService {
private final DiscountPolicy discountPolicy;
@Autowired // 생략 가능
public OrderServiceImpl(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(int age, String itemName, int itemPrice) {
int discountPrice = discountPolicy.discount(age, itemPrice);
return new Order(itemName, itemPrice, discountPrice);
}
}
- setter 주입
setter를 통해 의존관계를 주입하는 방법이다. 주로 선택이나 변경 가능성이 있는 의존 관계에 사용된다.
setter 메소드 위에 @Autowired를 붙여줌으로써 의존관계를 주입할 수 있다.
@Component
public class OrderServiceImpl implements OrderService {
private DiscountPolicy discountPolicy;
@Override
public Order createOrder(int age, String itemName, int itemPrice) {
int discountPrice = discountPolicy.discount(age, itemPrice);
return new Order(itemName, itemPrice, discountPrice);
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
- 필드 주입
필드에 바로 주입하는 방법이다.
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private DiscountPolicy discountPolicy;
@Override
public Order createOrder(int age, String itemName, int itemPrice) {
int discountPrice = discountPolicy.discount(age, itemPrice);
return new Order(itemName, itemPrice, discountPrice);
}
}
위의 3가지 방법 중 가장 권장되는 방법은 생성자를 통한 주입이라고 한다.
필수적으로 사용해야하는 의존성 없이는 인스턴스를 만들지 못하도록 강제할 수 있기 때문이다.
예를들어, SampleController가 SampleRepository 없이는 제대로 동작할 수 없다면 SampleController 입장에서 SampleRepository는 반드시 있어야 하는 객체이다. 그것을 강제할 수 있는 가장 좋은 방법이 생성자 주입 방법을 쓰는것이다.
하지만 필드와 setter 주입 방법 역시 필요한 때가 있다.
A 클래스와 B 클래스가 순환 참조 관계이고 둘 다 생성자 주입을 사용한다면 A와 B중 어떤 인스턴스도 생성할 수 없고 결과적으로 어플리케이션이 실행조차 되지 않는다.
가급적이면 순환 참조를 피하는게 좋지만 어쩔수 없는 상황이라면 필드나 setter 주입 방법을 사용할 수 있다.
빈 생명주기 콜백
- 스프링에서 제공하는 인터페이스 (InitializingBean, DisposableBean)
@Component
public class TestComponent implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception { // 의존관계 주입이 끝나면 호출해주는 콜백
System.out.println("초기화 콜백 테스트");
}
@Override
public void destroy() throws Exception {
System.out.println("소멸전 콜백 테스트");
}
}
초기화 콜백: InitializingBean은 afterPropertiesSet()메소드로 초기화를 지원한다.
소멸 콜백: DisposableBean은 destroy() 메소드로 소멸을 지원한다.
단점
스프링 전용 인터페이스에 코드가 의존한다.
메소드를 오버라이드하기 때문에 메소드명 변경 불가능하다.
코드를 커스터마이징할 수 없는 외부 라이브러리에 적용 불가능하다.
+
이 방식은 스프링 초창기에 나온 방법들이라 지금은 거의 사용하지 않는다고 한다.
- 설정 정보에서 초기화 메소드, 종료 메소드 지정하는 방법
public class ExapmleBean {
// 중략
public void init() throws Exception {
// 초기화
}
public void close() throws Exception {
// 메모리 반납, 연결 종료와 같은 과정
}
@Configuration
static class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public ExampleBean exampleBean() {
// 생략
}
}
장점
메소드 이름을 자유롭게 부여 가능하다
스프링 코드에 의존하지 않는다.
설정 정보를 사용하기 때문에 코드를 커스터마이징 할 수 없는 외부 라이브러리에서도 적용 가능
단점
Bean 지정시 initMethod와 destroyMethod를 직접 지정해야하는 번거로움이 있다.
❗ 종료 메소드 추론
@Bean의 destroyMethod 속성에는 아주 특별한 기능이 있다.
라이브러리는 대부분 close, shutdown이름을 종료 메소드로 사용한다.
@Bean의 destroyMethod는 기본값이 (inferred)(추론)으로 되어있다.
이 추론 기능은 close, shutdown이라는 이름의 메소드를 자동호출 해준다. (이름 그대로 종료 메소드를 추론해 호출해준다.)
따라서 직접 스프링 빈으로 등록하면 종료 메소드를 따로 적어주지 않아도 잘 동작한다. 추론 메소드를 사용하기 싫다면 destroyMethod = “” 이렇게 빈 공백을 지정해주면 된다.
- @PostConsturct, @PreDestroy 어노테이션
public class ExapmleBean {
// 중략
@PostConstruct
public void init() throws Exception {
// 초기화
}
@PostDestroy
public void close() throws Exception {
// 메모리 반납, 연결 종료와 같은 과정
}
@Configuration
static class LifeCycleConfig {
@Bean
public ExampleBean exampleBean() {
// 생략
}
}
장점
어노테이션만 붙이면 되기 때문에 편리하다.
스프링에 종속적인 기술이 아니라 자바 표준 코드이기 때문에 다른 컨테이너에서도 동작한다.
컴포넌트 스캔과 잘 어울린다.
단점
커스터마이징이 불가능한 외부 라이브러리에서 사용 불가능하다.
+
이 방식이 최신 스프링에서 권장하는 방법이라고 한다.
참조
스프링 특징
http://melonicedlatte.com/2021/07/11/174700.html
https://goddaehee.tistory.com/156
빈의 생명주기
http://melonicedlatte.com/2021/07/11/174700.html
https://goddaehee.tistory.com/156
https://jaimemin.tistory.com/1787
스프링 컨테이너
https://steady-coding.tistory.com/458
스프링 컨테이너의 종류
https://jjunii486.tistory.com/84
https://steady-coding.tistory.com/459
스프링 빈 등록
https://atoz-develop.tistory.com/entry/Spring-스프링-빈Bean의-개념과-생성-원리
빈 생명주기 콜백
https://jaimemin.tistory.com/1787
'Study > Spring boot' 카테고리의 다른 글
[Spring boot] Snake_case to camelCase (0) | 2022.03.22 |
---|---|
[Spring boot] 의존성 주입(DI) 복습 (0) | 2022.02.23 |
[Spring boot] @RequestParam과 @PathVariable (0) | 2022.01.28 |
[Spring boot] Lombok(롬복) (0) | 2022.01.26 |
[Spring] Spring mvc life cycle (0) | 2022.01.02 |