====== Java-based configuration ======
===== 개요 =====
Java 코드에서 주석을 사용하여 스프링 컨테이너를 구성하는 방법에 대해 알아본다.
===== 설명 =====
==== 기본 개념 : @Bean, @Configuration ====
스프링의 자바 기반 설정에서는 @Configuration 어노테이션 클래스와 @Bean 어노테이션 메소드를 지원한다.\\
@Bean 어노테이션은 Spring IoC 컨테이너가 관리할 새로운 객체를 인스턴스화하고, 초기화하는데 사용되며, Spring의 XML 설정에서의 과 같은 역할을 한다.\\
@Bean 어노테이션은 붙인 메소드는 스프링 @Component와 함께 사용할 수 있지만, 대체로 @Configuration Bean과 사용한다.\\
@Configuration 어노테이션은 해당 클래스의 목적이 Bean 설정을 위한 소스임을 나타내며, @Configuration 클래스는 같은 클래스 안에 있는 @Bean 메소드들끼리 서로를 호출하여
Bean 사이의 의존성을 정의할 수 있게 한다.\\
@Configuration 클래스를 아래와 같이 구성할 수 있다.
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
예제의 AppConfig 클래스는 아래의 Spring XML과 동일한 역할을 한다.
==== AnnotationConfigApplicationContext를 이용한 스프링 컨테이너 인스턴스화 ====
AnnotationConfigApplicationContext는 다재다능한 ApplicationContext 구현체로, 입력으로 @Configuration 클래스뿐만 아니라 평범한 @Component 클래스와 JSR-330 메타데이터로 어노테이션이 붙은 클래스들도 받아들일 수 있다.\\
@Configuration 클래스가 입력으로 제공되면 @Configuration 클래스 자체가 Bean 정의로 등록되고, 해당 클래스내의 선언된 모든 @Bean 메서드들도 Bean 정의로 등록된다.\\
@Component와 JSR-330 클래스들이 제공되면 이 클래스들은 Bean 정의로 등록되고 해당 클래스내에서 필요한 곳에 @Autowired나 @Inject 같은 DI 메타데이터가 사용되었다고 가정한다.
=== 간단한 구성 ===
ClassPathXmlApplicationContext를 인스턴스화 할 때 스프링 XML 파일을 사용하는 방법과 거의 동일하게 AnnotationConfigApplicationContext를 인스턴스화 할 때 @Configuration 클래스들을 입력으로 사용할 것이다.\\
이를 통해 전혀 XML을 사용하지 않고 스프링 컨테이너를 사용할 수 있다.
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
=== register(Class>...)로 프로그래밍 방식으로 컨테이너 빌드 ===
인수 없는 생성자를 사용하여 AnnotationConfigApplicationContext를 인스턴스화한 다음 register() 메서드를 사용하여 구성할 수 있다.\\
이 접근 방식은 AnnotationConfigApplicationContext를 프로그래밍 방식으로 빌드할 때 특히 유용하며 아래와 같이 사용할 수 있다.
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
=== scan(String...)으로 컴포넌트 스캔 ==
@Configuration구성 요소 스캔을 활성화하려면 다음과 같이 구성할 수 있다.
@Configuration
@ComponentScan(basePackages = "com.acme") // 구성요소 스캔을 활성화함
public class AppConfig {
// ...
}
경험있는 스프링 사용자들은 다음과 같이 일반적으로 사용되는 스프링의 context: 네임스페이스로 XML을 선언하는데 익숙할 것이다.
위의 예제에서 com.acme 팩키지는 스캔되고 @Component 어노테이션이 붙은 클래스들을 찾고 이러한 클래스를 컨테이너내 스프링 빈 정의로 등록할 것이다.\\
AnnotationConfigApplicationContext에는 같은 컴포넌트 스캔 기능을 하는 scan(String...) 메서드가 있다.
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
=== AnnotationConfigWebApplicationContext를 사용한 웹 어플리케이션 지원 ===
AnnotationConfigApplicationContext의 WebApplicationContext 변형은 AnnotationConfigWebApplicationContext로 사용할 수 있다.\\
이 구현체는 스프링 ContextLoaderListener 서블릿 리스너, 스프링 MVC DispatcherServlet 등을 설정할 때 사용할 수 있다.\\
아래는 전형적인 스프링 MVC 웹 어플리케이션을 설정하는 web.xml의 예제로 contextClass context-param과 init-param의 사용방법을 보여준다.
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
com.acme.AppConfig
org.springframework.web.context.ContextLoaderListener
dispatcher
org.springframework.web.servlet.DispatcherServlet
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
com.acme.web.MvcConfig
dispatcher
/app/*
==== @Bean 어노테이션 사용 ====
@Bean은 메소드 레벨 어노테이션이며 XML 요소와 동일하며 에서 제공하는 일부 속성을 지원한다.
또한, @Configuration 어노테이션 또는 @Component 어노테이션 클래스에서 @Bean 어노테이션을 사용할 수 있다.
=== Bean 선언 ===
메소드에 @Bean 어노테이션을 사용하면 Bean으로 선언된다. 이 메서드를 사용하여 메서드의 반환 값으로 지정된 유형의 ApplicationContext 내에 Bean 정의를 등록되며 Bean 이름은 메서드 이름과 동일하다.\\
다음 예제에서 @Bean 메소드 선언을 확인할 수 있다.
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
위 예제는 다음 Spring XML과 정확히 동일하다.
=== Bean 종속성 ===
@Bean 어노테이션 메서드는 해당 Bean을 빌드하는 데 필요한 종속성을 설명하는 임의 개수의 매개변수를 가질 수 있다.\\
예를 들어 TransferService에 AccountRepository가 필요한 경우 다음 예제와 같이 메서드 매개 변수를 사용하여 해당 종속성을 구체화할 수 있다.
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
=== @Scope 어노테이션 사용 ===
@Bean 어노테이션으로 정의된 Bean이 특정 범위를 갖도록 지정할 수 있으며, 선언된 Bean의 범위는 [[eGovframework:rte4.1:fdl:IoC Container:Bean scope]]에 지정된 표준 범위를 사용할 수 있다.\\
기본 범위는 싱글톤이지만 다음 예제와 같이 @Scope 어노테이션으로 이를 재정의할 수 있다.
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
==== @Configuration 어노테이션 사용 ====
@Configuration은 객체가 Bean 정의의 소스임을 나타내는 클래스 수준 어노테이션이며 @Configuration 클래스는 @Bean 어노테이션 메서드를 통해 빈을 선언한다.\\
@Configuration 클래스의 @Bean 메소드에 대한 호출은 bean 간 종속성을 정의하는 데에도 사용할 수 있다.
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
==== Java 기반 설정 구성 ====
Spring의 Java 기반 설정 기능을 사용하면 구성의 복잡성을 줄일 수 있는 어노테이션을 작성할 수 있다.
=== @Import 어노테이션 사용 ===
스프링 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 클래스들을 기억해야 하는 대신 하나의 클래스만 다루면 되므로 컨테이너 인스턴스화를 단순화한다.
=== 임포트한 @Bean 정의에 의존성 주입 ===
위의 예제는 작동하지만 단순하다. 대부분의 실제 시나리오에서 Bean은 다른 설정 클래스들에 대한 의존성을 가지게 된다.\\
XML을 사용할 때 컴파일러가 관여하지 않고 그냥 ref="someBean"만 선언한 뒤 스프링이 컨테이너를 인스턴스화 하면 제대로 동작하기를 믿으면 되기 때문에 의존성 자체는 이슈가 아니었다.\\
@Configuration를 사용할 때 자바 컴파일러는 다른 빈에 대한 참조는 유효한 자바문법이어야 한다는 제약을 설정 모델에 둔다.
다행히도 이 문제를 해결하는 것은 간단하다. 이미 논의한 것처럼 @Bean 메소드는 Bean 종속성을 설명하는 임의의 수의 매개변수를 가질 수 있다.\\
각각 다른 클래스에서 선언된 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");
}
===== 참고자료 =====
* [[https://docs.spring.io/spring-framework/docs/5.3.20/reference/html/core.html#beans-java|Spring Framework - Reference Document / 1.12. Java-based Container Configuration]]