https://sundaland.tistory.com/481
▶ Composing Java-based Configurations
Spring의 Java 기반 구성 기능을 사용하면 어노테이션을 작성할 수 있어 구성의 복잡성을 줄일 수 있다.
▷ Using the @Import Annotation
<import/> 엘리먼트가 Spring XML 파일 내에서 구성의 모듈화를 돕기 위해 사용되는 것처럼, @Import 어노테이션을 사용하면 다음 예제와 같이 다른 구성 클래스에서 @Bean 정의를 로드할 수 있다.
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
이제 컨텍스트를 인스턴스화할 때 ConfigA.class와 ConfigB.class를 모두 지정할 필요 없이 다음 예제와 같이 ConfigB만 명시적으로 제공하면 된다.
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
이 접근 방식은 컨테이너 인스턴스화를 간소화한다. 컨테이너를 생성하는 동안 잠재적으로 많은 수의 @Configuration 클래스를 기억할 필요가 없고, 하나의 클래스만 처리하면 되기 때문이다.
▷ Injecting Dependencies on Imported @Bean Definitions
앞의 예제는 작동하지만 단순하다. 대부분의 실제 시나리오에서 빈은 구성 클래스 간에 서로 종속성이 있다. XML을 사용하는 경우 컴파일러가 관여하지 않기 때문에 문제가 되지 않으며 ref="someBean"을 선언하고 컨테이너 초기화 중에 Spring이 이를 해결하도록 신뢰할 수 있다. @Configuration 클래스를 사용하는 경우 Java 컴파일러는 구성 모델에 제약 조건을 두므로 다른 빈에 대한 참조는 유효한 Java 구문이어야 한다.
@Bean 메서드는 빈 종속성을 설명하는 임의의 수의 파라미터를 가질 수 있다. 각각 다른 빈에 선언된 빈에 따라 달라지는 여러 @Configuration 클래스가 있는 다음과 같은 보다 현실적인 시나리오를 고려 해야한다.
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
// SpringTransactionManager 참조
@Configuration 클래스는 궁극적으로 컨테이너의 또 다른 빈일 뿐이다. 즉, 다른 빈과 마찬가지로 @Autowired 및 @Value 주입과 다른 기능을 활용할 수 있다.
▼ 구성 클래스내에 @Autowired를 사용한 의존성 주입
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import javax.sql.DataSource;
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository; // 자동 주입
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository); // 필드에서 가져온 빈 사용
}
}
@Configuration
public class RepositoryConfig {
@Autowired
private DataSource dataSource; // 자동 주입
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource); // 필드에서 가져온 빈 사용
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// DataSource 구현체 반환
// 예를 들어, HikariDataSource 등
}
}
public class MainApp {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// 모든 설정 클래스가 적절하게 연결됨
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
}
@Configuration 클래스의 생성자 주입은 Spring Framework 4.3부터만 지원된다. 또한 대상 빈이 생성자를 하나만 정의하는 경우 @Autowired를 지정할 필요가 없다는 점에 유의해야 한다.
▷ Fully-qualifying imported beans for ease of navigation
이전 시나리오에서 @Autowired를 사용하면 잘 작동하고 원하는 모듈성을 제공하지만 자동 와이어링된 빈 정의가 정확히 어디에 선언되는지 확인하는 것은 다소 모호하다. Eclipse용 Spring Tools는 모든 것이 어떻게 와이어링되는지 보여주는 그래프를 렌더링할 수 있는 도구를 제공(스프링 부트 프로젝트에서만 지원)하며, 이것만으로도 충분할 수 있다. 또한 Java IDE는 AccountRepository 타입의 모든 선언과 사용을 쉽게 찾고 해당 타입을 리턴하는 @Bean 메서드의 위치를 빠르게 보여줄 수 있다.
이러한 모호성이 허용되지 않고 IDE 내에서 한 @Configuration 클래스에서 다른 @Configuration 클래스로 직접 이동하려는 경우 구성 클래스 자체를 자동 와이어링하는 것을 고려해야 한다. 다음 예는 그 방법을 보여다.
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
이전 상황에서 AccountRepository가 정의된 위치는 완전히 명시적이다. 그러나 ServiceConfig는 이제 RepositoryConfig에 밀접하게 결합되었다. 이것이 트레이드오프다. 이러한 밀접 결합은 인터페이스 기반 또는 추상 클래스 기반 @Configuration 클래스를 사용하여 다소 완화할 수 있다.
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
이제 ServiceConfig는 구체적인 DefaultRepositoryConfig와 관련하여 느슨하게 결합되었으며, 내장된 IDE 툴링은 여전히 유용하다. RepositoryConfig 구현의 타입 계층을 쉽게 얻을 수 있다. 이런 식으로 @Configuration 클래스와 해당 의존성을 탐색하는 것은 인터페이스 기반 코드를 탐색하는 일반적인 프로세스와 다르지 않다.'
특정 빈의 시작 시 생성 순서에 영향을 주고 싶다면, 일부 빈을 @Lazy(시작 시가 아닌 첫 번째 액세스 시 생성)로 선언하거나, 다른 특정 빈에 @DependsOn(현재 빈의 직접적인 의존성이 의미하는 것 이상으로, 다른 특정 빈이 현재 빈보다 먼저 생성되도록 함)으로 선언하는 것을 고려해야 한다.
▶ Conditionally Include @Configuration Classes or @Bean Methods
임의의 시스템 상태에 따라 전체 @Configuration 클래스 또는 개별 @Bean 메서드를 조건부로 활성화하거나 비활성화하는 것이 종종 유용하다. 이에 대한 일반적인 예 중 하나는 @Profile을 사용하여 Spring 환경에서 특정 프로필이 활성화된 경우에만 Bean을 활성화하는 것이다.
@Profile은 실제로 @Conditional이라는 훨씬 더 유연한 어노테이션을 사용하여 구현된다. @Conditional은 @Bean이 등록되기 전에 참조해야 하는 특정 org.springframework.context.annotation.Condition 구현을 나타낸다.
Condition 인터페이스의 구현은 true 또는 false를 반환하는 matches(…) 메서드를 제공한다. 예를 들어, 다음 목록은 @Profile에 사용된 실제 Condition 구현을 보여다.
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
이 코드는 @Profile 애노테이션을 사용해 특정 프로파일이 활성화된 경우에만 빈을 등록하는 방법을 예시로 들고, 나아가 사용자 정의 Condition 인터페이스를 구현하여 @Conditional을 사용하는 방법도 포함한다.
◎ 1. HikariCP 디펜던시 추가
▼ pom.xml
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
◎ 2. @Profile 애노테이션을 사용한 샘플 코드
▼ AppConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import com.zaxxer.hikari.HikariDataSource;
@Configuration
public class AppConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
// 개발 환경용 데이터소스 설정
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(com.mysql.cj.jdbc.Driver.class);
dataSource.setUrl("jdbc:mysql://localhost:3306/sbdt_db?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("1234");
return dataSource;
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
// 프로덕션 환경용 데이터소스 설정
return new HikariDataSource(); // 다른 데이터베이스 설정
}
}
▼ MainApp.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
// 프로파일 설정: "dev" 또는 "prod"
System.setProperty("spring.profiles.active", "dev");
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
DataSource dataSource = ctx.getBean(DataSource.class);
System.out.println("Using DataSource: " + dataSource.getClass().getName());
}
}
◎ 3. @Conditional 애노테이션을 사용한 사용자 정의 Condition 구현
▼ CustomCondition.java
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class CustomCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 예를 들어 특정 시스템 속성이 존재하는 경우에만 true 반환
String expectedProperty = "my.custom.property";
String propertyValue = context.getEnvironment().getProperty(expectedProperty);
return propertyValue != null && propertyValue.equals("enabled");
}
}
▼ AppConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
@Conditional(CustomCondition.class)
public MyService myService() {
return new MyServiceImpl();
}
}
▼ MainApp.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
// 조건에 맞는 시스템 속성 설정
System.setProperty("my.custom.property", "enabled");
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
if (ctx.containsBean("myService")) {
MyService myService = ctx.getBean(MyService.class);
System.out.println("MyService Bean is available: " + myService.getClass().getName());
} else {
System.out.println("MyService Bean is not available");
}
}
}
◎ 4. 주요 클래스 정의
▼ MyService.java
public interface MyService {
void performService();
}
▼ MyServiceImpl.java
public class MyServiceImpl implements MyService {
@Override
public void performService() {
System.out.println("Service is being performed.");
}
}
◎ 설명
1. @Profile 사용
- AppConfig 클래스는 @Profile 애노테이션을 사용하여 특정 프로파일이 활성화된 경우에만 빈을 등록한다.
- MainApp에서는 spring.profiles.active 속성을 설정하여 "dev" 또는 "prod" 프로파일을 활성화할 수 있다.
2. @Conditional 사용
- CustomCondition 클래스는 Condition 인터페이스를 구현하여, 특정 시스템 속성이 설정된 경우에만 빈을 등록하도록 한다.
- AppConfig 클래스의 myService 빈은 @Conditional을 사용하여 CustomCondition이 참일 경우에만 등록된다.
- MainApp에서 시스템 속성을 설정하고, 해당 조건에 맞는 빈이 등록되었는지 확인한다.
이 샘플 코드는 @Profile과 @Conditional 애노테이션을 사용하여 Spring에서 조건부로 @Configuration 클래스나 @Bean 메서드를 포함하거나 제외하는 방법을 보여준다. 이러한 기능을 사용하면 특정 환경이나 조건에 맞게 애플리케이션 구성을 유연하게 제어할 수 있다.
'스프링 프레임워크 > IoC (Inversion of Control)' 카테고리의 다른 글
Using the @Configuration annotation (0) | 2024.11.19 |
---|---|
Using the @Bean Annotation (0) | 2024.11.19 |
Instantiating the Spring Container by Using AnnotationConfigApplicationContext (0) | 2024.11.19 |
Fine-tuning Annotation-based Autowiring with Qualifiers (0) | 2024.11.19 |
Annotation-based Container Configuration (0) | 2024.11.19 |