Spring

[Spring Boot] 01. Spring Boot Core (1) - Spring Boot 소개, IoC, DI

Joo.v7 2024. 11. 5. 23:27

1. Spring Boot 소개

  • Spring Boot
  • Spring Framework
  • Spring Framework Modules
  • Spring Framework 특징
  • Spring Boot 목표
  • Spring Boot 시작
  • Spring Boot 기능
  • Spring Boot vs Spring Framework

2. Spring Boot 프로젝트 생성 및 실행

  • Spring Boot 프로젝트 생성
  • Spring Boot 프로젝트를 실행하는 여러가지 방법

3. Spring IoC

  • IoC: Inversion of Control (제어 역전)
  • Bean Factory
  • ApplicationContext
  • Spring Bean
  • Bean 등록
  • Stereotype Annotation
  • Spring Bean 객체의 생명주기 설정

4. 의존성 주입 (Dependency Injection)

  • DIP, IoC, DI 관계
  • Spring DI (Dependency Injection)

1. Spring Boot 소개

Spring Boot

  • Spring Boot == Framework.
  • Framework: 원하는 기능을 구현하기 위하여, 일정한 형태(골격)을 제공.
  • Library: 함수(기능)의 집합.

Spring Framework

  • Spring is a opensource lightweight application framework that makes it easy to create Java enterprise applications
  • 표준: Java enterprise 개발에 있어 사실상의 표준이다. Java EE를 기반으로 하며, 많은 Java 개발자들이 익숙하게 사용하고 있는 기술과 패턴을 채택.
  • 신뢰성: 오랜 기간 동안 안정적으로 유지되어 왔으며, Pivotal과 같은 강력한 기업이 지원하는 강력한 커뮤니티가 있다.
  • 생태계: Spring Boot/Cloud/Date 등 다양한 프로젝트와 라이브러리를 통해 다양한 영역에서 솔루션을 제공함.

 

Spring Framework Modules

 

Spring Framework 특징

  1. 경량 컨테이너로서, Spring Bean을 직접 관리한다.
    • Spring Bean 객체와 Life Cycle을 관리.
      (Spring Bean: Spring Container가 관리하는 중요 객체)
    • Container: Spring Bean 객체의 생성/보관/제거에 관한 모든 일을 처리.
  2. POJO(Plain Old Java Object) 기반의 Framework
    • 일반적인 J2EE Framework와 비교하여, 특정한 interface를 구현하거나 상속을 받을 필요가 없다.
      (c.f. Servlet API는 HttpServlet을 상속 받아야함)
    • 기존에 존재하는 라이브러리를 사용하기 편리하다 (자바 사용에 불편함이 없다)
  3. 제어 역전(IoC: Inversion of Control)
    • 제어권이 개발자가 아니라 Framework에 있다.
    • DI: IoC의 구현 방식으로 객체의 의존성을 외부에서 주입하는 방법.
  4. 관점 지향 프로그래밍(AOP: Aspect-Oriented Programming)을 지원
    • 복잡한 비즈니스 영역의 문제공통된 지원 영역의 문제를 분리할 수 있다.
    • 문제 해결을 위한 집중.
  5. 서비스 추상화 (PSA: Portable Service Abstractions)
    • 개발자가 특정 환경이나 기술에 종속되지 않고, 일관된 방식으로 서비스를 사용할 수 있도록 추상화를 제공.
    • 코드를 간단하게 유지하면서도 다양한 기술과 서비스를 쉽게 전환 가능. (구현체 - implements만 갈아끼우면 됨)
더보기

Spring의 3대 요소

Spring의 3대 요소
  • IoC: 객체의 생성 및 생명 주기를 개발자가 직접 관리하지 않고, 프레임워크(Spring 컨테이너)가 관리
    ex) DI
  • AOP: 핵심 로직과 공통 기능(로깅, 트랜잭션 등)을 분리하여, 코드 중복 없이 횡단 관심사를 모듈화하는 프로그래밍 기법
    ex) @ControllerAdvice에서 @ExceptionHandler로 동일한 에러 처리
  • PAS: 다양한 기술(JDBC, 트랜잭션, 스케줄링 등)을 일관된 방식으로 사용할 수 있도록 인터페이스를 제공하는 Spring의 철학
    ex) @Controller와 같이 편의성 제공

 

Spring Boot 목표

  • java -jar 로 실행이 가능해야 한다.
  • ex) java -jar target/demo-0.0.1-SNAPSHOT.jar

 

Spring Boot 시작

  • Spring Framewok를 더 편하게 사용하기 위해서, Spring Boot 프로젝트 시작.
  • Spring Boot는 내부적으로 Spring Framework 기반으로 동작.

 

Spring Boot의 기능

  • 단독으로 실행 가능한 application 생성 (Framework도 5.0부터 가능)
    • 실행형 jar, 실행형 war
  • 내장형 Web Application Server 지원 (Framework도 5.0부터 가능)
    • Tomcat, Jetty, Undertow, Netty for WebFlux
  • 기본 설정된 Starter 모듈
    • 의존성 (library dependency)
    • 버전 호환성 보장 (dependencyManagement)
    • 자동 설정 (Auto Configuration)
  • 상용화에 필요한 통계, 상태점검 외부설정 지원 (Framework도 5.0부터 가능)
    • Actuator (Health, metrics)
    • 외부 설정

 

Spring Boot vs Spring Framework

  • 라이브러리 의존성을 pom.xml에 직접 설정할 필요가 없다.
    • spring-boot-starter-{module}만 설정하면 필요한 라이브러리 설정됨.
  • 버전 정보를 직접 설정하고 테스트하지 않아도 된다.
    • spring-boot-starter-parent에서 spring 모듈의 버전 및 3rd Party 라이브러리 버전도 제공.
  • Web Application Server에 배포하지 않아도 된다.
    • spring boot에서는 내장형 Web Application Server를 제공하기 때문에 이와 관련해서 따로 설정할 필요없다.

 


 

2. Spring Boot 프로젝트 생성 및 실행

Spring Boot 프로젝트 생성

 

Spring Boot 프로젝트를 실행하는 여러가지 방법

  • Executable Jar/War
    ex) mvn package / gradle bootjar
  • Build Tool
    ex) mvn spring-boot: run / gradle bootRun

 


 

3. Spring IoC

IoC: Inversion of Control (제어 역전)

  • 프로그램의 제어 흐름이 프로그램 코드에 의해 결정되는 것이 아니라, 외부(ex. Framework)에서 결정된다는 개념
  • 제어(아래 2개)를 Framework에게 맡긴다.
    • 프로그램의 흐름
    • 객체의 생성
  • 개발자는 프로그램의 흐름이나, 객체 생성과 관련된 코드를 직접 작성하지 않는다.
  • 개발자는 Framework가 제공하는 정의 방법을 사용하여 코드를 정의만 한다.
  • Framework는 이 정의를 보고, 객체를 생성하고 코드가 동작하는 순서를 결정하여 실행한다.

 

Bean Factory

  • Spring Framework의 핵심 컨테이너 중 하나이며, 스프링의 스펙이다.
  • Bean Factory는 Spring IoC 컨테이너의 가장 기본적인 interface로, Bean의 생성 및 관리를 담당한다.
  • 이를 통해 DI를 구현하며, Spring Application에서 객체의 생명주기와 구성을 관리하는 중요한 역할을 담당.

 

ApplicationContext

  • Spring Framework에서 중앙 interface로, application의 설정 정보를 관리한다.
  • Bean Factory 기능을 확장한 것으로, application의 이벤트 처리 / 리소스 로딩 / 메시지 지원 등의 기능을 제공.
  • BeanFactory, ApplicationContext, Ioc Containerm, Spring Container 등 다 비슷한 개념이다.

 

Spring Bean

  • ApplicationContext에 의해 관리되는 객체.
  • Spring Framework에서 Application의 객체들을 Bean으로 등록하고, 이들의 생성/생명주기/의존성 관리 등을 컨테이너가 담당함.
  • 일반적으로 annotation 등으로 DI가 이루어짐.

 

* JavaBeans

  • Spring Bean과 전혀 다른 개념.
  • Java Bean은 재사용 가능한 Component로, 기본 생성자를 가지며, Getter와 Setter 메소드를 통해 property에 접근할 수 있는 단순한 POJO를 의미.

 

* @ApplicationRunner

  • Spring Application이 시작될 때 같이 실행되는 코드.

 

Bean 등록

1. @Configuration + @Bean 

  • 클래스에 @Configuration을 붙이고, Bean에 등록할 객체를 반환하는 메소드에 @Bean을 붙인다.
    • @Configuration은 클래스 내의 @Bean이 붙은 메소드를 통해 Bean을 정의하고 생성.
  • 클래스가 하나 이상의 @Bean 메소드를 포함하고 있으며, 이 메소드들이 Application Context에 Bean 정의를 제공한다는 걸 의미한다. (@Bean 메소드는 프로그래밍 방식으로 Bean 인스턴스를 생성하고 구성함)

2. @Component

  • Bean에 등록한 클래스에 @Component를 붙인다.
    • 클래스 자체를 Bean으로 등록.
  • 클래스 레벨에서 사용되며, 해당 클래스의 instance를 Spring 컨테이너가 관리하는 Bean으로 등록.
    (주로 Service, Repository, Controller와 같은 Stereotype Component에 사용됨)
/* 1. @Configuration + @Bean */
@Configuration
public class config {

    @Bean
    OrderReceiverBean orderReceiverBean() {
        return new OrderReceiverBean();
    }


    @Bean
    PaymentProcessorBean paymentProcessorBean() {
        return new PaymentProcessorBean();
    }
}


/* 2. @Component */
@Component
public class DeliveryServiceBean implements Employee {

    @Override
    public void start() {
        System.out.println("배달 한다");
    }

}

 

 

Stereotype Annotation

  • 개발자가 application의 아키텍처에서 특정 클래스가 어떤 역할을 하는지를 명확히 표현하는 데 사용.
  • 기능적으로 @Component와 동일한 역할.
  • 역할에 맞는 annotation을 사용하여, 코드의 가독성과 유지 보수에 도움.
  • @Controller: 클라이언트의 요청을 처리하고, View를 반환하는 역할. 
  • @Service: 비즈니스 로직을 처리.
  • @Repository: DB와의 상호작용을 담당.

 

 

Spring Bean 객체의 생명주기 설정

 1) Bean Scope

  • 생명주기 종류
    • singleton: ApplicationContext가 시작될 때 초기화되고, ApplicationContext가 종료될 때 소멸됨. 이는 애플리케이션 전체에서 단 하나의 인스턴스만을 유지함을 의미.
    • prototype: 요청될 때마다 새로운 인스턴스가 생성. 생성된 각 인스턴스는 호출자에 의해 관리되며, Spring 컨테이너는 생성 이후에 이 인스턴스를 관리하지 않음.
    • request(web): HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.
    • session(web): HTTP Session과 동일한 생명주기를 가지는 스코프
    • application(web): 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
    • websocket(web): 웹 소켓과 동일한 생명주기를 가지는 스코프

 

 2) Singleton과 Prototype

  • Singleton: Spring Bean 객체의 default.
  • Prototype: Bean의 생성과 DI 까지는 Spring Container가 관리해주지만, 소멸은 관리해주지 않는다.

(좌) Singleton, (우) Prototype

 

3) Bean의 생명 주기에 개입하기

  • Spring Bean 생명주기
    Spring IoC 컨테이너 생성 -> Spring Bean 생성 -> DI -> (초기화 콜백 메소드 호출) -> 사용 -> (소멸 전 콜백 메소드 호출) -> 스프링 종료
  • Bean의 생명주기에 개입하는 3가지 방법
    1. Interface인 InitializingBean, DisposableBean을  구현 (afterPropertiesSet(), destroy() 오버라이딩)
    2. @Bean annotation 설정 사용 (@Bean의 scope에 init과 destroy 메소드 직접 지정)
    3. @Postconstruct, @PreDestroy 사용. -> 사용 권장
더보기
/* 1. InitializingBean, DisposableBean을 구현 (afterPropertiesSet(), destroy() 메소드 오버라이드) */

public class EnglishGreeting implements Greeting, InitializingBean, DisposableBean {

    public EnglishGreeting() {
        System.out.println("english java constructor");
    }

    @Override
    public void sayHello() {
        System.out.println("hello world");
    }

	// DisposableBean 구현
    @Override
    public void destroy() {
        System.out.println("english DisposableBean destroy");
    }

	// InitializingBean 구현
    @Override
    public void afterPropertiesSet() {
        System.out.println("english InitializingBean afterPropertiesSet");
    }
}


/* 2. @Bean annotation 설정 사용 */

@Configuration
public class GreetingConfig {

    @Bean(initMethod = "customInit", destroyMethod = "customDestroy")
    Greeting englishGreeting() {
        return new EnglishGreeting();
    }
}

public class EnglishGreeting implements Greeting {

    public EnglishGreeting() {
        System.out.println("eng java constructor");
    }

    @Override
    public void sayHello() {
        System.out.println("hello world");
    }

    @Override
    public void customInit() {
        System.out.println("eng custom init");
    }

    @Override
    public void customDestroy() {
        System.out.println("eng custom custom destroy");
    }
    
}


/* 3. @PostConstruct, @PreDestroy 사용 */
public class EnglishGreeting implements Greeting {


    public EnglishGreeting() {
        System.out.println("eng java constructor");
    }

    @Override
    public void sayHello() {
        System.out.println("hello world");
    }
    
    @PostConstruct
    public void customPostConstruct() {
        System.out.println("env PostConstruct");
    }

    @PreDestroy
    public void customPreDestroy() {
        System.out.println("env preDestroy");
    }
    
}

 

 

위 3가지 방법을 동시에 실행하면?

  • 순서
    1. @PostConstruct
    2. afterPropertiesSet()
    3. custom init() method
    4. @PreDestroy
    5. destroy()
    6. custom destroy() method

 

요약

더보기

IoC

  • 개발 편의성을 올려주기 위한 디자인 패턴의 하나
  • 프로그램의 제어 흐름이 프로그램 코드에 의해 결정되는 것이 아니라, 외부에서 결정
  • 주요 객체의 생성 및 객체 간의 의존성을 사용자가 관리하지 않는다.

Spring

  • Ioc 를 지원해주는 프레임워크 중 하나로 Bean 이라는 개념을 이용해서 이를 편하게 하고 있음

ApplicationContext

  • Spring 에서 Ioc 패턴을 유지해주는 구현체
  • Bean 의 생성, 등록, 관리를 해준다.

Bean

  • ApplicationContext 에 의해서 관리되는 객체
  • Spring 애플리케이션은 다양한 Bean 들이 상호작용하는 형태로 구성된다.
  • Bean 은 생명주기를 가지며, 우리는 중간에 개입하는 메소드를 생성할 수 있다.

 


 

4. 의존성 주입 (Dependency Injection)

DIP, IoC, DI 관계

  • DIP는 원칙이고, IoC는 원칙을 지키기 위한 패턴이고, DI는 패턴을 구현한 방법 중 하나이다.
  • DIP: 상위 모듈이 하위 모듈에 의존관계를 가지면 안된다는 원칙.
  • IoC: 프로그램의 흐름을 제어하는 주체가 프로그래머가 아닌 외부 시스템(ex. Framework)에 의해 결정된다는 개념.
  • DI: 객체가 필요로 하는 의존성을 외부에서 직접 주입받는 것.

 

Spring DI (Dependency Injection)

  • 프로그래밍에서 구성 요소간의 의존관계가 소스코드 내부가 아닌 외부의 설정파일 등을 통해 정의되게 하는 디자인 패턴 중 하나.
  • IoC 패턴 중의 하나로, Object 간의 의존성을 낮추고 외부에서 객체를 생성하고 전달한다.
  • DI는 디자인 패턴으로 IoC의 구현이다.

@AutoWired

  • 객체가 필요한 곳을 알려준다.
  • Spring에서 DI(등록한 Bean을 사용)하는 3가지 방법.
    1. Constructor Injection
    2. Setter Injection
    3. Field Injection
더보기
/* 1. Constructor Injection */
@Component
public class AppStartupRunner implements ApplicationRunner{

    private Greeting greeting;

    // 생성자에서 주입.
    @Autowired
    public void setGreeting(Greeting greeting){
        this.greeting = greeting;
    }
    
    @Override
    public void run(ApplicationArguments args){
        greeting.sayHello();
    }
}


/* 2. Setter Injection */
@Component
public class AppStartupRunner implements ApplicationRunner{

    private Greeting greeting;
    
   // setter 메소드에서 주입.
    @Autowired
    public void setGreeting(Greeting greeting){
    	this.greeting = greeting;
    }

    @Override
    public void run(ApplicationArguments args){
        greeting.sayHello();
    }
}


/* 3. Field Injection */
@Component
public class AppStartupRunner implements ApplicationRunner{

    @Autowired
    private Greeting greeting;

    @Override
    public void run(ApplicationArguments args){
        greeting.sayHello();
    }
}

 

 

* Injection할 Bean을 더 명확하게 하고 싶다면?

@Primary

  • Spring에서 자동 와이어링 시 여러 Bean 후보가 있을 경우, 해당 Bean이 우선적으로 선택.

@Qualifier

  • 자동 주입 시 특정 Bean을 명시적으로 지정할 수 있게 해줌. 이름으로 Bean을 구분하여 주입할 때 사용.
/* 1. @Primary */

/* (1) @Configuration + @Bean */
@Configuration
public class GreetingConfig {

    @Primary // 우선 순위 지정
    @Bean
    Greeting englishGreeting() {
        return new EnglishGreeting();
    }

    @Bean
    Greeting koreanGreeting() {
        return new KoreanGreeting();
    }

    // 이름 등록
    @Bean("myCustomBeanName")
    Greeting japaneseGreeting() {
        return new JapaneseGreeting();
    }

}

/* (2) @Component */
@Primary // @Component 를 사용하는 경우에도 @Primary 를 사용할 수 있다.
@Component("myCustomBeanName") // 이름 등록
public class EnglishGreeting implements Greeting {

    @Override
    public void sayHello() {
        System.out.println("hello world");
    }

}


/* 2. @Qualifier */

public AppStartupRunner(Greeting greeting,
                            Greeting koreanGreeting,
                            Greeting japaneseGreeting,
                            Greeting myCustomBeanName,
                            //TODO-2 @Bean 에서 이름을 지정하지 않으면 자동으로 적절한 이름을 선택한다(메소드명)
                            @Qualifier("koreanGreeting") Greeting qKoreanGreeting,
                            @Qualifier("myCustomBeanName") Greeting qMyCustomBeanName,
                            //TODO-3 이름을 지정하는 경우 자동으로 이름을 지정하지 않는다.
                            //@Qualifier("japaneseGreeting") Greeting qJapaneseGreeting,
                            KoreanGreeting cKoreanGreeting,
                            JapaneseGreeting cJapaneseGreeting) {
        this.greeting = greeting;
        this.koreanGreeting = koreanGreeting;
        this.japaneseGreeting = japaneseGreeting;
        this.myCustomBeanName = myCustomBeanName;
        this.qKoreanGreeting = qKoreanGreeting;
        this.qMyCustomBeanName = qMyCustomBeanName;
        this.cKoreanGreeting = cKoreanGreeting;
        this.cJapaneseGreeting = cJapaneseGreeting;
    }

 


 

출처: https://nhnacademy.dooray.com/share/pages/ABFgz8KgRv62A85x9KClUw

 

Spring Boot Core

 

nhnacademy.dooray.com