이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.
4.11 JSR 330 표준 어노테이션 사용
스프링 3.0부터는 JSR-330 표준 어노테이션 (의존성 주입)을 지원한다. 이러한 어노테이션들은 스프링 어노테이션과 같은 방법으로 스캔한다. 클래스패스에 적절한 jar를 두기만 하면 된다.
Note
메이븐을 사용한다면 표준 메이븐 저장소(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/)에서 javax.inject artifact를 사용할 수 있다. pom.xml 파일에 다음 의존성을 추가할 수 있다.
메이븐을 사용한다면 표준 메이븐 저장소(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/)에서 javax.inject artifact를 사용할 수 있다. pom.xml 파일에 다음 의존성을 추가할 수 있다.
1 2 3 4 5 6 7 | Xml <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> |
4.11.1 @Inject와 @Named를 사용한 의존성 주입
@Autowired 대신 @javax.inject.Inject 를 다음처럼 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Java import javax.inject.Inject; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } |
@Autowired를 사용하듯이 클래스레벨, 필드레벨, 메서드레벨, 생성자 아규먼트레벨에서 @Inject를 사용하는 것이 가능하다. 주입되어야 하는 의존성에 검증된 이름을 사용하기를 좋아한다면 다음과 같이 @Named 어노테이션을 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Java import javax.inject.Inject; import javax.inject.Named; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(@Named("main") MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } |
4.11.2 @Named: @Component 어노테이션과 동일한 표준
@Component 대신에 @javax.inject.Named를 다음과 같이 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Java import javax.inject.Inject; import javax.inject.Named; @Named("movieListener") public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } |
컴포넌트에 이름을 지정하지 않고 @Component를 사용하는 것은 아주 일반적이다. 동일한 방법으로 @Named를 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Java import javax.inject.Inject; import javax.inject.Named; @Named public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } |
@Named를 사용하면 스프링 어노테이션을 사용할 때와 완전히 같은 방법으로 컴포넌트 스캔을 사용할 수 있다.
1 2 3 4 5 | Xml <beans> <context:component-scan base-package="org.example"/> </beans> |
4.11.3 표준 접근의 한계
표준 어노테이션들을 사용해서 작업할 때 다음 표에 나온 것처럼 몇가지 중요한 기능들을 사용할 수 있다는 것을 꼭 알아야 한다.
Table 4.6. 스프링 어노테이션 vs. 표준 어노테이션
Spring | javax.inject.* | javax.inject의 제약 / 설명 |
---|---|---|
@Autowired | @Inject | @Inject에는 'required'속성이 없다 |
@Component | @Named | - |
@Scope("singleton") | @Singleton | JSR-330의 기본 범위는 스프링의 prototype과 비슷하다. 하지만 스프링의 일반적인 기본값과 일관성을 유지하기 위해 스프링 컨테이너에서 선언된 JSR-330 빈은 기본적으로 singleton이다. The JSR-330 default scope is like Spring's prototype. singleton 대신 다은 범위를 사용하려면 스프링의 @Scope 어노테이션을 사용해야 한다. javax.inject도 @Scope 어노테이션을 제공한다. 그렇기는 하지만 이 어노테이션은 자신만의 어노테이션을 생성할 때만 사용하도록 만들어졌다. |
@Qualifier | @Named | - |
@Value | - | 동일한 것이 없다 |
@Required | - | 동일한 것이 없다 |
@Lazy | - | 동일한 것이 없다 |
4.12 자바기반의 컨테이너 설정
4.12.1 기본 개념: @Configuration와 @Bean
스프링의 새로운 자바설정 지원의 핵심부분은 @Configuration 어노테이션이 붙은 클래스다. 이러한 클래스들은 스프링 IoC 컨테이너가 관리하는 객체의 인스턴스화, 설정, 초기화 로직을 정의하는 @Bean 어노테이션이 붙은 메서드들을 주로 이루어져 있다.
클래스에 @Configuration 어노테이션을 붙히는 것은 스프링 IoC 컨테이너가 해당 클래스를 빈 정의의 소스로 사용한다는 것을 나타낸다. 가장 간단한 @Configuration 클래스는 다음과 같을 것이다.
1 2 3 4 5 6 7 8 9 | Java @Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } } |
스프링 <beans/> XML에 익숙하다면 앞의 AppConfig 클래스는 다음과 같을 것이다.
1 2 3 4 5 | Xml <beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans> |
여기서 보듯이 @Bean 어노테이션은 <bean/> 요소와 같은 역할을 한다. @Bean 어노테이션은 이번 섹션 후반에 더 깊게 살펴볼 것이다. 하지만 우선 자바기반의 설정을 사용해서 스프링 컨테이서를 생성하는 여러가지 방법을 살펴보자.
4.12.2 AnnotationConfigApplicationContext를 사용하는 스프링 컨테이너 예제 살펴보기
이 섹션에서는 스프링 3.0의 새 기능인 AnnotationConfigApplicationContext를 설명한다. 이 다재다능한 ApplicationContext 구현체는 인풋으로 @Configuration 클래스뿐만 아니라 평범한 @Component 클래스와 JSR-330 메타데이터로 어노테이션이 붙은 클래스들도 받아들일 수 있다.
인풋으로 @Configuration클래스를 받았을 때 @Configuration 클래스 자체가 빈 정의로 등록되고 해당 클래스내의 선언된 모든 @Bean 메서드들도 빈 정의로 등록된다.
@Component와 JSR-330 클래스들이 제공되었을 때 이 클래스들은 빈 정의로 등록되고 해당 클래스내에서 필요한 곳에 @Autowired나 @Inject 같은 DI 메타데이터가 사용되었다고 가정한다.
4.12.2.1 간단한 구성
ClassPathXmlApplicationContext를 인스턴스화 할 때 인풋으로 스프링 XML 파일을 사용하는 방법과 거의 동일하게 AnnotationConfigApplicationContext를 인스턴스화 할 때 @Configuration 클래스들을 인풋으로 사용할 것이다. 이를 통해 전혀 XML을 사용하지 않고 스프링 컨테이너를 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 | Java 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(); } |
4.12.2.3 scan(String...)으로 컴포넌트 스캔 가능하게 하기
경험있는 스프링 사용자들은 다음과 같이 일반적으로 사용되는 스프링의 context: 네임스페이스로 XML을 선언하는데 익숙할 것이다.
1 2 3 4 5 | Xml <beans> <context:component-scan base-package="com.acme"/> </beans> |
위의 예제에서 com.acme 팩키지는 스캔되고 @Component 어노테이션이 붙은 클래스들을 찾고 이러한 클래스를 컨테이너내 스프링 빈 정의로 등록할 것이다. AnnotationConfigApplicationContext에는 같은 컴포넌트 스캔 기능을 하는 scan(String...) 메서드가 있다.
1 2 3 4 5 6 7 8 | Java public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("com.acme"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); } |
Note
@Configuration 클래스들은 @Component로 메타 어노테이션이 붙은 클래스라는 것을 기억해라. 그래서 이 클래스들은 컴포넌트 스캔의 후보들이 된다. 위의 예제에서 com.acme 팩키지 (또는 그 하위의 어떤 팩키지)내에 AppConfig가 선언었다는 것을 가정하고 이는 scan()을 호출하는 동안 선택될 것이고 클래스의 모든 @Bean 메서드들을 refresh() 할 때 컨테이너내 빈 정의로 처리되고 등록될 것이다.
@Configuration 클래스들은 @Component로 메타 어노테이션이 붙은 클래스라는 것을 기억해라. 그래서 이 클래스들은 컴포넌트 스캔의 후보들이 된다. 위의 예제에서 com.acme 팩키지 (또는 그 하위의 어떤 팩키지)내에 AppConfig가 선언었다는 것을 가정하고 이는 scan()을 호출하는 동안 선택될 것이고 클래스의 모든 @Bean 메서드들을 refresh() 할 때 컨테이너내 빈 정의로 처리되고 등록될 것이다.
4.12.2.4 AnnotationConfigWebApplicationContext를 사용한 웹 어플리케이션 지원
AnnotationConfigApplicationContext의 WebApplicationContext 변형은 AnnotationConfigWebApplicationContext로 사용할 수 있다. 이 구현체는 스프링 ContextLoaderListener 서블릿 리스너, 스프링 MVC DispatcherServlet 등을 설정할 때 사용할 수 있다. 다음은 전형적인 스프링 MVC 웹 어플리케이션을 설정하는 web.xml의 예제이다. contextClass context-param과 init-param의 사용방법을 보여준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | Xml <web-app> <!-- 기본 XmlWebApplicationContext 대신 AnnotationConfigWebApplicationContext를 사용하는 ContextLoaderListener를 설정한다 --> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </context-param> <!-- 설정 위치는 반드시 콤마나 공백을 구분자로 사용하는 하나 이상의 정규화된 @Configuration 클래스들로 구성되어야 한다. 정규화된 팩키지는 컴포넌트 스캔으로 지정될 수도 있다. --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.AppConfig</param-value> </context-param> <!-- 평소처럼 ContextLoaderListener를 사용해서 루트 어플리케이션 시작하기 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 평소처럼 스프링 MVC DispatcherServlet 선언 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 기본 XmlWebApplicationContext 대신 AnnotationConfigWebApplicationContext를 사용한 DispatcherServlet 설정 --> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <!-- 다시한번, 설정 위치는 반드시 콤마나 공백을 구분자로 사용하는 하나 이상의 정규화된 @Configuration 클래스들로 구성되어야 한다. --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.web.MvcConfig</param-value> </init-param> </servlet> <!-- /app/*에 대한 모든 요청을 디스패쳐 서블릿에 매핑한다 --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app> |
4.12.3 자바 기반으로 설정 구성하기
4.12.3.1 @Import 어노테이션의 사용
스프링 XML 파일에서 설정 모듈화에 <import/>요소를 사용하기는 하지만 @Import 어노테이션은 다른 설정 클래스에서 @Bean 설정을 로딩한다.
1 2 3 4 5 6 7 8 9 10 11 12 | Java @Configuration public class ConfigA { public @Bean A a() { return new A(); } } @Configuration @Import(ConfigA.class) public class ConfigB { public @Bean B b() { return new B(); } } |
이제 컨텍스트를 인스턴스화 할 때 ConfigA.class와 ConfigB.class를 둘 다 지정해야하는 대신 ConfigB만 명시적으로 제공하면 된다.
1 2 3 4 5 6 7 8 9 | Java 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 정의에서 의존성 주입하기
위의 예제는 동작하기는 하지만 너무 간단하다. 대부분의 실무에서는 빈에 다른 설정 클래스들에 대한 의존성이 있을 것이다. XML을 사용할 때 컴파일러가 관여하지 않고 그냥 ref="someBean"만 선언한 뒤 스프링이 컨테이너를 인스턴스화 하면제 제대로 동작하기를 믿으면 되기 때문에 의존성 자체는 이슈가 아니었다. 물론 @Configuration를 사용할 때 자바 컴파일러는 다른 빈에 대한 참조는 유효한 자바문법이어야 한다는 제약을 설정 모델에 둔다.
다행히도 이 문제의 해결책은 간단하다. @Configuration 클래스들은 결국 컨테이너내의 다른 빈일 뿐이라는 것을 기억해라. 이는 @Configuration 클래스들이 다른 빈처럼 @Autowired 주입 메타데이터의 이점을 취할 수 있다는 것을 의미한다!
다른 빈들에 선언된 빈에 따라 각각 다수의 @Configuration 클래스들이 있는 더 현실적인 시나리오를 생각해 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | Java @Configuration public class ServiceConfig { private @Autowired AccountRepository accountRepository; public @Bean TransferService transferService() { return new TransferServiceImpl(accountRepository); } } @Configuration public class RepositoryConfig { private @Autowired DataSource dataSource; public @Bean AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } } @Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public class SystemTestConfig { public @Bean DataSource dataSource() { /* return new 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"); } |
네비게이션을 편하게 하기 위해 임포트한 빈들을 정규화하기
위의 시나리오에서 @Autowired는 잘 동작하고 원하는 모듈화를 제공하지만 정확히 자동연결된 빈정의가 어디인지 결정하는 것은 여전히 약간 모호하다. 예를 들어 한 개발자가 ServiceConfig를 보듯이 정확히 어디에 @Autowired AccountRepository 빈이 선언되었는지 어떻게 알 수 있는가? 이는 코드에서 명백하지 않지만 괜찮을 것이다. SpringSource Tool Suite가 모든 것들이 어떻게 연결되는지 보여주는 그래프를 그리는 도구를 제공한다. 이 그래프가 당신이 바라는 전부일 것이다. 게다가 자바 IDE는 쉽게 모든 선언과 AccountRepository의 사용을 찾을 수 있고 해당 타입을 리턴하는 @Bean 메서드의 위치를 빠르게 보여줄 것이다.
이 애매모호함을 받아들일 수 없고 한 @Configuration 클래스에서 다른 클래스까지 IDE에서 직접 네비게이션하기를 원하는 경우에는 설정 클래스들 자체를 자동연결하는 것을 고려해 봐라.
1 2 3 4 5 6 7 8 9 10 11 | Java @Configuration public class ServiceConfig { private @Autowired RepositoryConfig repositoryConfig; public @Bean TransferService transferService() { // 설정 클래스 '전체에서' @Bean 메서드를 탐색한다! return new TransferServiceImpl(repositoryConfig.accountRepository()); } } |
위의 상황에서는 어디 AccountRepository가 정의되었는지가 아주 명백하다. 하지만 이제 ServiceConfig가 RepositoryConfig에 강하게 연결되어 있다. 이는 트레이드 오프(tradeoff)다. 인터페이스 기반이나 추상 클래스 기반의 @Configuration 클래스들을 사용해서 이 강한 커플링을 약간 완화할 수 있다. 다음을 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | Java @Configuration public class ServiceConfig { private @Autowired RepositoryConfig repositoryConfig; public @Bean TransferService transferService() { return new TransferServiceImpl(repositoryConfig.accountRepository()); } } @Configuration public interface RepositoryConfig { @Bean AccountRepository accountRepository(); } @Configuration public class DefaultRepositoryConfig implements RepositoryConfig { public @Bean AccountRepository accountRepository() { return new JdbcAccountRepository(...); } } @Configuration @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // 구체적인(concrete) 설정을 임포트한다! public class SystemTestConfig { public @Bean DataSource dataSource() { /* 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는 관련된 구체적인(concrete) DefaultRepositoryConfig에 약하게 연결되어 있고 내장 IDE 도구는 여전히 유용하다. 개발자는 쉽게 RepositoryConfig 구현체들의 타입 계층을 얻을 수 있을 것이다. 이 방법으로 @Configuration 클래스들과 그 의존성들을 네비게이션 하는 것은 인터페이스 기반의 코드를 네비게이션하는 일반적인 과정과 다르지 않아졌다.
4.12.3.2 자바와 XML 설정을 조합하기
스프링의 @Configuration 클래스 지원은 스프링의 XML을 100% 완전히 대체하지 못한다. 스프링 XML 네임스페이스같은 몇몇 기능들은 여전히 컨테이너를 설정하는 이상적인 방법이다. XML이 편리하거나 필수적인 상황에서 선택권이 있다. ClassPathXmlApplicationContext등을 사용한 "XML 중심적인" 방법으로 컨테이너를 인스턴스화하거나 AnnotationConfigApplicationContext와 필요한 XML을 임포트하는 @ImportResource 어노테이션을 사용하는 "자바 중심적인"방법 중에서 선택할 수 있다.
@Configuration 클래스의 XML 중심적인 사용
애드훅으로 @Configuration 클래스와 함께 XML로 스프링 컨테이너를 시작하는 것을 선호할 수도 있다. 예를 들어 스프링 XML을 사용하는 많은 양의 코드가 이미 있는 경우 필요한만큼의 원리에 따라 @Configuration 클래스를 생성하고 존재하는 XML파일에서 이 클래스들을 포함하는 것을 쉬울 것이다. 아래에서 "XML 중심적인" 상황에 위와 같은 경우에서 @Configuration 클래스들을 사용하는 옵션들을 볼 것이다.
평범한 스프링 <bean/> 요소처럼 @Configuration 클래스 선언하기
@Configuration는 결국은 그냥 컨테이너의 빈 정의라는 것을 기억해라. 이 예제에서 AppConfig라는 @Configuration 클래스를 생성하고 이 클래스를 <bean/>정의로 system-test-config.xml안에 포함시켰다. <context:annotation-config/>를 활성화 했기 때문에 컨테이너는 @Configuration 어노테이션을 인식하고 AppConfig에 선언된 @Bean 메서드를 적절히 처리할 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | Java @Configuration public class AppConfig { private @Autowired DataSource dataSource; public @Bean AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } public @Bean TransferService transferService() { return new TransferService(accountRepository()); } } Xml system-test-config.xml <beans> <!-- @Autowired와 @Configuration같은 어노테이션 처리가 가능하게 하기 --> <context:annotation-config/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="com.acme.AppConfig"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans> C-like jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password= Java public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml"); TransferService transferService = ctx.getBean(TransferService.class); // ... } |
Note
위의 system-test-config.xml에서 AppConfig<bean/>에 id 요소를 선언하지 않았다. 이렇게 하는 것은 허용되지 않으며 이를 참조하는 빈이 없으므로 필요하지 않다. 그리고 이름으로 컨테이너에서 명시적으로 가져올 가능성도 없다. DataSource 빈과 마찬가지로 이는 타입으로만 자동연결되므로 명시적인 빈 id가 엄격하게 필요하지 않다.
위의 system-test-config.xml에서 AppConfig<bean/>에 id 요소를 선언하지 않았다. 이렇게 하는 것은 허용되지 않으며 이를 참조하는 빈이 없으므로 필요하지 않다. 그리고 이름으로 컨테이너에서 명시적으로 가져올 가능성도 없다. DataSource 빈과 마찬가지로 이는 타입으로만 자동연결되므로 명시적인 빈 id가 엄격하게 필요하지 않다.
@Configuration를 선택하는 <context:component-scan/>의 사용
@Configuration에 @Component 메타 어노테이션이 붙었으므로 @Configuration 어노테이션이 붙은 클래스들은 자동적으로 컴포넌트 스캔의 후보가 된다. 위와 같은 시나리오에서 컴포넌트 스캔의 이점을 얻기 위해 system-test-config.xml를 재정의 할 수 있다. 이 경우에 <context:component-scan/>가 같은 기능을 모두 사용가능하게 하므로 명시적으로 <context:annotation-config/>를 선언할 필요가 없다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Xml system-test-config.xml <beans> <!-- 빈 정의로 AppConfig를 선택하고 등록한다 --> <context:component-scan base-package="com.acme"/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans> |
@ImportResource로 @Configuration 클래스 중심적인 XML의 사용
@Configuration클래스가 컨테이너를 설정하는 주요 메카니즘인 어플리케이션에서 여전히 최소한 약간의 XML은 사용할 필요가 있을 것이다. 이러한 시나리오에서 간단히 @ImportResource를 사용하고 필요한만큼의 XML을 정의한다. 이렇게 해서 최소한의 XML만으로 컨테이너를 설정하는 "자바 중심적인" 접근을 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | Java @Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig { private @Value("${jdbc.url}") String url; private @Value("${jdbc.username}") String username; private @Value("${jdbc.password}") String password; public @Bean DataSource dataSource() { return new DriverManagerDataSource(url, username, password); } } Xml properties-config.xml <beans> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> </beans> C-like jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password= Java public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); TransferService transferService = ctx.getBean(TransferService.class); // ... } |
4.12.4 @Bean 어노테이션의 사용
@Bean는 메서드 레벨의 어노테이션이고 XML <bean/> 요소와 바로 대응되는 요소이다. 이 어노테이션은 init-method, destroy-method, autowiring, name처럼 <bean/>가 제공하는 속성들의 일부를 지원한다.
@Configuration 어노테이션이나 @Component 어노테이션이 붙은 클래스에서 @Bean 어노테이션을 사용할 수 있다.
4.12.4.1 빈 선언하기
빈을 선언하려면 메서드에 @Bean 어노테이션을 붙히면 된다. 메서드가 리턴하는 값으로 지정한 타임의 ApplicationContext내에서 빈 정의를 등록하기 위해 이 메서드를 사용한다. 기본적으로 빈 이름은 메서드의 이름과 같을 것이다. 다음은 @Bean 메서드를 선언하는 간단한 예제이다.
1 2 3 4 5 6 7 8 9 10 | Java @Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } } |
앞의 설정은 다음 스프링 XML과 완전히 똑같다.
1 2 3 4 5 | Xml <beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/> </beans> |
두 선언은 모두 ApplicationContext에서 사용할 수 있는 transferService라는 빈을 만들고 TransferServiceImpl 타입의 인스턴스에 바인딩한다.
1 2 3 | C-like transferService -> com.acme.TransferServiceImpl |
4.12.4.2 의존성 주입
@Bean 들이 다른 빈에 대한 의존성이 있을 때 의존성을 나타내는 것은 하나의 빈 메서드가 다른 메서드를 호출하는 것만큼 간단하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}
위의 예제에서 foo은 생성자 주입으로 bar에 대한 참조를 받는다.4.12.4.3 라이프사이클 콜백 받기
@Configuration 어노테이션이 붙은 클래스에서 선언한 빈들은 정규 라이프사이클 콜백을 지원한다. @Bean 어노테이션으로 정의된 어떤 클래스들도 JSR-250의 @PostConstruct와 @PreDestroy 어노테이션을 사용할 수 있다. JSR-250의 자세한 내용은 JSR-250 annotations를 봐라.
정규 스프링 라이프사이클 콜백을 완전히 지원한다. 빈이 InitializingBean, DisposableBean, Lifecycle를 구현하면 컨테이너가 각각의 메서드를 호출한다.
BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware 등과 같은 *Aware 인터페이스 표준 세트도 완전히 지원한다.
스프링 XML의 bean 요소에서 init-method와 destroy-method 속성처럼 @Bean 어노테이션은 임의의 초기화와 파괴 콜백 메서드 지정하는 것을 지원한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Java public class Foo { public void init() { // 초기화 로직 } } public class Bar { public void cleanup() { // 파괴 로직 } } @Configuration public class AppConfig { @Bean(initMethod = "init") public Foo foo() { return new Foo(); } @Bean(destroyMethod = "cleanup") public Bar bar() { return new Bar(); } } |
물론 위의 Foo의 경우는 생성과정동안 init() 메서드를 유효하게 직접 호출하는 것과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Java @Configuration public class AppConfig { @Bean public Foo foo() { Foo foo = new Foo(); foo.init(); return foo; } // ... } |
Tip
자바로 직접 작업할 때 객체로 할 수 있는 모든 것을 할 수 있고 항상 컨테이너 라이프사이클에 의존한 필요가 없다!
자바로 직접 작업할 때 객체로 할 수 있는 모든 것을 할 수 있고 항상 컨테이너 라이프사이클에 의존한 필요가 없다!
4.12.4.4 빈 범위 지정하기
@Scope 어노테이션의 사용
@Bean 어노테이션으로 정의한 빈이 특정 범위를 갖도록 지정할 수 있다. Bean Scopes 섹션에서 지정한 표준 범위들은 모두 사용할 수 있다.
기본 범위는 singleton이지만 @Scope 어노테이션으로 이 기본 범위를 오버라이드할 수 있다.
1 2 3 4 5 6 7 8 9 10 | Java @Configuration public class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... } } |
@Scope와 범위를 가진 프록시
스프링은 범위를 가진 프록시를 통해서 범위를 가진 의존성과 동작하도록 하는 편리한 방법을 제공한다. XML 설정을 사용할 때 이러한 프록시를 생성하는 가장 쉬운 방법은 <aop:scoped-proxy/> 요소이다. 자바로 @Scope 어노테이션이 붙은 빈을 설정할 때 proxyMode 속성으로 동일한 기능을 제공한다. 기본값은 프록시가 없는 것이지만 (ScopedProxyMode.NO) ScopedProxyMode.TARGET_CLASS나 ScopedProxyMode.INTERFACES를 지정할 수 있다.
XML 레퍼런스 문서의 범위를 가진 프록시 예제를 (앞의 링크를 봐라) 자바를 사용하는 @Bean으로 포팅하면 다음과 같을 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Java // HTTP 세션 범위를 가진 빈을 프록시로 노출한다 @Bean @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public UserPreferences userPreferences() { return new UserPreferences(); } @Bean public Service userService() { UserService service = new SimpleUserService(); // 프록시 userPreferences 빈에 대한 참조 service.setUserPreferences(userPreferences()); return service; } |
검색 메서드 주입
앞에서 얘기했듯이 검색 메서드 주입은 드물게 사용하는 고급 기능이다. 이 기능은 싱글톤 범위를 가진 빈이 프로토타입 범위를 가진 빈에 의존성이 있는 경우에 유용하다. 자바로 이러한 타입의 설정을 사용한다는 것은 이 패턴을 구현한다는 자연스러운 의미가 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Java public abstract class CommandManager { public Object process(Object commandState) { // 적절한 Command 인터페이스의 새로운 인스턴스를 획득한다 Command command = createCommand(); // (완전히 새로운) Command 인스턴스에 상태를 설정한다 command.setState(commandState); return command.execute(); } // 괜찮다... 하지만 이 메서드의 구현은 어디에 있는가? protected abstract Command createCommand(); } |
자바로 설정을 사용할 때 새로운 (프로토타입) 커맨드 객체를 검색하는 방법같은 추상 createCommand() 메서드를 오버라이드한 CommandManager의 서브클래스를 생성할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Java @Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // 필요한 대로 여기서 의존성을 주입한다 return command; } @Bean public CommandManager commandManager() { // 새로운 프로토타입 Command 객체를 리턴하도록 command()를 오버라이드한 CommandManager의 // 새로운 익명 구현체를 리턴한다 return new CommandManager() { protected Command createCommand() { return asyncCommand(); } } } |
4.12.4.5 빈 이름 커스터마이징하기
기본적으로 설정 클래스들은 @Bean 메서드의 이름을 생성된 빈의 이름으로 사용한다. 하지만 이 기능은 name 속성으로 오버라이드 할 수 있다.
1 2 3 4 5 6 7 8 9 10 | Java @Configuration public class AppConfig { @Bean(name = "myFoo") public Foo foo() { return new Foo(); } } |
4.12.4.6 빈 별칭짓기
Section 4.3.1, “빈 이름짓기”에서 얘기했듯이 때로는 빈 별칭짓기로 알려진 단일 빈에 여러 가지 이름을 주어야 한다. @Bean 어노테이션의 name 속성은 이 용도를 위해서 문자열 배열을 받아들인다.
1 2 3 4 5 6 7 8 9 10 | Java @Configuration public class AppConfig { @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" }) public DataSource dataSource() { // 인스턴스화하고 설정하고 DataSource 빈을 리턴한다... } } |
4.12.5 자바기반의 설정의 내부동작에 대한 추가적인 내용
다음 예제는 @Bean 어노테이션이 분은 메서드가 두번 호출되는 것을 보여준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | Java @Configuration public class AppConfig { @Bean public ClientService clientService1() { ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; } @Bean public ClientService clientService2() { ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; } @Bean public ClientDao clientDao() { return new ClientDaoImpl(); } } |
clientDao()는 clientService1()에서 한번 clientService2()에서 한번 호출된다. 이 메서드가 ClientDaoImpl의 새로운 인스턴스를 생성하고 리턴하기 때문에 2개의 인스턴스를 (각 서비스마다 하나씩) 기대하는 것이 정상적이다. 이는 명확하게 문제의 소지가 있다. 스프링에서 인스턴스화 된 빈들은 기본적으로 singleton 범위를 가진다. 여기가 마법이 일어나는 곳이다. 모든 @Configuration 클래스는 시작할 때(startup-time) CGLIB과 함께 서브클래스가 된다. 서브클래스에서 자식 메서드는 부모 메서드를 호출하고 새로운 인스턴스를 생성하기 전에 캐싱된 (범위를 가진) 빈이 컨터이너에 있는지 먼저 확인한다.
Note
이 동작은 빈의 범위에 따라 다를 수 있다. 여기서는 싱글톤에 대해서 얘기한 것이다.
이 동작은 빈의 범위에 따라 다를 수 있다. 여기서는 싱글톤에 대해서 얘기한 것이다.
Note
JavaConfig가 동작하도록 하려면 의존성 리스트에 CGLIB jar를 반드시 포함시켜야 한다는 것을 주의해라.
JavaConfig가 동작하도록 하려면 의존성 리스트에 CGLIB jar를 반드시 포함시켜야 한다는 것을 주의해라.
Note
CGLIB은 시작할 때 동적으로 기능을 추가하기 때문에 몇 가지 제약사항이 있다.
CGLIB은 시작할 때 동적으로 기능을 추가하기 때문에 몇 가지 제약사항이 있다.
- Configuration 클래스는 final이 될 수 없다.
- Configuration 클래스에는 아규먼트가 없는 생성자가 있어야 한다.
댓글 없음:
댓글 쓰기