스프링 원리

x·2023년 2월 8일
1

spring

목록 보기
3/3


스프링 역사

로드 존슨이 책 출간함. EJB 문제점 지적

스프링의 핵심 코드 상당수는 유겐 휠러가 개발

EJB라는 겨울은 넘어 새로운 시작이라는 의미로 스프링이라고 지음

스프링 프레임워크

핵심 기술: 스프링 DI 컨테이너, AOP, 이벤트, 기타
• 웹 기술: 스프링 MVC, 스프링 WebFlux
• 데이터 접근 기술: 트랜잭션, JDBC, ORM 지원, XML 지원
• 기술 통합: 캐시, 이메일, 원격접근, 스케줄링
• 테스트: 스프링 기반 테스트 지원
• 언어: 코틀린, 그루비
• 최근에는 스프링 부트를 통해서 스프링 프레임워크의 기술들을 편리하게 사용

스프링 부트

스프링을 편리하게 사용할 수 있도록 지원, 최근에는 기본으로 사용
• 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성
• Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버를 설치하지 않아도 됨
• 손쉬운 빌드 구성을 위한 starter 종속성 제공
• 스프링과 3rd parth(외부) 라이브러리 자동 구성
• 메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공
• 관례에 의한 간결한 설정

스프링의 핵심

객체 지향 언어인 자바 기반 프레임워크. 객체 지향 언어가 가진 특징을 살리는 프레임워크임. 좋은 객체 지향 앱을 개발할 수 있게 해줌

다형성이 중요함

다형성의 실세계 비유

역할(인터페이스)과 구현(객체)으로 세상을 구분해본다

키보드가 바뀌어도 usb 포트 인터페이스만 맞으면 됨

클라이언트는 인터페이스만 알면 됨, 내부 구조를 몰라도 됨, 변경되어도 영향이 없음

구현보다는 인터페이스가 우선임

자바 언어의 다형성

클라이언트는 인터페이스에만 의존

다형성이 가장 중요. 스프링은 다형성을 극대화해서 이용할 수 있게 도와줌. 제어의 역전, 의존관계 주입은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원함

스프링을 쓰면 구현을 편리하게 변경할 수 있음

SOLID

SRP 단일 책임 원칙

한 클래스는 하나의 책임만 가져야 함

하나라는 건 상황에 따라 클 수도 작을 수도 있음

중요한 기준은 변경이 있을 때 파급이 적으면 원칙을 잘 따른 것임

OCP 개방 폐쇄 원칙

확장에는 열려있고 변경에는 닫혀있어야 함

다형성을 생각해보면 인터페이스를 만들고 그걸 구현하는 객체를 여러 개 만들어서 기존 코드 변경 없이 갈아 끼울 수 있다

하지만 객체를 갈아끼우는 것을 코드 내에서 직접 하면 변경이 발생하게 되는 문제가 있다. 이를 해결하기 위해 스프링 컨테이너에 스프링 빈을 등록하고 제어의 역전을 통해 컨테이너가 의존성을 주입하게 하면 된다.

LSP 리스코프 치환 원칙

상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다

LSP가 지켜지지 않으면 OCP도 위배됨

ISP 인터페이스 분리 원칙

인터페이스를 잘게 쪼개야함

인터페이스가 명확해지고 대체 가능성이 높아짐

DIP 의존관계 역전 원칙

추상화에 의존하고 구체화에 의존하면 안된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나다.

클래스 대신 인터페이스에 의존하기

비즈니스 요구사항과 설계

스프링 없이 자바로만 개발하고 스프링은 나중에 사용한다.

도메인 협력 관계는 기획자도 볼 수 있는 다이어그램.

클래스 다이어그램은 실제 서버를 실행하지 않았을 때 구성을 나타냄.

객체 다이어그램은 서버가 실행될 때 동적으로 결정되는 구현체를 나타냄

구현체는 인터페이스랑 다른 패키지에 두면 설계상 좋지만 예제에서 복잡해지니 같은 패키지에 둠

Grade enum, Member class, MemberRepository Interface 생성

저장을 위해 HashMap을 쓰는데 상용에선 동시성 문제 때문에 concurrencymap을 써야 함

주문과 할인 도메인 설계

객체 지향 원리 적용

클래스 내에서 인스턴스를 만들어서 쓰면 인터페이스와 구현체 모두에 의존하기 때문에 DIP, OCP를 위반하게 된다.

앱의 전체 동작 방식을 구성하기 위해 구현 객체를 생성하고 연결하는 책임을 갖는 별도의 설정 클래스가 필요함

AppConfig에 의해 구현체는 인터페이스(추상)에만 의존함

관심사의 분리 : 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리됨

클래스 내에서 필요한 인스턴스를 생생했지만 AppConfig를 만들면 인터페이스에 어떤 인스턴스를 할당할지 정해서 관심사를 분리하게 됨

AppConfig는 앱이 어떻게 동작할지 전체 구성을 책임짐

구현체는 기능을 실행하는 책임만 짐

AppConfig 리팩토링

역할에 대한 구현이 드러나도록 변경

인스턴스 생성 중복 제거됨. 변경할 부분이 적음.

메서드명을 보면 역할을 알 수 있고 반환값을 보면 구현 클래스를 알 수 있다.

package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;

// 앱의 실제 동작에 필요한 구현 객체를 생성함. 인스턴스의 참조를 생성자를 통해 주입함
public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository()); // 생성자 주입
    }

    private static MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}

FixDiscountPolicy → RateDiscountPolicy

AppConfig 로 앱이 크게 사용 영역과 객체를 생성하고 구성하는 영역으로 분리됨

변경 시 구성 영역인 AppConfig만 고치면 됨

public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
//        return new FixDiscountPolicy();
    }

IoC, DI, 컨테이너

프로그램의 제어 흐름은 AppConfig가 가져간다. 구현체는 인터페이스를 호출하지만 어떤 구현 객체들이 실행될 지 모른다. 프로그램의 제어 호름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(Inversion of Control)이라고 한다

프레임워크 vs 라이브러리

내가 작성한 코드를 제어하고 대신 실행하면 프레임워크(JUnit은 JUnit의 라이프 사이클에 맞게 실행됨)

직접 제어의 흐름을 담당한다면 라이브러리

의존관계 주입 DI

구현체는 인터페이스에 의존함. 어떤 구현 객체가 사용될지는 모름

의존관계는 정적인 클래스 의존 관계와 실행시점에 결정되는 동적인 객체(인스턴스) 의존 관계를 분리해서 생각해야한다

IoC, DI 컨테이너

AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC, DI 컨테이너라고 함

의존관계 주입에 초점을 맞춰 최근에는 주로 DI 컨테이너라고 함

스프링으로 전환하기

스프링에선 설정 정보를 담은 곳에 @Configuration 을 붙임

스프링은 모든게 ApplicationContext에서 시작됨. @Bean 객체를 다 관리해줌

기본 스프링 객체, 직접 생성한 객체가 빈에 등록됨

ApplicationContext 를 스프링 컨테이너라고 함

스프링 컨테이너는 @Configuration 이 붙은 AppConfig를 설정 정보로 사용함. @Bean이라고 적힌 메서드를 호출해서 반환된 객체를 스프링 컨테이너에 등록함. 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 함

스프링 빈은 @Bean 이 붙은 메서드명을 스프링 빈의 이름으로 사용함

스프링 빈은 applicationContext.getBean() 메서드로 찾을 수 있음

스프링 컨테이너와 빈

스프링 컨테이너 생성

스프링은 빈을 생성하고 의존관계를 주입하는 단계가 나뉘어있다. 자바 코드로 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다.

컨테이너에 등록된 모든 빈 조회

package hello.core.beanfind;

import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationContextInfoTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("모든 빈 출력하기")
    void findAllBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name = " + beanDefinitionName + " object =" + bean);
        }
    }

    @Test
    @DisplayName("앱 빈 출력하기")
    void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            // ROLE_APPLICATION : 직접 등록한 앱 빈
            // ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name = " + beanDefinitionName + " object =" + bean);
            }
        }
    }
}

스프링 빈 상속관계

부모 타입으로 조회하면 자식 타입도 다 함께 조회됨

자바는 모든 최상위 부모는 Object를 상속받음

BeanFactory와 ApplicationContext

BeanFactory

스프링 컨테이너의 최상위 인터페이스

스프링 빈을 관리하고 조회하는 역할 담당

getBean() 제공

ApplicationContext

BeanFactory 기능을 모두 상속받아 제공

빈을 관리하고 검색하는 기능을 BeanFactory가 제공

앱 개발 시 빈은 관리하고 조회하는 기능뿐만 아니라 많은 부가기능이 필요

스프링 빈 설정 메타 정보 BeanDefinition

스프링 컨테이너는 자바코드인지 XML인지 몰라도 되고 BeanDefinition만 알면됨

@Bean 당 각각 하나의 메타 정보가 생성됨

스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성함

실무에서 직접 정의하거나 쓸 일은 거의 없음

싱글톤 컨테이너

요청이 올 때마다 인스턴스를 계속 만들면 메모리 낭비됨.

한번 인스턴스 생성 후 공유해서 씀

싱글톤 패턴

0개의 댓글