이번주 화요일 부터 오늘까지는
스프링과 친숙해지는 시간이 시작되었다.

프레임워크란? 라이브러리란? POJO 원칙에 대한 내용들
아키텍처란? 등등 여러가지 스프링 프레임워크를 사용하기 위한
내용에 대한 전반 적인 학습이 이루어졌다.

이해했다고 하면 거짓말이고, 어느정도 이런 것이구나?
라는 개념은 잡혔다. 이제 본격적으로 맛보고 즐기면서
스프링에 대해 탐구해보자


오늘은 어그제 배웠던 POJO의
DI 의존성 주입에 대한 내용을 좀 더 파고들어보자


















DI

Dependency Injection
의존성 주입. 어제 포스팅에서 간단하게 설명했기에 넘어가겠다.
객체간의 강한 결합 (new 키워드를 사용해 만든객체)들과 느슨한 결합 등이있다.
우리는 느슨한 결합을 통해 최대한 서비스코드 수정포인트를 줄여야한다.

의존성 주입예제

1). 의존성 주입이 아닌경우
-. 의존 관계이긴하나 DI적용이 안되어 강한 관계를 가짐

public class MemberService {
	private final MemberRepository memberRepository = new MemberRepository();
}

위와 같은 코드의 경우 MemberReposity의 메서드나 필드변수를 사용하기 위해서
MemberRepository 클래스를 참조해 객체를 만들었다.

2). 의존성 주입의 경우
-. 의존 관계이면서 DI가 적용되어 관계가 느슨해짐

public class MemberService {
    private final MemberRepository memberRepository;
    public MemberService (MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }
}

위의 코드 같은 경우는 동일하게 MemberRepository의 메서드나 필드변수를 사용하기 위해서
객체를 참조하게 만들었는데, 방법이 다르다.

필드변수 memberRepository에 값을 할당하지 않고
생성자를 통해 받은 객체의 참조값을 this.memberRepository에 넣어주고있다.
그렇다는건 지금 현재 코드가 작성되어 있는 클래스가아닌
다른 클래스에서 MemberSerivce를 생성자를 통해 객체를 전달하면
그 전달된 객체를 받아 쓸 수 있는 것이다.

여기서 중요한점은 1번의 프로그램 같은 경우에는 내가 직접 어떤 데이터를 넣을지 정해주고
2번과 같은 경우에는 저코드만봐서 어떤 객체가 들어오는지 모른다는 점이다.
1번,2번 둘다 동일한 코드고
memberRepository 객체를 참조해서
안에 있는 메서드와 필드변수를 사용할 수 있다.
위에까지는 의존성 주입의 방법이며
아래의 코드는 순수 자바코드로 외부에서 객체를 정해주는 코드라 볼 수 있다.

public class DependencyConfig {
    public MemberService memberService (){
        return new MemberService(memberRepository());
    }
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }
}
public class MemberTest {
    public static void main(String[] args) {
        DependencyConfig dependencyConfig = new DependencyConfig();
        MemberService memberService = dependencyConfig.memberService();
}

MemberService 쪽의 코드를 건들지 않아도
새로 추가되거나 변경되어도
외부에 있는 클래스(DependencyConfig, MemberTest)의 내용 만 변경해서
다른 객채를 넣어주면 MemeberService 변경된 객체안에서
동일하게 동작하게되어진다.

Spring Container

스프링 컨테이너, 이름만 보아도 무언가를 담고있는
통? 이라는 느낌이 드는 네이밍이다.

스프링 컨테이너는 Bean 생성,관리, 제거 등의 역할을 담당한다.
즉, 내부에 존재하는 애플리케이션 빈의 생명주기를 관리한다.
객체간의 의존성을 낮추기 위해 사용한다고한다.

위의 예제에서 DependencyConfig클래스의 역할을 한다 보면 될 것 같다.
스프링 컨테이너 의 빈을 넣는 방법이다.

ApplicationContext ac = new AnnotationConfigApplicationContext(DependencyConfig.class);

ApplicationContext는 인터페이스이다. 일반적으로 스프링 컨테이너라고 부른다
AnnotationConfigApplicationContext는 ApplicationContext의 구현체 이다.
이름그대로 어노테이션 기반의 스프링컨테이너를 만들어주는거고
다형성에 의해 참조가 가능하다.

우선 위의 코드를 적용시 아래와 같은 그림 형식을 띈다고 생각하면 될 것 같다.


















컨테이너 안에있는 Bean 객체에 대한 구성정보를 활용할 수 있다.
위의 예제로든 클래스를 이렇게 어노테이션을 붙여주면

@Configuration
public class DependencyConfig {
	
    @Bean
    public MemberService memberService (){
        return new MemberService(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }
}

스프링 컨테이너 안에 2개의 빈이 등록되는 것이다.

  1. 빈이름 : memberService / 빈객체 : 반환된 MemberService 객체
  2. 빈이름 : memberRepository / 빈객체 : 반환된 MemberRepository 객체

위에서 얘기했듯이
ApplicationContext를 일반적으로 스프링 컨테이너라 부르는데
계층도를 이해할 필요가 있다.


















최상위에 BeanFactory를 상속 받고 있는 모습이다.

BeanFactory

  1. 스프링 컨테이너의 최상위 인터페이스이다.
  2. 스프링 빈으 관리하고 조회하는 역할을 담당
  3. getBean()을 제공한다.
  4. 위에서 사용했던 대부분의 기능이 BeanFactroy의 기능이다.

ApplicationContext가 상속받아 BeanFactory의 기능을
확장하여 사용하고 있는 것이다.

그것의 구현체인 AnnotationConfigApplicationContext를
하위 클래스로 참조하기때문에 우리는 getBean()으로 Bean값을 얻어올 수 있는 것이다.
AnnotationConfigApplicationContext 이외에도
XML파일로 스프링 컨테이너를 만들 수 있다.

ApplicationContext context = new GenericXmlApplicationContext("DependencyConfig.xml");

똑같이 AppicationContext의 구현체이며 이전에는
어노테이션 기반이였으면, 위의 클래스는 XML파일을 기반으로한다.
최종적으로 상속도를 보면 이러한 식으로 전개된다













ApplicationContext는 상위에 있는 기능들을 확장해서 쓰고
실제로 구현체인 하위 클래스들이 내용을 구현해준다.

Bean

우리의 콩. 빈이다.
인스턴스화된 객체라고 생각하면 쉬 울 것 같다.
위에서 얘기했던 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다.
@Bean으로 적힌 메서드들을 저장한다.

반대로 호출도 가능하다.
빈을 호출하는 방법이다.

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService",MemberService.class);

위에서 MemberService라는 메서드를 @Bean으로 지정 해주었기에
해당 이름으로 해당객체를 getBean(메서드이름, 클래스이름);
통해서 객체를 가져올 수 있다.

즉, memberService로 해당 스프링컨테이너의 빈을 참조 할 수 있다.
(실제로는 getBean은 사용하지 않음)

BeanDefinition
위의 코드처럼 XML, Java 코드를 구분해주고 다양하게 설정 형식을 지원하는
이유의 중심에는 BeanDefinition이라는 추상화가 있기 때문이다.








스프링 컨테이너는 어떠한 형식의 파일이 오던
BeanDefinition만 알면된다.
그것을 기반으로 Bean을 관리하는 것이다.
이 BeanDefinition을 빈 설정 메타정보라고한다.

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

빈 설정 메타 정보

  • beanClassName : 빈 오브젝트의 클래스 이름. 빈 오브젝트는 이 클래스의 인스턴스가 된다.
    -> default : 없음. 필수항목

  • id/name : 빈의 식별
    -> default : 없음

  • parentName : 빈 메타정보를 상속받을 부모 BeanDefinition의 이름. 빈의 메타정보는 계층구조로 상속할 수 있다.
    -> default : 없음.

  • factoryBeanName : 팩토리 역할을 하는 빈을 이용해 빈 오브젝트를 생성하는 경우에 팩토리 빈의 이름을 지정한다.
    -> default : 없음.

  • factoryMethodName : 다른 빈 또는 클래스의 메소드를 통해 빈 오브젝트를 생성하는 경우 그 메소드 이름을 지정한다.
    -> default : 없음.

  • scope : 빈 오브젝트의 생명주기를 결정하는 스코프를 지정한다. 크게 싱글톤과 비싱글톤 스코프로 구분할 수 있다.
    -> default : 싱글톤.

  • lazyInit : 빈 오브젝트의 생성을 최대한 지연할 것인지를 지정한다. 이 값이 true이면 컨테이너는 빈 오브젝트의 생성을 꼭 필요한 시점까지 미룬다.
    -> default : false

  • dependsOn : 먼저 만들어져야 하는 빈을 지정할 수 있다. 빈 오브젝트의 생성 순서가 보장돼야 하는 경우 이용한다. 하나 이상의 빈 이름을 지정할 수 있다.
    -> default : 없음.

  • autowireCandidate : 명시적인 설정이 없어도 미리 정해진 규칙을 가지고 자동으로 DI 후보를 결정하는 자동와이어링의 대상으로 포함시킬지의 여부
    -> default : true

  • primary : 자동와이어링 작업 중에 DI 대상 후보가 여러 개가 발생하는 경우가 있다. 이때 최종 선택의 우선권을 부여할지 여부. primary가 지정된 빈이 없이 여러 개의 후보가 존재하면 자동와이어링 예외가 발생한다.
    -> default : false

  • abstract : 메타정보 상속에만 사용할 추상 빈으로 만들지의 여부. 추상 빈이 되면 그 자체는 오브젝트가 생성되지 않고 다른 빈의 부모 빈으로만 사용된다.
    -> default : false

  • autowireMode : 오토와이어링 전략. 이름,타입,생성자,자동인식 등의 방법이 있다.
    -> default : 없음.

  • dependencyCheck : 프로퍼티 값 또는 레퍼런스가 모두 설정되어 있는지 검증하는 작업의 종류
    -> default : 체크하지 않음.

  • initMethod : 빈이 생성되고 DI를 마친 뒤에 실행할 초기화 메소드의 이름
    -> default : 없음.

  • destroyMethod : 빈의 생명주기가 다 돼서 제거하기 전에 호출할 메소드의 이름
    -> default : 없음.

  • propertyValues : 프로퍼티의 이름과 설정 값 또는 레퍼런스. 수정자 메소드를 통한 DI작업에서 사용한다.
    -> default : 없음.

  • constructorArgumentValues : 생성자의 이름과 설정 값 또는 레퍼런스. 생성자를 통한 DI작업에서 사용한다.
    -> default : 없음.

  • annotationMetadata : 빈 클래스에 담긴 애노테이션과 그 애트리뷰트 값. 애노테이션을 이용하는 설정에서 활용한다.
    -> default : 없음.

Singleton

스프링 프레임워크에서 Bean 스코프는 6개의 범위를 지원하고
위에서 알아본 ApplicationContext를 사용하는 경우에는 4개를 사용할 수 있다.

스코프 설명
Singleton 각 Spring 컨테이너에 대한 단일 객체 인스턴스에 bean definition의 범위를 지정 (기본값임)
Prorotype 스프링 컨테이너는 프로토탕비 빈의 생성과 의존관계 주입까지만 관여함
Request 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
Session 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
Application 웹 서블릿 컨텍스와 같은 범위로 유지되는 스코프
Websocket 단일 bean definition 범위를 Websocket의 라이프사이클까지 확장한다.

이중에서 우선은 싱글톤(Singleton) 스코프에 대해 알아보자.
싱글톤 스코프를 알아보기 전에
우리는 프로그램 작성시 여러가지 디자인 패턴들이 있다.

그 중 싱글톤 패턴도 있는데
최초의 메모리를 한번만 할당하고 그 메모리로 인스턴스를 만들어 사용하는 패턴이다.

즉, 여러가지 객체를 만들지 않고 한가지 객체로 돌려쓰는? 것 같은 느낌이다.
공유한다고하니 딱 static이라는 공유변수가 떠오른다
실제로도 싱글톤패턴으로 프로그래밍을 할 수 있다.

위에서 했던 예제를 들어보자

기존 객체 만드는 패턴

public class DependencyConfig {
	
    public MemberService memberService (){
        return new MemberService(memberRepository());
    }
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }
}
public class MemberTest {
    public static void main(String[] args) {
        DependencyConfig dependencyConfig = new DependencyConfig();
        MemberService memberService1 = dependencyConfig.memberService();
        MemberService memberService2 = dependencyConfig.memberService();

        System.out.println(memberService1);
        System.out.println(memberService2);
    }
}

우리가 이렇게 코드가 있다고 가정할때
memberService1과 memberService2는 각각 새로운 객체가 생긴다.
출력하게되면

MemberService@8bcc55f
MemberService@58644d46

이런 상이한 객체로 출력되게 되어진다.
이렇게 될 경우에는 싱글톤패턴이아니게되고 클라이언트에서 요청할 때마다
새로운 객체를 계속 만들게 되는 셈이다.

싱글톤 패턴 적용

public class SingletonService {
    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance(){
        return instance;
    }

    private SingletonService(){}
}
public class SingletonTest {

    SingletonService singletonService1 = SingletonService.getInstance();
    SingletonService singletonService2 = SingletonService.getInstance();

    public static void main(String[] args) {
        System.out.println("singletonService1 : " + singletonService1);
        System.out.println("singletonService2 : " + singletonService2);

    }
}

만약에 위와 같이 static공유 변수를 이용한다면
서로 다른 객체를 만들어도 동일한 객체를 사용하게 되어진다.
출력할 경우

MemberService@8bcc55f
MemberService@8bcc55f

와 같이 동일한 객체가 출력되는 모습을 볼 수 있다.

즉, 처음 인스턴스 생성시에 객체가 만들어지고
해당하는 객체로 같이 쓰게 되어지는 것이다.
하지만 매번 메서드의 싱글톤 코드를 사용할 수 없을 뿐더러
여러가지 문제점이 존재한다.

DIP를 위반하기도하고, 클라이언트가 구체 클래세 의존해서
OCP원칙을 위반할 가능성도 높다.
내부 속성을 변경하거나 초기화하기도 여렵고, private 생성자로 자식 클래스를
만들기도 어렵다. 즉, 유연성이 떨어진다.

이러한 문제점들을 Spring은 보완해주고 있다. 스프링 컨테이너 ApplicationContext를 이용하면
Default로 싱글톤을 지원하고 있다.

스프링 컨테이너를 이용한 싱글톤

@Configuration
public class DependencyConfig {
	
    @Bean
    public MemberService memberService (){
        return new MemberService(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }
}
public class MemberApp {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(DependencyConfig.class);
        MemberService memberService1 = applicationContext.getBean("memberService",MemberService.class);
        MemberService memberService2 = applicationContext.getBean("memberService",MemberService.class);

        System.out.println(memberService1);
        System.out.println(memberService2);
    }
}

위에서 얘기했듯이 기본적으로 스프링컨테이너는
싱글톤을 지원하기 때문에 getBean으로 얻은 객체인
memberService1 과 memberService2 각각 객체를 받아도
스프링 컨테이너 내부에서 하나의 객체로 사용하게되어
출력하게되면

MemberService@8bcc55f
MemberService@8bcc55f

이렇게 동일한 객체가 출력되는 것을 볼 수 있다.
싱글톤 방식을 쓸 때 주의할점은
같은 객체로 여러명의 클라이언트가 사용할 수 있기 때문에
객체는 상태를 유지하게 설계하면 안된다.

즉! 무상태(stateless)로 설계해야한다
1). 특정 클라이언트에 의존적인 필드 x
2). 특정 클라이언트가 값을 변경할 수 있는 필드 x
3). 가급적 읽기만 가능해야한다.
4). 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야한다.


오늘은 많은 내용이라면 많은내용이지만
여러가지 유익한 내용들을 많이배웠다.
스프링 컨테이너와 빈에 대해 조금 더 이해한 느낌이고
이제 이렇게 관리를하면서 어떻게 프로그램을 설계하고 활용하는지?가
조금 궁금하다. 오늘 했던것 스프링 컨테이너에 빈을 등록해 해당 빈을 호출하
그 객체로 의존성주입이되고, 호출한 객체로 여러가지 확인해보고
작업해보는 시간이었다. 이제 웹서비스 전반적으로
어떤것을 스프링컨테이너에 담는지? 담아놓고 어떻게 서비스를 구축하는지?에
대해서 여러가지 물음이 생기지만 차차 지식을 쌓아가면서
알아가는 것으로 해보자

오늘 공부는 여기서 끝!!!


오늘의 커피량: ☕️ ☕️ ☕️
오늘의 점심: 라면