2024. 11. 14. 15:29ㆍSpring Framework/Spring IoC
이 장의 대부분의 예제에서는 Spring 컨테이너 내에서 각 BeanDefinition을 생성하는 구성 메타데이터를 XML로 지정합니다. 이전 섹션(애너테이션 기반 컨테이너 구성)에서는 소스 레벨 애너테이션을 통해 많은 구성 메타데이터를 제공하는 방법을 설명했습니다. 그러나 이러한 예제에서도 디폴트 빈 정의는 XML 파일에 명시적으로 정의되며, 애너테이션은 오직 의존성 주입만을 제어합니다. 이 섹션에서는 클래스 경로를 스캔하여 후보 컴포넌트를 암시적으로 감지하는 옵션을 설명합니다. 후보 컴포넌트는 필터 기준에 맞는 클래스이며, 해당 빈 정의가 컨테이너에 등록된 클래스입니다. 이를 통해 XML을 사용하여 빈 등록을 수행할 필요가 없어지며, 대신 애너테이션(@Component 등), AspectJ 타입 표현식, 또는 사용자 지정 필터 기준을 사용하여 컨테이너에 등록될 빈 정의가 있는 클래스를 선택할 수 있습니다.
XML 파일 대신 Java를 사용하여 빈을 정의할 수 있습니다. 이러한 기능을 사용하는 방법의 예로는 @Configuration, @Bean, @Import, @DependsOn 애너테이션을 참고하십시오.
@Component and Further Stereotype Annotations
@Repository 애너테이션은 저장소(또는 데이터 접근 객체, DAO) 역할이나 스테레오타입을 충족하는 클래스에 대한 마커입니다. 이 마커의 사용 중 하나는 예외 변환을 자동으로 처리하는 기능이며, 이는 Exception Translation에서 설명됩니다.
Spring은 추가적인 스테레오타입 애너테이션으로 @Component, @Service, @Controller를 제공합니다. @Component는 모든 Spring 관리 컴포넌트에 대한 일반적인 스테레오타입입니다. @Repository, @Service, @Controller는 각각 특정 사용 사례에 맞춘 @Component의 특수화된 형태로, 영속성 계층, 서비스 계층, 표현 계층에 적용됩니다. 따라서 컴포넌트 클래스에 @Component를 사용해도 되지만, 대신 @Repository, @Service, @Controller를 사용하면 클래스가 도구나 애스펙트와의 연관성에서 더욱 적합하게 처리될 수 있습니다. 예를 들어, 이러한 스테레오타입 애너테이션은 포인트컷의 이상적인 대상이 됩니다. @Repository, @Service, @Controller는 향후 Spring Framework의 릴리스에서 추가 의미를 가질 수 있습니다. 따라서 서비스 계층에서 @Component와 @Service 중 선택할 때는 @Service가 명확히 더 좋은 선택입니다. 마찬가지로, 앞서 언급한 것처럼 @Repository는 영속성 계층에서 자동 예외 변환의 마커로 이미 지원됩니다.
Using Meta-annotations and Composed AnnotationsSpring이 제공하는 많은 애너테이션은 코드에서 메타 애너테이션으로 사용할 수 있습니다. 메타 애너테이션은 다른 애너테이션에 적용될 수 있는 애너테이션을 의미합니다. 예를 들어, 앞서 언급한 @Service 애너테이션은 @Component로 메타 애너테이션 처리되어 있습니다. 다음 예시는 이를 보여줍니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {
// ...
}
@Component는 @Service를 @Component와 동일한 방식으로 처리하도록 합니다.메타 애너테이션을 결합하여 "복합(Compose) 애너테이션"을 생성할 수도 있습니다. 예를 들어, Spring MVC의 @RestController 애너테이션은 @Controller와 @ResponseBody로 구성되어 있습니다.
또한, 복합 애너테이션은 메타 애너테이션의 속성을 재선언하여 사용자 정의를 허용합니다. 이는 메타 애너테이션의 속성 중 일부만 노출하려는 경우에 특히 유용합니다. 예를 들어, Spring의 @SessionScope 애너테이션은 스코프 이름을 session으로 고정하면서도 proxyMode를 사용자 정의할 수 있도록 합니다. 다음은 SessionScope 애너테이션의 정의를 보여줍니다:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
이제 proxyMode를 선언하지 않고 @SessionScope를 다음과 같이 사용할 수 있습니다.
@Service
@SessionScope
public class SessionScopedService {
// ...
}
또한, 다음 예시와 같이 proxyMode의 값을 재정의할 수도 있습니다.
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
자세한 내용은 Spring Annotation Programming Model 위키 페이지를 참조하십시오.
Automatically Detecting Classes and Registering Bean Defintions
Spring은 스테레오타입이 지정된 클래스를 자동으로 감지하고, 해당하는 BeanDefinition 인스턴스를 ApplicationContext에 등록할 수 있습니다. 예를 들어, 다음 두 클래스는 이러한 자동 감지에 적합합니다.
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
이러한 클래스를 자동 감지하고 해당 빈을 등록하려면, @Configuration 클래스에 @ComponentScan을 추가해야 하며, basePackages 속성에는 두 클래스의 공통 부모 패키지를 지정합니다. (또는 각 클래스의 부모 패키지를 포함하는 쉼표, 세미콜론 또는 공백으로 구분된 리스트를 지정할 수도 있습니다.)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
간결하게 표현하기 위해, 앞의 예시에서는 애너테이션의 value 속성을 사용할 수 있습니다(예: @ComponentScan("org.example") ).
클래스패스 패키지의 스캔에는 클래스패스에 해당 디렉터리 항목이 필요합니다. Ant로 JAR을 빌드할 때는 JAR 작업의 files-only 옵션을 활성화하지 않도록 주의하세요. 또한, 일부 환경에서는 보안 정책에 따라 클래스패스 디렉터리가 노출되지 않을 수 있습니다. 예를 들어, JDK 1.7.0_45 이상에서 독립형 애플리케이션은 'Trusted-Library' 설정이 매니페스트에 필요합니다 (자세한 내용은 [stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources](https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources) 참고).
모듈 경로(Java Module System)에서는 Spring의 클래스패스 스캔이 일반적으로 예상대로 작동합니다. 그러나 모듈-info 파일의 디스크립터에서 컴포넌트 클래스가 내보내져 있는지 확인해야 합니다. Spring이 클래스의 비공개 멤버를 호출하도록 기대하는 경우, 이를 'opened' 것(opens 선언 사용)이 필요합니다 (즉, module-info 디스크립터에서 exports 선언 대신 opens 선언을 사용해야 합니다).
또한, component-scan 엘리먼트를 사용할 때 AutowiredAnnotationBeanPostProcessor와 CommonAnnotationBeanPostProcessor가 모두 암시적으로 포함됩니다. 이는 두 컴포넌트가 자동으로 감지되고 연결된다는 의미이며, XML에서 빈 구성 메타데이터를 제공하지 않아도 됩니다.
annotation-config 속성을 false로 설정하여 AutowiredAnnotationBeanPostProcessor와 CommonAnnotationBeanPostProcessor의 등록을 비활성화할 수 있습니다.
Using Filters to Customize Scanning디폴트로 @Component, @Repository, @Service, @Controller, @Configuration으로 애너테이션된 클래스나 @Component로 메타 애너테이션된 커스텀 애너테이션만이 후보 컴포넌트로 감지됩니다. 그러나, 커스텀 필터를 적용하여 이 동작을 수정하고 확장할 수 있습니다. 이러한 필터는 @ComponentScan 애너테이션의 includeFilters 또는 excludeFilters 속성으로 추가하거나, XML 구성에서 <context:component-scan> 엘리먼트의 자식 엘리먼트로 <context:include-filter /> 또는 <context:exclude-filter />로 추가할 수 있습니다. 각 필터 엘리먼트에는 type과 expression 속성이 필요합니다. 다음 테이블은 필터링 옵션을 설명합니다.
Table 1. Filter Types
Filter Type | Example Expression | Description |
annotation (default) | org.example.SomeAnnotation | An annotation to be present or meta-present at the type level in target components. |
assignable | org.example.SomeClass | A class (or interface) that the target components are assignable to (extend or implement). |
aspectj | org.example..*Service+ | An AspectJ type expression to be matched by the target components. |
regex | org\.example\.Default.* | A regex expression to be matched by the target components' class names. |
custom | org.example.MyTypeFilter | A custom implementation of the org.springframework.core.type.TypeFilter interface. |
다음 예시는 모든 @Repository 애너테이션을 무시하고 stub 리포지토리들을 사용하는 구성을 보여줍니다.
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
디폴트 필터는 애너테이션에서 useDefaultFilters=false로 설정하여 비활성화할 수 있습니다. 이는 @Component, @Repository, @Service, @Controller, @RestController, @Configuration로 애너테이션되거나 메타 애너테이션된 클래스의 자동 감지를 효과적으로 비활성화합니다.
Defining Bean Metadata within Components
Spring 컴포넌트는 컨테이너에 빈 정의 메타데이터를 추가할 수도 있습니다. @Configuration으로 애너테이션된 클래스 내에서 빈 메타데이터를 정의하는 데 사용하는 동일한 @Bean 애너테이션을 통해 이를 수행할 수 있습니다. 다음 예시는 이를 구현하는 방법을 보여줍니다.
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
위 클래스는 doWork() 메서드에 애플리케이션 특화 코드가 포함된 Spring 컴포넌트입니다. 또한, publicInstance() 메서드를 참조하는 팩토리 메서드로 빈 정의를 추가합니다. @Bean 애너테이션은 팩토리 메서드를 식별하고, @Qualifier 애너테이션을 통해 qualifier 값과 같은 기타 빈 정의 속성을 지정합니다. 지정할 수 있는 다른 메서드 레벨 애너테이션으로는 @Scope, @Lazy, 그리고 커스텀 qualifier 애너테이션이 있습니다.
@Lazy 애너테이션은 컴포넌트 초기화에 사용되는 역할 외에도 @Autowired 또는 @Inject로 표시된 주입 지점에 추가할 수 있습니다. 이 경우 지연-해결 프록시가 주입됩니다. 그러나 이러한 프록시 접근 방식은 다소 제한적입니다. 특히 선택적 의존성과 결합하여 정교한 지연 상호작용이 필요한 경우, ObjectProvider<MyTargetBean>을 사용하는 것이 권장됩니다.
이전에 설명한 바와 같이 @Autowired 필드와 메서드를 지원하며, @Bean 메서드에 대한 자동 주입도 추가로 지원됩니다. 다음 예시는 이를 구현하는 방법을 보여줍니다.
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
예제에서는 String 타입의 메서드 파라미터인 country에 대해 다른 빈 privateInstance의 age 속성 값을 자동 주입합니다. Spring Expression Language 요소는 #{ <expression> } 표기법을 통해 속성 값을 정의합니다. @Value 애너테이션의 경우, 표현식 텍스트를 해결할 때 빈 이름을 찾도록 사전 구성된 표현식 리졸버가 있습니다.
Spring Framework 4.3부터는 현재 빈의 생성을 트리거하는 주입 지점에 접근하기 위해 InjectionPoint 타입(또는 그보다 구체적인 서브클래스인 DependencyDescriptor)의 팩토리 메서드 파라미터를 선언할 수 있습니다. 이 기능은 실제로 빈 인스턴스를 생성할 때만 적용되며, 기존 인스턴스의 주입에는 적용되지 않습니다. 따라서 이 기능은 프로토타입 스코프의 빈에 가장 적합합니다. 다른 스코프의 경우, 팩토리 메서드는 해당 스코프 내에서 새로운 빈 인스턴스 생성을 트리거한 주입 지점만을 보게 됩니다(예: 지연된 싱글톤 빈 생성을 트리거한 의존성). 이러한 시나리오에서 제공된 주입 지점 메타데이터를 의미 있게 사용할 수 있습니다. 다음 예시는 InjectionPoint를 사용하는 방법을 보여줍니다.
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
일반 Spring 컴포넌트 내의 @Bean 메서드는 Spring @Configuration 클래스 내부의 @Bean 메서드와 다르게 처리됩니다. 차이점은 @Component 클래스가 CGLIB으로 보강되지 않아 메서드와 필드 호출을 가로채지 않는다는 점입니다. @Configuration 클래스에서는 CGLIB 프록시를 통해 @Bean 메서드 내에서 메서드나 필드를 호출할 때, 협력 객체에 대한 빈 메타데이터 참조가 생성됩니다. 이러한 메서드 호출은 일반 Java 호출 방식이 아니라 컨테이너를 통해 실행되어 Spring 빈의 라이프사이클 관리 및 프록싱을 제공합니다. 이는 다른 빈에 대한 호출이 프로그램적으로 @Bean 메서드를 통해 이루어질 때도 마찬가지입니다. 반면, 일반 @Component 클래스 내의 @Bean 메서드에서 메서드나 필드를 호출하는 경우에는 특별한 CGLIB 처리 없이 표준 Java 방식으로 처리됩니다.
@Bean 메서드를 static으로 선언하면 해당 메서드가 포함된 구성 클래스를 인스턴스화하지 않고 호출할 수 있습니다. 이는 주로 post-processor 빈(예: BeanFactoryPostProcessor 또는 BeanPostProcessor 타입)을 정의할 때 유용한데, 이러한 빈은 컨테이너 라이프사이클 초기에 초기화되며 다른 구성 요소들을 불필요하게 트리거하지 않도록 해야 합니다.
기술적 한계로 인해 static @Bean 메서드 호출은 @Configuration 클래스 내부에서도 컨테이너에 의해 가로채지지 않습니다. 이는 CGLIB 서브클래싱이 정적 메서드를 재정의할 수 없기 때문입니다. 따라서 다른 @Bean 메서드를 직접 호출하면 표준 Java 방식으로 처리되어, 팩토리 메서드 자체에서 독립적인 인스턴스가 반환됩니다.
@Bean 메서드의 Java 언어 가시성은 Spring 컨테이너의 결과 빈 정의에 즉각적인 영향을 미치지 않습니다. @Configuration이 아닌 클래스에서 자유롭게 팩토리 메서드를 선언할 수 있으며, 정적 메서드도 어디서나 사용할 수 있습니다. 그러나 @Configuration 클래스의 일반 @Bean 메서드는 재정의 가능해야 하므로 private 또는 final로 선언할 수 없습니다.
@Bean 메서드는 주어진 컴포넌트 또는 구성 클래스의 기본 클래스뿐만 아니라, 해당 컴포넌트나 구성 클래스가 구현한 인터페이스에 선언된 Java 8 기본 메서드에서도 발견됩니다. 이는 복잡한 구성 구성을 유연하게 조합할 수 있게 하며, Spring 4.2부터 Java 8 기본 메서드를 통한 다중 상속도 가능합니다.
마지막으로, 단일 클래스에서 동일한 빈을 위한 여러 @Bean 메서드를 보유할 수 있으며, 이는 런타임 시 사용 가능한 의존성에 따라 여러 팩토리 메서드를 사용하도록 구성하는 방식입니다. 이는 다른 구성 시나리오에서 "가장 많은" 의존성을 충족할 수 있는 생성자나 팩토리 메서드를 선택하는 알고리즘과 동일합니다.
Naming Autodetected Components
스캔 과정에서 컴포넌트가 자동으로 감지될 때, 해당 스캐너에 설정된 BeanNameGenerator 전략에 의해 빈 이름이 생성됩니다.
기본적으로 AnnotationBeanNameGenerator가 사용됩니다. Spring 스테레오타입 애너테이션의 경우, 애너테이션의 value 속성을 통해 이름을 지정하면 해당 이름이 빈 정의의 이름으로 사용됩니다. 이 규칙은 Spring 스테레오타입 애너테이션 대신 다음 JSR-250 및 JSR-330 애너테이션을 사용하는 경우에도 적용됩니다: @jakarta.annotation.ManagedBean, @javax.annotation.ManagedBean, @jakarta.inject.Named, @javax.inject.Named.
Spring Framework 6.1부터 빈 이름을 지정하는 애너테이션 속성의 이름이 반드시 value일 필요가 없어졌습니다. 커스텀 스테레오타입 애너테이션은 다른 이름(예: name)의 속성을 선언할 수 있으며, 해당 속성에 @AliasFor(annotation = Component.class, attribute = "value")로 애너테이션을 지정할 수 있습니다. 구체적인 예로는 ControllerAdvice#name()의 소스 코드 선언을 참조하십시오.
Spring Framework 6.1부터는 관례 기반의 스테레오타입 이름 지원이 더는 권장되지 않으며, 향후 프레임워크 버전에서 제거될 예정입니다. 따라서 커스텀 스테레오타입 애너테이션은 @AliasFor를 사용하여 @Component의 value 속성에 대한 명시적인 별칭을 선언해야 합니다. 구체적인 예로는 Repository#value()와 ControllerAdvice#name()의 소스 코드 선언을 참조하십시오.
명시적인 빈 이름을 애너테이션에서 유도할 수 없거나(또는 커스텀 필터로 발견된 컴포넌트 등) 다른 감지된 컴포넌트의 경우, 디폴트 빈 이름 생성기는 대문자화되지 않은 비-정규화 클래스 이름을 반환합니다. 예를 들어, 다음과 같은 컴포넌트 클래스가 감지되면 이름은 myMovieLister와 movieFinderImpl이 됩니다.
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
디폴트 빈 이름 지정 전략에 의존하지 않으려면, 커스텀 빈 이름 지정 전략을 제공할 수 있습니다. 먼저 BeanNameGenerator 인터페이스를 구현하고, 디폴트 생성자를 포함하도록 합니다. 그런 다음, 스캐너를 구성할 때 완전한 클래스 이름을 제공하면 됩니다. 다음 예시 애너테이션과 빈 정의는 이를 보여줍니다.
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
일반적으로 다른 컴포넌트에서 명시적으로 참조할 가능성이 있는 경우, 애너테이션을 사용해 이름을 지정하는 것이 좋습니다. 반면, 컨테이너가 의존성 주입을 담당하는 경우에는 자동 생성된 이름으로 충분합니다.
Providing a Scope for Autodetected Components
일반적으로 Spring이 관리하는 컴포넌트와 마찬가지로, 자동 감지된 컴포넌트의 디폴트 스코프이자 가장 일반적인 스코프는 싱글톤입니다. 그러나 때때로 다른 스코프가 필요할 수 있으며, 이는 @Scope 애너테이션을 통해 지정할 수 있습니다. 다음 예시는 애너테이션 내에 스코프 이름을 지정하는 방법을 보여줍니다.
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope 애너테이션은 구체적인 빈 클래스(애너테이션이 적용된 컴포넌트의 경우)나 팩토리 메서드(@Bean 메서드의 경우)에서만 검사됩니다. XML 빈 정의와 달리, 빈 정의 상속 개념은 없으며, 클래스 수준에서의 상속 계층은 메타데이터 관점에서 무관합니다.
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
특정 비-싱글톤 스코프를 사용할 때, 스코프 객체에 대한 프록시를 생성해야 할 수도 있습니다. 이에 대한 이유는 Scoped Beans as Dependencies에서 설명됩니다.
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
Providing Qualifier Metadata with Annotations
@Qualifier 애너테이션은 Fine-tuning Annotation-based Autowiring with Qualifiers에서 다루고 있습니다. 해당 섹션의 예제에서는 @Qualifier 애너테이션과 커스텀 @Qualifier 애너테이션을 사용하여 자동 주입 후보를 세밀하게 제어하는 방법을 보여줍니다. 이러한 예제는 XML 빈 정의를 기반으로 했기 때문에, XML의 빈 요소 내 qualifier 또는 meta 자식 엘리먼트를 사용하여 후보 빈 정의에 Qualifier 메타데이터를 제공했습니다. 컴포넌트의 자동 감지를 위해 클래스 경로 스캐닝에 의존하는 경우, 후보 클래스에 type-level 애너테이션을 통해 Qualifier 메타데이터를 제공할 수 있습니다. 다음 세 가지 예시는 이 기법을 보여줍니다.
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
대부분의 애너테이션 기반 대안과 마찬가지로, 애너테이션 메타데이터는 클래스 정의 자체에 바인딩된다는 점을 기억해야 합니다. 반면, XML을 사용하면 동일한 타입의 여러 빈이 서로 다른 퀄리파이어 메타데이터를 제공할 수 있습니다. 이는 메타데이터가 클래스 단위가 아닌 인스턴스 단위로 제공되기 때문입니다.
'Spring Framework > Spring IoC' 카테고리의 다른 글
Using JSR 330 Standard Annotations (0) | 2024.11.14 |
---|---|
Container Extension Points (3) | 2024.11.14 |
Customizing the Nature of a Bean (0) | 2024.11.14 |
Composing Java-based Configurations (1) | 2024.11.14 |
Using the @Configuration annotation (0) | 2024.11.14 |