====== 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]]