본문 바로가기

WINK-(Web & App)/Spring Boot 스터디

[2024-2 Spring Boot 스터디] 조상혁 #5주차

반응형

이 글은 스프링 핵심 원리 - 기본편을 기반으로 작성되었습니다.

 

여러가지 싱글톤을 구현하는 방법..

싱글톤 컨테이너 

 

웹 어플리케이션과 싱글톤

스프링은 보통 웹 서비스에서 사용된다.

웹은 다양한 사용자가 동시에 많은 요청을 한다는 특징이 있다.

 

그렇다면 지금까지 개발해온 것들은 그 요청을 잘 수행할까?

아래 그림을 보며 생각해보자.

 

클라이언트 한명 당 하나의 객체가 생성되는 모습을 볼 수 있다. 

너무나 메모리 낭비가 심하다. 이는 결국 비용적 문제로 이어질 수 있다.

 

음 그럼 어떤 해결방법이 있을까?

3형제가 있는데 장난감을 하나씩 사주는건 너무 비싸다.

그러면 하나사서 나눠쓰라고 하면 좋지 않을까!

객체도 하나로 나눠쓰는게 좋지 않을까..!

 

일단 객체가 생성되는지 확인해보자.

객체 생성 검증

 

싱글톤 패턴

 

객체를 하나만 만들어 사용하는건 또 어떻게 하는가....

 

의외로 간단한데 private로 인스턴스(구현 객체)를 생성해서

불러오는 매서드를 만들고 사용하면 된다!

 

package singletonTest;

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

    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService() {}

    public void logic(){
        System.out.println("싱글톤 객체 로직 호출");
    }
}

 

 

위에서의 테스트와 달리 같은 객체를 사용하고 있음이 확인된다!

 

와! 그럼 싱글톤 무적이네!  라고 한다면 아니다.

 

구현 코드가 많다, 구체 클래스에 의존한다, 테스트가 어렵다 등의 여러 문제들이 있다.

 

스프링 컨테이너는 위 문제점들을 모두 해결하고 싱글톤으로 관리해준다.

 

스프링 컨테이너

스프링에서 빈을 관리해 주기 때문에 여러개의 객체 생성의 걱정없이 개발을 진행할 수 있다.

싱글톤을 적용하여 그림은 아래와 같이 바뀌었다.

 

 

@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer(){
    //AppConfig appConfig = new AppConfig();
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    MemberService memberService1 = ac.getBean("memberService",MemberService.class);

    MemberService memberService2 = ac.getBean("memberService",MemberService.class);

    System.out.println("memberService1=" + memberService1);
    System.out.println("memberService2" + memberService2);

    Assertions.assertThat(memberService1).isSameAs(memberService2);
}

 

 

싱글톤의 주의점

 

하나만 생성해서 좋은 것만 있는 것은 아니다. 

하나이기에 항상 같은 상태를 유지해야하고 특정 클라이언트에게 객체가 변화해선 안된다.

 

class StatefulServiceTest {
    @Test
    void statefulServiceSingleton() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService service1 =ac.getBean(StatefulService.class);
        StatefulService service2 =ac.getBean(StatefulService.class);

        service1.order("유라면",10000);
        service2.order("김비빔",20000);

        int price = service1.getPrice();
        System.out.println("가격: "+ price);

        Assertions.assertThat(service1.getPrice()).isEqualTo(20000);
    }

    static class TestConfig{
        @Bean
        public StatefulService statefulService() {
            return new StatefulService();
        }
    }
}

 

고객 유라면의 결제 금액은 10000원 인데 두번째 김비빔도 결제를 위해 주문을 한 상황이라면

유라면은 10000원이 아닌 20000원을 결제하게 된다.

 

 

 

이걸 해결하는건 지금은 간단하다.

공유되던 변수를 제거하고 원하는 가격 부분을 반환할 수 있도록 변경하면 된다.

하지만 실무에서 상속과 여러가지 요인들이 결합하면 다 뒤집어야하니 주의해야 한다...!!!!

public class StatefulService {

    //private int price;

    public int Fixorder(String name, int price){
        System.out.println("이름: "+ name + " 가격: " + price);
        //this.price = price; // 문제가 되는 부분
        return price;
    }
/*
    public void order(String name, int price){
        System.out.println("이름: "+ name + " 가격: " + price);
        this.price = price; // 문제가 되는 부분
    }

    public int getPrice(){
        return price;
    }
 */
}

 

 

 

@Configuration

어노테이션 설명

위 어노테이션을 배우면서 다시한번 우리가 싱글톤을 잘 지키고 있었는지 확인해보자

 

우리가 빈을 등록해서 사용하는 설정파일을 들여다볼 것이다.

 

public class AppConfig {

    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }

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

    @Bean
    public DiscountPolicy discountPolicy(){
        return new RateDiscountPolicy();
    }

}

 

여태 잘 됐는데? 문제 없는데?? 라고 생각이 든다. 하지만 구조를 살펴보면 

memberService , orderService , memberRepository 에서 중복적으로 등록이후에 호출이 된다는 것을 알 수 있다.

그럼 memberRepository 만 3번 호출되는 건데,

하지만 확인해보면 한번씩 호출됨을 알 수가 있다.

 

이 마술같은 일을 해주는 것이 @Configuration 어노테이션이다.

 

어떻게 해주는가

아래 그림을보면 내 설정파일을 상속받는 CGLIB 가 있는데 설정파일을 상속받아

컨테이너에 내 코드가 아니라 상속받은 코드를 설정으로 입력한다. 

이것이 중복이 일어나도 싱글톤을 유지할 수 있는 방법이다! 어노테이션 하나로 해결된다니 혁신적이다.

스프링 만세

 

 

스프링 부트 - 동적 프록시 기술(CGLIB, ProxyFactory)

본 게시물은 스스로의 공부를 위한 글입니다.잘못된 내용이 있으면 댓글로 알려주세요!JDK 동적 프록시는 여기를 참고 바란다.CGLIB는 바이트코드를 조작해서 동적으로 클래스를 생성하는 기술을

velog.io

 

 

자동 빈 등록과 자동 의존성 주입

빈 으로 등록하는 것들이 별로 없으면 상관이 없지만 그게 100개 200개가 된다면...?

 

하나하나 하는 것은 그냥 가내수공업과 같다.. 그럼 자동으로 등록하는 방법을 알아보자

 

새로운 설정파일을 만든다.

 

package hello.demo;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static org.springframework.context.annotation.ComponentScan.*;
@Configuration
@ComponentScan(
        excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
                Configuration.class))
public class AutoAppConfig {
}

 

필터는 이전 설정파일이 겹치면 자동등록의 의미가 없으니 사용하였다.

 

빈 자동등록은 2가지를 알면 되는데

 

@ComponentScan , @Component  

 

컴포넌트 스캔은 모든 파일을 탐색하고 컴포넌트 어노테이션이 등록되어있는 부분을 모두 컨테이너에 등록해준다.

 

내가 이해한 이미지는 컴포넌트 스캔은 반장 , 컴포넌트는 손들기로 설정하고

반장이 손든 친구들을 모두 검사하는 방식이다.

 

@Component("service")

이런식으로 이름을 붙여서 손을 들 수 있다. 빈에 저 이름으로 등록

 

이렇게 등록하면 하나 의문이 생길 수 있는데 의존성 주입은 어떻게 하는가... 이다.

 

@Bean
public MemberService memberService(){
    return new MemberServiceImpl(memberRepository());
}

 

기존은 이런 방식으로 지정하여 주입을 해줬지만 저걸 다 써주면 수동이랑 다를 것이 없다.

 

그렇기에 자동으로 주입해주는 @Autowired 가 있다.

 

오토 와이어드 , 자동연결..? 바로 이해가 되는 느낌이다.

 

사용방법은 컴포넌트나 오토와이어드나 같다. 필요한거 위에 어노테이션만 붙이면 된다.

 

@Component
public class MemberServiceImpl implements MemberService {
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

 

 

 

탐색 위치 등록

탐색 한다고 했는데 어디부터 하는걸까?

 

아마 모든 패키지를 돌아다니며 찾을 것이다. 하지만 그건 너무 오래 걸릴거 같기도 하다.

 

그래도 이런 걱정은 할 필요가 없는데 기본적으로는 아래와 같은 설정파일의 경로부터 찾는다고 한다.

package hello.demo;

 

지정하는 방법은 아래와 같이 설정하면 hello.demo.member 부터 탐색한다.

@ComponentScan(
        basePackages = "hello.demo.member",
        excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
                Configuration.class))

 

위에서 컴포넌트 등록을 component로 손을든다 라고 했는데 이것만 있는 것이 아니다.

아래와 같은 것들은 안에 컴포넌트를 포함하기에 똑같은 손들기이다.

 

필터

 

내가 원하는 것을 등록을 하거나 등록을 하지 않을 수 있는데 필터로 이를 적용한다.

@ComponentScan(
        includeFilters = @Filter(type = FilterType.ANNOTATION, classes =
                MyIncludeComponent.class),
        excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
                MyExcludeComponent.class)
)

 

위와 같은 사용법이고 사실 include는 잘 사용되지 않고 exclude는 가끔 사용되는 편이다.

 

중복등록과 충돌

여러 사람과 개발을 하다보면 등록시에 충돌이 발생할 수 있다.

 

보통 수동등록과 자동등록이 충돌하는 상황이 발생하는데 

이때 스프링은 수동등록을 기본으로 설정해 버리고 우선순위로 결정한다.

 

하지만 부트를 사용하면 오류를 발생하는 것을 확인할 수 있는데 

 

부트에서 이 관련설정을 false가 기본값이 되도록 설정하였기 때문이다.

 

application.properties 에서 변경이 가능하다.

 

 

 

후기..

 

뭔가 하다보니 느끼는 거지만 이미 좋은 기능들이 많이 개발되어 있으니까 잘 배워서 써먹을 수 있도록 잘 공부해야겠다는 생각이 들고 스프링은 어노테이션을 공부하는게 스프링 사용에 있어서는 본질적인 공부가 아닐까? 라는 생각이 든다.

 

역시 누군가는 모두 정리해 놓으셨군요! 블로그 만세

 

[Spring] Annotation 정리

Annotation(@)은 사전적 의미로는 주석이라는 뜻이다. 자바에서 사용될 때의 Annotation은 코드 사이에 주석처럼 쓰여서 특별한 의미, 기능을 수행하도록 하는 기술이다.

velog.io

 

반응형