[Spring] 싱글톤과 컴포넌트스캔, 메타 어노테이션
서버개발

[Spring] 싱글톤과 컴포넌트스캔, 메타 어노테이션

 

싱글톤(Singleton)이란?

# 싱글톤 : 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장해주는 디자인 패턴

- 객체 인스턴스가 현재 jvm안에 하나만 존재함

- 여러 명이 동시에 Service를 요청하면 DI 컨테이너에서 각각의 Service를 생성해서 반환

-> 계속 메모리에 누적되면서 문제가 생겨벌임 ->뿌리에 뿌리까지 재생성
-> 해당 객체가 하나만 생성되고 공유하도록 설계

- 데이터 공유가 쉽다. 싱글톤 인스턴스가 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들이 접근하여 사용할 수 있다. 하지만 여러 클래스의 인스턴스에서 싱글톤 인스턴스의 데이터에 동시에 접근하게 되면 동시성 문제가 발생할 수 있으니 이점을 유의해서 설계하는 것이 좋다.

 

# Spring 내부에서 싱글톤 클래스 생성법

public class SingletonA{
	// 모든 인스턴스가 이것을 공유
    private static final SingletonService instance = new SingletonService();

    // 외부에서 필드를 조회하는 함수
    public static SingletonService getInstance(){
        return instance;
    }

    // private로 기본 생성자를 선언해 밖에서 생성자를 통해 생성하지 못하도록 막음
    private SingletonService(){

    }
}

- private static으로 instance를 만들고 public static으로 getInstance를 만들어 외부에서 접근토록 만듬

- 기본생성자를 private로 선언해 외부에서 생성을 막음 ( 중복 생성 방지 )

 

# 싱글톤의 문제점

- 구현 코드가 많이 들어간다
- 의존관계상 클라이언트 코드가 구체 클래스에 의존한다 DIP 위반
- 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다
- 테스트하기 어렵다
- private 생성자로 자식 클래스를 만들기 어렵다
- 유연성이 떨어진다
- 안티패턴으로 불리기도 한다.

 

스프링 프레임워크에서의 싱글톤

# 싱글톤의 단점을 제거

- 스프링 프레임워크는 위 싱글톤들의 단점을 전부 제거하고 사용한다

- 스프링 컨테이너는 자동으로 싱글톤을 관리해주고 싱글톤 컨테이너 역할을 한다

- 지저분한 코드와 DIP, OCP, test 등을 내부적으로 관리해줌으로서 해결된다 ( 프로그래머가 신경쓰지 않도록 )

 

# 남은 싱글톤의 단점 

- 객체 인스턴스를 하나만 생성해서 공유하는 방식은 여러 클라이언트가 하나의 같은 
객체 인스턴스를 공유하기 때문에 싱글톤 객체는 무상태성을 유지해야한다

- 특정 클라이언트에 의존적인 필드가 있으면 안된다

- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다

- 가급적 읽기만 가능해야한다

- 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야한다.

- 대체적으로 동기화 문제를 고려하며 설계해야한다 ( 트랜잭션같은..? )

특정 클라이언트가 값을 변경하기 때문에 생기는 문제, 그렇기에 공유필드는 항상 무상태로 설계하자

 

@Configuration

# @Configuration의 효능 

- CGLIB라는 바이트코드 조작 라이브러리를 사용하여 AppConfig 클래스를 상속받는 임의의 다른 클래스를 만들고 그 다른 클래스를 스프링 빈으로 등록한 것 ( 이렇게 동적으로 코드를 생성하여 싱글톤을 유지시켜 준다 )

 

<예상코드>

@Bean
public MemberRepository memberRepository(){
	if( 이미 스프링 컨테이너에 등록되어 있으면?)
		return 스프링 컨테이너에서 찾아서 반환
	else
		기존 로직을 호출해서 생성하고 스프링 컨테이너에 등록 후 반환
}

고로 @Configuration을 붙이지 않고 @Bean만 적용한다면 싱글톤이 전혀 적용되지 않는다 ( 순수 자바 코드와 같음 )

 

@ComponentScan

# @ComponentScan를 쓰게된 이유

- 지금까지는 @Bean으로 직접 등록을 했다 이렇게하면 실무에서는 힘들어진다 ( 누락 문제도 발생 )

- 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능 제공

- 의존관계도 자동으로 주입하는 @AutoWired 제공

- ComponentScan시 다른 Configuration도 내부에 Component를 포함해서 스캔되므로
이럴 경우 예외처리를 해주어야한다

@ComponentScan(
    excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)

- @Component 어노테이션이 붙은 것을 자동으로 등록해줌

-> 그러나 클래스 자체가 빈에 등록이 되기때문에 내부 필드는 어떻게 주입될지 알 수가 없음
-> @AutoWired 사용

 

# @ComponentScan 동작방식

1. 일단 @Component가 붙은 모든 클래스를 빈에 등록
스프링빈의 기본이름은 클래스명을 사용하되 맨 앞글자만 소문자 사용
이름 지정  @Component("이름")

 

2. @Autowired로 의존관계 자동 주입
같은 타입이 있는지 조회, 그러나 같은 타입이 여러 개 있을 경우 충돌이 있을 수 있음 따로 처리해주어야함
생성자에 파라미터가 많아도 자동으로 해준다.

 

3.탐색위치 시작지정
basePackages를 이용해 위치 지정 : 이를 이용해 자바 프로젝트를 모두 탐색하지 않도록 도움
basePackageClasses = AutoAppConfig.class, <- 지정하지 않으면 현재 위치에 모든 하위 패키지를 전부 찾아본다

 

4. 패키지 위치를 지정하지 않고 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이 관례
이렇게 두고 basePackages를 생략 + AppConfig는 대표하는 정보이기 때문에 상단에 두는 것이 가시적으로 좋다
CoreApplication에 @SpringBootApplication이 있는데 이와 같은 곳에 두어야 좋다

 

# 컴포넌트 스캔 기본 대상

- @Component 컴포넌트 스캔에 사용
- @Controller MVC 컨트롤러
- @Service비즈니스 로직
- @Repository 데이터 접근 계층
- @Configuration 스프링 설정 정보

 

# 컴포넌트 스캔 필터 종류

- annotation 기본값, 어노테이션을 인식해서 동작
- assignable_type 지정한 타입과 자식 타입을 인식해서 동작
- aspectj aspectj 패턴 사용
- regex 정규식 사용
- custom typefilter라는 인터페이스를 구현해서 처리

 

어노테이션(Annotation) 추가 

# 내부어노테이션

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

- 메타 어노테이션들로 부가적인 기능이나 제약조건을 정의하기 위한 어노테이션이다.

@Target Annotation이 적용될 범위를 지정한다.
value값으로는 ElementType의 enum상수값이 사용되며 다중 선택도 가능하다.
@Retention Annotation 의 LifeCycle을 지정한다.
즉, 어노테이션이 메모리에 유지되는 시간을 지정한다.
value값으로는 RententionPolicydml enum상수값이 사용된다.
@Inherited 어노테이션이 하위 클래스에서도 적용될 수 있도록 상속된다.
@Documented JavaDoc 생성시 Annotation에 대한 정보도 함께 생성한다.
@Repeatable 지정한 어노테이션을 사용처에서 중복해서 정의할 수 있게끔 해준다.

 

# @Target(ElementType.TYPE)

- 해당 사용자가 만든 어노테이션이 부착될 수 있는 타입을 지정하는 것이다. 

( 타입이란? -> 클래스  / 생성자 / 메서드 등등 )

여기서 CONSCONSTRUCTOR / METHOD / FIELD는 말 그대로 생성자 / 메서드 / 필드에 어노테이션을 부착할 수 있게 하겠다는 의미이며, TYPE의 경우 조금 생소할 수 있느데, 이는 클래스 / 인터페이스 / 열거 타입(enum)을 뜻한다.

 

그 외에 @Target의 매개변수로는 아래와 같은 것들이 있다.

  • ANNOTATION_TYPE : 어노테이션
  • LOCAL_VARIABLE : 지역(로컬) 변수
  • PACKAGE : 패키지

 

# @Retention(RetentionPolicy.RUNTIME)

- 어느 시점까지 어노테이션의 메모리를 가져갈 지(영향을 미치는지) 설정

  • SOURCE : 어노테이션을 사실상 주석처럼 사용하는 것. 컴파일러가 컴파일할때 해당 어노테이션의 메모리를 버립니다.
  • CLASS : 컴파일러가 컴파일에서는 어노테이션의 메모리를 가져가지만 실질적으로 런타임시에는 사라지게 됩니다. 런타임시에 사라진다는 것은 리플렉션으로 선언된 어노테이션 데이터를 가져올 수 없게 됩니다. 디폴트값입니다.
  • RUNTIME : 어노테이션을 런타임시에까지 사용할 수 있습니다. JVM이 자바 바이트코드가 담긴 class 파일에서 런타임환경을 구성하고 런타임을 종료할 때까지 메모리는 살아있습니다.

 

# @Documented

- 메타 데이터 어노테이션이다. 어노테이션을 정의할 때@documented 형태로 적용하여,
해당 어노테이션을 사용하는 클래스가 javadoc과 같은 문서화 될 때, 해당 어노테이션이 적용되었음을 명시하도록 한다.

 

 

 

#스프링 프레임워크 #스프링 컴포넌트스캔 #싱글톤 #자바 싱글톤 #스프링 싱글톤 #스프링 @Target #스프링@Retention #스프링@Documented #스프링@Indexed #스프링 컴포넌트스캔 #스프링 ComponentScan #Singleton단점