0919 ~ 0925


# 0919 ~ 0925

# 0919 - @Value와 @ConfigurationProperties의 사용법 및 차이

Spring에서 프로퍼티에 있는 값을 불러오는 다양한 방법들이 있다. 대표적인 방식으로 @Value와 @ConfigurationProperties가 있다.

# @Value와 @ConfigurationProperties의 사용법 및 차이

스프링의 properties나 yaml에 있는 값들은 애플리케이션이 실행되면서 환경변수로 Environment에 등록이 된다. 그리고 해당 값들을 꺼내서 애플리케이션에서 활용하기 위한 방법으로는 @Value와 @ConfigurationProperties가 있다.

# [ @Value 어노테이션 ]

@Value는 단일 값을 주입받기 위해서 사용된다. 값을 가져오기 위해서는 PropertyPlaceHolderConfigurer를 통해 ${}을 사용하는 방식과 SpEL을 통해 #{}을 이용하는 방식 크게 2가지를 사용할 수 있다.
@Value를 이용해 값을 가져올 때에는 RelaxedBinding이라는 것이 적용되지 않는다. Relaxed Binding이란 프로퍼티 값의 이름이 조금 달라도 유연하게 바인딩을 시켜주는 규칙을 의미한다.

server.admin-what-is-real-name=ajs

@Value는 Relaxed Binding이 적용되지 않으므로 완벽히 일치하는 프로퍼티 이름으로만 값을 가져올 수 있따.

@Value("${server.admin-what-is-real-name}")
private String adminName;
  • 단일 값을 주입받기 위해 사용됨
  • RelaxedBinding 적용되지 않음

# [ @ConfigurationProperties 어노테이션 ]

@ConfigurationProperties는 프로퍼티에 있는 값을 클래스로 바인딩하기 위해 사용되는 어노테이션이다. @ConfigurationProperties는 값의 바인딩을 위해 Setter를 필요로 하며 생성자가 바인딩하기 위해서는 ConstructorBinding을 붙여주어야 한다.

@ConfigurationProperties를 사용하기 위해서는 @EnableConfigurationProperties에 해당 클래스를 지정해주거나 @ConfigurationPropertiesScan를 통해 스캐닝의 대상이 되도록 해주어야 한다.

@ConfigurationProperties는 기본적을 RelaxedBinding이 적용된다. 예를 들어 위에서 봤던 프로퍼티 값을 불러오고자 한다면 다음과 같이 바인딩해도 된다.

@Getter
@Setter
@ConfigurationProperties(prefix = "server")
public class ServerInfo {

    private final String adminWhatIsRealName;

}

RelaxedBinding이 적용되는 규칙들로는 다음과 같은 것들이 있다.

  • server.admin-what-is-real-name : properties와 .yml에 권장되는 표기법
  • server.adminWhatIsRealName : 표준 카멜 케이스 문법
  • server.admin_what_is_real_name : .properties와 .yml 에서 사용가능한 표기법 ( - 표기법이 더 표준임)
  • SERVER_ADMIN_WHAT_IS_REAL_NAME : 시스템 환경 변수를 사용할 때 권장되는 표기법

위의 4가지 표기법들은 @ConfigurationProperties로 값이 바인딩될 때 RelaxedBinding에 의해 올바르게 바인딩 된다. 또한 @ConfigurationProperties는 클래스로 값을 바인딩하므로 한번에 여러 값을 바인딩 받을 수 있다.
하지만 @ConfigurationProperties에는 spEL이 사용될 수 없다는 단점이 있다.

# @Value와 @ConfigurationProperties의 차이 요약

# [ @Value와 @ConfigurationProperties의 차이 요약 ]

image

Spring에서는 여러 값을 바인딩받아야 하는 경우에 @ConfigurationProperties를 사용하도록 권장하고 있다. 또한 @ConfigurationProperties는 RelaxedBinding이 적용되므로 보다 유연하게 값을 바인딩할 수 있다.


# 0920 - 설정 값 분리의 필요성과 @Value의 사용법 및 동작과정

# 설정 값 분리의 필요성

# [ 설정 값 분리의 필요성 ]

개발을 진행하다 보면 데이터베이스 연결 정보나 외부 API 주소 등과 같은 메타 정보들을 관리해야 한다. 해당 메타정보들을 클래스 파일에 넣을 수 도 있겠지만 다음과 같은 이유로 프로퍼티 파일(properties)나 야믈 파일(yaml, yml)로 분리해서 관리할 것을 권장한다.

  1. 환경에 따라 유연하게 값을 설정할 수 있음
    일반적으로 로컬 환경이나 알파/스테이지 환경 그리고 운영 환경에 따라 서로 다른 데이터베이스 서버를 가지고 있다. 만약 환경에 따라 다른 값을 자바 코드만으로 설정해주려면 상당히 번거롭고 중복되는 코드가 상당히 많아진다. 그래서 설정값은 별도의 파일(properties 또는 yaml)로 분리하고, 자바 코드에서는 환경 정보에 맞는 설정값을 불러오도록 하면 상당히 유용하다.

  2. 초기값을 설정할 수 있음
    또한 불러오는 설정 값에 초기값을 지정해줄 수 있는데, 이를 통해 설정한 값이 불러지지 않은 경우에 대비할 수 있으며 테스트 작성을 용이하게 할 수 있다. 즉, 초기값을 설정함으로써 테스트 작성에 유연하고 안전하게 개발을 할 수 있는 것이다.

  3. 불필요한 컴파일을 줄일 수 있음
    만약 프로퍼티를 분리하지 않는다면 값이 클래스 안에 있을 것이다. 그럴때 만약 값이 수정되면 소스코드가 변하는 것이므로 다시 컴파일을 해주어야 할 것이다. 하지만 설정 파일을 분리하고 @Value로 참조하게 한다면 불필요한 컴파일없이 주입되는 값을 변경할 수 있다.

# [ @Value의 사용법과 처리 및 동작 과정 ]

@Value로 메타 정보를 가져오는 방법으로는 PropertyPlaceHolderConfigurer를 통해 ${}을 사용하는 방식과 SpEL을 통해 #{}을 이용하는 방식 크게 2가지가 있다.

  1. PropertyPlaceHolderConfigurer를 통한 수동 변환
    PropertyPlaceHolderConfigurer은 빈 팩토리 후처리기로써 매번 빈 오브젝트가 만들어진 직후에 오브젝트의 내용이나 오브젝트 자체를 변경하는 빈 후처리기와 달리 빈 설정 메타정보가 모두 준비됐을 때 빈 메타정보 자체를 조작하기 위해 사용된다.
    예를 들어 database.username과 같이 properties에 작성된 키 값을 ${}안에 넣어주면 Spring이 PropertyPlaceHolderConfigurer를 통해 초기화 작업 중에 해당 값을 실제 값으로 치환한다.
    예를 들어 다음과 같이 사용할 수 있다.
database.username = admin
database.className=org.mariadb.jdbc.Driver

@Value("${database.username}")
private String userName;

@Value("${database.className}")
private String className;

하지만 이런 방법은 대체할 위치를 치환자로 지정해두고, 별도의 후처리기가 값을 변경해주기를 기대하기 때문에 수동적이다. 그래서 초기 빈 메타정보에는 ${database.username}와 같은 문자열이 등록되어 있다. 그러다가 스프링 컨테이너가 설정 파일에서 대체할 적절한 키 값을 찾지 못하면 해당 문자열이 그대로 남아있게 된다. 그래서 치환자의 값이 변경되지 않더라도 예외가 발생하지 않으므로 SpEL을 통한 능동변환을 권장한다.
초기에는 PropertyPlaceHolderConfigurer를 사용하는 방식을 사용했어야 했지만 Spring3부터 SpEL을 지원하면서 이를 이용하는 방식을 권장하고 있다.
물론 최근 Spring의 버전이 높아지고, 이러한 문제를 개선한 PropertySourcesPlaceholderConfigurer가 등장하면서 적절한 값을 찾기 못하면 에러가 발생하도록 수정되었지만 그래도 수동적인 방법보다는 빈에서 값을 꺼내는 SpEL이 권장된다.

  1. SpEL을 통한 능동 변환
    SpEL은 스프링 3.0에서 처음 소개된 스프링 전용 표현식 언어로 이를 통해 다른 빈 오브젝트나 프로퍼티에 손쉽게 접근할 수 있다. SpEL은 기본적으로 #{} 안에 표현식을 넣도록 되어있는데, user.name이라는 표현식은 이름의 user인 빈의 name 프로퍼티를 의미한다. SpEL은 일반 프로그래밍 언어 수전에 가까운 강력한 표현식 기능을 지원한다. 다른 빈의 프로퍼티에 접근가능할 뿐만 아니라 메소드 호출도 가능하고 다양한 연산도 지원하며 클래스 정보에도 접근할 수 있다. 심지어 생성자를 호출해서 객체를 생성할 수도 있다.
    SpEL(Spring Expression Language)를 이용하는 방법은 프로퍼티의 대체 위치를 설정해두고 빈 팩토리 후처리기가 바꿔주기를 기다리는 수동적인 방법과 달리 다른 빈 오브젝트에 직접 접근할 수 있는 표현식을 이용해 원하는 프로퍼티 값을 능동적으로 가져온다.
    별도의 설정이 없다면 다음과 같이 설정 파일에 작성된 값을 사용할 수 있는 것이다.
database.username = admin
database.className=org.mariadb.jdbc.Driver

@Value("#{environment['database.username']}")
private String userName;

@Value("#{environment['database.className']}")
private String className;

이는 environment라는 이름의 빈으로부터 해당 키에 해당하는 값을 읽어오는 것이다. 여기서 가져오는 bean은 Properties 타입 등의 빈이 될 수 있다.
SpEL을 이용하면 오타와 같은 실수가 있을 때 에러 검증이 가능하다는 장점이 있다. 그래서 이름을 잘못 적는 실수가 있어도 예외가 발생하지 않는 ${} 기반의 프로퍼티 치환자 방식보다는 SpEL의 사용을 권장한다.

여기서 참고해야 하는 것은 @Value 어노테이션은 스프링 컨테이너가 참조하는 정보를 의미할 뿐이지 그 자체로 값을 넣어주는 것은 아니라는 것이다. 그래서 Spring 컨테이너 밖에서 사용된다면 @Value 어노테이션이 무시된다.

# [ 타입의 변환 ]

설정 파일의 내용들은 모두 텍스트로 작성된다. 값을 넣을 프로퍼티 타입이 스트링이라면 아무런 문제가 없지만 그 외의 타입인 경우라면 타입을 변경하는 과정이 필요하다.
이를 위해 스프링의 다음의 2가지 종류의 타입 변환 서비스를 제공한다.

  1. PropertyEditory
    디폴트로 사용되는 타입 변환기는 java.beans의 PropertyEditor 인터페이스를 구현한 것이다. PropertyEditor는 원래 GUI 개발 환경을 위해 개발되었지만 이제는 XML에 작성된 내용이나 @Value의 String 값에서 프로퍼티 타입으로 변경하기 위해 사용한다.
    PropertyEditory는 boolean, Boolean, int, Interger ... 등의 기본 타입의 변환가 배열의 변환 외에도 Charset, Class, Currency, File, Locale, Pattern, Resource, Timezone, URI, URL 등의 변환을 제공한다.
    예를 들어 다음과 같은 String을 URI로 받을 수 있다는 것이다.
database.url=jdbc:mariadb://localhost/security?characterEncoding=utf-8

@Value("${database.url}")
private URI url;

이렇게 다양한 타입으로의 변환이 가능한 이유는 각각의 타입 변환기를 Spring이 구현해두었기 때문이다. 예를 들어 String을 URI로 바꾸는 경우에는 URIEditor, Charset으로 바꾸는 경우에는 CharsetEditor가 사용되는 것이다.
만약 Spring이 지원하지 않는 타입의 오브젝트를 직접 값으로 주입하고 싶다면, PropertyEditory 인터페이스를 구현해서 직접 변환기를 구현할 수도 있다. 물론 권장하는 방법은 아니다. 이러한 방법 대신에 빈을 등록하고 DI 받는 방법이 있기 때문이다.
또한 PropertyEditory는 내부적으로 상태값을 관리하기 때문에 멀티쓰레드 환경에서 사용이 불가능하며 String <-> Object 관계의 변환만 가능하다는 단점이 있다.

  1. ConversionService
    스프링 3.0부터는 PropertyEditory 대신 사용할 수 있는 ConversionService를 지원하기 시작했다. ConversionService는 자바빈에서 차용해오던 PropertyEditory와 달리 스프링이 직접 제공하는 타입 변한 API이다.
    ConversionService는 변환기의 작성이 간편하며 PropertyEditory와 달리 멀티쓰레드 환경에서 공유해 사용될 수 있다는 장점이 있다. 만약 우리가 ConversionService라는 이름의 빈으로 ConversionService 타입의 빈을 등록하면 스프링 컨테이너가 이를 자동인식해서 PropertyEditor를 대신해 사용한다.
    예를 들어 String을 int로 바꾸는 경우에 직접 만든 Converter를 사용하려면 다음과 같이 구현할 수 있다.
 @Configuration
 public class ConverterConfig {
 
    @Bean
    public ConversionServiceFactoryBean conversionService () {
        final ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
        conversionServiceFactoryBean.setConverters(converters());
        return conversionServiceFactoryBean;
    }

    @Bean
    public Set<Converter> converters() {
        final Set<Converter> set = new HashSet<>();
        set.add(new CustomConverter());
        return set;
    }
}

@Component
public class CustomConverter implements Converter<String, Integer> {
    public Integer convert(String username) {
       try{
          return Integer.parseInt(username);
       } catch (NumberFormatException e) {
           return 0;
       }
    }
}

물론 Spring에서 거의 모든 Conversion 기능을 제공해주기 때문에 거의 사용할 일이 없다. 하지만 특별하게 값을 변환해주어야 하는경우라면 위와 같이 CustomConverter를 만들어 등록하면 된다.


# 0922 - RequestContextHolder로 HttpServletRequest 가져오기

HttpServletRequest를 메서드 파라미터로 선언하지 않고 가져오는 방법이다.

request를 서비스 레이어까지 전달하지 않더라도 RequestContextHolder를 이용하면 되는데, RequestContextHolder는 Spring 프레임워크 전 구간에서 HttpServletRequest에 접근할 수 있게 도와주는 구현체이다.

RequestContextHolder는 ThreadLocal을 사용해서 sevvlet이 호출되면 thread, HttpServletRequest를 key-value로 보관하고 있다가 요청을 하면 동일한 thread내에서는 어느 곳에서든 같은 HttpServletRequest를 돌려주는 역할을 한다.

RequestContextHolder에서는 HttpServletRequest를 조회하는 메서드가 두 가지 존재한다.

# 1. getRequestAttributes

@Nullablepublic static RequestAttributes getRequestAttributes()Return the RequestAttributes currently bound to the thread.Returns:the RequestAttributes currently bound to the thread, or null if none bound

# 2. currentRequestAttributes

public static RequestAttributes currentRequestAttributes() throws IllegalStateExceptionReturn the RequestAttributes currently bound to the thread.Exposes the previously bound RequestAttributes instance, if any. Falls back to the current JSF FacesContext, if any.Returns:the RequestAttributes currently bound to the threadThrows:IllegalStateException - if no RequestAttributes object is bound to the current threadSee Also:setRequestAttributes(org.springframework.web.context.request.RequestAttributes), ServletRequestAttributes, FacesRequestAttributes, FacesContext.getCurrentInstance()

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class HttpRequestUtils {

	// (1)
	public static HttpServletRequest getRequest() {
		return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
	}
    
	// (2)
	public static HttpServletRequest getCurrentRequest() {
		return ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
	}
}

간단하게 정리하면 GetRequestAttributes()는 RequestAttributes가 없으면 Null을 반환하고, currentRequestAttributes()는 RequestAttributes가 없으면 IllegalStateException을 발생시킨다.

비동기 쓰레드에서는 호출한 쓰레드와 다른 쓰데르이므로 RequestContextHolder에서 HttpServletRequest를 조회하지 못한다. 비동기 호출시 HttpServletRequest에서 값을 조회해야 한다면 비동기 호출 전에 값을 조회하여 해당 값을 전달하는 것이 좋다.

++ https://www.notion.so/Spring-Boot-RequestContext-2d7fea0f4582435a9d9dd273804da9a3


# 0923 - Git Refs

# Refs

git의 커밋은 key-value 형식으로 이루어져 있는데, key 값은 SHA-1으로 만들어진 해쉬값이다. 이를 사라들이 기억하기 어렵기 때문에 외우기 쉬운 이름의 파일에 저장되어 있는데, 이를 저장해 놓은 파일을 References 혹은 Refs라고 한다.

모든 refs는 .git/refs에 저장된다.

# 브랜치

git의 브랜치는 어떤 작업 중 마지막 작업을 가르키는 포인터 또는 Refs이다. 브랜치는 직접 가리키는 커밋과 그 커밋으로 따라갈 수 있는 모든 커밋을 포함한다.

git의 git branch <branch>를 실행하면 내부적으로 update-ref명령을 실행하고, 입력받은 브랜치 이름과 현 브랜치의 마지막 커밋의 SHA-1 값을 가져다 update-ref명령을 실행

HEAD 파일은 현 브랜치를 가리키는 간접 Refs다. 이 Refs는 다른 Refs를 가르키는 것이라서 SHA-1 값이 없다.

git commit을 실행하면 커밋 개체가 만들어지는데, 지금 HEAD가 가르키고 있던 커밋의 SHA-1 값이 그 커밋 개체의 부모로 사용된다.
HEAD 파일도 손으로 직접 편집할 수 있지만 git symbolic-ref라는 명령어가 있어서 좀 더 안전하게 사용할 수 있다. 이 명령으로 HEAD의 값을 읽고 변경할 수 있다. (다만 refs 형식에 맞춰야 한다.)

# 태그

태그는 커밋이랑 매우 비슷하다. 커밋처럼 누가, 언제 태그를 달았는지 태그 메시지는 무엇이고 어떤 커밋을 가리키는지에 대한 정보가 포함된다. 태그는 Tree가 아니라 커밋을 가르킨다.
브랜치처럼 커밋을 가리키지만 옮길 수는 없다. 태그는 늘 그 이름이 뜻하는 커밋만 가리킨다.

# 리모트

refs/heads에 있는 Refs인 브랜치와 달리 리모트 Refs는 Checkout 할 수 없고 읽기 용도로만 쓸 수 있는 브랜치인 것이다. 이 리모트 Refs는 서버의 브랜치가 가리키는 커밋이 무엇인지 적어둔 일종의 북마크이다.

리모트를 추가하고 Push하면 Git은 각 브랜치마다 Push 한 마지막 커밋이 무엇인지 refs/remotes디렉토리에 저장한다.


# 0926 - HTTPS 동작 과정

# HTTPS란?

image HTTPS는 HyperText Transfer Protocol Secure의 약자이며 HTTP의 보안 버전이다.
HTTPS는 TCP 위에 SSL/TLS 층을 추가하여 암호화, 인증 그리고 무결성 보장을 통해 더 안전하게 만들어주는 프로토콜이다.

# [ 대칭키 ]

암호화에 쓰이는 키와 복호화에 쓰이는 키가 동일한 기법
image 만약 클라이언트와 서버가 대칭키 방식으로 통신을 한다면 클라이언트도 키를 가지고 있어야 한다.
클라이언트에게 키를 전달하기도 위험하며 클라이언트의 소스코드는 누구든지 열어볼 수 있으므로 가지고 있기도 굉장히 위험하다.
즉, 원거리에서 대칭키를 안전하게 전달하는 것은 매우 어렵다.

# [ 공개키(비대칭키) ]

공개키와 개인키(비밀키)라는 2가지 키를 사용하는 기법이다.
공개키는 말그대로 누구나 획득할 수 있는 공개된 키를 뜻한다. 정보를 보내는쪽(클라이언트)은 이 키를 가지고 데이터를 암호화해서 전송한다.
개인키(비밀키)는 공개키로 암호화된 데이터를 복호화 할 수 있는 키로써 자신(서버)만이 가지고 있는 키다. image 이 방법은 안전하게 데이터를 주고받을 수 있게 만들어주지만 속도가 느리다는 단점이있다.

# [ 인증서와 CA(Certificate authority) ]

SSL을 적용하기 위해서는 인증서라는 것이 필요하다.
인증서의 내용은 크게 2가지로 구분할 수 있다.

  1. 서비스의 정보 (CA, 도메인 등등)
  2. 서버 측의 공개키 (공개키의 내용, 공개키의 암호화 방식)
    이 인증서를 발급해주는 기업을 CA라고 한다. 인증서가 보안에 관련된 것인 만큼 이 CA는 영향력있고 신뢰할 수 있는 기업에서만 가능하다.
    그리고 우리의 브라우저는 CA리스트를 미리 가지고 있다. CA목록에 있는 기업을 공인된 CA라고 하며 목록에 없는 기업을 사설 CA라고한다.

# HTTPS 통신과정 및 원리

간단하게 들여다보면 동작방식은 대칭키와 공개키(비대칭키) 방식을 전부 사용하는 하이브리드 방식이다.
데이터 전송을 위해 대칭키 방식을 사용하며 대칭키를 안전하게 전달하기 위해 공개키 방식을 사용한다. image

# 1. Client Hello

브라우저 마다 지원하는 암호화 알고리즘과 TLS 버전이 다르므로 해당 정보를 전송하며, 난수 값을 생성하여 전송한다.

# 2. Server Hello

사용할 TSL 버전, 사용할 암호화 알고리즘, 난수값을 전송

# 3. Certificate

CA로 부터 발급받은 인증서를 전송한다.

# 4. Server Key Exchange

키 교환에 필요한 정보를 제공한다. 만약 필요하지 않으면 이 과정은 생략이 가능한데, 예를 들어 키교환 알고리즘을 Diffie-Hellman으로 사용한다면 소수, 원시근 등이 필요하므로 이것을 전송한다.

# 5. Certificate Request

서버가 클라이언트를 인증해야할때 인증서를 요구하는 단계이다. 요청하지 않을 수 도 있다.

# 6. ServerHello Done

# 7. Client Key Exchange, Change Cipher Spec

pre-master-key 라는 것을 전송한다. 이 키는 1,2 단계에서 생성한 난수를 조합하여 생성하게되며 대칭키로 사용하게될 예정이다. 그러므로 안전한 정송을 위해서 공개키 방식을 사용하여 전송한다.

# 8. Change Cipher Spec

클라이언트로 부터 전송받은 Pre-master-key를 정상적으로 복호화 후 master-key(대칭키)로 승격 후 보안 파라미터를 적용하거나 변경될때 보내는 알람이다.


# 0927 - 조회 빈(Bean)이 2개 이상일때 동적으로 선택하는 방법

  • 인터페이스 및 구현체
interface SampleInterface {  
   ...  
}

@Repository  
public class SampleInterfaceImpl implements SampleInterface {  
    ...  
}

@Repository  
public class SampleInterfaceOtherImpl implements SampleInterface {  
    ...  
}

# 모든 인터페이스를 선언해놓고 분기처리

@Controller  
public class SampleController {  
    @Autowired  
    private SampleInterfaceImpl basic;  
  
    @Autowired  
    private SampleInterfaceOtherImpl other;  
  
    @RequestMapping(path = "/path/Basic", method = RequestMethod.GET)  
    public void basic() {  
        basic.sampleMethod();  
    }  
  
    @RequestMapping(path = "/path/Other", method = RequestMethod.GET)  
    public void other() {  
        other.sampleMethod();  
    }  
}

# 인터페이스를 Map에 넣어두고 꺼내서 사용

@Controller  
public class SampleController {  
    @Autowired  
    private SampleInterfaceImpl basic;  
  
    @Autowired  
    private SampleInterfaceOtherImpl other;  
  
    Map<String, SampleInterface> services;  
  
    @PostConstruct  
    void init() {  
        services = new HashMap()<>;  
        services.put("Basic", basic);  
        services.put("Other", other);  
    }  
  
    @RequestMapping(path = "/path/{service}", method = RequestMethod.GET)  
    public void method(@PathVariable("service") String service){  
        SampleInterface sample = services.get(service);  
        // remember to handle the case where there's no corresponding service  
        sample.sampleMethod();  
    }  
}

# ApplicationContext를 활용

구현체를 bean으로 등록하고

@Component  
public class SampleInterfaceImpl implements SampleInterface {  
    public void sampleMethod() {  
        // ...  
    }  
}  
  
@Component  
public class SampleInterfaceOtherImpl implements SampleInterface {  
    public void sampleMethod() {  
        // ...  
    }  
}

ApplicationContext의 getBean()으로 구현체를 로딩하여 사용한다.

@Controller  
public class SampleController {  
    @Autowired  
    private ApplicationContext appContext;  
  
    @RequestMapping(path = "/path/{service}", method = RequestMethod.GET)  
    public void method(@PathVariable("service") String service){  
        SampleInterface sample = appContext.getBean(service, SampleInterface.class);  
        sample.sampleMethod();  
    }  
}

# Spring의 DI 활용

set이나 list, map을 통해 자동으로 등록한다.

@Autowired  
private List<SampleInterface> SampleInterfaces;

---

@Autowired  
private Map<String, SampleInterface> SampleInterfaceMap;

---

void excuteSampleMethod() {  
    String interfaceName = ""  
      
    //enum으로 구분하거나, 조건에 맞게 수동으로분기하거나 서비스에맞게 고른다.  
    if (isFirst()) {  
        interfaceName = "SampleInterfaceImpl"  
    } else {  
        interfaceName = "SampleInterfaceOtherImpl"  
    }  
  
    SampleInterface impl = SampleInterfaceMap.get(interfaceName);  
    impl.sampleMethod();
  • enum으로 정책 정하기
public enum SampleType {  
    SAMPLE("SampleInterfaceImpl"),  
    OTHERS("SampleInterfaceOtherImpl")  
  
    SampleType(String implementation) {  
        this.implementation = implementation;  
    }  
  
    public String getImplementation() {  
        return this.implementation;  
    }  
}

--- 

void excuteSampleMethod(SampleType sampleType) {      
  
    SampleInterface impl = SampleInterfaceMap.get(sampleType.getImplementation);  
    impl.sampleMethod();  
}

--- 

@Component  
@RequiredArgsConstructor  
public class SampleInterfaceRouter {  
  
    private final List<SampleInterface> sampleInterfaces; //의존성 List로 주입  
  
    public SampleInterface getImplemetationByType(SampleType sampleType) {  
        return sampleInterfaces.stream()  
                            .filter(e -> e.isAvailableType(sampleType)) //각 구현체에서 판단  
                            .findFirst().orElseThrow(() -> new NotSupportedTypeException());  
    }  
}

# 0929 - Java HashMap

# Hash란?

Hash는 자료를 일정한 형식의 식별 값이다.

  • Key : 고유한 값, Hash function의 input.
  • Hash function : Key를 Hash로 변경해주는 역할을 한다.
  • Hash Code(또는 Hash Value) : 해시 함수를 통해 얻는 값으로, 해시를 인덱스 또는 주소로 삼아 Value에 접근이 가능하도록 한다.
  • Value : Key와 1:1로 매핑되어 있는 값으로 Bucket안에 들어가 있는 실제 값이다.
  • Bucket : 해시와 매핑되는 값(Value)이 저장되어 있는 공간이다.

# [ 요약 ]

  • HashMap은 Key-Value가 1:1로 Mapping 되는 자료구조이며, Mapping으로 인해 삽입, 삭제, 검색이 평균적으로 O(1)인 자료구조이다. Key는 중복을 허용하지 않지만, Value는 중복을 허용한다.
  • Java HashMap의 Key는 Object 형을 지원하기 때문에 완전한 해시 함수가 아니다.
  • Java HashMap의 index는 hashCode() % M(M = 해시 버킷의 개수)로 산출할 수 있다.
  • Java HashMap 해시 충돌이 일어날 경우, 충돌 처리방법으로 Worst Case발생 빈도를 줄일 수 있는 Separate Chaining방식을 사용한다.
  • Java8이상 HashMap에서는 하나의 해시 버킷에 8개의 key-value 쌍이 모이면 링크드리스트를 트리로 변경하고, 데이터가 삭제되어 8개에 이르면 다시 링크드 리스트로 변경한다.
  • String 클래스의 hashCode() 메서드에서는 성능 향상 도모를 위해 31을 승수로 사용한다
Last update: September 29, 2022 23:09
Contributors: jaesungahn91