이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.
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 파일에 다음 의존성을 추가할 수 있다.
1 2 3 4 5 |
<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 |
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 |
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 |
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 |
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 |
<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 |
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } } |
스프링 <beans/> XML에 익숙하다면 앞의 AppConfig 클래스는 다음과 같을 것이다.
1 2 3 |
<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 |
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 |
<beans> <context:component-scan base-package="com.acme"/> </beans> |
위의 예제에서 com.acme 팩키지는 스캔되고 @Component 어노테이션이 붙은 클래스들을 찾고 이러한 클래스를 컨테이너내 스프링 빈 정의로 등록할 것이다. AnnotationConfigApplicationContext에는 같은 컴포넌트 스캔 기능을 하는 scan(String...) 메서드가 있다.
1 2 3 4 5 6 |
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() 할 때 컨테이너내 빈 정의로 처리되고 등록될 것이다.
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 |
<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 |
@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 |
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 |
@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 |
@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 |
@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 |
@Configuration public class AppConfig { private @Autowired DataSource dataSource;
public @Bean AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); }
public @Bean TransferService transferService() { return new TransferService(accountRepository()); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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> |
1 2 3 4 |
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password= |
1 2 3 4 5 |
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가 엄격하게 필요하지 않다.
@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 |
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 |
@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); } } |
1 2 3 4 |
properties-config.xml <beans> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> </beans> |
1 2 3 4 |
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password= |
1 2 3 4 5 |
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 |
@Configuration public class AppConfig {
@Bean public TransferService transferService() { return new TransferServiceImpl(); }
} |
앞의 설정은 다음 스프링 XML과 완전히 똑같다.
1 2 3 |
<beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/> </beans> |
두 선언은 모두 ApplicationContext에서 사용할 수 있는 transferService라는 빈을 만들고 TransferServiceImpl 타입의 인스턴스에 바인딩한다.
1 |
transferService -> com.acme.TransferServiceImpl |
1 2 |
<font style="font-weight: bold;" size="3">4.12.4.2 의존성 주입</font> @Bean들이 다른 빈에 대한 의존성이 있을 때 의존성을 나타내는 것은 하나의 빈 메서드가 다른 메서드를 호출하는 것만큼 간단하다. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@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 |
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 |
@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 |
@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 |
// 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 |
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 |
@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 |
@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 |
@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 |
@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를 반드시 포함시켜야 한다는 것을 주의해라.
'개발 > Spring' 카테고리의 다른 글
IoC 컨테이너 12장 (0) | 2015.08.18 |
---|---|
IoC 컨테이너 10장 (0) | 2015.08.18 |
IoC 컨테이너 9장 (0) | 2015.08.18 |
IoC 컨테이너 8장 (0) | 2015.08.18 |
IoC 컨테이너 7장 (0) | 2015.08.18 |