Ver2.0 ๐ฑ Spring @Conditional์ ์ด์ฉํ ์กฐ๊ฑด๋ถ ๋น ๋ฑ๋ก ๐ฑ

๐ฑ Spring @Conditional์ ์ด์ฉํ ์กฐ๊ฑด๋ถ ๋น ๋ฑ๋ก ๐ฑ
์คํ๋ง์ ๋ง๋ฒ ๊ฐ์ ์กฐ๊ฑด๋ถ ๋น ๋ฑ๋ก ๊ธฐ๋ฅ์ ํจ๊ป ์์๋ณด์!
์๋ ! ์ค๋์ Spring Framework์์ ์ ๋ง ์ ์ฉํ๊ฒ ์ฐ์ด๋ @Conditional ์ ๋ ธํ ์ด์ ์ ๋ํด ํจ๊ป ์์๋ณผ ๊ฑฐ์ผ. ์ด ๊ธฐ๋ฅ์ ํน์ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋น์ ๋ฑ๋กํ ์ง ๋ง์ง ๊ฒฐ์ ํ ์ ์๊ฒ ํด์ฃผ๋ ์คํ๋ง์ ๊ฟ ๊ธฐ๋ฅ์ด์ง! ๐
ํ๋ก๊ทธ๋๋ฐ์ ํ๋ค ๋ณด๋ฉด "์ด๋ฐ ์ํฉ์์ ์ด ๋น์, ์ ๋ฐ ์ํฉ์์ ์ ๋น์ ์ฌ์ฉํ๊ณ ์ถ์ด!"๋ผ๋ ์๊ฐ ํ ๋ฒ์ฏค ํด๋ดค์ ๊ฑฐ์ผ. ๋ฐ๋ก ๊ทธ๋ฐ ์ํฉ์์ @Conditional์ด ๋น์ ๋ฐํ๋ค๊ณ ! ๋ง์น ์ฌ๋ฅ๋ท์์ ์ํฉ์ ๋ง๋ ๋ค์ํ ์ฌ๋ฅ์ ์ฐพ์ ํ์ฉํ๋ ๊ฒ์ฒ๋ผ, ์คํ๋ง์์๋ ์ํฉ์ ๋ง๋ ๋น์ ์ ์ฐํ๊ฒ ๋ฑ๋กํ ์ ์์ด. ๐จโ๐ป
๐ค @Conditional์ด ๋ญ๊ธธ๋?
@Conditional์ Spring 4.0๋ถํฐ ๋์ ๋ ์ ๋ ธํ ์ด์ ์ผ๋ก, ํน์ ์กฐ๊ฑด์ด ๋ง์กฑ๋ ๋๋ง ๋น์ ๋ฑ๋กํ๋๋ก ํด์ฃผ๋ ๊ธฐ๋ฅ์ด์ผ. ์ด๊ฒ ์ ์ค์ํ๋๋ฉด, ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ฐ์ฑ๊ณผ ํ์ฅ์ฑ์ ํฌ๊ฒ ๋์ฌ์ฃผ๊ฑฐ๋ !
์๋ฅผ ๋ค์ด ๋ด:
- ๊ฐ๋ฐ ํ๊ฒฝ๊ณผ ์ด์ ํ๊ฒฝ์์ ๋ค๋ฅธ ๋น์ ์ฌ์ฉํ๊ณ ์ถ์ ๋
- ํน์ ํด๋์ค๊ฐ ํด๋์คํจ์ค์ ์์ ๋๋ง ๋น์ ๋ฑ๋กํ๊ณ ์ถ์ ๋
- ํน์ ํ๋กํผํฐ ๊ฐ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋น์ ๋ฑ๋กํ๊ณ ์ถ์ ๋
- ์ด์์ฒด์ ๋ JVM ๋ฒ์ ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋น์ ๋ฑ๋กํ๊ณ ์ถ์ ๋
์ด๋ฐ ์ํฉ๋ค, ์ด๋ป๊ฒ ํด๊ฒฐํ ๊น? ๋ฐ๋ก @Conditional์ ์ฌ์ฉํ๋ฉด ๋ผ! ๐
๐ @Conditional ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
@Conditional์ Condition ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค๋ฅผ ์ธ์๋ก ๋ฐ์. ์ด ์ธํฐํ์ด์ค๋ ๋จ ํ๋์ ๋ฉ์๋๋ง ๊ฐ์ง๊ณ ์์ด:
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
matches ๋ฉ์๋๊ฐ true๋ฅผ ๋ฐํํ๋ฉด ๋น์ด ๋ฑ๋ก๋๊ณ , false๋ฅผ ๋ฐํํ๋ฉด ๋น์ด ๋ฑ๋ก๋์ง ์์. ์ ๋ง ๊ฐ๋จํ์ง? ๐
๊ฐ๋จํ ์์ ๋ฅผ ํตํด ์ดํด๋ณด์:
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");
}
}
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
}
}
์ด์ ์ด Condition ๊ตฌํ์ฒด๋ค์ ์ฌ์ฉํด์ ์กฐ๊ฑด๋ถ๋ก ๋น์ ๋ฑ๋กํด๋ณด์:
@Configuration
public class AppConfig {
@Bean
@Conditional(LinuxCondition.class)
public CommandService linuxCommandService() {
return new LinuxCommandService();
}
@Bean
@Conditional(WindowsCondition.class)
public CommandService windowsCommandService() {
return new WindowsCommandService();
}
}
์ด๋ ๊ฒ ํ๋ฉด ๋ฆฌ๋ ์ค ํ๊ฒฝ์์๋ LinuxCommandService ๋น์ด, ์๋์ฐ ํ๊ฒฝ์์๋ WindowsCommandService ๋น์ด ๋ฑ๋ก๋ผ. ์ด์์ฒด์ ์ ๋ฐ๋ผ ๋ค๋ฅธ ๊ตฌํ์ฒด๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ๋ ๊ฑฐ์ง! ๐
๐ Spring Boot์ ๋ค์ํ @Conditional ์ ๋ ธํ ์ด์ ๋ค
Spring Boot๋ @Conditional์ ๊ธฐ๋ฐ์ผ๋ก ํ ๋ ํธ๋ฆฌํ ์ ๋ ธํ ์ด์ ๋ค์ ์ ๊ณตํด. ์ด๊ฒ๋ค์ ๋ฉํ ์ ๋ ธํ ์ด์ ์ด๋ผ๊ณ ๋ถ๋ฅด๋๋ฐ, ์์ฃผ ์ฌ์ฉ๋๋ ์กฐ๊ฑด๋ค์ ๋ฏธ๋ฆฌ ๊ตฌํํด๋์ ๊ฑฐ์ผ. ๐
- @ConditionalOnClass: ํน์ ํด๋์ค๊ฐ ํด๋์คํจ์ค์ ์์ ๋ ๋น ๋ฑ๋ก
- @ConditionalOnMissingClass: ํน์ ํด๋์ค๊ฐ ํด๋์คํจ์ค์ ์์ ๋ ๋น ๋ฑ๋ก
- @ConditionalOnBean: ํน์ ๋น์ด ์ด๋ฏธ ๋ฑ๋ก๋์ด ์์ ๋ ๋น ๋ฑ๋ก
- @ConditionalOnMissingBean: ํน์ ๋น์ด ๋ฑ๋ก๋์ด ์์ง ์์ ๋ ๋น ๋ฑ๋ก
- @ConditionalOnProperty: ํน์ ํ๋กํผํฐ๊ฐ ์ง์ ๋ ๊ฐ์ ๊ฐ์ง ๋ ๋น ๋ฑ๋ก
- @ConditionalOnResource: ํน์ ๋ฆฌ์์ค๊ฐ ์กด์ฌํ ๋ ๋น ๋ฑ๋ก
- @ConditionalOnWebApplication: ์น ์ ํ๋ฆฌ์ผ์ด์ ์ผ ๋ ๋น ๋ฑ๋ก
- @ConditionalOnNotWebApplication: ์น ์ ํ๋ฆฌ์ผ์ด์ ์ด ์๋ ๋ ๋น ๋ฑ๋ก
- @ConditionalOnExpression: SpEL ํํ์์ด true์ผ ๋ ๋น ๋ฑ๋ก
- @ConditionalOnJava: ํน์ ์๋ฐ ๋ฒ์ ์ผ ๋ ๋น ๋ฑ๋ก
์ด ์ ๋ ธํ ์ด์ ๋ค์ ์ฌ์ฉํ๋ฉด ๋ณต์กํ Condition ๊ตฌํ์ฒด๋ฅผ ์ง์ ๋ง๋ค์ง ์์๋ ๋ผ์ ์ฝ๋๊ฐ ํจ์ฌ ๊ฐ๊ฒฐํด์ง๋ค๊ตฌ! ๐
๐ ์ค์ ์์ ๋ก ์์๋ณด๋ @Conditional
1. @ConditionalOnProperty ์ฌ์ฉ ์์
ํน์ ํ๋กํผํฐ ๊ฐ์ ๋ฐ๋ผ ๋น์ ๋ฑ๋กํ๋ ์์ ๋ฅผ ์ดํด๋ณผ๊ฒ:
@Configuration
public class DataSourceConfig {
@Bean
@ConditionalOnProperty(name = "datasource.type", havingValue = "mysql")
public DataSource mysqlDataSource() {
// MySQL DataSource ์ค์
return new MysqlDataSource();
}
@Bean
@ConditionalOnProperty(name = "datasource.type", havingValue = "postgresql")
public DataSource postgresqlDataSource() {
// PostgreSQL DataSource ์ค์
return new PGSimpleDataSource();
}
}
์ด๋ ๊ฒ ํ๋ฉด application.properties๋ application.yml์ ์ค์ ๋ datasource.type ๊ฐ์ ๋ฐ๋ผ ๋ค๋ฅธ DataSource ๋น์ด ๋ฑ๋ก๋ผ. ์ ๋ง ํธ๋ฆฌํ์ง? ๐
2. @ConditionalOnMissingBean ์ฌ์ฉ ์์
ํน์ ๋น์ด ์์ ๋๋ง ๊ธฐ๋ณธ ๋น์ ๋ฑ๋กํ๋ ์์ ์ผ:
@Configuration
public class SecurityConfig {
@Bean
@ConditionalOnMissingBean
public PasswordEncoder passwordEncoder() {
// ๊ธฐ๋ณธ PasswordEncoder ์ ๊ณต
return new BCryptPasswordEncoder();
}
}
์ด ์ฝ๋๋ ๋ค๋ฅธ ๊ณณ์์ PasswordEncoder ํ์ ์ ๋น์ ๋ฑ๋กํ์ง ์์์ ๋๋ง ๊ธฐ๋ณธ BCryptPasswordEncoder๋ฅผ ๋ฑ๋กํด. ๊ธฐ๋ณธ๊ฐ์ ์ ๊ณตํ๋ฉด์๋ ์ปค์คํฐ๋ง์ด์ง์ ํ์ฉํ๋ ํจํด์ด์ง! ๐จโ๐จ
3. @ConditionalOnClass์ @ConditionalOnMissingClass ์ฌ์ฉ ์์
ํน์ ํด๋์ค์ ์กด์ฌ ์ฌ๋ถ์ ๋ฐ๋ผ ๋น์ ๋ฑ๋กํ๋ ์์ :
@Configuration
public class CacheConfig {
@Bean
@ConditionalOnClass(name = "org.springframework.data.redis.core.RedisTemplate")
public CacheManager redisCacheManager() {
// Redis ๊ธฐ๋ฐ ์บ์ ๋งค๋์
return new RedisCacheManager();
}
@Bean
@ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")
public CacheManager simpleCacheManager() {
// ๊ธฐ๋ณธ ์ธ๋ฉ๋ชจ๋ฆฌ ์บ์ ๋งค๋์
return new ConcurrentMapCacheManager();
}
}
์ด ์์ ์์๋ Redis ๊ด๋ จ ํด๋์ค๊ฐ ํด๋์คํจ์ค์ ์์ผ๋ฉด Redis ๊ธฐ๋ฐ ์บ์ ๋งค๋์ ๋ฅผ, ์์ผ๋ฉด ์ธ๋ฉ๋ชจ๋ฆฌ ์บ์ ๋งค๋์ ๋ฅผ ์ฌ์ฉํด. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ์ ๋ฐ๋ผ ์๋์ผ๋ก ์ ์ ํ ๋น์ด ๋ฑ๋ก๋๋ ๊ฑฐ์ง! ๐ง
๐ ๏ธ ๋๋ง์ ์ปค์คํ @Conditional ๋ง๋ค๊ธฐ
Spring Boot๊ฐ ์ ๊ณตํ๋ @Conditional ๋ณํ๋ค์ด ๋ง์ง๋ง, ๋๋ก๋ ๋ ํน๋ณํ ์กฐ๊ฑด์ด ํ์ํ ์๋ ์์ด. ๊ทธ๋ด ๋ ์ง์ ๋ง๋ค๋ฉด ๋ผ! ๐
์๋ฅผ ๋ค์ด, ํน์ ์๊ฐ๋์๋ง ํ์ฑํ๋๋ ๋น์ ๋ง๋ค๊ณ ์ถ๋ค๊ณ ํด๋ณด์:
// 1. Condition ์ธํฐํ์ด์ค ๊ตฌํ
public class TimeOfDayCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnTimeOfDay.class.getName());
int startHour = (int) attributes.get("startHour");
int endHour = (int) attributes.get("endHour");
LocalTime now = LocalTime.now();
int currentHour = now.getHour();
return currentHour >= startHour && currentHour < endHour;
}
}
// 2. ์ปค์คํ
์ ๋
ธํ
์ด์
์ ์
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(TimeOfDayCondition.class)
public @interface ConditionalOnTimeOfDay {
int startHour() default 0;
int endHour() default 24;
}
// 3. ์ฌ์ฉ ์์
@Configuration
public class SchedulerConfig {
@Bean
@ConditionalOnTimeOfDay(startHour = 9, endHour = 18)
public TaskScheduler businessHoursScheduler() {
// ์
๋ฌด ์๊ฐ ์ค์๋ง ํ์ฑํ๋๋ ์ค์ผ์ค๋ฌ
return new ConcurrentTaskScheduler();
}
@Bean
@ConditionalOnTimeOfDay(startHour = 18, endHour = 9)
public TaskScheduler afterHoursScheduler() {
// ์
๋ฌด ์๊ฐ ์ธ์๋ง ํ์ฑํ๋๋ ์ค์ผ์ค๋ฌ
return new ConcurrentTaskScheduler();
}
}
์ด๋ ๊ฒ ํ๋ฉด ์๊ฐ๋์ ๋ฐ๋ผ ๋ค๋ฅธ ์ค์ผ์ค๋ฌ๊ฐ ํ์ฑํ๋ผ! ์ฌ๋ฅ๋ท์์ ์๊ฐ๋๋ณ๋ก ๋ค๋ฅธ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ฒ๋ผ, ์ ํ๋ฆฌ์ผ์ด์ ๋ ์๊ฐ์ ๋ฐ๋ผ ๋ค๋ฅธ ๊ธฐ๋ฅ์ ํ์ฑํํ ์ ์๋ ๊ฑฐ์ง. ๐
๐ก @Conditional ์ฌ์ฉ ์ ๋ฒ ์คํธ ํ๋ํฐ์ค
@Conditional์ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ ์ํ ๋ช ๊ฐ์ง ํ์ ์๋ ค์ค๊ฒ! ๐ค
- ์กฐ๊ฑด ๋ก์ง์ ๋จ์ํ๊ฒ ์ ์งํ๊ธฐ
matches() ๋ฉ์๋ ๋ด์ ๋ก์ง์ด ๋ณต์กํด์ง๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์์ ์๊ฐ์ด ๊ธธ์ด์ง ์ ์์ด. ๊ฐ๋ฅํ ํ ๋จ์ํ๊ฒ ์ ์งํ๋ ๊ฒ ์ข์! - ์กฐ๊ฑด๋ถ ๋น์ ์์กด์ฑ ์ฃผ์ํ๊ธฐ
์กฐ๊ฑด๋ถ๋ก ๋ฑ๋ก๋๋ ๋น์ ์์กดํ๋ ๋ค๋ฅธ ๋น์ด ์๋ค๋ฉด, ๊ทธ ๋น๋ ์กฐ๊ฑด๋ถ๋ก ๋ฑ๋ก๋์ด์ผ ํ ์ ์์ด. ๊ทธ๋ ์ง ์์ผ๋ฉด NoSuchBeanDefinitionException์ด ๋ฐ์ํ ์ ์์ด. - ํ
์คํธ ์ฝ๋ ์์ฑํ๊ธฐ
์กฐ๊ฑด๋ถ ๋น ๋ฑ๋ก์ ํ ์คํธํ๊ธฐ ๊น๋ค๋ก์ธ ์ ์์ด. ๋ค์ํ ์กฐ๊ฑด์์ ๋น์ด ์ ๋๋ก ๋ฑ๋ก๋๋์ง ํ์ธํ๋ ํ ์คํธ ์ฝ๋๋ฅผ ๊ผญ ์์ฑํ์! - ๋ฌธ์ํํ๊ธฐ
์ด๋ค ์กฐ๊ฑด์์ ์ด๋ค ๋น์ด ๋ฑ๋ก๋๋์ง ๋ช ํํ๊ฒ ๋ฌธ์ํํ๋ ๊ฒ์ด ์ค์ํด. ๋์ค์ ์ฝ๋๋ฅผ ์ ์ง๋ณด์ํ ๋ ํฐ ๋์์ด ๋ ๊ฑฐ์ผ. - Spring Boot์ ์๋ ๊ตฌ์ฑ ์ดํดํ๊ธฐ
Spring Boot๋ ๋ด๋ถ์ ์ผ๋ก ๋ง์ @Conditional์ ์ฌ์ฉํด. ์ด ๋์ ๋ฐฉ์์ ์ดํดํ๋ฉด ์์ ์ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๋ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ด!
์ด๋ฐ ๋ฒ ์คํธ ํ๋ํฐ์ค๋ฅผ ๋ฐ๋ฅด๋ฉด ๋ ์ ์ง๋ณด์ํ๊ธฐ ์ฝ๊ณ ์์ ์ ์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์์ ๊ฑฐ์ผ. ๋ง์น ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ๋ค์ด ์ ๊ณตํ๋ ๊ณ ํ์ง ์๋น์ค์ฒ๋ผ ๋ง์ด์ผ! ๐
๐ ์ค์ ํ๋ก์ ํธ์์์ @Conditional ํ์ฉ ์ฌ๋ก
1. ํ๋กํ๋ณ ๊ตฌ์ฑ ๊ด๋ฆฌ
๊ฐ๋ฐ, ํ ์คํธ, ์ด์ ํ๊ฒฝ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋น์ ๋ฑ๋กํ๋ ๊ฒฝ์ฐ:
@Configuration
public class DatabaseConfig {
@Bean
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "prod")
public DataSource prodDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://production-db:3306/myapp");
config.setUsername("prod_user");
config.setPassword("prod_password");
return new HikariDataSource(config);
}
}
์ด๋ ๊ฒ ํ๋ฉด ํ๊ฒฝ์ ๋ฐ๋ผ ์ ์ ํ ๋ฐ์ดํฐ์์ค๊ฐ ์๋์ผ๋ก ๋ฑ๋ก๋ผ! ๐จโ๐ป
2. ๊ธฐ๋ฅ ํ ๊ธ(Feature Toggle) ๊ตฌํ
ํน์ ๊ธฐ๋ฅ์ ๋์ ์ผ๋ก ์ผ๊ณ ๋ ์ ์๋ ๊ธฐ๋ฅ ํ ๊ธ ํจํด:
@Configuration
public class FeatureConfig {
@Bean
@ConditionalOnProperty(name = "features.new-recommendation-engine", havingValue = "true")
public RecommendationEngine newRecommendationEngine() {
return new AIBasedRecommendationEngine();
}
@Bean
@ConditionalOnProperty(name = "features.new-recommendation-engine", havingValue = "false", matchIfMissing = true)
public RecommendationEngine legacyRecommendationEngine() {
return new RuleBasedRecommendationEngine();
}
}
์ด ํจํด์ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฐฐํฌํ์ง ์๊ณ ๋ ๊ธฐ๋ฅ์ ์ผ๊ณ ๋ ์ ์์ด. ์ค์ ํ์ผ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฐ๋ง ๋ณ๊ฒฝํ๋ฉด ๋๋๊น! ๐
3. ์ ํ์ ์์กด์ฑ ์ฒ๋ฆฌ
์ ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ฐ๋ผ ๋ค๋ฅธ ๊ธฐ๋ฅ์ ํ์ฑํํ๋ ๊ฒฝ์ฐ:
@Configuration
public class MonitoringConfig {
@Bean
@ConditionalOnClass(name = "io.micrometer.prometheus.PrometheusConfig")
public MeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Bean
@ConditionalOnClass(name = "io.micrometer.influx.InfluxConfig")
@ConditionalOnMissingBean(MeterRegistry.class)
public MeterRegistry influxMeterRegistry() {
return new InfluxMeterRegistry(InfluxConfig.DEFAULT, Clock.SYSTEM);
}
@Bean
@ConditionalOnMissingBean(MeterRegistry.class)
public MeterRegistry simpleMeterRegistry() {
return new SimpleMeterRegistry();
}
}
์ด ์์ ์์๋ ํด๋์คํจ์ค์ ์๋ ๋ชจ๋ํฐ๋ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ฐ๋ผ ์ ์ ํ MeterRegistry๋ฅผ ๋ฑ๋กํด. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ๋ง ์ถ๊ฐํ๋ฉด ์๋์ผ๋ก ํด๋น ๊ธฐ๋ฅ์ด ํ์ฑํ๋๋ ๊ฑฐ์ง! ๐งฉ
๐ฅ ๊ณ ๊ธ @Conditional ํ ํฌ๋
1. ์ฌ๋ฌ ์กฐ๊ฑด ์กฐํฉํ๊ธฐ
์ฌ๋ฌ ์กฐ๊ฑด์ AND๋ OR๋ก ์กฐํฉํด์ ์ฌ์ฉํ ์ ์์ด:
@Configuration
public class AdvancedConfig {
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
@ConditionalOnClass(name = "org.springframework.data.redis.core.RedisTemplate")
@ConditionalOnBean(RedisConnectionFactory.class)
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.builder(redisConnectionFactory).build();
}
}
์ด ์์ ์์๋ ์ธ ๊ฐ์ง ์กฐ๊ฑด์ด ๋ชจ๋ ์ถฉ์กฑ๋ ๋๋ง redisCacheManager ๋น์ด ๋ฑ๋ก๋ผ:
- cache.enabled ํ๋กํผํฐ๊ฐ true์ผ ๋
- RedisTemplate ํด๋์ค๊ฐ ํด๋์คํจ์ค์ ์์ ๋
- RedisConnectionFactory ํ์ ์ ๋น์ด ์ด๋ฏธ ๋ฑ๋ก๋์ด ์์ ๋
2. ์ปค์คํ ์กฐ๊ฑด ๋ก์ง์์ ์ ๋ ธํ ์ด์ ์์ฑ ์ฌ์ฉํ๊ธฐ
์ปค์คํ @Conditional์์ ์ ๋ ธํ ์ด์ ์ ์์ฑ๊ฐ์ ํ์ฉํ๋ ๋ฐฉ๋ฒ:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBeanProperty {
String beanName();
String propertyName();
String expectedValue();
}
public class OnBeanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnBeanProperty.class.getName());
String beanName = (String) attributes.get("beanName");
String propertyName = (String) attributes.get("propertyName");
String expectedValue = (String) attributes.get("expectedValue");
// ๋น์ด ์กด์ฌํ๋์ง ํ์ธ
if (!context.getBeanFactory().containsBean(beanName)) {
return false;
}
// ๋น์ ํ๋กํผํฐ ๊ฐ ํ์ธ
Object bean = context.getBeanFactory().getBean(beanName);
try {
BeanWrapper wrapper = new BeanWrapperImpl(bean);
Object actualValue = wrapper.getPropertyValue(propertyName);
return expectedValue.equals(String.valueOf(actualValue));
} catch (Exception e) {
return false;
}
}
}
์ด ์์ ๋ ํน์ ๋น์ ํน์ ํ๋กํผํฐ ๊ฐ์ ํ์ธํ๋ ์กฐ๊ฑด์ ๋ง๋ค์์ด. ์ ๋ง ๊ฐ๋ ฅํ์ง? ๐ช
3. ์กฐ๊ฑด๋ถ ์๋ ๊ตฌ์ฑ(Auto-Configuration) ๋ง๋ค๊ธฐ
Spring Boot์ ์๋ ๊ตฌ์ฑ ๋ฐฉ์์ ํ์ฉํ ์์ :
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = "app.audit", name = "enabled", havingValue = "true")
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class AuditAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AuditService auditService(DataSource dataSource) {
return new JdbcAuditService(dataSource);
}
@Bean
@ConditionalOnMissingBean
public AuditAspect auditAspect(AuditService auditService) {
return new AuditAspect(auditService);
}
}
์ด ์์ ๋ ์๋ ๊ตฌ์ฑ ํด๋์ค๋ฅผ ๋ง๋ค์ด์ ํน์ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋ ๋ ๊ฐ์ฌ(Audit) ๊ธฐ๋ฅ์ ์๋์ผ๋ก ํ์ฑํํด. Spring Boot์ ํ์ฅ์ฑ์ ๋ณด์ฌ์ฃผ๋ ์ข์ ์์์ง! ๐
โ ๏ธ @Conditional ์ฌ์ฉ ์ ์ฃผ์์ฌํญ
@Conditional์ ์ฌ์ฉํ๋ค ๋ณด๋ฉด ๋ช ๊ฐ์ง ํจ์ ์ ๋น ์ง ์ ์์ด. ๋ฏธ๋ฆฌ ์์๋๋ฉด ๋์์ด ๋ ๊ฑฐ์ผ! ๐ง
- ์ํ ์์กด์ฑ ๋ฌธ์
์กฐ๊ฑด๋ถ ๋น์ด ๋ค๋ฅธ ๋น์ ์์กดํ๊ณ , ๊ทธ ๋น์ด ๋ค์ ์กฐ๊ฑด๋ถ ๋น์ ์์กดํ๋ฉด ์ํ ์์กด์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ด. ๋น ์์กด์ฑ ๊ทธ๋ํ๋ฅผ ์ ์ค๊ณํด์ผ ํด! - ์กฐ๊ฑด ํ๊ฐ ์์ ์ดํดํ๊ธฐ
@Conditional์ ์ ํ๋ฆฌ์ผ์ด์ ์ปจํ ์คํธ ๋ก๋ฉ ์ค์ ํ๊ฐ๋ผ. ์ด ์์ ์๋ ์ผ๋ถ ๋ฆฌ์์ค๋ ์์คํ ์ํ๊ฐ ์์ง ์ค๋น๋์ง ์์์ ์ ์์ด. ์ด ์ ์ ๊ณ ๋ คํด์ ์กฐ๊ฑด ๋ก์ง์ ์ค๊ณํด์ผ ํด. - ๊ณผ๋ํ ์ฌ์ฉ ํผํ๊ธฐ
๋๋ฌด ๋ง์ @Conditional์ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑ์ด ๋ณต์กํด์ง๊ณ ์ดํดํ๊ธฐ ์ด๋ ค์์ง ์ ์์ด. ํ์ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํ๋ ๊ฒ ์ข์! - ํ
์คํธ ์ด๋ ค์
์กฐ๊ฑด๋ถ ๋น์ ํ ์คํธํ๊ธฐ ์ด๋ ค์ธ ์ ์์ด. ํ ์คํธ ํ๊ฒฝ์์ ์กฐ๊ฑด์ ์ ์ดํ ์ ์๋ ๋ฐฉ๋ฒ์ ๋ง๋ จํด๋๋ ๊ฒ ์ข์. - ๋๋ฒ๊น
๋ณต์ก์ฑ
๋น์ด ๋ฑ๋ก๋์ง ์์์ ๋ ๊ทธ ์ด์ ๋ฅผ ํ์ ํ๊ธฐ ์ด๋ ค์ธ ์ ์์ด. Spring Boot๋ --debug ์ต์ ์ ์ ๊ณตํด์ ์๋ ๊ตฌ์ฑ ๋ณด๊ณ ์๋ฅผ ๋ณผ ์ ์๊ฒ ํด์ฃผ๋ ํ์ฉํด๋ด!
์ด๋ฐ ์ฃผ์์ฌํญ์ ์ผ๋์ ๋๊ณ @Conditional์ ์ฌ์ฉํ๋ฉด ๋ ์์ ์ ์ด๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ด ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ ๊ฑฐ์ผ. ๐
๐ Spring Boot ๋ด๋ถ์์์ @Conditional ํ์ฉ
Spring Boot๋ ๋ด๋ถ์ ์ผ๋ก @Conditional์ ๋งค์ฐ ๊ด๋ฒ์ํ๊ฒ ์ฌ์ฉํด. ์ด ๋ถ๋ถ์ ์ดํดํ๋ฉด Spring Boot์ ์๋ ๊ตฌ์ฑ(Auto-Configuration) ๋ฉ์ปค๋์ฆ์ ๋ ์ ํ์ฉํ ์ ์์ด! ๐ง
Spring Boot์ ์๋ ๊ตฌ์ฑ ํด๋์ค๋ค์ ์ดํด๋ณด๋ฉด ๋๋ถ๋ถ ์ด๋ฐ ํจํด์ ๋ฐ๋ฅด๊ณ ์์ด:
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(JdbcOperations.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class JdbcTemplateAutoConfiguration {
@Bean
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
@ConditionalOnMissingBean(NamedParameterJdbcOperations.class)
public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
return new NamedParameterJdbcTemplate(jdbcTemplate);
}
}
์ด๋ฐ ํจํด์ ์ฅ์ ์:
- ํ์ํ ํด๋์ค๊ฐ ์์ ๋๋ง ๊ตฌ์ฑ์ด ํ์ฑํ๋จ (@ConditionalOnClass)
- ์ฌ์ฉ์๊ฐ ์ง์ ๋น์ ์ ์ํ์ง ์์์ ๋๋ง ๊ธฐ๋ณธ ๋น์ด ์ ๊ณต๋จ (@ConditionalOnMissingBean)
- ์๋ ๊ตฌ์ฑ ๊ฐ์ ์์๊ฐ ๋ช ํํ ์ ์๋จ (@AutoConfigureAfter)
์ด๋ฐ ๋ฐฉ์์ผ๋ก Spring Boot๋ "๊ด๋ก์ ๊ตฌ์ฑ(Convention over Configuration)" ์์น์ ๊ตฌํํ๊ณ ์์ด. ๊ธฐ๋ณธ๊ฐ์ ์ ๊ณตํ๋ฉด์๋ ์ฌ์ฉ์๊ฐ ์ํ๋ฉด ์ธ์ ๋ ์ปค์คํฐ๋ง์ด์งํ ์ ์๊ฒ ํด์ฃผ๋ ๊ฑฐ์ง! ๐
Spring Boot์ ์๋ ๊ตฌ์ฑ์ ๋ ๊น๊ฒ ์ดํดํ๊ณ ์ถ๋ค๋ฉด spring-boot-autoconfigure ๋ชจ๋์ ์์ค ์ฝ๋๋ฅผ ์ดํด๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ๋ง์ ์๊ฐ์ ์ป์ ์ ์์ ๊ฑฐ์ผ! ๐
๐ฏ ๋ง๋ฌด๋ฆฌ: @Conditional์ ํ์ ํ์ฉํ์
์ง๊ธ๊น์ง Spring์ @Conditional ์ ๋ ธํ ์ด์ ์ ๋ํด ๊น์ด ์์๋ดค์ด! ์ด ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ฐ์ฑ๊ณผ ํ์ฅ์ฑ์ ํฌ๊ฒ ๋์ผ ์ ์์ง. ๐
@Conditional์ ํ์ฉํ๋ฉด:
- ํ๊ฒฝ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋น์ ๋ฑ๋กํ ์ ์์ด
- ํน์ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋ ๋๋ง ๊ธฐ๋ฅ์ ํ์ฑํํ ์ ์์ด
- ์ ํ์ ์์กด์ฑ์ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ด
- ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑ์ ๋ ์ ์ฐํ๊ฒ ๋ง๋ค ์ ์์ด
- ์ฌ์ฉ์ ์ ์ ์กฐ๊ฑด์ผ๋ก ๋น ๋ฑ๋ก์ ์ ์ดํ ์ ์์ด
Spring๊ณผ Spring Boot๋ ์ด๋ฏธ ๋ด๋ถ์ ์ผ๋ก @Conditional์ ๊ด๋ฒ์ํ๊ฒ ์ฌ์ฉํ๊ณ ์์ด. ์ด ํจํด์ ์ดํดํ๊ณ ํ์ฉํ๋ฉด ๋ ์ค๋งํธํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์์ ๊ฑฐ์ผ! ๐
์ฌ๋ฅ๋ท์์ ๋ค์ํ ์ฌ๋ฅ์ ์ฐพ์ ํ์ฉํ๋ฏ์ด, Spring์ ๋ค์ํ ๊ธฐ๋ฅ์ ํ์ฉํด์ ๋ ์ข์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณด์! ๐
๋ค์์ ๋ ๋ค๋ฅธ Spring์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ผ๋ก ๋ง๋์! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.