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 ์ด๋
ธํ
์ด์
์ ํ์ฉํ์ฌ ๋ฐํ์์ ์กฐ์์ด ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ด์๋ค!
์ค์ ๋ก ๊ณต์ ๋ฌธ์๋ฅผ ๋ณด๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ํจ๊ป ์ฌ์ฉํ ์ ์๋ ์์ ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ์๋ค.
@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 ํ
์คํธ๋ฅผ ์์ ํ ๋ฏฟ๊ณ ์ ๋ขฐํ ์ ์์ ๋งํผ ๋ด๊ฐ ์ ๊ตํ๊ฒ ์์ฑํ์ง ๋ชปํ ํ๋ ํฐ ๊ฒ ๊ฐ๋ค. ์ค์ ๋ก ๋ฉ์ธ ๋น์ฆ๋์ค ๋ก์ง์ ๋ํ ํตํฉ ํ
์คํธ ์ฝ๋๋ ์์ฑ์ด ๋์ด ์์ผ๋, ํด๋น ํ
์คํธ ์ฝ๋๋ฅผ ๋๋ฆฌ๊ณ ํญ์ ๊ฐ๋ฐ ์ฑ ํ๊ฒฝ์์ ๋ค์ ํ ๋ฒ ํ
์คํธ๋ฅผ ๋๋ ค๋ณด๋ ๋ด ๋ชจ์ต์ ๋ฐ๋ผ๋ณด๋ฉด์ ์์ง์ ๋ง์ด ๋ถ์กฑํ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. ๊ฐ๋ฐ์๋ ์ ๋ต์ด ์๋ ๊ฒ์ด๋๊น... ์ฐ์ ์ ํ ์๋ฆฌ์์ ์ต๋ํ ์ด์ฌํ ๋
ธ๋ ฅํด๋ด์ผ๊ฒ ๋ค. ใ
ใ
์๋ฌดํผ, ์ค๋๋ง์ ๊ธ์ ์ฐ๋ค ๋ณด๋ ๊ธธ์ด์ง ๊ฒ ๊ฐ๋ค.
์ฐ๊ณ ์ถ์ ๊ธ์ ๋ง์๋ฐ ๊ฒ์ผ๋ฆ์ผ๋ก ์ธํด ๊ธ์ ๋ง์ด ๋ชป ์ฐ๊ณ ์๋ ๊ฒ ๊ฐ๋คโฆ ์ข
์ข
์๊ฐ๋ ๋ ์กฐ๊ธ์ฉ ์จ์ผ๊ฒ ๋คโฆ ๋!