DevLog ๐Ÿ˜ถ

[Spring] ํ”„๋กœํผํ‹ฐ ์ฃผ์ž… ์‹œ ๋™์ ์œผ๋กœ ํ•ธ๋“ค๋งํ•˜๊ธฐ ๋ณธ๋ฌธ

๊ฐœ๋ฐœ์ผ์ง€

[Spring] ํ”„๋กœํผํ‹ฐ ์ฃผ์ž… ์‹œ ๋™์ ์œผ๋กœ ํ•ธ๋“ค๋งํ•˜๊ธฐ

dolmeng2 2024. 9. 28. 09:21

 

๐ŸŒฑ ๋“ค์–ด๊ฐ€๊ธฐ ์ „

๋ธ”๋กœ๊ทธ๋ฅผ ์ž์ฃผ ์จ์•ผ๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ ๋˜ ๋งˆ์ง€๋ง‰ ๊ธ€์„ ์“ด ์ง€ 3๊ฐœ์›”์ด ์ง€๋‚˜๋ฒ„๋ ธ๋‹ค. ๊ทธ๋ž˜์„œ, ์‚ฌ๋‚ด์—์„œ ๊ฐœ๋ฐœํ•˜๋˜ ์ค‘์— ๋ฐœ์ƒํ–ˆ๋˜ ์ด์Šˆ๊ฐ€ ์ƒ๊ฐ๋‚˜์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ ์–ด๋ณด๊ณ ์ž ํ•œ๋‹ค. ์‚ฌ์‹ค ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ–ˆ๋˜ ๋ฌธ์ œ์˜€๋Š”๋ฐ, ์–ด๋–ค ์‹์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด๋‚˜๊ฐ”๋Š”์ง€ ์ ์–ด๋‘๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์„œ ์ž‘์„ฑํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

ํ˜„์žฌ ์‚ฌ๋‚ด ์‹œ์Šคํ…œ์—์„œ๋Š” ์นดํ”„์นด๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ๋กœํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ถ€๋ถ„์ด ๋งŽ์€๋ฐ, ์šฐ๋ฆฌ ์„œ๋น„์Šค์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์„ ๋งŒํผ ์ค‘์š”ํ•œ ๊ธฐ๋Šฅ์ด๋‹ค ๋ณด๋‹ˆ๊นŒ ์ž‘์€ ํ”ผ์ฒ˜๋ฅผ ๊ฐœ๋ฐœํ•˜๋”๋ผ๋„ ๊ผญ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์ฃผ๊ณ  ๋„˜์–ด๊ฐ€๋Š” ๋ถ€๋ถ„๋“ค์ด ์žˆ๋‹ค.
์ด๋•Œ, ๋ณดํ†ต์€ ๋ณธ์ธ์˜ ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ๋„์›Œ์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ๋งŽ์ง€๋งŒ, ๋‚˜ ๊ฐ™์€ ๊ฒฝ์šฐ ๋กœ์ปฌ์—์„œ ์ฒ˜๋ฆฌ๋˜๋Š” ๋Š๋ฆฐ ์†๋„ & ์‹ค์ œ ์•ฑ ํ™˜๊ฒฝ์—์„œ ์–ด๋–ค ์‹์œผ๋กœ ๋Œ์•„๊ฐ€๋Š”์ง€ (๋ชจ๋“  ํ”Œ๋กœ์šฐ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€) ํ™•์ธํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์„œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์— ์ž‘์—…๋ฌผ์„ ๋„์›Œ์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๋‹ค.
 
์ด๋•Œ, ํ˜„์žฌ ์‚ฌ๋‚ด ์‹œ์Šคํ…œ ์ƒ์œผ๋กœ๋Š” ๋‚ด๊ฐ€ ์ž‘์—…ํ•œ branch๋ฅผ ๋Œ€์ƒ์œผ๋กœ ๋‚ด ์š”์ฒญ์ด ์ž๋™์œผ๋กœ ํฌ์›Œ๋”ฉ ๋˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์–ด ํ…Œ์ŠคํŠธํ•˜๋Š”๋ฐ ์–ด๋ ต์ง€๋Š” ์•Š์ง€๋งŒ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

โ˜๏ธŽ ์นดํ”„์นด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ๋Š” ๋ณ„๋„๋กœ ํ† ํ”ฝ, ํ˜น์€ ์ปจ์Šˆ๋จธ ๊ทธ๋ฃน์„ ๋ถ„๋ฆฌํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด ๋‚ด ๊ฐœ์ธ ์ž‘์—… ํ™˜๊ฒฝ์—์„œ ํ”„๋กœ๋“€์‹ฑํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐœ๋ฐœํ™˜๊ฒฝ์—์„œ ์ปจ์Š˜์ด ๋˜์–ด ์›ํ•˜๋Š”๋Œ€๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์Œ
โ˜๏ธŽ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์— ๋ฐ”๋กœ merge๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด ํƒ€์ธ์˜ ์ž‘์—…๋ฌผ๊ณผ ๊ฒน์น˜๊ธฐ๋„ ํ•˜๊ณ , ์‹ค์ˆ˜๋กœ ์šด์˜ ํ™˜๊ฒฝ์œผ๋กœ ๋ฐฐํฌ๋ณธ์ด ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ์Œ

 
์ฒซ ๋ฒˆ์งธ ๋ฌธ์ œ์˜ ๊ฒฝ์šฐ, ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ๋‚ด๊ฐ€ ์ž‘์—…ํ•œ ๋ธŒ๋žœ์น˜ ํ™˜๊ฒฝ์ด ๋‘˜ ๋‹ค ์ปจ์Š˜์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜์–ด์„œ ์ผ๋ถ€ ํŠธ๋ž˜ํ”ฝ์€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์œผ๋กœ, ์ผ๋ถ€ ํŠธ๋ž˜ํ”ฝ์€ ๋‚˜์˜ ๊ฐœ์ธ ๋ธŒ๋žœ์น˜ ํ™˜๊ฒฝ์œผ๋กœ ์ปจ์Š˜์ด ๋˜์–ด, ๋ชจ๋“  ๋กœ์ง๋“ค์ด ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธ์ด ์–ด๋ ค์šด ๋ฌธ์ œ์˜€๋‹ค.
 
๊ทธ๋ž˜์„œ ๋ณดํ†ต ์šฐ๋ฆฌ ํŒ€ ์ธ์›๋“ค์€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์— ๋ฐ”๋กœ merge๋ฅผ ์ง„ํ–‰ํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์—ˆ๋Š”๋ฐ, ์‚ฌ์‹ค ์˜ฌ๋ฐ”๋ฅธ ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋ผ๋Š” ์ƒ๊ฐ์ด ๊ณ„์† ๋“ค์—ˆ์—ˆ๋‹ค. (๋ฏฟ์Œ ์ฃผ๋„ ๊ฐœ๋ฐœ....) ํ˜น์€, ๊ฐœ์ธ ์ž‘์—…๋ฌผ ๋Œ€์ƒ์œผ๋กœ ๋งค๋ฒˆ ํ† ํ”ฝ์ด๋‚˜ ์ปจ์Šˆ๋จธ ๊ทธ๋ฃน์„ ๋ณ€๊ฒฝํ•˜์—ฌ push ํ•˜๊ณ , merge ์‹œ์— ์ œ๊ฑฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์—ˆ๋‹ค. ๋‹น์—ฐํ•˜๊ฒŒ๋„ ์ด๋Š” ๋งค์šฐ ๊ท€์ฐฎ๋‹ค ๋ณด๋‹ˆ๊นŒ ์ž์ฃผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ฐฉ๋ฒ•์ด์—ˆ๋‹ค... ใ… ใ… 
 
๋ฌผ๋ก ! ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด์„œ ์–ด๋Š ์ •๋„ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ (embedded kafka ํ™œ์šฉ) ์•ฑ ๋‚ด์—์„œ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์กฐ๊ธˆ ๋” ํŽธํ•˜๊ฒŒ ๋Š๊ปด์ ธ์„œ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๊ฒฐ์„ ํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค. (์‚ฌ์‹ค ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋งž๋‹ค...! ๋‹ค๋งŒ ์šฐ๋ฆฌ์˜ ๋ชจ๋“  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋Œ€ํ•ด ์™„๋ฒฝํ•˜๊ฒŒ ์ ์šฉ์ด ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋ผ์„œ ํ…Œ์ŠคํŠธ์— ์กฐ๊ธˆ ๋” ๊ณ ๋„ํ™”๊ฐ€ ํ•„์š”ํ•œ ์ƒํ™ฉ์ด์—ˆ๋˜ ๊ฒƒ๋„ ๋งž๋‹ค.)
 
๊ทธ๋ž˜์„œ, โ€˜๊ฐœ์ธ branch์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๋•Œ ์‚ฌ์šฉ์ž์— ๋”ฐ๋ผ ์ž๋™์œผ๋กœ ํ† ํ”ฝ์ด ๋ณ€๊ฒฝ๋˜๋„๋ก ๋งŒ๋“ค๊ธฐโ€™๋ฅผ ๋ชฉํ‘œ๋กœ ์‚ผ๊ณ  ์ด๋ฒˆ์— ์ž‘์—…์„ ์ง„ํ–‰ํ•ด๋ณด์•˜์—ˆ๋‹ค.


 

๐ŸŒฑ ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ ๋„์ž…ํ•ด๋ณด๊ธฐ

ํ˜„์žฌ ์‚ฌ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ํ† ํ”ฝ์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ application.yml ํŒŒ์ผ์— ์ •์˜๋˜์–ด ์žˆ์–ด, ํ† ํ”ฝ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด @Value ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœํผํ‹ฐ ์ •๋ณด๋ฅผ ์ฝ์–ด์™”์—ˆ๋‹ค.

@Value("\${very.important.topic}")
val topic: String

    
๊ทธ๋ž˜์„œ ์ฒ˜์Œ ์ž‘์—…์„ ๋“ค์–ด๊ฐˆ ๋•Œ๋Š” ์Šคํ”„๋ง์˜ @Value ์–ด๋…ธํ…Œ์ด์…˜์ด ์–ด๋–ค ์‹์œผ๋กœ ๋Œ์•„๊ฐ€๋Š”์ง€ ์‚ดํŽด๋ณด๊ณ  ์‹ถ์–ด, @Value ์–ด๋…ธํ…Œ์ด์…˜์— ์ ํ˜€ ์žˆ๋Š” ์ฃผ์„์„ ๋จผ์ € ์ฝ์–ด๋ณด์•˜์—ˆ๋‹ค.
 

Note that actual processing of the @Value annotation is performed by a BeanPostProcessor which in turn means that you cannot use @Value within BeanPostProcessor or BeanFactoryPostProcessor types. Please consult the javadoc for the AutowiredAnnotationBeanPostProcessor class (which, by default, checks for the presence of this annotation)


์œ„ ๋‚ด์šฉ์„ ๋ณด๋‹ˆ, ์‹ค์งˆ์ ์œผ๋กœ @Value ์–ด๋…ธํ…Œ์ด์…˜์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋Š” BeanPostProcessor ์— ์˜ํ•ด์„œ ์ˆ˜ํ–‰์ด ๋œ๋‹ค๊ณ  ์จ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ์œผ๋ฉฐ, AutowiredAnnotationBeanPostProcessor ์„ ์ฐธ๊ณ ํ•ด๋ณด๋ผ๋Š” ๋‚ด์šฉ์ด์—ˆ๋‹ค. ์˜คํ˜ธ, ๊ทธ๋Ÿฌ๋ฉด ์ด ์นœ๊ตฌ๋ฅผ ํ†ตํ•ด์„œ ์ง„ํ–‰ํ•˜๊ฒ ๋‹ค ์‹ถ์–ด์„œ ๋Œ€์ถฉ ๋‚ด๋ถ€ ๋‚ด์šฉ์„ ๋ณด์•˜๋‹ค.

public AutowiredAnnotationBeanPostProcessor() {
    this.autowiredAnnotationTypes.add(Autowired.class);
    this.autowiredAnnotationTypes.add(Value.class);
    ...
}


์ƒ์„ฑ์ž์—์„œ โ€˜autowiredAnnotationTypesโ€™ ๋ผ๋Š” ํ•„๋“œ์— @Value ์–ด๋…ธํ…Œ์ด์…˜์„ ๋„ฃ์–ด๋‘๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ , ์กฐ๊ธˆ ๋” ํŒŒ๊ณ  ๋“ค๋‹ค ๋ณด๋‹ˆ ๋Œ€์ถฉ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ํŒŒ์•…ํ•˜์˜€๋‹ค.

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // ๋นˆ์— ๋Œ€ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์กฐํšŒํ•˜๊ธฐ
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    // ์˜์กด์„ฑ ์ฃผ์ž…ํ•˜๊ธฐ
    metadata.inject(bean, beanName, pvs);
    return pvs;
}

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    ...
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    ...
    // ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ
    metadata = buildAutowiringMetadata(clazz);
}

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
    // @Value๋‚˜ @Autowired ๊ฐ€ ๋ถ™์€ ๊ฒŒ ์•„๋‹ˆ๋ผ๋ฉด ํŒจ์Šค
    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    }

    ...

    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();

    final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

    // ์–ด๋…ธํ…Œ์ด์…˜ ์ฐพ์•„์„œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ
    ReflectionUtils.doWithLocalFields(targetClass, field -> {
    MergedAnnotation<?> ann = findAutowiredAnnotation(field);
    if (ann != null) {
        if (Modifier.isStatic(field.getModifiers())) {
            if (logger.isInfoEnabled()) {
                logger.info("Autowired annotation is not supported on static fields: " + field);
            }
            return;
        }
        boolean required = determineRequiredStatus(ann);
        currElements.add(new AutowiredFieldElement(field, required));
    }
});

    ...

    return InjectionMetadata.forElements(elements, clazz);
}


- ํŠน์ • ํด๋ž˜์Šค์—์„œ @Autowired๊ฐ€ ๋ถ™์€ ํ•„๋“œ์™€ ๋ฉ”์„œ๋“œ๋ฅผ ์ฐพ์•„๋‚ด๊ณ , ํ•ด๋‹น ํ•„๋“œ๋‚˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์˜์กด์„ฑ ์ฃผ์ž…์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์ถ•ํ•œ๋‹ค.
- ๊ตฌ์ถ•๋œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‹ค์ œ๋กœ ์ฃผ์ž…์„ ์ง„ํ–‰ํ•œ๋‹ค.
- ์ด๊ฒƒ์„ ๋ณด๊ณ  ๋Œ€์ถฉ ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฆฌํ”Œ๋ ‰์…˜์„ ํ†ตํ•ด ๋™์ ์œผ๋กœ ํ•„๋“œ ๋‚ด์šฉ์„ ๋ณ€๊ฒฝํ•ด์ค„ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.
 


 

๐ŸŒฑ  ์‚ฌ์šฉ์ž ๊ตฌ๋ถ„ํ•˜๊ธฐ

๊ทธ๋ ‡๋‹ค๋ฉด, ์ด๋ฒˆ์—๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์–ด๋–ป๊ฒŒ ์‹๋ณ„ํ•˜๋ฉด ์ข‹์„์ง€๊ฐ€ ๊ณ ๋ฏผ์ด ๋˜์—ˆ๋‹ค.
์‹ค์ œ๋กœ ๊ฐœ์ธ branch๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋„์›Œ์งˆ ๋•Œ ์„œ๋ฒ„์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋™์ ์œผ๋กœ ์ฃผ์ž…ํ•ด์ค„ ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด๋„ ๋˜์ง€๋งŒ, ์ธํ”„๋ผ ๋ ˆ๋ฒจ๊นŒ์ง€ ๋ฌด์–ธ๊ฐ€ ์ˆ˜์ •์„ ํ•˜๊ณ  ์‹ถ์ง€ ์•Š์•„์„œ ์–ด๋–ค ์ •๋ณด๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„์ง€ ๊ณ ๋ฏผ์„ ํ–ˆ์—ˆ๋‹ค.
 
๊ทธ๋Ÿฌ๋‹ค๊ฐ€ ๋ฐœ๊ฒฌํ•œ ๊ฒƒ์ด, ์ฒ˜์Œ ์Šคํ”„๋ง์ด ์‹œ์ž‘๋  ๋•Œ ๋œจ๋Š” ๋ฌธ๊ตฌ์˜€๋‹ค.
์ฒ˜์Œ ์Šคํ”„๋ง์ด ์‹œ์ž‘๋˜๋ฉด โ€˜Starting TestApplication using Java 17.0.10 on jwjw-1 with PID โ€ฆโ€˜ ์™€ ๊ฐ™์€ ๋ฌธ๊ตฌ๊ฐ€ ๋œฌ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด๊ณ , ์—ฌ๊ธฐ์—์„œ โ€˜jwjw-1โ€™๋ผ๋Š” ์ด๋ฆ„์€ ์–ด๋””์—์„œ ๊ฐ€์ ธ์˜ค๋Š”์ง€ ๊ถ๊ธˆํ•ด์กŒ์—ˆ๋‹ค.
 
์‹ค์ œ๋กœ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋ถ„๋“ค์ด ์„œ๋ฒ„๋ฅผ ๋œฐ ๋•Œ ๋‹ค ๋‹ค๋ฅธ ์ด๋ฆ„์ด ๋…ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ๋ณด๊ณ , ์ด ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์—ฌ ํ† ํ”ฝ์˜ postfix๋กœ ๋ถ™์—ฌ์ค€๋‹ค๋ฉด ๋”ฑ ์‚ฌ์šฉ์ž ์ˆ˜๋งŒํผ๋งŒ ์‹ ๊ทœ ํ† ํ”ฝ์ด ์ƒ์„ฑ๋˜๋‹ˆ ๊ดœ์ฐฎ์„ ๊ฒƒ์ด๋ผ๊ณ  ํŒ๋‹จํ•˜์˜€๋‹ค.
 
๊ทธ๋ž˜์„œ, ์ด ๋ฌธ๊ตฌ๋ฅผ ์–ด๋””์—์„œ ์ฐ์–ด์ค„์ง€ ํ™•์ธํ•ด๋ณด๊ธฐ ์œ„ํ•ด์„œ ๋˜ ๋‹ค์‹œ ํ•˜๋‚˜์”ฉ ํŒŒ๊ณ  ๋“ค๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค.
๊ธฐ๋ณธ์ ์œผ๋กœ ์ฝ”ํ‹€๋ฆฐ + ์Šคํ”„๋ง ๋ถ€ํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด main ํ•จ์ˆ˜์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‹คํ–‰ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

fun main(args: Array<String>) {
    runApplication<TestApplication>(*args)
}

inline fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext =
    SpringApplication.run(T::class.java, *args)

    
์—ฌ๊ธฐ์—์„œ ๋‚ด๋ถ€๋ฅผ ๊ณ„์† ํŒŒ๊ณ  ๋“ค์–ด๊ฐ€๋‹ค ๋ณด๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด start messsage๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์—ฌ๊ธฐ์—์„œ appendOn() ํ•จ์ˆ˜๋ฅผ ๋ณด๋ฉด, ๋‚ด๋ถ€์ ์œผ๋กœ InetAddress.getLocalHost().getHostName() ๋ฅผ ํ†ตํ•ด์„œ ๋ฐ›์•„์˜ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค!
 

private CharSequence getStartingMessage() {
    StringBuilder message = new StringBuilder();
    message.append("Starting ");
    appendApplicationName(message);
    appendVersion(message, this.sourceClass);
    appendJavaVersion(message);
    // ์—ฌ๊ธฐ!
    appendOn(message);
    appendPid(message);
    appendContext(message);
    return message;
}

private void appendOn(StringBuilder message) {
    long startTime = System.currentTimeMillis();
    append(message, "on ", () -> InetAddress.getLocalHost().getHostName());
    ...
}


ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์„ ํ™œ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ๊ฐœ์ธ branch์—์„œ ์ž‘์„ฑํ•œ ๋‚ด์šฉ์ด ๋‚˜๊ฐ„ ์„œ๋ฒ„์˜ ๊ฒฝ์šฐ ํ•ด๋‹น branch๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๊ฐ€ ๋‚จ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‹๋ณ„์„ ํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ํŒ๋‹จํ•˜์˜€๋‹ค.
 


 

๐ŸŒฑ  ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ํ™œ์šฉํ•œ ์ดˆ๊ธฐ ๊ตฌ์„ฑ

 
์–ด๋Š ์ •๋„ ์œค๊ณฝ์ด ์žกํ˜”์œผ๋‹ˆ, ์‹ค์ œ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž.
์ดˆ๊ธฐ์—๋Š” `@ConvertTopic` ์ด๋ผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์„ ๋งŒ๋“ค์–ด์„œ, ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด ์žˆ๋Š” ํ•„๋“œ๋“ค์— ๋Œ€ํ•ด ๋ฆฌํ”Œ๋ ‰์…˜์œผ๋กœ ์ ‘๊ทผํ•˜์—ฌ ๊ฐ์ฒด๋ฅผ ๋‹ค์‹œ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•˜์˜€๋‹ค.

@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class ConvertTopic(
    val topic: String
)

 
์—ฌ๊ธฐ์—์„œ ํ•„๋“œ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” โ€˜topicโ€™์— ์‹ค์ œ ํ† ํ”ฝ ์ •๋ณด๋ฅผ ๋‹ด์€ ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„์„ ๋‹ด์„ ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•˜์˜€๋‹ค.
์ดํ›„, ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ ํ•จ์ˆ˜์˜ ์ธํ„ฐํŽ˜์ด์Šค ์ค‘์—์„œ ์Šคํ”„๋ง ๋นˆ์ด ์ƒ์„ฑ๋œ ์ดํ›„, ์ดˆ๊ธฐํ™” ์ฝœ๋ฐฑ์ด ํ˜ธ์ถœ๋˜๊ธฐ ์ด์ „์— ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ๋„๋ก postProcessBeforeInitialization() ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•˜์˜€๋‹ค.
    

@Component
class ConvertTopicAnnotationBeanPostProcessor(
    private val environment: Environment,
    @Value("\${spring.profiles.active}")
    private val profile: String,
) : BeanPostProcessor {

    override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
        ReflectionUtils.doWithFields(bean::class.java, ConvertTopicCallback(bean, environment, profile))
        return bean
    }
}


๋‹คํ–‰ํžˆ๋„ ์Šคํ”„๋ง์—์„œ๋Š” ReflectionUtils ๋ผ๋Š” ๊ณ ๊ธ‰ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์กฐ๊ธˆ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.
๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ž‘์„ฑํ•œ FieldCallback ์„ ํ†ตํ•˜์—ฌ ํŠน์ • ํ•„๋“œ์— ๋Œ€ํ•ด ํ•ด๋‹น ์ฝœ๋ฐฑ ๋‚ด๋ถ€์—์„œ ์กฐ์ž‘ํ•ด ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ๋ฐ›์€ ํด๋ž˜์Šค๋ฅผ ์›ํ•˜๋Š”๋Œ€๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
 

class ConvertTopicCallback(
    private val bean: Any,
    private val environment: Environment,
    private val profile: String,
) : ReflectionUtils.FieldCallback {

    override fun doWith(field: Field) {
        // ์›ํ•˜๋Š” profile์ด ์•„๋‹ˆ๋ฉด ์กฐ์ž‘ X
        if (profile != "...") {
            return
        }

        val targetAnnotation = field.getAnnotation(ConvertTopic::class.java) ?: return
        field.isAccessible = true

        // topic ํ•„๋“œ์— ๋‹ด๊ธด ํ”„๋กœํผํ‹ฐ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‹ค์ œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
        val original = environment.getProperty(targetAnnotation.topic)

        // ๋ฐ”๋€ ๊ฐ’
        val hostName = InetAddress.getLocalHost().hostName
        val new = original.plus(".$hostName")
        field.set(bean, new)
    }
}

 
๊ทธ๋ฆฌ๊ณ , ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์ด ๋‹ฌ๋ ค ์žˆ๋Š” ํ•„๋“œ์— ๋Œ€ํ•ด์„œ๋งŒ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐ„๋‹จํ•œ ๊ฒ€์ฆ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ , ํ”ํ•œ ๋ฆฌํ”Œ๋ ‰์…˜ ์ฝ”๋“œ์—์„œ ๋งŽ์ด ๋ณผ ์ˆ˜ ์žˆ๋Š” isAccessible ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด ํ•„๋“œ ๊ฐ’์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์—ˆ๋‹ค. ์‹ค์ œ๋กœ๋Š” profile ๋ณ„๋กœ ์กฐ๊ธˆ ๋” ์ปจ๋ฒ„ํŒ… ํ•˜๋Š” ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜์˜€์ง€๋งŒ, ์—ฌ๊ธฐ์—์„œ๋Š” ๊ฐ„๋žตํ•˜๊ฒŒ ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์„ ๋ถˆ๋Ÿฌ์˜จ ํ”„๋กœํผํ‹ฐ์— ์ถ”๊ฐ€ํ•˜๋Š” ์ •๋„๋กœ๋งŒ ์ž‘์„ฑํ•ด๋‘์—ˆ๋‹ค.
๊ต‰์žฅํžˆ ๋ณต์žกํ•ด ๋ณด์—ฌ๋„, ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ๋‹จ์ˆœํžˆ ํ•„๋“œ ๊ฐ’์„ ์›ํ•˜๋Š”๋Œ€๋กœ ๋ฐ”๊พธ์–ด์„œ ์…‹ํŒ…ํ•ด์ฃผ๋Š” ์ •๋„์ด๋‹ค.
 
์ •์ƒ์ ์œผ๋กœ ๊ธฐ๋Šฅ์ด ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•ด๋ณด์ž.

@Component
class TestClass(
    @Value("\${test-topic1}")
    private val testTopic1: String
) {
    @ConvertTopic("test-topic2")
    private lateinit var testTopic2: String

    fun getTestTopic1(): String {
        return testTopic1
    }

    fun getTestTopic2(): String {
        return testTopic2
    }
}


๋จผ์ €, ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜ ์ •์˜ํ•˜์—ฌ ๋งŒ๋“ค์–ด์ค€ @ConvertTopic ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ธ ํ•„๋“œ์™€ ๊ธฐ๋ณธ @Value ๋ฅผ ํ†ตํ•˜์—ฌ ์ •์˜ํ•œ ํ•„๋“œ๋ฅผ ๊ฐ๊ฐ ์„ ์–ธํ•ด์ฃผ์—ˆ๋‹ค.
   

@Import(TestClass::class)
@TestPropertySource(
    properties = [
        "test-topic1=topic1",
        "test-topic2=topic2"
    ]
)
class ConvertTopicAnnotationBeanPostProcessorTest(
    private val testClass: TestClass,
) : BehaviorSpec({

    Given("ํ† ํ”ฝ ์น˜ํ™˜ ํ…Œ์ŠคํŠธ") {
        When("@ConvertTopic ๊ฐ€ ๋ถ™์–ด ์žˆ์ง€ ์•Š์€ ๋ณ€์ˆ˜์— ๋Œ€ํ•ด์„œ") {
            Then("์ปจ๋ฒ„ํŒ…์„ ์ง„ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.") {
                testClass.getTestTopic1() shouldBe "topic1"
            }
        }

        When("@ConvertTopic ๊ฐ€ ๋ถ™์€ ๋ณ€์ˆ˜์— ๋Œ€ํ•ด์„œ") {
            Then("์ปจ๋ฒ„ํŒ…์„ ์ง„ํ–‰ํ•œ๋‹ค.") {
                testClass.getTestTopic2() shouldBe "topic2.test"
            }
        }
    }
})


๊ทธ๋ฆฌ๊ณ , @TestPropertySource ๋ฅผ ํ†ตํ•ด์„œ ์‹ค์ œ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉํ•  ํ…Œ์ŠคํŠธ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ •์˜ํ•ด์ฃผ์—ˆ๋‹ค. application-test.yml ๋“ฑ์„ ํ™œ์šฉํ•ด๋„ ์ข‹์ง€๋งŒ, ํ•ด๋‹น ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค์—์„œ๋งŒ ์œ ํšจํ•˜๊ธฐ๋ฅผ ๋ฐ”๋žฌ๊ณ , ๋ฌด์—‡๋ณด๋‹ค ๋ช…์‹œ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ์— ๋“œ๋Ÿฌ๋‚˜๊ธฐ๋ฅผ ๋ฐ”๋žฌ๋‹ค. (๋ณ„๋„์˜ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๊ฒŒ ๋˜๋ฉด ์•„๋ฌด๋ž˜๋„ ํ•ด๋‹น ํŒŒ์ผ์„ ๋‹ค์‹œ ๋“ค์–ด๊ฐ€์„œ ํ™•์ธ์„ ํ•ด๋ด์•ผ ํ•˜๋‹ˆ๊นŒ ๋ถˆํŽธํ•˜๊ฒŒ ๋Š๊ปด์งˆ ์ˆ˜๋„ ์žˆ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค,)
 
์‹ค์ œ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค๋ณด๋ฉด ์˜๋„๋Œ€๋กœ ์ž˜ ๋™์ž‘์„ ํ•˜์˜€์œผ๋ฉฐ, test-topic1์˜ ๊ฒฝ์šฐ ๊ทธ๋Œ€๋กœ topic1 ์ด๋ผ๋Š” ๊ฐ’์œผ๋กœ, test-topic2์˜ ๊ฒฝ์šฐ topic2๊ฐ€ ์•„๋‹Œ topic2.test๋กœ ์ปจ๋ฒ„ํŒ…์ด ์ž˜ ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค!
 
๊ทธ๋Ÿฌ๋‚˜, ์—ฌ๊ธฐ์—์„œ ํ•œ ๊ฐ€์ง€ ์ฐœ์ฐœํ•œ ์ ์ด ์žˆ์—ˆ๋‹ค. ๋ฐ”๋กœ, TestClass์—์„œ๋„ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ƒ์„ฑ์ž ์ฃผ์ž…์„ ํ•  ์ˆ˜ ์—†์—ˆ๋˜ ๊ฒƒ์ด์—ˆ๋‹ค.
์•„๋ฌด๋ž˜๋„ ํด๋ž˜์Šค์˜ ์ƒ์„ฑ์ž ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์•„๋‹Œ ํ•„๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ ‘๊ทผํ•˜๋„๋ก ํ•˜๋ ค๊ณ  ํ•˜๋‹ค ๋ณด๋‹ˆ ๊ทธ๋žฌ๋˜ ๊ฒƒ ๊ฐ™์•˜์—ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด @Value + @ConvertTopic ์„ ํ•จ๊ป˜ ํ˜ผ์šฉํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ , ํ•„๋“œ์— ๋Œ€ํ•œ ์ฝœ๋ฐฑ ๋กœ์ง์„ ์‚ด์ง ์ˆ˜์ •ํ•ด์ฃผ์—ˆ๋‹ค.
 

override fun doWith(field: Field) {
    // ํ•„๋“œ์— ์–ด๋…ธํ…Œ์ด์…˜์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
    if (field.isAnnotationPresent(ConvertTopic::class.java).not()) {
        return
    }

    // ...

    // bean ๊ฐ์ฒด์— ๋Œ€ํ•ด field๋กœ ์ง€์ •๋œ ํ•„๋“œ ๊ฐ’์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋„๋ก ์ˆ˜์ •
    // ์›๋ณธ ๊ฐ’
    val original = field.get(bean).toString()

    // ๋ฐ”๋€ ๊ฐ’
    val hostName = InetAddress.getLocalHost().hostName
    val new = original.plus(".$hostName")
    field.set(bean, new)
}


์–ด๋…ธํ…Œ์ด์…˜์˜ ํ•„๋“œ๋กœ ์ฃผ์–ด์กŒ๋˜ ๊ฐ’์„ ํ™œ์šฉํ–ˆ๋˜ ๊ฒƒ๊ณผ ๋‹ค๋ฅด๊ฒŒ, ์ด๋ฒˆ์—๋Š” bean ๊ฐ์ฒด์˜ field ๊ฐ’์„ ์ง์ ‘ ์ ‘๊ทผํ•˜๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค. ์ด ์ฝ”๋“œ๊ฐ€ ์ž˜ ์ดํ•ด๊ฐ€ ์•ˆ ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์•„๋ž˜ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค์˜ ์˜ˆ์ œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์•Œ์•„๋ณด์ž.
 

@Component
class TestClass(
    @Value("\${test-topic1}")
    private val testTopic1: String,

    @ConvertTopic
    @Value("\${test-topic2}")
    private val testTopic2: String,
)

 
๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ์˜ ๊ฒฝ์šฐ ๋นˆ์ด ์ƒ์„ฑ๋œ ์ดํ›„, ์ฆ‰ ์Šคํ”„๋ง์—์„œ ํ”„๋กœํผํ‹ฐ์— ๋Œ€ํ•œ placeHolder ์ž‘์—…์ด ์™„๋ฃŒ๋œ ์ดํ›„์— ์ฒ˜๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— testTopic2์—๋Š” @Value ๋กœ ๋ถ€ํ„ฐ ์ฃผ์ž…๋ฐ›์€ โ€˜topic2โ€™ ๋ผ๋Š” ๊ฐ’์ด ๋“ค์–ด๊ฐ€ ์žˆ๊ฒŒ ๋œ๋‹ค.
์ดํ›„, field.get(bean) ๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด ์ฃผ์ž…๋œ ๊ฐ’์ธ โ€˜topic2โ€™ ๋ผ๋Š” ๊ฐ’์ด ๋ฐ˜ํ™˜๋˜๊ธฐ ๋•Œ๋ฌธ์—, environment์— ๋Œ€ํ•ด์„œ๋„ ์ ‘๊ทผํ•  ํ•„์š” ์—†์ด ๋ฐ”๋กœ ํ•ด๋‹น ๊ฐ’์„ original๋กœ ํŒ๋‹จํ•˜์—ฌ ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์„ postfix๋กœ ๋ถ™์—ฌ์ฃผ๋ฉด ๋œ๋‹ค.
 




๐ŸŒฑ ํ•˜์ง€๋งŒ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€๋‹ค

๊ทธ๋Ÿฌ๋‚˜, ํ•ญ์ƒ ๊ทธ๋ ‡๋“ฏ์ด ์ด๋ ‡๊ฒŒ ๋๋‚˜๋ฉด ์ฐธ ์ข‹์•˜๊ฒ ์ง€๋งŒโ€ฆ ์œ„์˜ ๋กœ์ง์—๋Š” 2๊ฐ€์ง€ ์ •๋„์˜ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค.

โ˜๏ธŽ ์ฒ˜์Œ ๋ณด๋ฉด ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šด ์ฝ”๋“œ + ๋ฆฌํ”Œ๋ ‰์…˜ ์‚ฌ์šฉ์œผ๋กœ ์ธํ•œ ๊ฑฐ๋ถ€๊ฐ
โ˜๏ธŽ lazy ๋Œ€๋ฆฌ์ž์— ๋Œ€ํ•ด์„œ ์ œ๋Œ€๋กœ ์ž‘๋™๋˜์ง€ ์•Š์Œ

 
๋จผ์ €, ๋ฆฌํ”Œ๋ ‰์…˜์˜ ๊ฒฝ์šฐ ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋Š” ์žˆ์œผ๋‚˜, ์–ด์ฐจํ”ผ ์ฒ˜์Œ์— ๋นˆ์ด ๋„์›Œ์งˆ ๋•Œ ์ˆ˜์ •์ด ๋˜๋Š” ๊ฒƒ์ด๋ผ์„œ ํฌ๊ฒŒ ์šฐ๋ ค๋˜์ง€ ์•Š์•˜๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ์ด๊ฒƒ๋ณด๋‹ค๋Š” ๋„ˆ๋ฌด ์Šคํ”„๋ง์— ์˜์กด์ ์ธ ์ฝ”๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ฒ˜์Œ ๋ณด๋Š” ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์–ด๋ ค์šด ์ฝ”๋“œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.
๋˜ํ•œ, ์‚ฌ์‹ค์ƒ ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์„ ๊ฐ€์ ธ์™€์„œ ํ”„๋กœํผํ‹ฐ์— ๋ถ™์—ฌ์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ์ธ๋ฐ, bean์ด๋‚˜ ReflectionUtils ๊ฐ™์€ ๊ฒƒ๋“ค์ด ๋“ค์–ด๊ฐ€๋ฉด์„œ ์™œ์ธ์ง€ ๋ชจ๋ฅผ ๊ฑฐ๋ถ€๊ฐ์ด ๋“ค ์ˆ˜ ์žˆ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

์‚ฌ์‹ค, ์œ„์˜ ๋ฌธ์ œ๋ณด๋‹ค ๊ทธ ์•„๋ž˜ ๋ฌธ์ œ๊ฐ€ ๋” ํฌ๊ฒŒ ๊ฑธ๋ฆฌ๋Š” ๋ถ€๋ถ„์ด์—ˆ๋‹ค. ์ด์ „ ๋ฌธ์ œ์˜ ๊ฒฝ์šฐ ํŒ€์›๋“ค์—๊ฒŒ ์ถฉ๋ถ„ํ•œ ๊ณต์œ ๋ฅผ ํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฆฌ๋”ฉํ•  ์˜ˆ์ •์ด์—ˆ๊ธฐ์— ๊ดœ์ฐฎ์•˜์ง€๋งŒ, ์ด๋Š” ์•„์˜ˆ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋‚˜ํƒ€๋‚ฌ๋˜ ๊ฒƒ์ด์—ˆ๋‹ค.
 
์‚ฌ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”ํ‹€๋ฆฐ์—์„œ ์ œ๊ณตํ•˜๋Š” lazy ๋Œ€๋ฆฌ์ž๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ๋“ค์— ๋Œ€ํ•ด ์ถ”์ƒํ™”๋ฅผ ์ง„ํ–‰ํ•ด๋‘์–ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๋ฐœ์ทŒํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ๋Œ€๋žต ์ด๋Ÿฐ ๋Š๋‚Œ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค๊ณ ๋งŒ ์ƒ๊ฐํ•ด๋‘์ž.

@Component
class ImportantHandler(
    @Value("\${very.important.topic}")
    topic: String,
    factory: Factory,
    ...
): Handler by factory.create(
    topic = topic
    ...
)

inline fun <reified T: ...> Factory.create(
    topic: String
    ...
): ConcurrentHandler<String, T> {
    ...
}


๋‹ค๋งŒ, ์œ„์˜ ์ƒํ™ฉ์—์„œ @ConvertTopic์„ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

@Component
class ImportantHandler(
    @ConvertTopic
    @Value("\${very.important.topic}")
    topic: String,
    factory: Factory,
    ...
): Handler by factory.create(
    topic = topic
    ...
)


๋จผ์ €, ์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” @ConvertTopic์˜ ํƒ€๊ฒŸ ํƒ€์ž…์— value parameter๋„ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class ConvertTopic


์ด๋Ÿฌ๋ฉด, factory.create() ๋กœ ๋„˜์–ด๊ฐ€๋Š” topic ๊ฐ’์ด ๋ณ€ํ™”๊ฐ€ ๋  ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ๊ทธ๋ ‡๊ฒŒ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ด๋Š”, ์ด ํ•จ์ˆ˜๊ฐ€ ์‹ค์ œ๋กœ ์ž๋ฐ”์—์„œ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ฉด ์•Œ ์ˆ˜ ์žˆ๊ธฐ์— ๋””์ปดํŒŒ์ผ์„ ์ง„ํ–‰ํ•ด๋ณด์ž.

public class ImportantHandler implements Handler {
    private final ConcurrentHandler $$delegate_0;

    public ImportantHandler(@ConvertTopic @Value("\${very.important.topic}") @NotNull String topic, ...) {
    Intrinsics.checkNotNullParameter(topic, "topic");
    ...
    String[] var12 = new String[]{topic};
    this.$$delegate_0 = DefaultImpls.create$default(var12, ...)
}

    // ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด์€ ํ•จ์ˆ˜. ๋Œ€๋ฆฌ์ž์— ์˜ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜ํ–‰๋œ๋‹ค.
    public void run() {
        this.$$delegate_0.run();
    }
}


์œ„์ž„์„ ์œ„ํ•ด ImportantHandler์˜ ํ•„๋“œ๋กœ ConcurrentHandler ๋ฅผ ๊ฐ€์ง€๊ณ , ์ƒ์„ฑ์ž ๋‚ด๋ถ€์—์„œ delegate ํ•„๋“œ๊ฐ€ ์ฑ„์›Œ์ง€๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์•ž์„œ ๋งํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ, ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ์˜ ์ดˆ๊ธฐํ™” ๊ณผ์ •์€ ๋นˆ์ด ์ƒ์„ฑ๋œ ์ดํ›„ = ์ฆ‰, ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋œ ์ดํ›„์— ์ง„ํ–‰๋œ๋‹ค. delegate ํ•„๋“œ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜๋Š” ์‹œ์ ์—์„œ topic ํ•„๋“œ๋Š” ์•„์ง ์ปจ๋ฒ„ํŒ…์ด ๋˜๊ธฐ ์ด์ „์˜ ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ปจ๋ฒ„ํŒ…์ด ๋˜๊ธฐ ์ด์ „์˜ ๊ฐ’์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์ดˆ๊ธฐํ™”๊ฐ€ ๋˜๋ฉด์„œ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

์ดํ›„ ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ ๊ณผ์ •์—์„œ topic์ด ์ดˆ๊ธฐํ™”๊ฐ€ ๋˜๋”๋ผ๋„, ์ด๋ฏธ delegate ํ•„๋“œ๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์†์„ฑ์€ ์ดˆ๊ธฐํ™”๊ฐ€ ์™„๋ฃŒ๊ฐ€ ๋œ ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ณ€ํ™”๋˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ž˜์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์œผ๋กœ ์ธํ•ด run() ๋ฉ”์„œ๋“œ๊ฐ€ ์ˆ˜ํ–‰์ด ๋˜๋”๋ผ๋„ topic ๊ฐ’์ด ๋ณ€ํ•˜์ง€ ์•Š์€ ์†์„ฑ์„ ๊ฐ€์ง„ delegate.run()์ด ์ˆ˜ํ–‰์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์˜๋„๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค.
 
๊ทธ๋ž˜์„œ, ์œ„์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ฒ˜์Œ์—๋Š” lazy ๋Œ€๋ฆฌ์ž๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ง์ ‘ ์œ„์ž„์„ ํ•˜๋Š” ๊ฒƒ์€ ์–ด๋–จ์ง€ ์ƒ๊ฐํ•ด๋ณด์•˜๋‹ค.

@Component
class ImportantHandler(
    @ConvertTopic
    @Value("\${very.important.topic}")
    topic: String,
    factory: Factory,
    ...
): Handler {

    private lateinit var delegate: ConcurrentHandler

    @PostConsturct
    fun init() {
        delegate = factory.create(topic, ..)
    }

    override fun run() {
        delegate.run()
    }

    // ...
}


์ด๋•Œ, @PostConsturct ๋ฅผ ํ†ตํ•ด์„œ ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ๊ฐ€ ๋™์ž‘ํ•œ ์ดํ›„์— delegate ํ•„๋“œ๊ฐ€ ์ดˆ๊ธฐํ™”๊ฐ€ ๋  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค. ์ด๋Ÿฌ๋ฉด ์ปจ๋ฒ„ํŒ…์ด ๋œ topic ๊ฐ’์„ ํ†ตํ•ด delegate ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์˜๋„๋Œ€๋กœ ์ž˜ ๋™์ž‘์€ ํ•˜์˜€๋‹ค.
 
๊ทธ๋Ÿฌ๋‚˜, override๋ฅผ ํ†ตํ•ด ๋ชจ๋“  ๋ฉ”์„œ๋“œ๋“ค์„ ๋‹ค์‹œ ์žฌ์ •์˜ํ•˜๋Š” ๊ฒƒ๋„ ๋„ˆ๋ฌด ๋ฒˆ๊ฑฐ๋กญ๊ณ , ์ฝ”ํ‹€๋ฆฐ ์–ธ์–ด์˜ ์žฅ์ ์„ ํ•˜๋‚˜๋„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๋Š” ์ฝ”๋“œ์ธ ๊ฒƒ ๊ฐ™์•˜๋‹ค. ์ฝ”๋“œ์˜ ๊ธธ์ด ์—ญ์‹œ ์ด์ „๋ณด๋‹ค ํ›จ์”ฌ ๊ธธ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ํŒ€์›๋“ค์˜ ๋™์˜๋ฅผ ์–ป๋Š” ๊ฒƒ๋„ ์–ด๋ ค์šธ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.
 


 

๐ŸŒฑ SpEL ํ™œ์šฉํ•˜๊ธฐ

๊ทธ๋Ÿฌ๋˜ ์ค‘, ๊ณต์‹ ๋ฌธ์„œ์—์„œ SpEL ์ด๋ผ๋Š” ์นœ๊ตฌ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. SpEL์€ โ€˜๋Ÿฐํƒ€์ž„โ€™์— ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„๋ฅผ ์ฟผ๋ฆฌํ•˜๊ณ  ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ํ‘œํ˜„์‹ ์–ธ์–ด๋กœ, @Value ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜์—ฌ ๋Ÿฐํƒ€์ž„์— ์กฐ์ž‘์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์ด์—ˆ๋‹ค!
์‹ค์ œ๋กœ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์ œ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋‹ค.
 

Using @Value :: Spring Framework

A default lenient embedded value resolver is provided by Spring. It will try to resolve the property value and if it cannot be resolved, the property name (for example ${catalog.name}) will be injected as the value. If you want to maintain strict control o

docs.spring.io

 
 

@Component
class MovieRecommender(
    @Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String
)


์œ„์˜ ์˜ˆ์ œ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ์€ ํ‘œํ˜„์‹์„ #{} ๋‚ด๋ถ€์— ์ž‘์„ฑํ•˜๊ฒŒ ๋˜๋ฉด, ๋‚ด๋ถ€์ ์œผ๋กœ ์ปจ๋ฒ„ํŒ…์„ ์ง„ํ–‰ํ•ด์ค€๋‹ค.
 
์ ์šฉ ๋ฐฉ๋ฒ•์€ ์™„์ „ ์‰ฌ์šฐ๋‹ˆ, ํ•œ ๋ฒˆ ์ด์ „ ์ฝ”๋“œ์— ์ ์šฉ์„ ํ•ด๋ณด์ž.
๊ธฐ์กด์— ์ž‘์„ฑํ•˜์˜€๋˜ ์ฝœ๋ฐฑ ๋กœ์ง์„ ์กฐ๊ธˆ ๋” ์ ํ•ฉํ•œ ์ด๋ฆ„์˜ ํด๋ž˜์Šค๋กœ ์ถ”์ถœ์„ ์ง„ํ–‰ํ•ด์ฃผ์—ˆ๋‹ค. ๋˜ํ•œ, ์ด์ œ๋Š” ์ด ํด๋ž˜์Šค ์ž์ฒด๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ๋นˆ์— ๋Œ€ํ•ด ์ฃผ์ž…๋„ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์„œ ๋” ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ด์กŒ๋‹ค.

@Component
class TopicConverter(
    private val environment: Environment,
    private val profile: String,
) {

    fun execute(property: String): String {
        // ... ๊ธฐ์กด์˜ ๋กœ์ง ์ž‘์„ฑ
        return original.plus(".$hostName")
    }
}

 

ํ•จ์ˆ˜์˜ ์ธ์ž๋กœ๋Š” ๋ณ€๊ฒฝ์„ ์›ํ•˜๋Š” ํ”„๋กœํผํ‹ฐ์˜ ํ‚ค๋ฅผ, ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ๊ฐ’์œผ๋กœ๋Š” ์ปจ๋ฒ„ํŒ…๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ , ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

@Value("#{topicConverter.execute('very.important.topic')}")
topic: String,


๋” ์ด์ƒ ๋ณ„๋„์˜ ์–ด๋…ธํ…Œ์ด์…˜๋„ ํ•„์š”ํ•˜์ง€ ์•Š๊ณ , ๊ธฐ์กด @Value ๋‚ด๋ถ€์—์„œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋งŒ๋“ค๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์„ฑ ์—ญ์‹œ ํ›จ์”ฌ ํŽธ๋ฆฌํ•ด์กŒ๋‹ค! ๋˜ํ•œ, intellij ๊ธฐ๋Šฅ ๋•๋ถ„์ธ์ง€, ํ•˜์ด๋ผ์ดํŒ…๋„ ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ๋ฅผ ๋”ฐ๋ผ๊ฐ€๊ธฐ๋„ ์‰ฌ์›Œ์ ธ์„œ ๋” ๋งˆ์Œ์— ๋“ค์—ˆ๋‹ค.


๊ทธ๋ž˜์„œ ์ตœ์ข…์ ์œผ๋กœ๋Š” ์œ„์™€ ๊ฐ™์ด ๊ธฐ์กด ์ฝ”๋“œ์— ๋ชจ๋‘ ์ ์šฉ์„ ์™„๋ฃŒํ•˜์˜€์œผ๋ฉฐ, ๋•๋ถ„์— ๊ฐœ์ธ branch ์ž‘์—…๋ฌผ์— ๋Œ€ํ•ด ๊ฐœ๋ฐœ์ž๋งˆ๋‹ค ํŠธ๋ž˜ํ”ฝ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ์‰ฝ๊ฒŒ ํ•ธ๋“ค๋ง์ด ๊ฐ€๋Šฅํ•ด์กŒ๋‹ค.
 




๐ŸŒฑ ๋งˆ๋ฌด๋ฆฌ

๊ฝค๋‚˜ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๋กœ ์‚ฝ์งˆ์„ ํ–ˆ์—ˆ์ง€๋งŒ, ๋•๋ถ„์— ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๊ผญ ๋ด์•ผ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋˜ ์‹œ๊ฐ„์ด์—ˆ๋‹ค. (์‚ฌ์‹ค, ์ฒ˜์Œ์— @Value ์–ด๋…ธํ…Œ์ด์…˜์˜ ์„ค๋ช…์—์„œ๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜์™€์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ œ๋Œ€๋กœ ์ฝ์ง€ ์•Š์€ ๋‚ด ๋ฌธ์ œ๋„ ์ปธ์—ˆ๋‹ค... ใ…Žใ…Ž

A common use case is to inject values using #{systemProperties. myProp} style SpEL (Spring Expression Language) expressions. Alternatively, values may be injected using ${my. app. myProp} style property placeholders.

 
์ฒ˜์Œ์—๋Š” ๋‚ด๊ฐ€ ์ง๋ฉดํ•œ ๋ฌธ์ œ๋ฅผ spEL์ด๋ž‘ ์—ฎ์–ด๋ด์•ผ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์กฐ์ฐจ ๋ชปํ–ˆ์—ˆ๋‹ค๊ฐ€ ๋‚˜์ค‘์— ๊ณต์‹ ๋ฌธ์„œ ์Šคํ„ฐ๋””๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ spEL์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์•Œ๊ฒŒ ๋˜๊ณ  ๊ทธ๋•Œ ๋ˆˆ์— ๋„์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ์‚ฌ๋žŒ๋“ค์ด ์ด๋ž˜์„œ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฝ์œผ๋ผ๊ณ  ํ•˜๋Š” ๊ฑด๊ฐ€... ํ•˜๋ฉด์„œ ๋งŽ์ด ๋ฐฐ์› ๋˜ ์‹œ๊ฐ„์ด์—ˆ๋‹ค.
 
์‚ฌ์‹ค ์ •๋ง ์ด ๋ฐฉ๋ฒ•์ด ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•์ด ๋˜๋Š”์ง€๋Š” ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค. ๋ณดํ†ต์˜ ์‚ฌ๋žŒ๋“ค์ด๋ผ๋ฉด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ์ž˜ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋Š”๊ฒŒ ๋งž์ง€ ์•Š๋‚˜์š”? ๋ผ๊ณ  ์ถฉ๋ถ„ํžˆ ๋ฐ˜๋ฐ•ํ•  ์ˆ˜๋„ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ ๋‹ค.
 
์•„์ง๊นŒ์ง€๋Š” E2E ํ…Œ์ŠคํŠธ๋ฅผ ์™„์ „ํžˆ ๋ฏฟ๊ณ  ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ์„ ๋งŒํผ ๋‚ด๊ฐ€ ์ •๊ตํ•˜๊ฒŒ ์ž‘์„ฑํ•˜์ง€ ๋ชปํ•œ ํƒ“๋„ ํฐ ๊ฒƒ ๊ฐ™๋‹ค. ์‹ค์ œ๋กœ ๋ฉ”์ธ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋Œ€ํ•œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์ž‘์„ฑ์ด ๋˜์–ด ์žˆ์œผ๋‚˜, ํ•ด๋‹น ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋Œ๋ฆฌ๊ณ  ํ•ญ์ƒ ๊ฐœ๋ฐœ ์•ฑ ํ™˜๊ฒฝ์—์„œ ๋‹ค์‹œ ํ•œ ๋ฒˆ ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค๋ณด๋Š” ๋‚ด ๋ชจ์Šต์„ ๋ฐ”๋ผ๋ณด๋ฉด์„œ ์•„์ง์€ ๋งŽ์ด ๋ถ€์กฑํ•˜๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค. ๊ฐœ๋ฐœ์—๋Š” ์ •๋‹ต์ด ์—†๋Š” ๊ฒƒ์ด๋‹ˆ๊นŒ... ์šฐ์„ ์€ ํ˜„ ์ž๋ฆฌ์—์„œ ์ตœ๋Œ€ํ•œ ์—ด์‹ฌํžˆ ๋…ธ๋ ฅํ•ด๋ด์•ผ๊ฒ ๋‹ค. ใ…Žใ…Ž
 
 
์•„๋ฌดํŠผ, ์˜ค๋žœ๋งŒ์— ๊ธ€์„ ์“ฐ๋‹ค ๋ณด๋‹ˆ ๊ธธ์–ด์ง„ ๊ฒƒ ๊ฐ™๋‹ค.
์“ฐ๊ณ  ์‹ถ์€ ๊ธ€์€ ๋งŽ์€๋ฐ ๊ฒŒ์œผ๋ฆ„์œผ๋กœ ์ธํ•ด ๊ธ€์„ ๋งŽ์ด ๋ชป ์“ฐ๊ณ  ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹คโ€ฆ ์ข…์ข… ์ƒ๊ฐ๋‚  ๋•Œ ์กฐ๊ธˆ์”ฉ ์จ์•ผ๊ฒ ๋‹คโ€ฆ ๋!

Comments