๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“• Spring Framework

[Spring Framework] Bean ๋“ฑ๋ก์— ๋Œ€ํ•œ ์žฌ๊ณ  V2

by GroovyArea 2025. 3. 18.
 

Bean ๋“ฑ๋ก์— ๋Œ€ํ•œ ์žฌ๊ณ 

SpringBoot์—์„œ Bean์„ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ด ์žˆ์„๊นŒ. 1. ํด๋ž˜์Šค์— ์ง์ ‘ @Component ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹ 2. @Configuration ์• ๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•ด @Bean ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ์‹ ์œ„ 2๊ฐ€์ง€ ๋ฐฉ์‹์ด ๋ณดํŽธ์ ์ด๋‹ค.

sweeeetgoguma.tistory.com

์ด์ „ ํฌ์ŠคํŠธ์— ์ด์–ด ์ด๋ฒˆ์—๋Š” Spring Framework์˜ ์ž๋ž‘! Conditional ์‹œ๋ฆฌ์ฆˆ๋ฅผ ํ™œ์šฉํ•œ Bean ๋“ฑ๋ก์— ๋Œ€ํ•ด ์ž‘์„ฑํ•ด๋ณด๋ ค ํ•œ๋‹ค.

 

์‹œ์ž‘ํ•˜๋ฉฐ

 

์˜ฌํ•ด ์ดˆ๋ถ€ํ„ฐ ์ •๋ง ์ผ์ด ๋งŽ๋‹ค. ๋ญ.. ๊ฐˆ ์ˆ˜๋ก ์Œ“์—ฌ๊ฐ„๋‹ค. ๊ธฐ์ˆ ์  ๋ฐฑ๋กœ๊ทธ๋งŒ ๋ช‡ ์‹ญ ๊ฐœ ์ •๋„ ๋˜๋Š” ๋“ฏํ•˜๋‹ค.

๋ค์œผ๋กœ ๊ธฐ์ˆ  ๊ณผ์ œ์™€ ๋ถ€์ฑ„๋„ ๋งŽ์ด ๋Š˜์–ด๋‚ฌ๋‹ค. ๋‹น์žฅ ํ•ด๊ฒฐํ•ด์•ผ ํ•˜๋Š” ๊ธ‰ํ•œ ๊ฑด์€ ์•„๋‹ˆ์ง€๋งŒ, ์ด๋ฒˆ ์ฃผ๋Š” ๋‹คํ–‰ํžˆ ์‹œ๊ฐ„์ด ์—ฌ์œ ๋กœ์šด ๊ฒƒ ๊ฐ™์•„ ํ‹ˆํ‹ˆ์ด ๊ฐœ์„ ์„ ํ–ˆ๋‹ค. (๋ธ”๋กœ๊น…ํ•  ์†Œ์Šค๊ฐ€ ๋งŽ์ด ๋Š˜์–ด๋‚ฌ์ง€๋งŒ! ์‹œ๊ฐ„์ด ์—ฌ์œ ๋กญ์ง€ ์•Š์•„ ๋ฐœํ–‰ ๋Œ€๊ธฐ ์ค‘~)

 

์ฃผ๋ ฅ์œผ๋กœ ๋งก๊ณ  ์žˆ๋Š” ํ”„๋กœ์ ํŠธ๋Š” ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆํ˜• ๋ฉ€ํ‹ฐ bootjar๋ฅผ ๊ฐ€์ง€๋Š” ํ”„๋กœ์ ํŠธ์ด๋‹ค.

api, consumer, batch ๋“ฑ์˜ ๋ชจ๋“ˆ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋“ค์ด ์žˆ๋Š”๋ฐ, ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์ฝ”๋“œ ์–‘์ด ์ ์ฐจ ๋Š˜์–ด๋‚˜๊ฒŒ ๋˜์–ด ๋‹ค์‹œ ํ•œ๋ฒˆ ์ •๋ง ํ•„์š”ํ•œ Bean๋งŒ์„ ๋“ฑ๋กํ•ด์•ผ ๋˜๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ์Šคํ”„๋ฆฐํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ๋“ค์—ˆ๋‹ค. ์ด ์Šคํ”„๋ฆฐํŠธ๊ฐ€ ๋๋‚˜๊ณ  ๋ฐฐํฌํ•˜๋ฉด, ๊ธฐํ•„์ฝ” ๊ฐœ์„ ํ•ด ๋ณด๋ฆฌ๋ผ!!

 

๊ทธ๋ž˜์„œ ์–ด์ œ๋ถ€ํ„ฐ SpringFramework์˜ Condition์„ ์ ๊ทน ํ™œ์šฉํ•˜์—ฌ ๊ฐœ์„  ์ž‘์—…์„ ํ–ˆ๋‹ค.

V1 ๋ฒ„์ „์— ๋น„ํ•ด ์ข€ ๋” ์†์‰ฝ๊ฒŒ, ์• ๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜์œผ๋กœ ์กฐ๊ฑด๋ถ€ Bean ๋“ฑ๋ก์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ใ…Žใ…Ž

 

๊ทธ ๊ณผ์ •๊ณผ ๊ฒฐ๊ณผ๋ฅผ ํ•œ๋ฒˆ ๊ณต์œ ํ•ด๋ณด๋ ค ํ•œ๋‹ค.

 

Condition, Conditional Configuration์˜ ์ž๋ž‘

์•„๋ž˜ ์˜ˆ์‹œ ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ๋“ค์„ ์‚ดํŽด๋ณด์ž.
- api (BootJar)
- batch (BootJar)

- application
- core
- infrastructure
- consumer (BootJar)

  

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์˜ ๊ตฌ์กฐ์ด๋‹ค.

core๋Š” ๋„๋ฉ”์ธ ๋ฐ ๊ด€๋ จ ๋น„์ฆˆ๋‹ˆ์Šค, ๊ณง ์ˆœ์ˆ˜ POJO์™€ public interface๋ฅผ ๊ฐ€์ง„๋‹ค.

application ์€ Use Case๋ฅผ ๋‹ค๋ฃฌ๋‹ค.

infrastructure์—์„œ ๊ฐ์ข… ์™ธ๋ถ€ ์„ค์ •๊ณผ public interface์— ๋Œ€ํ•œ ๊ตฌํ˜„์ฒด๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋ชจ๋“ˆ์ธ๋ฐ, ์กฐ๊ฑด๋ถ€ bean ๋“ฑ๋ก์ด ํ•„์š”ํ•œ ๋…€์„์ด๋‹ค.

 

์šฐ๋ฆฌ ํŒ€์—์„œ ๋‹ด๋‹นํ•˜๋Š” ํ”„๋กœ์ ํŠธ๋Š” ์ตœ์†Œ 2, 3๊ฐœ์˜ DB๋ฅผ ๋ฌผ๊ฒŒ ๋˜๋Š”๋ฐ, ์ด๋ฅผ auto configuration ํ˜•ํƒœ๋กœ ๋ชจ๋“  BootJar์—์„œ Bean ๋กœ๋“œ๋˜์–ด ์‚ฌ์šฉ ์ค‘์ด์—ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ง€๋ฉด์„œ ์ƒ์„ฑํ•˜๋Š” ํ…Œ์ด๋ธ”, ๊ทธ๋ฆฌ๊ณ  infra saas, ์™ธ๋ถ€ http Client ์„ค์ • ๋“ฑ์ด ๋งŽ์•„์ง€๋ฉด์„œ ํŠน์ • BootJar์—์„œ ๋ถˆํ•„์š”ํ•œ, ์ •ํ™•ํžˆ๋Š” ๋ชฐ๋ผ๋„ ๋˜๋Š” Bean๋“ค๋„ ๋นŒ๋“œ๋˜์—ˆ๋‹ค.

 

์ผ๋‹จ ํ•„์š”ํ•œ DB Dao Bean๋ถ€ํ„ฐ ๋ถ„๋ฆฌํ•˜๊ฒ ๋‹ค.

 

SpringFramework์—์„œ ์ œ๊ณตํ•˜๋Š” interface ์ค‘ Conditional์ด๋ผ๋Š” ์• ๋…ธํ…Œ์ด์…˜์ด ์žˆ๋‹ค.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

 

์ด ์• ๋…ธํ…Œ์ด์…˜์€ Condition interface๋ฅผ ์ƒ์†ํ•˜๋Š” value ์†์„ฑ์„ ๊ฐ–๋Š”๋ฐ, ์ด๋Š” Functional interface์ด๋‹ค.

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

 

์ด๋ฅผ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” Bean ๋“ฑ๋ก ํ˜•ํƒœ์— ์•Œ๋งž๊ฒŒ ๊ตฌํ˜„ํ•˜์—ฌ Conditional ์• ๋…ธํ…Œ์ด์…˜์— ์ œ๊ณตํ•˜๋ฉด ๋  ๊ฒƒ์ด๋‹ค.

 

Custom annotation

์กฐ๊ฑด๋ถ€ Bean ๋“ฑ๋ก์„ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์ปค์Šคํ…€ ์• ๋…ธํ…Œ์ด์…˜์„ ๋งŒ๋“ค์—ˆ๋‹ค.

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Conditional(ApplicationTypeCondition::class)
annotation class ConditionalOnApplicationType(
    vararg val types: String,
    val matchIfMissing: Boolean = false,
)

 

์œ„์™€ ๊ฐ™์ด ์ž‘์„ฑ์„ ํ–ˆ๊ณ , Conditional ์• ๋…ธํ…Œ์ด์…˜ ์†์„ฑ์œผ๋กœ ์ปค์Šคํ…€ condition์„ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ class๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.

class ApplicationTypeCondition : Condition {
    override fun matches(
        context: ConditionContext,
        metadata: AnnotatedTypeMetadata,
    ): Boolean {
        val applicationType = context.environment.getProperty("application.type")
        val attributes =
            metadata.getAnnotationAttributes(
                ConditionalOnApplicationType::class.java.name,
            )

        val types = attributes?.get("types") as Array<*>?
        val matchIfMissing = attributes?.get("matchIfMissing") as Boolean? ?: false

        return when {
            applicationType == null -> matchIfMissing
            types == null -> matchIfMissing
            else -> types.contains(applicationType)
        }
    }
}

 

์ตœ์ข… ์‚ฌ์šฉ ํ˜•ํƒœ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

@Component
@ConditionalOnApplicationType("api", "consumer")
class DBBusinessReader(
    private val jpaDao: JpaBusinessesDao,
) : BusinessReader {
	// ...
}

// ์•„๋ž˜์™€ ๊ฐ™์ด configuration ์—๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
@Configuration
@ConditionalOnApplicationType("api", "batch", "consumer")
@EnableJpaRepositories(
    basePackages = ["infra.db.apple"],
    entityManagerFactoryRef = APPLE_ENTITY_MANAGER_FACTORY,
    transactionManagerRef = APPLE_JPA_TRANSACTION_MANAGER
)
class AppleDatabaseConfig : DatabaseConfigurationBase("apple") {
	// ...
}

 

 

์‹คํ–‰ํ•˜๋Š” BootJar์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ฝ์–ด type ์„ ์–ป๊ณ , ๊ทธ type ๊ณผ ์†์„ฑ์ด ์ผ์น˜ํ•˜๋ฉด Bean ๋“ฑ๋ก์„ ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ํ˜•ํƒœ์ด๋‹ค.

BootJar ์˜ type์˜ ํ˜•ํƒœ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด application.yml ์— ํ”„๋กœํผํ‹ฐ๋กœ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค.

application:
  type: api

 

๊ทธ๋Ÿผ ์‹ค์ œ๋กœ ์ง€์ •ํ•œ type ๋ณ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹œ, ์กฐ๊ฑด๋ถ€๋กœ Bean ๋“ฑ๋ก์ด ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๊ธฐ์กด์—๋Š” ๊ฐ ๋ชจ๋“ˆ ๋ณ„ configuration ํŒŒ์ผ์— ์ง์ ‘ Bean ๋“ฑ๋ก์„ ํ–ˆ๋Š”๋ฐ, ์ด๋ ‡๊ฒŒ ํ–ˆ์„ ๋•Œ ์ค‘๋ณต ๋˜๋Š” ์ฝ”๋“œ๋“ค์ด ์ƒ๊ฒผ๋‹ค.

์ด ์ปค์Šคํ…€์„ ํ†ตํ•ด ํŽธ๋ฆฌํ•˜๊ฒŒ ์• ๋…ธํ…Œ์ด์…˜ ํ˜•ํƒœ๋กœ ์กฐ๊ฑด๋ถ€ Bean ๋“ฑ๋ก์„ ์‰ฝ๊ฒŒ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

 

๋งˆ์น˜๋ฉฐ

๊ฐœ๋ฐœํ•˜๋ฉฐ ํ•ญ์ƒ ์ƒ๊ฐ์„ ๋งŽ์ด ํ•˜๋Š” ํŽธ์ธ๋ฐ, ํŠนํžˆ ์ฑ…์ž„์„ ๋ช…ํ™•ํžˆ ํ•˜๋ ค๋Š” ํŽธ์ด๋‹ค.

์ฑ…์ž„์— ๋งž๊ฒŒ ๋ฐฐ์น˜ํ•˜๊ณ  ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด ๊ฒฐ๊ตญ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ข€ ๋” ์‰ฝ๊ณ , ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์„๊นŒ๋ž€ ์ƒ๊ฐ์„ ๋งค๋ฒˆ ํ•˜๋Š”๋ฐ ํ˜ผ์ž ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ๊ฒฐ๊ตญ ์ฝ”๋“œ๋Š” ๊ณต์œ ๋˜๊ธฐ์—, ๋‚จ ๋ณด๊ธฐ ํŽธํ•˜๊ฒŒ ๋” ์‹ ๊ฒฝ์„ ์“ฐ๊ฒŒ ๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

 

๋ฉ€ํ‹ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์กฐ์˜ ํ”„๋กœ์ ํŠธ์—์„œ Spring์˜ ํ•ต์‹ฌ์ธ Ioc๋ฅผ ๋” ์ฑ…์ž„์— ๋งž๊ฒŒ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฒฐ๊ตญ ๋งˆ์ฐฌ๊ฐ€์ง€๋ž€ ์ƒ๊ฐ์ด ๋“ ๋‹ค.

์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์˜์กด์„ฑ์„ ๊ทธ๋Œ€๋กœ ๋ฌผ๊ณ  ์˜ฌ๋ผ๊ฐˆ ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์ข€ ๋” ๊น”๋”ํ•˜๊ฒŒ, ๊ทธ๋ฆฌ๊ณ  ์™„์„ฑ๋„ ์žˆ๊ฒŒ application ๊ตฌ์กฐ๋ฅผ ์žก์•„๋‚˜๊ฐ€๋Š” ๋ฐ‘๊ฑฐ๋ฆ„์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ๋‹ค!

 

๋ฐ˜์‘ํ˜•