Loading...
Spring Framework Reference Documentation 7.0.2의 Classpath Scanning and Managed Components의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
이 장의 대부분 예제는 Spring 컨테이너 내에서 각각의 BeanDefinition을 생성하는
설정 메타데이터를 지정하기 위해 XML을 사용합니다. 이전 섹션
(Annotation-based Container Configuration)
은 소스 수준 어노테이션을 통해 많은 설정 메타데이터를 제공하는 방법을
보여줍니다.
그러나 그러한 예제에서도 “base” 빈 정의는 XML 파일에 명시적으로 정의되고, 어노테이션은 의존성 주입만을 구동합니다.
이 섹션은 클래스패스를 스캐닝하여 후보 컴포넌트를 암시적으로 감지하는 옵션을 설명합니다. 후보 컴포넌트는 필터 기준과 일치하고 컨테이너에 등록된 해당 빈 정의를 가진 클래스입니다. 이는 빈 등록을 수행하기 위해 XML을 사용할 필요를 제거합니다.
대신, 어노테이션(예: @Component), AspectJ 타입
식 또는 사용자 정의 커스텀 필터 기준을 사용하여 어떤 클래스가 컨테이너에
빈 정의를 등록할지 선택할 수 있습니다.
XML 파일 대신 Java를 사용하여 빈을 정의할 수 있습니다. 이러한 기능을 사용하는<br>예제로
@Configuration,@Bean,@Import, 그리고@DependsOn어노테이션을<br>살펴보십시오.
@Component and Further Stereotype Annotations@Repository 어노테이션은 리포지토리(Data Access Object 또는 DAO라고도 함)의
역할이나 스테레오타입 을 수행하는 모든 클래스에 대한 마커입니다. 이 마커의
사용 용도 중 하나는 Exception Translation
에서 설명한 것처럼 예외를 자동으로 변환하는 것입니다.
Spring은 추가 스테레오타입 어노테이션인 @Component, @Service, @Controller를
제공합니다. @Component는 어떤 Spring-managed 컴포넌트에도 적용되는 일반적인
스테레오타입입니다. @Repository, @Service, @Controller는 보다 구체적인 사용
사례(각각 persistence, 서비스, presentation 레이어)에 대한 @Component의
특수화입니다.
따라서 컴포넌트 클래스에 @Component를 어노테이션으로
사용할 수 있지만, 대신 @Repository, @Service, 또는 @Controller로
어노테이션을 지정하면, 해당 클래스는 도구에 의한 처리나 aspect와의 연관에 더
적합하게 됩니다. 예를 들어, 이러한 스테레오타입 어노테이션은 포인트컷의 이상적인
대상이 됩니다.
@Repository, @Service, @Controller는 또한 Spring Framework의
향후 릴리스에서 추가적인 시맨틱을 가질 수 있습니다. 따라서 서비스 레이어에서
@Component와 @Service 중에서 선택해야 한다면, @Service가 명백히 더 나은
선택입니다. 마찬가지로, 앞에서 언급했듯이 @Repository는 persistence 레이어에서
자동 예외 변환을 위한 마커로 이미 지원됩니다.
Spring이 제공하는 많은 어노테이션은 사용자 코드에서 메타-어노테이션으로 사용할 수
있습니다. 메타-어노테이션은 다른 어노테이션에 적용될 수 있는 어노테이션입니다. 예를
들어, 앞에서 언급한 @Service
(earlier)
어노테이션은 다음 예제에서 보듯이 @Component로 메타-어노테이션되어 있습니다:
1@Target(ElementType.TYPE) 2@Retention(RetentionPolicy.RUNTIME) 3@Documented 4@Component // (1) 5public @interface Service { 6 7 // ... 8}
| 1 | @Component 메타-어노테이션은 @Service가 @Component와 동일한 방식으로 처리되도록 합니다. |
1@Target(AnnotationTarget.TYPE) 2@Retention(AnnotationRetention.RUNTIME) 3@MustBeDocumented 4@Component // (1) 5annotation class Service { 6 7 // ... 8}
| 1 | @Component 메타-어노테이션은 @Service가 @Component와 동일한 방식으로 처리되도록 합니다. |
또한 메타-어노테이션을 결합하여 “composed 어노테이션”을 생성할 수도 있습니다. 예를
들어, Spring MVC의 @RestController 어노테이션은 @Controller와
@ResponseBody로 구성됩니다.
또한 composed 어노테이션은 선택적으로 메타-어노테이션의 속성을 재선언하여
커스터마이징을 허용할 수 있습니다. 이는 메타-어노테이션의 속성 중 일부만
노출하려는 경우 특히 유용할 수 있습니다. 예를 들어, Spring의 @SessionScope
어노테이션은 스코프 이름을 session으로 고정하지만 proxyMode의 커스터마이징은
여전히 허용합니다.
다음 listing은 @SessionScope 어노테이션의 정의를 보여줍니다:
1@Target({ElementType.TYPE, ElementType.METHOD}) 2@Retention(RetentionPolicy.RUNTIME) 3@Documented 4@Scope(WebApplicationContext.SCOPE_SESSION) 5public @interface SessionScope { 6 7 /** 8 * Alias for {@link Scope#proxyMode}. 9 * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}. 10 */ 11 @AliasFor(annotation = Scope.class) 12 ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; 13 14}
1@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) 2@Retention(AnnotationRetention.RUNTIME) 3@MustBeDocumented 4@Scope(WebApplicationContext.SCOPE_SESSION) 5annotation class SessionScope( 6 @get:AliasFor(annotation = Scope::class) 7 val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS 8)
그런 다음 다음과 같이 proxyMode를 선언하지 않고 @SessionScope를 사용할 수
있습니다:
1@Service 2@SessionScope 3public class SessionScopedService { 4 // ... 5}
1@Service 2@SessionScope 3class SessionScopedService { 4 // ... 5}
다음 예제에서 보듯이 proxyMode의 값을 오버라이드할 수도 있습니다:
1@Service 2@SessionScope(proxyMode = ScopedProxyMode.INTERFACES) 3public class SessionScopedUserService implements UserService { 4 // ... 5}
1@Service 2@SessionScope(proxyMode = ScopedProxyMode.INTERFACES) 3class SessionScopedUserService : UserService { 4 // ... 5}
자세한 내용은 Spring Annotation Programming Model wiki 페이지를 참조하십시오.
Spring은 스테레오타입이 지정된 클래스를 자동으로 감지하고 해당하는 BeanDefinition
인스턴스를 ApplicationContext에 등록할 수 있습니다. 예를 들어, 다음 두 클래스는
이러한 자동 감지의 대상이 됩니다:
1@Service 2public class SimpleMovieLister { 3 4 private final MovieFinder movieFinder; 5 6 public SimpleMovieLister(MovieFinder movieFinder) { 7 this.movieFinder = movieFinder; 8 } 9}
1@Service 2class SimpleMovieLister(private val movieFinder: MovieFinder)
1@Repository 2public class JpaMovieFinder implements MovieFinder { 3 // implementation elided for clarity 4}
1@Repository 2class JpaMovieFinder : MovieFinder { 3 // implementation elided for clarity 4}
이러한 클래스를 자동 감지하고 해당 빈을 등록하려면 @Configuration 클래스에
@ComponentScan을 추가해야 하며, 여기서 basePackages 속성은 두 클래스의 공통
부모 패키지로 설정됩니다. 또는 각 클래스의 부모 패키지를 포함하는 comma, semicolon,
또는 space로 구분된 목록을 지정할 수도 있습니다.
1@Configuration 2@ComponentScan(basePackages = "org.example") 3public class AppConfig { 4 // ... 5}
1@Configuration 2@ComponentScan(basePackages = ["org.example"]) 3class AppConfig { 4 // ... 5}
간결함을 위해, 앞의 예제는 어노테이션의 암시적
value속성을 대신 사용할 수<br>있습니다:@ComponentScan("org.example")
다음 예제는 XML 설정을 사용합니다:
1<?xml version="1.0" encoding="UTF-8"?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 https://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context 8 https://www.springframework.org/schema/context/spring-context.xsd"> 9 10 <context:component-scan base-package="org.example"/> 11 12</beans>
<context:component-scan>의 사용은<context:annotation-config>의 기능을 암시적으로<br>활성화합니다.<context:component-scan>을 사용할 때는 일반적으로<br><context:annotation-config>요소를 포함할 필요가 없습니다.
클래스패스 패키지의 스캐닝에는 클래스패스에 해당 디렉터리 엔트리가 존재해야 합니다.<br>Ant로 JAR를 빌드할 때 JAR 태스크의 files-only 스위치를 활성화하지 않도록 하십시오.<br>또한 일부 환경에서는 보안 정책에 따라 클래스패스 디렉터리가 노출되지 않을 수<br>있습니다. 예를 들어, JDK 1.7.0_45 이상에서 standalone 앱의 경우(manifest에서<br>'Trusted-Library' 설정이 필요합니다. 자세한 내용은<br>stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources를<br>참조하십시오). 모듈 경로(Java 모듈 시스템)에서는 Spring의 클래스패스 스캐닝이<br>일반적으로 예상대로 동작합니다. 그러나 컴포넌트 클래스가
module-info디스크립터에서<br>export되고 있는지 확인하십시오. Spring이 클래스의 non-public 멤버를 호출해야 한다면,<br>module-info디스크립터에서exports선언 대신opens선언을 사용하여 해당 멤버가<br>'opened' 되었는지 확인해야 합니다.
또한 AutowiredAnnotationBeanPostProcessor와
CommonAnnotationBeanPostProcessor는 <context:component-scan> 요소를 사용할 때
암시적으로 포함됩니다. 이는 두 컴포넌트가 XML로 제공된 빈 설정
메타데이터 없이 자동 감지되고 서로 와이어된다는 것을 의미합니다.
annotation-config속성에false값을 포함하여<br>AutowiredAnnotationBeanPostProcessor와CommonAnnotationBeanPostProcessor의<br>등록을 비활성화할 수 있습니다.
@ComponentScan의 basePackages 및 value 속성은 ${…} 프로퍼티 플레이스홀더를
지원하며, 이는 Environment를 기준으로 해석되고 "org.example.**"와 같은
Ant 스타일 패키지 패턴도 지원합니다.
또한 여러 패키지나 패턴을 별도로 또는 하나의 문자열 내에 지정할 수 있습니다.
예를 들어 {"org.example.config", "org.example.service.**"} 또는
"org.example.config, org.example.service.**"와 같이 지정할 수 있습니다.
다음 예제는 @ComponentScan의 암시적 value 속성에 대해
app.scan.packages 프로퍼티 플레이스홀더를 지정합니다.
1@Configuration 2@ComponentScan("${app.scan.packages}") // (1) 3public class AppConfig { 4 // ... 5}
| 1 | Environment에 대해 해석될 app.scan.packages 프로퍼티 플레이스홀더 |
1@Configuration 2@ComponentScan(["${app.scan.packages}"]) // (1) 3class AppConfig { 4 // ... 5}
| 1 | Environment에 대해 해석될 app.scan.packages 프로퍼티 플레이스홀더 |
다음 listing은 app.scan.packages 프로퍼티를 정의하는 프로퍼티 파일을 나타냅니다.
앞의 예제에서는 이 프로퍼티 파일이 Environment에 등록되어 있다고 가정합니다.
예를 들어 @PropertySource 또는 유사한 메커니즘을 통해 등록되었을 수 있습니다.
1app.scan.packages=org.example.config, org.example.service.**
기본적으로 @Component, @Repository, @Service, @Controller, @Configuration 또는
자체가 @Component로 어노테이션된 커스텀 어노테이션이 지정된 클래스만이 후보
컴포넌트로 감지됩니다. 그러나 커스텀 필터를 적용하여 이러한 동작을 수정하고
확장할 수 있습니다. 이 필터를 @ComponentScan 어노테이션의 includeFilters 또는
excludeFilters 속성(또는 XML 설정에서 <context:component-scan> 요소의
자식 요소인 <context:include-filter /> 또는 <context:exclude-filter />)로
추가하십시오.
각 필터 요소는 type과 expression 속성이 필요합니다.
다음 표는 필터링 옵션을 설명합니다:
| Filter Type | Example Expression | Description |
|---|---|---|
| annotation (default) | org.example.SomeAnnotation | 대상 컴포넌트의 타입 레벨에 present 또는 meta-present 되어야 하는 어노테이션입니다. |
| assignable | org.example.SomeClass | 대상 컴포넌트가 assignable(extend 또는 implement)되어야 하는 클래스(또는 인터페이스)입니다. |
| aspectj | org.example..*Service+ | 대상 컴포넌트가 일치해야 하는 AspectJ 타입 식입니다. |
| regex | org.example.Default.* | 대상 컴포넌트의 클래스 이름과 일치해야 하는 정규식 표현식입니다. |
| custom | org.example.MyTypeFilter | org.springframework.core.type.TypeFilter 인터페이스의 커스텀 구현입니다. |
Table 1. Filter Types
다음 예제는 모든 @Repository 어노테이션을 제외하고 대신 “Stub” 리포지토리를
포함하는 @ComponentScan 설정을 보여줍니다:
1@Configuration 2@ComponentScan(basePackages = "org.example", 3 includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"), 4 excludeFilters = @Filter(Repository.class)) 5public class AppConfig { 6 // ... 7}
1@Configuration 2@ComponentScan(basePackages = ["org.example"], 3 includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])], 4 excludeFilters = [Filter(Repository::class)]) 5class AppConfig { 6 // ... 7}
다음 listing은 동등한 XML을 보여줍니다:
1<beans> 2 <context:component-scan base-package="org.example"> 3 <context:include-filter type="regex" 4 expression=".*Stub.*Repository"/> 5 <context:exclude-filter type="annotation" 6 expression="org.springframework.stereotype.Repository"/> 7 </context:component-scan> 8</beans>
어노테이션에서
useDefaultFilters=false를 설정하거나<br><component-scan/>요소의 속성으로use-default-filters="false"를 제공하여<br>기본 필터를 비활성화할 수도 있습니다. 이는@Component,@Repository,<br>@Service,@Controller,@RestController, 또는@Configuration으로<br>어노테이션되거나 메타-어노테이션된 클래스의 자동 감지를 효과적으로 비활성화합니다.
컴포넌트가 스캐닝 과정의 일부로 자동 감지되면, 해당 빈 이름은 스캐너에
알려진 BeanNameGenerator 전략에 의해 생성됩니다.
기본적으로 AnnotationBeanNameGenerator가 사용됩니다. Spring
stereotype annotations
의 경우 어노테이션의 value 속성을 통해 이름을 제공하면 해당 이름이
빈 정의의 이름으로 사용됩니다. 이 규칙은 Spring 스테레오타입 어노테이션 대신
@jakarta.inject.Named 어노테이션을 사용할 때도 적용됩니다.
Spring Framework 6.1부터 빈 이름을 지정하는 데 사용되는 어노테이션 속성의
이름은 더 이상 value일 필요가 없습니다. 커스텀 스테레오타입 어노테이션은 다른 이름
(예: name)을 가진 속성을 선언하고 해당 속성을
@AliasFor(annotation = Component.class, attribute = "value")로 어노테이션할 수
있습니다. 구체적인 예는 ControllerAdvice#name()의 소스 코드 선언을
참조하십시오.
Spring Framework 6.1부터 컨벤션 기반 스테레오타입 이름에 대한 지원은 deprecated되었으며<br>향후 프레임워크 버전에서 제거될 예정입니다. 따라서 커스텀 스테레오타입 어노테이션은<br>
@Component의value속성에 대한 명시적인 alias를 선언하기 위해@AliasFor를<br>사용해야 합니다. 구체적인 예는Repository#value()와ControllerAdvice#name()의<br>소스 코드 선언을 참조하십시오.
이러한 어노테이션에서 명시적인 빈 이름을 도출할 수 없거나(예: 커스텀 필터에 의해
발견된 것과 같은) 다른 감지된 컴포넌트의 경우, 기본 빈 이름 생성기는
non-qualified 클래스 이름의 첫 글자를 소문자로 바꾼 이름을 반환합니다. 예를 들어
다음 컴포넌트 클래스가 감지되면 이름은 myMovieLister와 movieFinderImpl이 됩니다.
1@Service("myMovieLister") 2public class SimpleMovieLister { 3 // ... 4}
1@Service("myMovieLister") 2class SimpleMovieLister { 3 // ... 4}
1@Repository 2public class MovieFinderImpl implements MovieFinder { 3 // ... 4}
1@Repository 2class MovieFinderImpl : MovieFinder { 3 // ... 4}
기본 빈 네이밍 전략에 의존하고 싶지 않다면, 커스텀 빈 네이밍 전략을
제공할 수 있습니다. 먼저
BeanNameGenerator
인터페이스를 구현하고, 기본 무인자 생성자를 포함해야 합니다. 그런 다음 다음
어노테이션과 빈 정의 예제에서 보듯이 스캐너를 설정할 때 완전 수식 클래스
이름을 제공하십시오.
동일한 non-qualified 클래스 이름을 가진 여러 자동 감지된 컴포넌트(즉, 서로 다른<br>패키지에 존재하지만 이름이 동일한 클래스)로 인해 네이밍 충돌이 발생하는 경우,<br>생성된 빈 이름에 대해 완전 수식 클래스 이름을 기본으로 사용하는<br>
BeanNameGenerator를 구성해야 할 수 있습니다.<br>org.springframework.context.annotation패키지에 위치한<br>FullyQualifiedAnnotationBeanNameGenerator는 이러한 목적에 사용할 수 있습니다.
1@Configuration 2@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class) 3public class AppConfig { 4 // ... 5}
1@Configuration 2@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class) 3class AppConfig { 4 // ... 5}
1<beans> 2 <context:component-scan base-package="org.example" 3 name-generator="org.example.MyNameGenerator" /> 4</beans>
일반적인 규칙으로, 다른 컴포넌트가 명시적으로 참조할 수 있는 경우 어노테이션으로 이름을 지정하는 것을 고려하십시오. 반면, 컨테이너가 와이어링을 책임지는 경우에는 자동 생성된 이름으로 충분합니다.
일반적인 Spring-managed 컴포넌트와 마찬가지로 자동 감지된 컴포넌트의 기본이자
가장 일반적인 스코프는 singleton입니다. 그러나 때때로 @Scope 어노테이션으로
지정할 수 있는 다른 스코프가 필요할 수 있습니다. 다음 예제에서 보듯이 어노테이션
내에 스코프 이름을 제공할 수 있습니다:
1@Scope("prototype") 2@Repository 3public class MovieFinderImpl implements MovieFinder { 4 // ... 5}
1@Scope("prototype") 2@Repository 3class MovieFinderImpl : MovieFinder { 4 // ... 5}
@Scope어노테이션은 구체적인 빈 클래스(어노테이션된 컴포넌트의 경우) 또는 팩터리<br>메서드(@Bean메서드의 경우)에 대해서만 인트로스펙트됩니다. XML 빈 정의와<br>달리 빈 정의 상속이라는 개념이 없으며, 메타데이터 관점에서 클래스 레벨의<br>상속 계층 구조는 관련이 없습니다.
Spring 컨텍스트에서 “request” 또는 “session”과 같은 web-specific 스코프에 대한 자세한
내용은
Request, Session, Application, and WebSocket Scopes
를 참조하십시오. 이러한 스코프에 대한 pre-built 어노테이션과 마찬가지로, Spring의
메타-어노테이션 방식을 사용하여 자체 스코핑 어노테이션을 구성할 수도 있습니다.
예를 들어, @Scope("prototype")으로 메타-어노테이션된 커스텀 어노테이션에
커스텀 scoped-proxy 모드를 선언할 수 있습니다.
어노테이션 기반 접근 방식에 의존하지 않고 스코프 해석을 위한 커스텀 전략을<br>제공하려면<br>
ScopeMetadataResolver<br>인터페이스를 구현할 수 있습니다. 기본 무인자 생성자를 포함해야 합니다. 그런 다음<br>다음 어노테이션 및 빈 정의 예제에서 보듯이 스캐너를 설정할 때 완전 수식<br>클래스 이름을 제공할 수 있습니다:
1@Configuration 2@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class) 3public class AppConfig { 4 // ... 5}
1@Configuration 2@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class) 3class AppConfig { 4 // ... 5}
1<beans> 2 <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/> 3</beans>
특정 non-singleton 스코프를 사용할 때는 스코프가 있는 객체에 대한 프록시를 생성해야 할 수도
있습니다. 그 이유는
Scoped Beans as Dependencies
에서 설명합니다. 이 목적을 위해 component-scan 요소에는 scoped-proxy 속성이
제공됩니다. 가능한 세 가지 값은 no, interfaces, targetClass입니다.
예를 들어 다음 설정은 표준 JDK 동적 프록시를 생성합니다:
1@Configuration 2@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES) 3public class AppConfig { 4 // ... 5}
1@Configuration 2@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES) 3class AppConfig { 4 // ... 5}
1<beans> 2 <context:component-scan base-package="org.example" scoped-proxy="interfaces"/> 3</beans>
@Qualifier 어노테이션은
Fine-tuning Annotation-based Autowiring with Qualifiers
에서 설명합니다. 해당 섹션의 예제는 자동 와이어링 후보를 해석할 때 세밀한 제어를
제공하기 위해 @Qualifier 어노테이션과 커스텀 qualifier 어노테이션의 사용을
보여줍니다.
이러한 예제는 XML 빈 정의를 기반으로 했기 때문에 qualifier
메타데이터는 XML에서 bean 요소의 자식 요소인 qualifier 또는 meta를 사용하여
후보 빈 정의에 제공되었습니다. 컴포넌트의 자동 감지를 위해
클래스패스 스캐닝에 의존하는 경우, 후보 클래스에 타입 레벨 어노테이션으로
qualifier 메타데이터를 제공할 수 있습니다. 다음 세 가지 예제는 이 기법을 보여줍니다:
1@Component 2@Qualifier("Action") 3public class ActionMovieCatalog implements MovieCatalog { 4 // ... 5}
1@Component 2@Qualifier("Action") 3class ActionMovieCatalog : MovieCatalog
1@Component 2@Genre("Action") 3public class ActionMovieCatalog implements MovieCatalog { 4 // ... 5}
1@Component 2@Genre("Action") 3class ActionMovieCatalog : MovieCatalog
1@Component 2@Offline 3public class CachingMovieCatalog implements MovieCatalog { 4 // ... 5}
1@Component 2@Offline 3class CachingMovieCatalog : MovieCatalog { 4 // ... 5}
대부분의 어노테이션 기반 대안과 마찬가지로, 어노테이션 메타데이터는 클래스 정의 자체에<br>묶여 있는 반면 XML을 사용하면 동일한 타입의 여러 빈이 qualifier 메타데이터의<br>variation을 제공할 수 있다는 점을 명심하십시오. 이는 메타데이터가 클래스 단위가 아니라<br>인스턴스 단위로 제공되기 때문입니다.
Spring 컴포넌트는 컨테이너에 빈 정의 메타데이터를 제공할 수도 있습니다.
이는 @Configuration으로 어노테이션된 클래스 내에서 빈 메타데이터를 정의하는 데
사용되는 것과 동일한 @Bean 어노테이션으로 수행할 수 있습니다. 다음 예제는 그
방법을 보여줍니다:
1@Component 2public class FactoryMethodComponent { 3 4 @Bean 5 @Qualifier("public") 6 public TestBean publicInstance() { 7 return new TestBean("publicInstance"); 8 } 9 10 public void doWork() { 11 // Component method implementation omitted 12 } 13}
1@Component 2class FactoryMethodComponent { 3 4 @Bean 5 @Qualifier("public") 6 fun publicInstance() = TestBean("publicInstance") 7 8 fun doWork() { 9 // Component method implementation omitted 10 } 11}
앞의 클래스는 doWork() 메서드에 애플리케이션 특화 코드를 가진 Spring 컴포넌트입니다.
그러나 이 클래스는 또한 publicInstance() 메서드를 참조하는 팩터리 메서드를 가진
빈 정의를 제공합니다. @Bean 어노테이션은 팩터리 메서드와
@Qualifier 어노테이션을 통한 qualifier 값과 같은 다른 빈 정의 프로퍼티를
식별합니다. 지정할 수 있는 다른 메서드 레벨 어노테이션은 @Scope, @Lazy, 그리고
커스텀 qualifier 어노테이션입니다.
컴포넌트 초기화 역할 외에도
@Autowired또는@Inject로 표시된 injection<br>포인트에@Lazy어노테이션을 배치할 수도 있습니다. 이 컨텍스트에서 이는 lazy-resolution<br>프록시의 주입을 유도합니다. 그러나 이러한 프록시 접근 방식은 다소 제한적입니다.<br>특히 optional 의존성과 결합된 정교한 지연 상호 작용의 경우에는<br>ObjectProvider<MyTargetBean>을 사용하는 것이 더 좋습니다.
앞에서 설명했듯이 자동 와이어링된 필드와 메서드가 지원되며, @Bean 메서드의 자동 와이어링도
추가로 지원됩니다. 다음 예제는 그 방법을 보여줍니다:
1@Component 2public class FactoryMethodComponent { 3 4 private static int i; 5 6 @Bean 7 @Qualifier("public") 8 public TestBean publicInstance() { 9 return new TestBean("publicInstance"); 10 } 11 12 // use of a custom qualifier and autowiring of method parameters 13 @Bean 14 protected TestBean protectedInstance( 15 @Qualifier("public") TestBean spouse, 16 @Value("#{privateInstance.age}") String country) { 17 TestBean tb = new TestBean("protectedInstance", 1); 18 tb.setSpouse(spouse); 19 tb.setCountry(country); 20 return tb; 21 } 22 23 @Bean 24 private TestBean privateInstance() { 25 return new TestBean("privateInstance", i++); 26 } 27 28 @Bean 29 @RequestScope 30 public TestBean requestScopedInstance() { 31 return new TestBean("requestScopedInstance", 3); 32 } 33}
1@Component 2class FactoryMethodComponent { 3 4 companion object { 5 private var i: Int = 0 6 } 7 8 @Bean 9 @Qualifier("public") 10 fun publicInstance() = TestBean("publicInstance") 11 12 // use of a custom qualifier and autowiring of method parameters 13 @Bean 14 protected fun protectedInstance( 15 @Qualifier("public") spouse: TestBean, 16 @Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply { 17 this.spouse = spouse 18 this.country = country 19 } 20 21 @Bean 22 private fun privateInstance() = TestBean("privateInstance", i++) 23 24 @Bean 25 @RequestScope 26 fun requestScopedInstance() = TestBean("requestScopedInstance", 3) 27}
이 예제는 String 메서드 파라미터 country를 privateInstance라는 이름의 다른
빈의 age 프로퍼티 값으로 자동 와이어링합니다. Spring Expression Language 요소는
#{ <expression> } 표기법을 통해 프로퍼티 값을 정의합니다. @Value 어노테이션의
경우 expression 텍스트를 해석할 때 빈 이름을 찾도록 미리 구성된 expression 리졸버가
있습니다.
Spring Framework 4.3부터 현재 빈의 생성을 유발하는 요청 injection 포인트에 접근하기
위해 InjectionPoint 타입(또는 그보다 구체적인 서브클래스인 DependencyDescriptor)의
팩터리 메서드 파라미터를 선언할 수도 있습니다. 이는 기존 인스턴스의 주입이
아니라 빈 인스턴스의 실제 생성에만 적용된다는 점에 유의하십시오.
결과적으로 이
기능은 프로토타입 스코프의 빈에 가장 적합합니다. 다른 스코프의 경우 팩터리 메서드는
주어진 스코프에서 새 빈 인스턴스의 생성을 유발한 injection 포인트(예: lazy singleton
빈의 생성을 유발한 의존성)만을 보게 됩니다. 이러한 시나리오에서는 제공된
injection 포인트 메타데이터를 시맨틱을 주의하여 사용할 수 있습니다. 다음 예제는
InjectionPoint를 사용하는 방법을 보여줍니다:
1@Component 2public class FactoryMethodComponent { 3 4 @Bean @Scope("prototype") 5 public TestBean prototypeInstance(InjectionPoint injectionPoint) { 6 return new TestBean("prototypeInstance for " + injectionPoint.getMember()); 7 } 8}
1@Component 2class FactoryMethodComponent { 3 4 @Bean 5 @Scope("prototype") 6 fun prototypeInstance(injectionPoint: InjectionPoint) = 7 TestBean("prototypeInstance for ${injectionPoint.member}") 8}
일반 Spring 컴포넌트에서의 @Bean 메서드는 Spring @Configuration 클래스 내에서의
동일한 메서드와 다르게 처리됩니다. 차이점은 @Component 클래스는 메서드와 필드의
호출을 가로채기 위해 CGLIB로 향상되지 않는다는 점입니다. CGLIB 프록시는
@Configuration 클래스의 @Bean 메서드 내에서 메서드나 필드를 호출할 때
협력 객체에 대한 빈 메타데이터 참조를 생성하는 수단입니다.
이러한
메서드는 일반적인 Java 시맨틱으로 호출되지 않고, 다른 빈에 대한 프로그래밍 호출을
통해 @Bean 메서드를 참조하더라도 일반적인 라이프사이클 관리 및 Spring 빈의 프록시를
제공하기 위해 컨테이너를 통해 호출됩니다. 반대로, 일반 @Component 클래스 내의
@Bean 메서드에서 메서드나 필드를 호출하면 특별한 CGLIB 처리나 기타 제약 없이
표준 Java 시맨틱이 적용됩니다.
@Bean메서드를static으로 선언하여 해당 메서드를 포함하는 설정 클래스의<br>인스턴스를 생성하지 않고도 호출할 수 있습니다. 이는 특히BeanFactoryPostProcessor나<br>BeanPostProcessor타입과 같은 post-processor 빈을 정의할 때 유용합니다. 이러한<br>빈은 컨테이너 라이프사이클 초기에 초기화되며, 이 시점에 설정의 다른 부분이<br>트리거되는 것을 피해야 합니다. static@Bean메서드에 대한 호출은 이 섹션의 앞에서<br>설명한 것처럼@Configuration클래스 내에서조차 컨테이너에 의해 가로채지 않습니다.<br>이는 기술적인 제한 사항 때문입니다. CGLIB 서브클래싱은 non-static 메서드만 override할<br>수 있습니다. 결과적으로 다른@Bean메서드에 대한 직접 호출은 표준 Java 시맨틱을<br>가져오며, 팩터리 메서드 자체에서 바로 반환되는 독립 인스턴스를 생성합니다.<br>@Bean메서드의 Java 언어 가시성은 Spring 컨테이너에서 생성되는 빈 정의에<br>즉각적인 영향을 미치지 않습니다. non-@Configuration클래스와 어디에서든 static<br>메서드에 대해 팩터리 메서드를 자유롭게 선언할 수 있습니다. 그러나@Configuration<br>클래스의 일반@Bean메서드는 override 가능해야 합니다. 즉,private또는final로<br>선언되어서는 안 됩니다.@Bean메서드는 주어진 컴포넌트 또는 설정 클래스의<br>base 클래스뿐만 아니라 컴포넌트 또는 설정 클래스가 구현하는 인터페이스에<br>선언된 Java default 메서드에서도 발견됩니다. 이는 Java default 메서드를 통한<br>다중 상속까지 가능하게 하여 복잡한 설정 구조를 구성하는 데 큰 유연성을<br>제공합니다.<br>마지막으로, 하나의 클래스는 동일한 빈에 대해 여러@Bean메서드를 보유할 수<br>있으며, 이는 런타임에 사용 가능한 의존성에 따라 사용할 여러 팩터리 메서드를<br>배치하는 방식입니다. 이는 다른 설정 시나리오에서 “가장 탐욕스러운”<br>생성자나 팩터리 메서드를 선택하는 것과 동일한 알고리즘입니다. 즉, 가장 많은<br>수의 만족 가능한 의존성을 가진 variant가 생성 시점에 선택되며, 이는<br>컨테이너가 여러@Autowired생성자 중에서 선택하는 방식과 유사합니다.
Using @PostConstruct and @PreDestroy
Using JSR 330 Standard Annotations