1.9. 어노테이션 기반의 컨테이너 구성
스프링을 구성하기 위해 XML보다 어노테이션이 더 유용합니까?
어노테이션 기반 구성의 도입으로 이 접근 법이 XML보다 “더 나은” 것인 지 여부에 대한 질문이 제기되었습니다. 간략한 대답은 “의존한다” 입니다. 긴 대답은 “각각의 접근 방식에 장단점이 있으며 일반적으로 어떤 전략이 더 적합한 지 결정하는 것은 개발자의 몫”입니다. 어노테이션이 정의된 방식으로 인해, 어노테이션은 선언문에 많은 문맥을 제공하므로 짧고 간결한 구성이 가능합니다. 그러나 XML은 소스 코드를 건드리거나 다시 컴파일 하지 않고도 구성 요소를 연결할 때 탁월합니다. 일부 개발자들은 소스에 가까운 배선을 선호하는 반면, 일부 다른 개발자들은 어노테이션이 달린 클래스는 더 이상 POJO가 아니며 구성이 분산되고 제어하기가 더 어려워 진다고 주장합니다.
(+)
XML Vs. Annotation
시스템 전반에 영향을 주는 메타데이터는 XML로 설정하여 코드로부터 독립적으로 분리되는 것이 바람직하다.
설계 시 확정되는 부분은 Annotation 기반 설정으로 개발의 생산성을 향상 시키는 것이 바람직하다.
POJO (= Plain Old Java Object)
순수 자바 객체 (순수하게 "setter"와 "getter"로 이루어진 Value Object성의 빈)
선택과 상관없이 스프링은 두 스타일을 모두 수용할 수 있으며 함께 혼합할 수도 있습니다. Java Config 옵션을 통해 스프링은 대상 구성 요소 소스 코드를 건드리지 않고 어노테이션을 비 침습적인 방법으로 사용할 수 있으며 툴링 측면에서 모든 구성 스타일이 Spring Tool Suite에서 지원된다는 점을 지적할 가치가 있습니다.
(+)
스프링의 Java Config 기능
XML이 아닌 자바 코드를 이용해서 컨테이너를 설정할 수 있는 기능.
Ex. @Configuration, @Bean
Cf. https://www.slideshare.net/arawnkr/spring-camp-2013-java-configuration
XML 설정의 대안은 어노테이션 기반 구성에 의해 제공됩니다. 이 구성은 꺽쇠 괄호 선언 대신 구성 요소를 연결하기 위해 바이트 코드 메타 데이터를 사용합니다. XML을 사용하여 Bean 배선을 설명하는 대신 개발자는 관련 클래스나 메소드 또는 필드 선언에 대한 어노테이션을 사용하여 구성을 구성 요소 클래스 자체로 이동합니다. 예제 RequiredAnnotationBeanPostProcess에서 언급한 것처럼, 어노테이션과 함께 BeanPostProcessor를 사용하는 것은 스프링 IoC 컨테이너를 확장하는 일반적인 방법입니다. 예를 들어, 스프링 2.0은 @Required 어노테이션으로 필수 속성을 적용할 수 있는 가능성을 소개했습니다. 스프링 2.5는 스프링의 의존성 삽입을 유도하는 일반적인 접근 방식을 따르는 것을 가능하게 했습니다. 본질적으로 @Autowired 어노테이션은 Autowiring Collaborators에서 설명한 것과 동일한 기능을 제공하지만, 보다 세분화된 제어와 폭 넓은 적용 가능성을 제공합니다. 스프링 2.5는 또한 @PostConstruct 및 @PreDestroy와 같은 JSR-250 어노테이션에 대한 지원을 추가했습니다. 스프링 3.0은 @Inject와 @Named와 같은 javax.inject 패키지에 포함된 JSR-330 (Dependency Injection for Java) 어노테이션을 지원했습니다. 이러한 어노테이션에 대한 자세한 내용은 관련 섹션에서 확인할 수 있습니다.
(+)
Annotation : 자바 소스 코드에 추가하여 사용하는 메타 데이터의 일종.
컴파일러에 의해 생성되는 .class 파일에 포함되며 그 .class 파일을 통해 읽혀진다. (클래스들과 마찬가지로 바이트 코드 파일로 컴파일 및 저장되기 때문에 일반 자바 객체와 마찬가지로 처리될 수 있음)
항상 그렇듯이, 개별 Bean 정의로 어노테이션을 등록할 수 있지만, 다음 태그를 XML 기반 스프링 구성에 포함하여 암시적으로 등록할 수도 있습니다. (context 네임스페이스 포함) :
(+)
BeanPostProcessor
개발자가 원하는(또는 컨테이너의 기본 로직을 오버라이트하는) 인스턴스화 및 의존성 처리 로직 등을 구현할 수 있는 메소드를 정의하는 인터페이스.
<context:annotation-config/>의 추가를 통해, 어노테이션 관련하여 아래의 BeanPostProcessor들을 함께 등록하여 어노테이션을 인식하게 한다.
RequireAnnotationBeanPostProcessor -> @Required 처리
AutowiredAnnotationBeanPostProcessor -> @Autowired 처리
CommonAnnotationBeanPostProcessor -> @Resource, @PostConstruct, @PreDestroy 처리
ConfigurationClassBeanPostProcessor -> @Configuration 처리
1.9.1. @Required
@Required
어노테이션은 다음 예제와 같이 빈 속성 setter 메소드에 적용됩니다 :
이 어노테이션은 영향을 받는 빈 프로퍼티가이 설정 시에 빈 정의의 명시적 프로퍼티 값이나 오토와이어링을 통해 채워 져야만 한다는 것을 의미합니다. 만일 영향을 받는 빈 프로퍼티가 채워지지 않는다면 컨테이너가 예외를 던집니다. 이것은 명시적으로 실패할 수 있으므로 NullPointerException
인스턴스를 피할 수 있습니다. Assertion을 Bean 클래스 자체에 넣는 것을 권장합니다. (예: init 메소드) 이렇게 하면 컨테이너 외부에서 클래스를 사용하는 경우에도 필요한 참조 및 값들이 적용됩니다.
1.9.2. Using @Autowired
(+)
@Autowired Vs. @Inject
두 어노테이션 모두 타입에 의해 의존 관계를 자동을 연결해주는 기능을 한다.
@Autowired
스프링에서 등장한 어노테이션. 스프링 이외에서 사용 불가능.
@Inject
JSR에 실려 있는 자바 기반의 어노테이션. 스프링 이외에서도 사용 가능.
다음 예제와 같이 @Autowired
어노테이션을 생성자에 적용할 수 있습니다:
다음 예제와 같이 @Autowired
어노테이션은 "전통적인" setter 메소드에도 적용할 수 있습니다:
다음의 예제 처럼, 임의의 이름 및 복수의 인수를 가지는 메소드에도 어노테이션을 적용할 수 있습니다:
@Autowired
를 필드에 적용할 수 있고 다음 예제와 같이 생성자와 혼합할 수도 있습니다:
다음의 예와 같이, 해당 타입의 배열을 필요로 하는 필드 또는 메소드에 어노테이션을 추가하는 것에 의해 ApplicationContext
로부터 특정한 타입의 빈들을 모두 제공할 수도 있습니다:
다음 예제와 같이 타입이 지정된 콜랙션에도 동일하게 적용됩니다:
(+)
@Order
스프링 4에서 도입한 기능으로, 같은 타입의 빈이 콜렉션에 오토와이어드될 때 순서를 지정하는 기능을 한다. (낮은 순서가 우선 순위가 높음)
예견된 키 타입이 String
인 동안 타입화된 Map 인스턴스 조차 자동 실행될 수 있습니다. Map 값들은 예상되는 타입의 모든 빈을 포함하며, 키는 다음 예제와 같이 해당하는 빈의 이름을 포함합니다:
기본적으로, 오토와이어링은 사용 가능한 후보 빈이 없다면 실패하게 된다. 기본 동작은 어노테이션이 선언된 메소드, 생성자 및 필드를 Required로 지정되어 있는 것으로 처리합니다. 다음 예제에서 이 동작을 설명된 대로 변경할 수 있습니다:
(+)
@Autowired를 적용한 필드를 반드시 설정할 필요가 없는 경우에는, @Autowired의 "required" 속성 값을 "false"로 지정한다.
이렇게 지정된 경우에는, 해당 타입에 맞는 빈 객체가 존재하지 않는다해도 예외를 발생시키지 않는다.
또는 다음 예제와 같이 Java 8의 java.util.Optional
을 통해 특정 종속성의 불필요한 특성을 표현할 수 있습니다:
(+)
Optional : NULL이 될 수도 있는 객체를 감싸고 있는 일종의 래퍼 클래스.
스프링 프레임워크 5.0에서 @Nullable
어노테이션을 사용할 수도 있습니다. (예: JSR-305의 javax.annotation.Nullable
과 같은 모든 패키지의 모든 종류):
BeanFactory
, ApplicationContext
, Environment
, ResourceLoader
, ApplicationEventPublisher
, and MessageSource
와 같이 잘 알려진 해결 가능한 종속성을 갖는 인터페이스에 @Autowired
를 사용할 수도 있습니다. 이러한 인터페이스와 ConfigurableApplicationContext
또는 ResourcePatternResolver
와 같은 확장 인터페이스는 특별한 설정이 필요 없이 자동으로 해결됩니다. 다음 예제에서는 ApplicationContext
객체를 오토와이어링합니다:
1.9.3. Fine-tuning Annotation-based Autowiring with @Primary
타입에 의한 오토와이어링은 여러 후보로 이어질 수 있으므로, 선택 프로세스를 더 잘 제어할 필요가 있습니다. 이를 수행하는 한 가지 방법이 스프링의 @Primary
어노테이션을 사용하는 것입니다. @Primary
는 여러 빈이 단일 값 종속성에 대해 오토와이어드 될 후보가 될 때 특정 빈에 우선 순위를 부여해야 함을 나타냅니다. 정확하게 하나의 primary 빈이 후보들 사이에 존재하면, 이것이 오토와이어드 된 값이 됩니다.
firstMovieCatalog
를 기본 MovieCatalog
로 정의하는 다음 구성을 고려하십시오:
앞의 구성에서 다음 MovieRecommender
는 firstMovieCatalog
이 자동 주입됩니다:
해당 빈의 정의는 다음과 같습니다:
1.9.4. Fine-tuning Annotation-based Autowiring with Qualifiers
@Primary
는 하나의 기본 후보가 결정될 수 있을 때 여러 개의 인스턴스와 함께 타입에 의한 오토와이어링을 사용하는 효과적인 방법입니다. 선택 프로세스에 대한 더 많은 제어가 필요한 경우 스프링의 @Qualifier
어노테이션을 사용할 수 있습니다. qualifier 값을 특정 인수와 연관시켜 타입 일치 집합을 좁혀 특정 인수가 각 인수에 대해 선택되도록 할 수 있습니다. 가장 단순한 경우, 다음 예제와 같이 일반적인 설명 값이 될 수 있습니다:
다음 예제와 같이 개별 생성자 인수 또는 메서드 매개 변수에 @Qualifier
어노테이션을 지정할 수도 있습니다:
다음 예제는 해당 빈의 정의를 보여줍니다.
"main” qualifier 값을 가진 빈은 동일한 값으로 규정된 생성자 인수와 연결됩니다.
"action" qualifier 값을 가진 빈은 동일한 값으로 규정된 생성자 인수와 연결됩니다.
폴백 일치의 경우(일치하는 값을 찾지 못했을 때를 대비해서) 빈 이름이 기본 규정 자(qualifier) 값으로 간주됩니다. 따라서, 중첩된 qualifier 요소 대신에 id가 “main
”인 빈을 정의해도 동일한 결과를 얻을 수 있습니다. 하지만 이름으로 특정 빈을 참조하는 이 관례를 사용할 수 있더라도 본질적으로 @Autowired
는 추가적인 의미 있는 qualifier를 사용하는 타입 기반의 주입에 대한 것입니다. 즉, 실패했을 때(fallback) 빈 이름을 사용하더라도 qualifier 값은 항상 일치된 타입의 세트 내에서 의미로 제한한다는 의미입니다. 그들은 유일한 빈 id에 대한 참조를 의미론적으로 표현하지 않습니다. 앞의 예제처럼 익명의 빈 정의의 경우에 자동으로 생성되는 빈 id와는 독립적으로 특정 컴포넌트의 특징을 표현하는 main
또는 EMEA
또는 persistent
가 좋은 qualifier 값입니다.
앞서 애기했듯이 qualifier는 Set 같은 타입 있는 컬렉션에도 적용됩니다. 이 경우에 선언된 qualifier에 일치하는 모든 빈이 컬렉션으로 주입됩니다. 이는 qualifier는 유일할 필요가 없다는 의미입니다. 오히려 필터링 기준을 구성합니다. 예를 들어 “action”이라는 동일한 qualifier 값을 가진 여러 개의 MovieCatalog
빈을 정의할 수 있습니다. 이 빈들은 모두 @Qualifier("action")
어노테이션이 붙은 Set로 주입될 것입니다.
(+) @Autowired 및 @Resource 적용 순서
고유의 사용자 정의 qualifier 어노테이션을 만들 수 있습니다. 이렇게 하려면, 다음 예제와 같이 어노테이션을 정의하고 정의 내에서 @Qualifier
어노테이션을 제공하십시오:
(+) 어노테이션 커스터마이징에 사용하는 메타 데이터
@Target : 어디에 선언?
ElementType.TYPE : 클래스, 인터페이스, enum 선언부
ElementType.CONSTRUCTOR : 생성자 선언부
ElementType.LOCAL_VARIABLE : 지역 변수 선언부
ElementType.METHOD : 메소드 선언부
ElementType.PACKAGE : 패키지 선언부
ElementType.PARAMETER : 파라미터 선언부
@Retention : 유지 방식은?
RetentionPolicy.RUNTIME : 컴파일러에 의해 class 파일에 추가되고 런타임 시 VM에서 유지
RetentionPolicy.SOURCE : 컴파일 시 class 파일에 추가되지 않는다.
RetentionPolicy.CLASS : 클래스 안에 애노테이션이 추가되지만 런타임 시 VM에서는 사용되지 않는다.
그런 다음, 다음의 예제와 같이 오토와이어드 된 필드 및 매개 변수에 사용자 지정 qualifier를 제공할 수 있습니다:
다음 단계는 자동 엮어질 후보 빈 정의에 해당 정보를 추가하는 것입니다. <qualifier/>
태그를 <bean/>
태그의 하위 요소로 추가한 다음, 사용자 정의 qualifier 어노테이션과 일치하도록 type
및 value
을 지정할 수 있습니다. 타입은 어노테이션의 패키지를 포함한 전체 클래스 명과 일치합니다. 또는, 이름이 충돌하는 위험이 없다면 ‘짧은’ 클래스 이름을 사용할 수 있습니다. 다음 예제는 두 가지 접근 방법을 보여줍니다:
경우에 따라서는 값 없이 어노테이션을 사용하는 것으로 충분할 수 있습니다. 이것은 어노테이션이 보다 일반적인 목적을 위해 사용되고 여러 개의 다른 타입의 종속성으로 적용될 수 있을 때 유용할 수 있습니다. 예를 들어, 인터넷 연결이 불가능할 때 검색될 수 있는 Offline 카탈로그를 제공할 수 있습니다. 먼저, 다음 예제와 같이 간단한 어노테이션을 정의합니다.
그런 다음, 다음 예제와 같이 어노테이션을 오토와이어드할 필드 또는 속성에 추가하십시오:
이제 빈의 정의는 다음 예와 같이 qualifier 타입만 필요합니다:
간단한 값 속성 대신에 이름이 지정된 속성을 허용하는 qualifier 어노테이션을 정의할 수도 있습니다. 만일 여러 개의 속성 값들이 자동 엮어질 필드나 매개 변수에 지정된다면, 빈 정의는 오토와이어링 대상이라고 생각되는 모든 속성 값과 일치해야 합니다. 예를 들어, 다음 어노테이션 정의를 고려하십시오:
이 경우 Format
은 다음과 같이 정의되는 enum 입니다:
오토와이어링 할 필드는 사용자 정의 qualifier가 어노테이션으로 지정되며 다음 예제와 같이 두 개의 속성(genre
, format
)에 대한 값이 포함됩니다:
마지막으로, 빈 정의는 qualifier 값들에 대한 일치하는 것들을 포함해야 합니다. 이 예제에서는 또한 <qualifier/>
요소 대신 빈 메타 속성이 사용될 수 있음을 보여줍니다. 가능하다면, <qualifier/>
요소와 그것의 속성들이 우선권을 가지지만, 자동 엮음 메커니즘은 qualifier가 존재하지 않는다면 <meta/>
태그 내에서 제공되는 값을 대신 사용하게 됩니다. (마지막 2개의 빈 정의 참조):
1.9.5. Using Generics as Autowiring Qualifiers
@Qualifier
어노테이션 외에도 암시적인 형태의 자격으로 자바 제네릭 타입을 사용할 수 있습니다. 예를 들어, 다음 구성을 가지고 있다고 가정합니다:
(+) 스프링 4.0 부터 자바 제네릭 입을 주입받을 수 있음. (4.0 미만도 가능은 했지만 번거로운 추가 작업이 필요)
(+) stringStor, integerStore 가 해당 빈을 나타내는 qualifier라고 생각하면 됨.
(+) 반환 타입은 Store<T>
앞의 빈이 제네릭 인터페이스(즉, Store<String>
and Store<Integer>
)를 구현한다고 가정하면, 다음 예제와 같이 Store 인터페이스를 호출하고 제네릭이 qualifier로 사용됩니다:
제네릭 qualifier는 리스트, 맵 인스턴스, 배열을 오토와이어링할 때에도 적용됩니다. 다음 예제에서는 제네릭 리스트를 오토와이어링합니다:
1.9.6. Using CustomAutowireConfigurer
(+)
BeanFactoryPostProcessor
: 컨텍스트의 기본 빈 펙토리의 빈 특성 값을 적용하여 응용 프로그램 컨텍스트의 빈 정의를 사용자 정의하여 수정할 수 있도록 하는 인터페이스. (순서 등)
AutowireCandidateResolver
는 오토와이어링 후보를 다음과 같이 결정합니다:
각 빈 정의의
default-autowire-candidates
값 (자바 5 이전 버전)요소에서 사용 가능한 모든
default-autowire-candidates
패턴 (자바 5 이전 버전)@Qualifier
어노테이션과CustomAutowireConfigurer
에 등록된 모든 사용자 지정 어노테이션의 존재 (자바 5 이상의 버전)(+) 자바 5 이전의 버전은 qualifier 어노테이션을 지원하지 않음.
자바 버전에 상관없이 여러 개의 빈이 오토와이어링 후보로 자격이 주어졌을 때, “primary” 후보가 결정되는 방식은 동일합니다 : 후보 중 정확하게 primary 속성이 true로 설정된 빈이 있다면, 이것이 선택됩니다.
1.9.7. Injection with @Resource
스프링은 필드 또는 빈 속성 setter 메소드에서 JSR-250 @Resource
어노테이션을 사용하여 주입을 지원합니다. 이는 Java EE 5 및 6 (예: JSF 1.2 관리 빈 또는 JAX-WS 2.0 엔드 포인트)의 공통 패턴입니다. 스프링은 스프링 관리 객체에 대해서도 이 패턴을 지원합니다
@Resource
는 name 속성을 취합니다. 기본적으로 스프링은 그 값을 주입할 빈 이름으로 해석합니다. 즉, 다음 예에서와 같이 이름 별 의미 체계를 따릅니다:
이름을 명시적으로 지정하지 않으면, 기본 이름이 필드 이름 또는 setter 메소드에서 파생됩니다. 필드의 경우, 필드 이름이 필요합니다. Setter 멧드의 경우, 빈 속성 이름을 사용합니다. 다음 예제에서는 setter 메소드에 삽입된 movieFinder
라는 빈을 갖게 됩니다:
명시적으로 이름을 지정하지 않고 @Autowired
와 비슷한 @Resource
사용의 독점적인 경우, @Resource
는 특정하게 이름 붙여진 빈 대신 일치하는 주요 타입을 찾고 잘 알려진 해결할 수 있는 의존성을 해결합니다. 잘 알려진 해결할 수 있는 의존성은 BeanFactory
,ApplicationContext
, ResourceLoader
, ApplicationEventPublisher,
MessageSource
인터페이스들입니다.
그러므로 다음 예제에서 customerPreferenceDao
필드는 customerPreferenceDao 라는 이름의 빈을 먼저 찾고, 못 찾으면 customerPreferenceDao
타입과 일치하는 주요 타입을 찾습니다:
1.9.8. Using @PostConstruct and @PreDestroy
(+)
PostConstruct And. @PreDestroy
@PostConstruct : 객체(빈)가 생성된 후 별도의 초기화 작업을 위해 실행하는 메소드를 선언한다.
Cf. 초기화 메서드 : 빈 객체가 생성되고 DI 작업까지 마친 다음 실행되는 메서드. (기본적으로 객체의 초기화 작업은 생성자에서 진행하지만, DI를 통해 빈이 주입된 후에 초기화할 작업이 있을 경우, 초기화 메서드를 이용)
@PreDestroy : 스프링 컨테이너에서 객체(빈)를 제거하기 전에 해야 할 작업이 있을 때 실행하는 메소드를 선언한다.
(공통) CommonAnnotationBeanPostProcessor 클래스를 빈으로 등록시키거나 <context:annotation-config/>태그를 사용하면 된다.
Last updated