Null-safety
Java에서는 타입 시스템으로 널 안전성을 표현할 수 없지만, Spring 프레임워크는 org.springframework.lang 패키지에서 다음과 같은 어노테이션을 제공하여 API 및 필드의 널 가능성을 선언할 수 있도록 합니다:
- nullable: 특정 매개변수, 반환값 또는 필드가 null일 수 있음을 나타내는 어노테이션입니다.
- nonNull: 특정 매개변수, 반환값 또는 필드가 널이 될 수 없음을 나타내는 어노테이션( @NonNullApi 및 @NonNullFields가 각각 적용되는 매개변수, 반환값 및 필드에는 필요하지 않음).
- @NonNullApi: 매개변수와 반환값에 대한 기본 의미로 null이 아닌 것을 선언하는 패키지 수준의 어노테이션입니다.
- @NonNullFields: 필드에 대한 기본 의미로 널이 아닌 것을 선언하는 패키지 수준의 어노테이션입니다.
Spring 프레임워크 자체는 이러한 어노테이션을 활용하지만, 모든 Spring 기반 Java 프로젝트에서 널 안전 API와 선택적으로 널 안전 필드를 선언하는 데에도 사용할 수 있습니다. 일반 타입 인수, vararg 및 배열 요소에 대한 널 가능성 선언은 아직 지원되지 않습니다. 널 가능성 선언은 부 릴리스를 포함하여 Spring 프레임워크 릴리스 간에 미세 조정될 것으로 예상됩니다. 메서드 본문 내부에서 사용되는 타입의 Null 가능성은 이 기능의 범위를 벗어납니다.
Reactor 및 Spring Data와 같은 다른 일반적인 라이브러리에서는 유사한 널 가능성 배열을 사용하는 널 안전 API를 제공하여 Spring 애플리케이션 개발자에게 일관된 전반적인 경험을 제공합니다 |
Use cases
이러한 어노테이션은 Spring 프레임워크 API의 널 안전성에 대한 명시적 선언을 제공하는 것 외에도 IDE(예: IDEA 또는 Eclipse)에서 런타임에 NullPointerException을 피하기 위해 널 안전성과 관련된 유용한 경고를 제공하는 데 사용할 수 있습니다.
또한 Kotlin은 기본적으로 널 안전성을 지원하므로 Kotlin 프로젝트에서 Spring API를 널 안전성으로 만드는 데 사용됩니다. 자세한 내용은 Kotlin 지원 문서에서 확인할 수 있습니다.
JSR-305 meta-annotations
Spring 어노테이션은 JSR 305어노테이션(휴면 상태이지만 널리 사용되는 JSR)으로 메타 어노테이션됩니다. JSR-305 메타 주석을 사용하면 IDEA 또는 Kotlin과 같은 도구 공급업체에서 Spring 어노테이션에 대한 지원을 하드코딩할 필요 없이 일반적인 방식으로 널 안전 지원을 제공할 수 있습니다.
Spring의 널 안전 API를 활용하기 위해 프로젝트 클래스 경로에 JSR-305 종속성을 추가할 필요는 없으며 권장하지도 않습니다. 코드베이스에서 널 안전 어노테이션을 사용하는 Spring 기반 라이브러리와 같은 프로젝트만 컴파일러 경고를 피하기 위해 compileOnly Gradle 구성 또는 Maven 제공 범위로 com.google.code.findbugs:jsr305:3.0.2를추가해야 합니다.
Data Buffers and Codecs
Java NIO는 ByteBuffer를 제공하지만 많은 라이브러리가 그 위에 자체적인 바이트 버퍼 API를 구축하며, 특히 버퍼를 재사용하거나 직접 버퍼를 사용하는 것이 성능에 유리한 네트워크 작업의 경우 더욱 그렇습니다. 예를 들어 Netty는 ByteBuf 계층 구조를, Undertow는 XNIO를, Jetty는 풀링된 바이트 버퍼와 해제 콜백을 사용하는 등입니다. 스프링 코어 모듈은 다음과 같이 다양한 바이트 버퍼 API로 작업하기 위한 추상화 집합을 제공합니다:
- DataBufferFactory는 데이터 버퍼의 생성을 추상화합니다.
- DataBuffer는풀링될 수 있는 바이트 버퍼를 나타냅니다.
- DataBufferUtils는 데이터 버퍼에 대한 유틸리티 메서드를 제공합니다.
- 코덱은 데이터 버퍼 스트림을 더 높은 수준의 객체로 디코딩하거나 인코딩합니다.
DataBufferFactory
DataBufferFactory는 두 가지 방법 중 하나로 데이터 버퍼를 생성하는 데 사용됩니다:
- 새 데이터 버퍼를 할당하고, 용량을 알고 있는 경우 선택적으로 미리 용량을 지정하는 것이 더 효율적이지만 DataBuffer의 구현은 필요에 따라 커지거나 줄어들 수 있습니다.
- 기존 byte[] 또는 java.nio.ByteBuffer를 래핑하여 주어진 데이터를 DataBuffer 구현으로 꾸미고 할당을 포함하지 않습니다.
웹플럭스 애플리케이션은 데이터버퍼팩토리를 직접 생성하지 않고 클라이언트 측의 서버HttpResponse 또는 클라이언트HttpRequest를 통해 액세스합니다. 팩토리 유형은 기본 클라이언트 또는 서버에 따라 다릅니다(예: Reactor Netty의 경우NettyDataBufferFactory, 기타의 경우 DefaultDataBufferFactory ).
DataBuffer
DataBuffer 인터페이스는 java.nio.ByteBuffer와 유사한 연산을 제공하지만 몇 가지 추가 이점을 제공하며, 그 중 일부는 Netty ByteBuf에서 영감을 받았습니다. 아래는 이점의 일부 목록입니다:
- 독립적인 위치로 읽기 및 쓰기, 즉 읽기와 쓰기를 번갈아 가며 flip() 을 호출할 필요가 없습니다.
- Java.lang.StringBuilder와 마찬가지로 필요에 따라 용량을 확장할 수 있습니다.
- 풀링된 버퍼와 풀링된Data버퍼를 통한 참조 카운팅.
- 버퍼를 java.nio.ByteBuffer, InputStream 또는 OutputStream으로 봅니다.
- 주어진 바이트의 인덱스 또는 마지막 인덱스를 결정합니다.
PooledDataBuffer
바이트버퍼용 자바독에 설명된 대로 바이트 버퍼는 직접 또는 비직접 버퍼일 수 있습니다. 직접 버퍼는 Java 힙 외부에 위치할 수 있으므로 네이티브 I/O 연산을 위해 복사할 필요가 없습니다. 따라서 직접 버퍼는 소켓을 통해 데이터를 수신하고 전송하는 데 특히 유용하지만, 생성 및 해제 비용이 더 많이 들기 때문에 버퍼를 풀링하는 아이디어로 이어집니다.
풀링된데이터버퍼는 바이트 버퍼 풀링에 필수적인 참조 카운팅에 도움을 주는 DataBuffer의 확장입니다. 어떻게 작동하나요? PooledDataBuffer가 할당되면 참조 카운트는 1이 됩니다. retain() 을 호출하면 카운트가 증가하고 release( )를 호출하면 카운트가 감소합니다. 카운트가 0보다 큰 한 버퍼는 해제되지 않도록 보장됩니다. 카운트가 0으로 감소하면 풀링된 버퍼가 해제될 수 있으며, 이는 실제로 버퍼를 위해 예약된 메모리가 메모리 풀로 반환된다는 것을 의미할 수 있습니다.
대부분의 경우 풀린 데이터 버퍼에서 직접 작업하는 대신 풀린 데이터버퍼의 인스턴스인 경우에만 해제 또는 유지를 적용하는 DataBufferUtils의 편리한 메서드를 사용하는 것이 좋습니다.
DataBufferUtils
DataBufferUtils는 데이터 버퍼에서 작업할 수 있는 다양한 유틸리티 메서드를 제공합니다:
- 기본 바이트 버퍼 API에서 지원하는 경우 복합 버퍼를 통해 데이터 버퍼 스트림을 복사본이 없는 단일 버퍼로 조인합니다(예: 복합 버퍼).
- 입력 스트림 또는 NIO 채널을 플럭스<데이터버퍼>로, 그 반대로게시자<데이터버퍼>를 출력 스트림 또는 NIO 채널로 전환합니다.
- 버퍼가풀린데이터버퍼의 인스턴스인 경우 데이터버퍼를 해제하거나 유지하는 메서드.
- 특정 바이트 수까지 바이트 스트림에서 건너뛰거나 가져옵니다.
Codecs
Org.springframework.core.codec 패키지는 다음과 같은 전략 인터페이스를 제공합니다:
- 인코더는 Publisher<T>를 데이터 버퍼 스트림으로 인코딩합니다.
- 디코더는 Publisher<DataBuffer>를 상위 수준의 객체 스트림으로 디코딩합니다.
Spring-core 모듈은 byte[], ByteBuffer, DataBuffer, Resource,String 인코더 및 디코더 구현을 제공합니다. Spring-web 모듈은 Jackson JSON, Jackson Smile, JAXB2, 프로토콜 버퍼 및 기타 인코더와 디코더를 추가합니다. WebFlux 섹션의코덱을 참조하세요.
Using DataBuffer
데이터 버퍼로 작업할 때는 버퍼가 풀링될 수 있으므로 버퍼가 해제되도록 특별한 주의를 기울여야 합니다. 코덱을 사용하여 그 작동 방식을 설명하지만 개념은 더 일반적으로 적용됩니다. 데이터 버퍼를 관리하기 위해 코덱이 내부적으로 어떤 작업을 수행해야 하는지 살펴봅시다.
디코더는 상위 레벨 객체를 생성하기 전에 입력 데이터 버퍼를 마지막으로 읽으므로 다음과 같이 버퍼를 해제해야 합니다:
- 디코더가 단순히 각 입력 버퍼를 읽고 즉시 해제할 준비가 되었다면, DataBufferUtils.release(dataBuffer)를 통해 해제할 수 있습니다.
- 디코더가 내부적으로 데이터 항목을 프리페치하고 캐시하는 플럭스 또는 모노 연산자( 플랫맵, 리듀스 등)를 사용하거나필터, 스킵 등 항목을 제외하는 연산자를 사용하는 경우, 오류나 취소 신호로 인해 버퍼가 해제되기 전에 해당 버퍼가 해제되도록 하기 위해 컴포지션 체인에doOnDiscard(데이터버퍼.클래스, 데이터버퍼유틸스::해제 )를 추가해야 합니다.
- 디코더가 다른 방식으로 하나 이상의 데이터 버퍼를 보유하는 경우, 완전히 읽었을 때 또는 캐시된 데이터 버퍼를 읽고 해제하기 전에 오류 또는 취소 신호가 발생하는 경우 해당 버퍼가 해제되도록 해야 합니다.
데이터 버퍼 스트림을 단일 데이터 버퍼로 집계하는 안전하고 효율적인 방법은 DataBufferUtils#join입니다. 마찬가지로 skipUntilByteCount와takeUntilByteCount도 디코더가 사용할 수 있는 안전한 메서드입니다.
인코더는 다른 사람이 읽어야 하는(그리고 해제해야 하는) 데이터 버퍼를 할당합니다. 따라서 인코더는할 일이 많지 않습니다. 그러나 인코더는 버퍼를 데이터로 채우는 동안 직렬화 오류가 발생하면 데이터 버퍼를 해제해야 합니다. 예를 들어
DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
// serialize and populate buffer..
release = false;
}
finally {
if (release) {
DataBufferUtils.release(buffer);
}
}
return buffer;
인코더의 소비자는 수신한 데이터 버퍼를 해제할 책임이 있습니다. 웹플럭스 애플리케이션에서 인코더의 출력은 HTTP 서버 응답 또는 클라이언트 HTTP 요청에 쓰는 데 사용되며, 이 경우 데이터 버퍼를 해제하는 것은 서버 응답 또는 클라이언트 요청에 쓰는 코드의 책임입니다.
Netty에서 실행할 때버퍼 누수 문제를 해결하기 위한 디버깅 옵션이 있다는 점에 유의하세요.
로깅
Spring 프레임워크 5.0부터 Spring에는 spring-jcl 모듈에 구현된 자체 커먼즈 로깅 브리지가 제공됩니다. 이 구현은 클래스 경로에 Log4j 2.x API와 SLF4J 1.7 API가 있는지 확인하고, 발견된 것 중 첫 번째를 로깅 구현으로 사용하며, Log4j 2.x와 SLF4J를 모두 사용할 수 없는 경우 Java 플랫폼의 핵심 로깅 기능( JUL 또는 java.util.logging이라고도 함)으로 되돌아갑니다.
별도의 브리지 없이 클래스 경로에 Log4j 2.x 또는 Logback(또는 다른 SLF4J 공급자)을 넣고 프레임워크가 선택에 따라 자동으로 적응하도록 하세요. 자세한 내용은Spring Boot 로깅 참조 문서를 참조하세요.
Spring의 커먼즈 로깅 변형은 핵심 프레임워크와 확장 프로그램에서 인프라 로깅 목적으로만 사용하도록 되어 있습니다.
애플리케이션 코드 내에서 로깅이 필요한 경우 Log4j 2.x, SLF4J 또는 JUL을 직접 사용하는 것을 선호합니다.
|
로그 구현은 다음 예제에서와 같이 org.apache.commons.logging.LogFactory를 통해 검색할 수 있습니다.
public class MyBean {
private final Log log = LogFactory.getLog(getClass());
// ...
}
Ahead of Time Optimizations
이 장에서는 Spring의 AOT(Ahead of Time) 최적화에 대해 설명합니다.
통합 테스트와 관련된 AOT 지원에 대해서는 테스트에 대한 사전 시간 지원을 참조하세요.
Introduction to Ahead of Time Optimizations
Spring의 AOT 최적화 지원은 빌드 시점에 ApplicationContext를 검사하고 일반적으로 런타임에 발생하는 결정 및 검색 로직을 적용하기 위한 것입니다. 이렇게 하면 주로 클래스 경로와 환경을 기반으로 고정된 기능 집합에 집중하는 애플리케이션 시작 배열을 보다 간단하고 간단하게 구축할 수 있습니다.
이러한 최적화를 조기에 적용하면 다음과 같은 제약이 따릅니다:
- 클래스경로는 빌드 시점에 고정되고 완전히 정의됩니다.
- 애플리케이션에 정의된 빈은 런타임에 변경할 수 없습니다:
-
- 프로필, 특히 프로필별 구성은 빌드 시점에 선택해야 하며 AOT가 활성화되면 런타임에 자동으로 활성화됩니다.
- 빈의 존재 여부에 영향을 미치는환경 속성(@Conditional)은 빌드 시에만 고려됩니다.
- 인스턴스 공급자(람다 또는 메서드 참조)가 있는 빈 정의는 미리 변환할 수 없습니다.
- 싱글톤으로 등록된 빈(일반적으로ConfigurableListableBeanFactory의 registerSingleton 사용)도 미리 변환할 수 없습니다.
- 인스턴스에 의존할 수 없으므로 빈 유형이 가능한 한 정확한지 확인하세요.
모범 사례 섹션도 참조하세요 |
이러한 제한이 적용되면 빌드 시점에 사전 처리를 수행하고 추가 자산을 생성할 수 있습니다. 일반적으로 Spring AOT 처리 애플리케이션이 생성됩니다:
- Java 소스 코드
- 바이트코드(일반적으로 동적 프록시용)
- 리플렉션, 리소스 로딩, 직렬화 및 JDK 프록시 사용을 위한런타임 힌트
현재 AOT는 Spring 애플리케이션이 GraalVM을 사용하여 네이티브 이미지로 배포될 수 있도록 하는 데 중점을 두고 있습니다. 향후에는 더 많은 JVM 기반 사용 사례를 지원할 계획입니다 |
AOT engine overview
애플리케이션 컨텍스트를 처리하기 위한 AOT 엔진의 진입점은 애플리케이션 컨텍스트AotGenerator입니다. 이 엔진은 최적화할 애플리케이션을 나타내는 GenericApplicationContext와 GenerationContext를 기반으로 다음 단계를 처리합니다:
- AOT 처리를 위해 애플리케이션 컨텍스트를 새로 고칩니다. 기존 새로 고침과 달리 이 버전은 빈 인스턴스가 아닌 빈 정의만 생성합니다.
- 사용 가능한 BeanFactoryInitializationAotProcessor 구현을 호출하고 생성 컨텍스트에 대한 기여를 적용합니다. 예를 들어, 핵심 구현은 모든 후보 빈 정의를 반복하고 BeanFactory의 상태를 복원하는 데 필요한 코드를 생성합니다.
이 프로세스가 완료되면 생성된 코드, 리소스 및 애플리케이션 실행에 필요한 클래스로 GenerationContext가 업데이트됩니다. RuntimeHints 인스턴스는 관련 GraalVM 네이티브 이미지 구성 파일을 생성하는 데에도 사용할 수 있습니다.
ApplicationContextAotGenerator#processAheadOfTime은 AOT 최적화를 통해 컨텍스트를 시작할 수 있는 ApplicationContextInitializer 진입점의 클래스 이름을 반환합니다.
이러한 단계는 아래 섹션에서 자세히 다룹니다.
Refresh for AOT Processing
AOT 처리를 위한 새로 고침은 모든 GenericApplicationContext 구현에서 지원됩니다. 애플리케이션 컨텍스트는 일반적으로 @Configuration 주석이 달린 클래스 형태로 여러 개의 진입점으로 생성됩니다.
기본적인 예시를 살펴보겠습니다:
@Configuration(proxyBeanMethods=false)
@ComponentScan
@Import({DataSourceConfiguration.class, ContainerConfiguration.class})
public class MyApplication {
}
일반 런타임으로 이 애플리케이션을 시작하려면 클래스 경로 검색, 구성 클래스 구문 분석, 빈 인스턴스화 및 수명 주기 콜백 처리 등 여러 단계가 포함됩니다. AOT 처리를 위한 새로 고침은 일반 새로 고침에서 발생하는 일의 일부만 적용합니다. AOT 처리는 다음과 같이 트리거할 수 있습니다:
RuntimeHints hints = new RuntimeHints();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MyApplication.class);
context.refreshForAotProcessing(hints);
// ...
context.close();
이 모드에서는 BeanFactoryPostProcessor 구현이 평소처럼 호출됩니다. 여기에는 구성 클래스 구문 분석, 가져오기 선택기, 클래스 경로 스캔 등이 포함됩니다. 이러한 단계에서는 BeanRegistry에 애플리케이션에 대한 관련 빈 정의가 포함되어 있는지 확인합니다. 빈 정의가 조건(예: @Profile)에 의해 보호되는 경우 이러한 조건이 평가되고 조건과 일치하지 않는 빈 정의는 이 단계에서 폐기됩니다.
사용자 지정 코드에서 프로그래밍 방식으로 추가 빈을 등록해야 하는 경우, 빈 정의만 고려되므로 사용자 지정 등록 코드가 BeanFactory 대신 BeanDefinitionRegistry를 사용하는지 확인하세요. 좋은 패턴은ImportBeanDefinitionRegistrar를 구현하고 구성 클래스 중 하나에서 @Import를 통해 등록하는 것입니다.
이 모드는 실제로 빈 인스턴스를 생성하지 않기 때문에 AOT 처리와 관련된 특정 변형을 제외하고는 BeanPostProcessor 구현이 호출되지 않습니다. 다음과 같습니다:
- MergedBeanDefinitionPostProcessor 구현은 빈 정의를 사후 처리하여 init 및 destroy 메서드와 같은 추가 설정을 추출합니다.
- SmartInstantiationAwareBeanPostProcessor 구현은 필요한 경우 보다 정확한 빈 유형을 결정합니다. 이렇게 하면 런타임에 필요한 프록시를 생성할 수 있습니다.
이 부분이 완료되면 BeanFactory에는 애플리케이션을 실행하는 데 필요한 빈 정의가 포함됩니다. 빈 인스턴스화를 트리거하지는 않지만 AOT 엔진이 런타임에 생성될 빈을 검사할 수 있도록 합니다.
Bean Factory Initialization AOT Contributions
이 단계에 참여하고자 하는 컴포넌트는 BeanFactoryInitializationAotProcessor 인터페이스를 구현할 수 있습니다. 각 구현은 빈 팩토리의 상태에 따라 AOT 기여를 반환할 수 있습니다.
AOT 기여는 특정 동작을 재현하는 생성된 코드를 기여하는 구성 요소입니다. 또한 리플렉션, 리소스 로딩, 직렬화 또는 JDK 프록시의 필요성을 나타내는 RuntimeHints를 기여할 수 있습니다.
BeanFactoryInitializationAotProcessor 구현은 인터페이스의 정규화된 이름과 동일한 키를 사용하여 META-INF/spring/aot.factories에 등록할 수 있습니다.
BeanFactoryInitializationAotProcessor 인터페이스는 빈에서 직접 구현할 수도 있습니다. 이 모드에서 빈은 일반 런타임에 제공하는 기능과 동일한 AOT 기여를 제공합니다. 따라서 이러한 빈은 AOT에 최적화된 컨텍스트에서 자동으로 제외됩니다.
빈이 BeanFactoryInitializationAotProcessor 인터페이스를 구현하는 경우, 빈과 모든 종속성은 AOT 처리 중에 초기화됩니다. 일반적으로 이 인터페이스는 종속성이 제한적이고 빈 팩토리 수명 주기 초기에 이미 초기화된 BeanFactoryPostProcessor와 같은 인프라 빈에서만 구현하는 것이 좋습니다. 이러한 빈이 @Bean 공장 메서드를 사용하여 등록되는 경우, 해당 빈을 둘러싼 @Configuration 클래스가 초기화될 필요가 없도록 메서드가 정적인지 확인해야 합니다.
|
빈 등록 AOT 기여
핵심 BeanFactoryInitializationAotProcessor 구현은 각 후보 BeanDefinition에 필요한 기여를 수집할 책임이 있습니다. 이 작업은 전용 BeanRegistrationAotProcessor를 사용하여 수행합니다.
이 인터페이스는 다음과 같이 사용됩니다:
- 런타임 동작을 대체하기 위해 BeanPostProcessor 빈에 의해 구현됩니다. 예를 들어 AutowiredAnnotationBeanPostProcessor는 이 인터페이스를 구현하여 @Autowired로 주석이 달린 멤버를 삽입하는 코드를 생성합니다.
- 인터페이스의 정규화된 이름과 동일한 키를 사용하여 META-INF/spring/aot.factories에 등록된 유형으로 구현됩니다. 일반적으로 핵심 프레임워크의 특정 기능에 맞게 빈 정의를 조정해야 할 때 사용됩니다.
빈이 BeanRegistrationAotProcessor 인터페이스를 구현하는 경우, 빈과 모든 종속성은 AOT 처리 중에 초기화됩니다. 일반적으로 이 인터페이스는 종속성이 제한적이고 빈 팩토리 수명 주기 초기에 이미 초기화된 BeanFactoryPostProcessor와 같은 인프라 빈에서만 구현하는 것이 좋습니다. 이러한 빈이 @Bean 공장 메서드를 사용하여 등록되는 경우, 메서드가 정적이므로 묶는 @Configuration 클래스가 초기화될 필요가 없는지 확인합니다.
|
등록된 특정 빈을 처리하는 BeanRegistrationAotProcessor가 없는 경우 기본 구현에서 처리합니다. 빈 정의에 대해 생성된 코드를 조정하는 것은 코너 케이스로 제한되어야 하므로 이것이 기본 동작입니다.
이전 예를 들어 DataSourceConfiguration이 다음과 같다고 가정해 보겠습니다:
@Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {
@Bean
public SimpleDataSource dataSource() {
return new SimpleDataSource();
}
}
잘못된 Java 식별자(문자로 시작하지 않거나 공백을 포함하는 등)를 사용하는 백틱이 있는 Kotlin 클래스 이름은 지원되지 않습니다 |
이 클래스에는 특별한 조건이 없으므로 dataSourceConfiguration 및 dataSource가 후보로 식별됩니다. AOT 엔진은 위의 구성 클래스를 다음과 유사한 코드로 변환합니다:
/**
* Bean definitions for {@link DataSourceConfiguration}
*/
@Generated
public class DataSourceConfiguration__BeanDefinitions {
/**
* Get the bean definition for 'dataSourceConfiguration'
*/
public static BeanDefinition getDataSourceConfigurationBeanDefinition() {
Class<?> beanType = DataSourceConfiguration.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.setInstanceSupplier(DataSourceConfiguration::new);
return beanDefinition;
}
/**
* Get the bean instance supplier for 'dataSource'.
*/
private static BeanInstanceSupplier<SimpleDataSource> getDataSourceInstanceSupplier() {
return BeanInstanceSupplier.<SimpleDataSource>forFactoryMethod(DataSourceConfiguration.class, "dataSource")
.withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(DataSourceConfiguration.class).dataSource());
}
/**
* Get the bean definition for 'dataSource'
*/
public static BeanDefinition getDataSourceBeanDefinition() {
Class<?> beanType = SimpleDataSource.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.setInstanceSupplier(getDataSourceInstanceSupplier());
return beanDefinition;
}
}
생성되는 정확한 코드는 빈 정의의 정확한 특성에 따라 다를 수 있습니다 |
생성된 각 클래스에는 정적 분석 도구 등에서 제외해야 하는 경우 이를 식별하기 위해 org.springframework.aot.generate.Generated라는 주석이 추가됩니다 |
위의 생성된 코드는 @Configuration 클래스와 동등한 빈 정의를 생성하지만 가능한 경우 리플렉션을 사용하지 않고 직접 생성합니다. dataSourceConfiguration에 대한 빈 정의와 dataSourceBean에 대한 빈 정의가 있습니다. 데이터소스 인스턴스가 필요한 경우 BeanInstanceSupplier가 호출됩니다. 이 공급자는 dataSourceConfiguration 빈에서 dataSource( ) 메서드를 호출합니다.
Running with AOT optimizations
AOT는 Spring 애플리케이션을 네이티브 실행 파일로 변환하기 위한 필수 단계이므로 이 모드에서 실행할 때 자동으로 활성화됩니다. Spring.aot.enabled System 속성을 true로 설정하여 JVM에서 이러한 최적화를 사용할 수 있습니다.
AOT 최적화가 포함되면 빌드 시 수행된 일부 결정이 애플리케이션 설정에 하드코딩됩니다. 예를 들어, 빌드 시 활성화된 프로파일은 런타임에도 자동으로 활성화됩니다 |
Best Practices
AOT 엔진은 애플리케이션에서 코드 변경 없이 가능한 한 많은 사용 사례를 처리하도록 설계되었습니다. 그러나 일부 최적화는 빌드 시점에 빈의 정적 정의에 따라 수행된다는 점에 유의하세요.
이 섹션에서는 애플리케이션이 AOT를 사용할 준비가 되었는지 확인하는 모범 사례를 나열합니다.
Programmatic bean registration
AOT 엔진은 @Configuration 모델과 구성 처리의 일부로 호출될 수 있는 모든 콜백을 처리합니다. 프로그래밍 방식으로 추가 빈을 등록해야 하는 경우 BeanDefinitionRegistry를 사용하여 빈 정의를 등록해야 합니다.
이 작업은 일반적으로 BeanDefinitionRegistryPostProcessor를 통해 수행할 수 있습니다. 빈으로 자체적으로 등록된 경우 BeanFactoryInitializationAotProcessor도 구현하지 않는 한 런타임에 다시 호출된다는 점에 유의하세요. 보다 관용적인 방법은 ImportBeanDefinitionRegistrar를 구현하고 구성 클래스 중 하나에서 @Import를 사용하여 등록하는 것입니다. 이렇게 하면 구성 클래스 구문 분석의 일부로 사용자 정의 코드가 호출됩니다.
다른 콜백을 사용하여 프로그래밍 방식으로 추가 빈을 선언하는 경우 AOT 엔진에서 처리되지 않을 가능성이 높으므로 해당 빈에 대한 힌트가 생성되지 않습니다. 환경에 따라 이러한 빈은 전혀 등록되지 않을 수도 있습니다. 예를 들어 네이티브 이미지에서는 클래스 경로에 대한 개념이 없기 때문에 클래스 경로 스캔이 작동하지 않습니다. 이러한 경우 빌드 시점에 스캔을 수행하는 것이 중요합니다.
가장 정확한 빈 유형 노출
애플리케이션이 빈이 구현하는 인터페이스와 상호 작용할 수 있지만 가장 정확한 유형을 선언하는 것은 여전히 매우 중요합니다. AOT 엔진은 @Autowired 멤버 또는 라이프사이클 콜백 메서드의 존재를 감지하는 등 빈 유형에 대한 추가 검사를 수행합니다.
구성 클래스의 경우 팩토리 @Bean 메서드의 반환 유형이 가능한 한 정확한지 확인하세요. 다음 예제를 살펴보세요:
@Configuration(proxyBeanMethods = false)
public class UserConfiguration {
@Bean
public MyInterface myInterface() {
return new MyImplementation();
}
}
위의 예에서 myInterface 빈의 선언된 유형은 MyInterface입니다. 일반적인 사후 처리 중 어느 것도 MyImplementation을 고려하지 않습니다. 예를 들어, 컨텍스트가 등록해야 하는 주석 처리 메서드가 MyImplementation에 있는 경우 이를 미리 감지하지 못합니다.
위의 예제는 다음과 같이 다시 작성해야 합니다:
@Configuration(proxyBeanMethods = false)
public class UserConfiguration {
@Bean
public MyImplementation myInterface() {
return new MyImplementation();
}
}
프로그래밍 방식으로 빈 정의를 등록하는 경우, 제네릭을 처리하는 ResolvableType을 지정할 수 있는 RootBeanBefinition을 사용하는 것을 고려하세요.
다중 생성자 피하기
컨테이너는 여러 후보를 기준으로 가장 적합한 생성자를 선택할 수 있습니다. 그러나 이는 모범 사례가 아니며 필요한 경우 선호하는 생성자에 @Autowired 플래그를 지정하는 것이 좋습니다.
수정할 수 없는 코드 베이스에서 작업하는 경우 관련 빈 정의에 preferredConstructors 속성을 설정하여 어떤 생성자를 사용해야 하는지 표시할 수 있습니다.
생성자 매개변수 및 속성에 대한 복잡한 데이터 구조 피하기
프로그래밍 방식으로 루트빈 정의를 작성할 때는 사용할 수 있는 유형에 제약이 없습니다. 예를 들어, 빈이 생성자 인수로 사용하는 여러 속성이 포함된 사용자 지정 레코드가 있을 수 있습니다.
이 경우 일반 런타임에서는 잘 작동하지만 AOT는 사용자 지정 데이터 구조의 코드를 생성하는 방법을 모릅니다. 좋은 경험 법칙은 빈 정의가 여러 모델 위에 추상화되어 있다는 점을 명심하는 것입니다. 이러한 구조를 사용하는 대신 간단한 유형으로 분해하거나 그렇게 빌드된 빈을 참조하는 것이 좋습니다.
최후의 수단으로, 자체 org.springframework.aot.generate.ValueCodeGenerator$Delegate를 구현할 수 있습니다. 이를 사용하려면 Delegate를 키로 사용하여 META-INF/spring/aot.factories에 정규화된 이름을 등록하면 됩니다.
사용자 지정 인수가 있는 Bean 생성 피하기
Spring AOT는 빈을 생성하기 위해 수행해야 하는 작업을 감지하고 인스턴스 공급자를 사용하여 생성된 코드에서 이를 변환합니다. 또한 컨테이너는 사용자 정의 인수가 있는 빈 생성을 지원하므로 AOT에서 몇 가지 문제가 발생할 수 있습니다:
- 사용자 정의 인수는 일치하는 생성자 또는 팩토리 메서드의 동적 인트로스펙션이 필요합니다. 이러한 인수는 AOT에서 감지할 수 없으므로 필요한 리플렉션 힌트를 수동으로 제공해야 합니다.
- 인스턴스 공급자를 우회한다는 것은 생성 후 다른 모든 최적화도 건너뛴다는 것을 의미합니다. 예를 들어 필드와 메서드에 대한 자동 배선은 인스턴스 공급자에서 처리되므로 건너뛸 수 있습니다.
사용자 지정 인수를 사용하여 프로토타입 범위의 빈을 생성하는 대신 빈이 인스턴스 생성을 담당하는 수동 팩토리 패턴을 사용하는 것이 좋습니다.
FactoryBean
FactoryBean은 개념적으로 필요하지 않을 수 있는 빈 유형 해결 측면에서 중간 계층을 도입하므로 주의해서 사용해야 합니다. 일반적으로 FactoryBean 인스턴스가 장기간 상태를 유지하지 않고 런타임에 나중에 필요하지 않은 경우, 선언적 구성 목적으로 FactoryBean 어댑터 계층이 위에 있는 일반 팩토리 메서드로 대체해야 합니다.
FactoryBean 구현이 객체 유형(예: T)을 확인하지 않는 경우 추가 주의가 필요합니다. 다음 예시를 고려하세요:
public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
// ...
}
구체적인 클라이언트 선언은 다음 예제와 같이 클라이언트에 대해 확인된 제네릭을 제공해야 합니다:
@Configuration(proxyBeanMethods = false)
public class UserConfiguration {
@Bean
public ClientFactoryBean<MyClient> myClient() {
return new ClientFactoryBean<>(...);
}
}
FactoryBean 빈 정의가 프로그래밍 방식으로 등록되어 있는 경우 다음 단계를 따르세요:
- RootBeanDefinition을 사용합니다.
- AOT가 중간 계층임을 알 수 있도록 beanClass를 FactoryBean 클래스로 설정합니다.
- 가장 정확한 유형이 노출되도록 ResolvableType을 해결된 제네릭으로 설정합니다.
다음 예제는 기본 정의를 보여줍니다:
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
// ...
registry.registerBeanDefinition("myClient", beanDefinition);
JPA
특정 최적화를 적용하려면 JPA 지속성 단위를 미리 알고 있어야 합니다. 다음 기본 예시를 살펴보세요:
@Bean
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setPackagesToScan("com.example.app");
return factoryBean;
}
스캔이 미리 수행되도록 하려면 다음 예와 같이 팩토리 빈 정의에서 PersistenceManagedTypes 빈을 선언하고 사용해야 합니다:
@Bean
PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
return new PersistenceManagedTypesScanner(resourceLoader)
.scan("com.example.app");
}
@Bean
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource, PersistenceManagedTypes managedTypes) {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setManagedTypes(managedTypes);
return factoryBean;
}
Runtime Hints
애플리케이션을 네이티브 이미지로 실행하려면 일반 JVM 런타임에 비해 추가 정보가 필요합니다. 예를 들어, 구성 요소가 리플렉션을 사용하는지 GraalVM이 미리 알아야 합니다. 마찬가지로 클래스 경로 리소스는 명시적으로 지정하지 않는 한 네이티브 이미지에 포함되지 않습니다. 따라서 애플리케이션이 리소스를 로드해야 하는 경우 해당 GraalVM 네이티브 이미지 구성 파일에서 참조해야 합니다.
런타임 힌트 API는 런타임에 리플렉션, 리소스 로드, 직렬화 및 JDK 프록시의 필요성을 수집합니다. 다음 예제는 네이티브 이미지 내 런타임에 클래스 경로에서 config/app.properties를 로드할 수 있는지 확인합니다:
runtimeHints.resources().registerPattern("config/app.properties");
예를 들어, @Controller 메서드의 반환 유형을 검사하고 Spring이 해당 유형을 직렬화(일반적으로 JSON으로)해야 한다고 감지하면 관련 리플렉션 힌트를 추가하는 등 여러 가지 컨트랙트가 AOT 처리 중에 자동으로 처리됩니다.
코어 컨테이너가 추론할 수 없는 경우에는 프로그래밍 방식으로 이러한 힌트를 등록할 수 있습니다. 일반적인 사용 사례를 위해 여러 가지 편리한 어노테이션도 제공됩니다.
임포트 런타임 힌트
런타임 힌트 리지스트라 구현을 사용하면 AOT 엔진에서 관리하는 런타임 힌트 인스턴스에 대한 콜백을 받을 수 있습니다. 이 인터페이스의 구현은 모든 Spring 빈 또는 @Bean 팩토리 메서드에서 @ImportRuntimeHints를 사용하여 등록할 수 있습니다.런타임 힌트 리지스트라 구현은 빌드시 감지 및 호출됩니다.
import java.util.Locale;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
@Component
@ImportRuntimeHints(SpellCheckService.SpellCheckServiceRuntimeHints.class)
public class SpellCheckService {
public void loadDictionary(Locale locale) {
ClassPathResource resource = new ClassPathResource("dicts/" + locale.getLanguage() + ".txt");
//...
}
static class SpellCheckServiceRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerPattern("dicts/*");
}
}
}
가능하면 @ImportRuntimeHints는 힌트가 필요한 구성 요소에 최대한 가깝게 사용해야 합니다. 이렇게 하면 구성 요소가 BeanFactory에 기여되지 않으면 힌트도 기여되지 않습니다.
런타임 힌트 등록자 인터페이스의 정규화된 이름과 동일한 키를 사용하여 META-INF/spring/aot.factories에 항목을 추가하여 구현을 정적으로 등록할 수도 있습니다.
리플렉티브
reflective는 어노테이션된 요소에 대한 리플렉션의 필요성을 표시하는 관용적인 방법을 제공합니다. 예를 들어, @EventListener는 기본 구현에서 리플렉션을 사용하여 어노테이션된 메서드를 호출하기 때문에 @Reflective로 메타 어노테이션됩니다.
기본적으로 Spring 빈만 고려되며, 어노테이션된 요소에 대한 호출 힌트가 등록됩니다. 이는@Reflective 어노테이션을 통해 사용자 정의 ReflectiveProcessor 구현을 지정하여 조정할 수 있습니다.
라이브러리 작성자는 이 어노테이션을 자신의 목적에 맞게 재사용할 수 있습니다. Spring 빈 이외의 구성 요소를 처리해야 하는 경우 BeanFactoryInitializationAotProcessor가 관련 유형을 감지하고 ReflectiveRuntimeHintsRegistrar를 사용하여 이를 처리할 수 있습니다.
@RegisterReflectionForBinding
리플렉션을 직렬화할 필요가 있는 임의의 유형을 등록하는 @Reflective의 특수화입니다. 일반적인 사용 사례는 메서드 본문 내에서 웹 클라이언트를 사용하는 것과 같이 컨테이너가 유추할 수 없는 DTO를 사용하는 경우입니다.
registerReflectionForBinding은 클래스 수준에서 모든 Spring 빈에 적용할 수 있지만, 메서드, 필드 또는 생성자에 직접 적용하여 힌트가 실제로 필요한 위치를 더 잘 나타낼 수도 있습니다. 다음 예는 직렬화를 위해 Account를 등록하는 예제입니다.
@Component
public class OrderService {
@RegisterReflectionForBinding(Account.class)
public void process(Order order) {
// ...
}
}
런타임 힌트 테스트
Spring Core는 기존 힌트가 특정 사용 사례와 일치하는지 확인하기 위한 유틸리티인 RuntimeHintsPredicates도 제공합니다. 이를 자체 테스트에서 사용하여 RuntimeHintsRegistrar에 예상 결과가 포함되어 있는지 확인할 수 있습니다. SpellCheckService에 대한 테스트를 작성하고 런타임에 사전을 로드할 수 있는지 확인할 수 있습니다:
@Test
void shouldRegisterResourceHints() {
RuntimeHints hints = new RuntimeHints();
new SpellCheckServiceRuntimeHints().registerHints(hints, getClass().getClassLoader());
assertThat(RuntimeHintsPredicates.resource().forResource("dicts/en.txt"))
.accepts(hints);
}
RuntimeHintsPredicates를 사용하면 리플렉션, 리소스, 직렬화 또는 프록시 생성 힌트를 확인할 수 있습니다. 이 접근 방식은 단위 테스트에 적합하지만 구성 요소의 런타임 동작이 잘 알려져 있다는 것을 의미합니다.
애플리케이션의 글로벌 런타임 동작에 대해 자세히 알아보려면 테스트 스위트(또는 앱 자체)를 GraalVM 추적 에이전트로 실행하면 됩니다. 이 에이전트는 런타임에 GraalVM 힌트가 필요한 모든 관련 호출을 기록하고 이를 JSON 구성 파일로 작성합니다.
보다 타겟화된 검색 및 테스트를 위해 Spring Framework는 핵심 AOT 테스트 유틸리티가 포함된 전용 모듈인 "org.springframework:spring-core-test"를 제공합니다. 이 모듈에는 런타임 힌트와 관련된 모든 메서드 호출을 기록하고 주어진 RuntimeHints 인스턴스가 모든 기록된 호출을 포함한다고 주장하는 데 도움을 주는 Java 에이전트인 RuntimeHints 에이전트가 포함되어 있습니다. AOT 처리 단계에서 우리가 기여하는 힌트를 테스트하려는 인프라의 일부를 고려해 봅시다.
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ClassUtils;
public class SampleReflection {
private final Log logger = LogFactory.getLog(SampleReflection.class);
public void performReflection() {
try {
Class<?> springVersion = ClassUtils.forName("org.springframework.core.SpringVersion", null);
Method getVersion = ClassUtils.getMethod(springVersion, "getVersion");
String version = (String) getVersion.invoke(null);
logger.info("Spring version:" + version);
}
catch (Exception exc) {
logger.error("reflection failed", exc);
}
}
}
그런 다음 기여한 힌트를 검사하는 단위 테스트(네이티브 컴파일 필요 없음)를 작성할 수 있습니다:
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
import org.springframework.aot.test.agent.RuntimeHintsRecorder;
import org.springframework.core.SpringVersion;
import static org.assertj.core.api.Assertions.assertThat;
// @EnabledIfRuntimeHintsAgent signals that the annotated test class or test
// method is only enabled if the RuntimeHintsAgent is loaded on the current JVM.
// It also tags tests with the "RuntimeHints" JUnit tag.
@EnabledIfRuntimeHintsAgent
class SampleReflectionRuntimeHintsTests {
@Test
void shouldRegisterReflectionHints() {
RuntimeHints runtimeHints = new RuntimeHints();
// Call a RuntimeHintsRegistrar that contributes hints like:
runtimeHints.reflection().registerType(SpringVersion.class, typeHint ->
typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE));
// Invoke the relevant piece of code we want to test within a recording lambda
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
SampleReflection sample = new SampleReflection();
sample.performReflection();
});
// assert that the recorded invocations are covered by the contributed hints
assertThat(invocations).match(runtimeHints);
}
}
힌트 제공을 잊어버린 경우 테스트가 실패하고 호출에 대한 세부 정보를 제공합니다:
org.springframework.docs.core.aot.hints.testing.SampleReflection performReflection
INFO: Spring version:6.0.0-SNAPSHOT
Missing <"ReflectionHints"> for invocation <java.lang.Class#forName>
with arguments ["org.springframework.core.SpringVersion",
false,
jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7].
Stacktrace:
<"org.springframework.util.ClassUtils#forName, Line 284
io.spring.runtimehintstesting.SampleReflection#performReflection, Line 19
io.spring.runtimehintstesting.SampleReflectionRuntimeHintsTests#lambda$shouldRegisterReflectionHints$0, Line 25
빌드에서 이 Java 에이전트를 구성하는 방법은 다양하므로 빌드 도구 및 테스트 실행 플러그인 설명서를 참조하세요. 에이전트 자체는 특정 패키지를 계측하도록 구성할 수 있습니다(기본적으로 org.springframework만 계측됨). 자세한 내용은 Spring Framework buildSrc README 파일에서 확인할 수 있습니다.
부록
섹션 요약
XML Schemas
부록의 이 부분에는 코어 컨테이너와 관련된 XML 스키마가 나열되어 있습니다.
The util Schema
이름에서 알 수 있듯이 util 태그는 컬렉션 구성, 상수 참조 등과 같은 일반적인 유틸리티 구성 문제를 처리합니다. util 스키마에서 태그를 사용하려면 Spring XML 구성 파일 상단에 다음과 같은 서문이 있어야 합니다(스니펫의 텍스트는 올바른 스키마를 참조하여 util 네임스페이스의 태그를 사용할 수 있도록 합니다):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!-- bean definitions here -->
</beans>
<util:constant/> 사용
다음 빈 정의를 고려해 보세요:
<bean id="..." class="...">
<property name="isolation">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
앞의 구성은 Spring FactoryBean 구현(FieldRetrievingFactoryBean)을 사용하여 빈의 격리 속성 값을 java.sql.Connection.TRANSACTION_SERIALIZABLE 상수의 값으로 설정합니다. 이것은 모두 훌륭하고 좋지만 장황하고 (불필요하게) 최종 사용자에게 Spring의 내부 배관을 노출시킵니다.
다음 XML 스키마 기반 버전은 더 간결하고 개발자의 의도를 명확하게 표현하며("이 상수 값 주입") 가독성도 더 좋습니다:
<bean id="..." class="...">
<property name="isolation">
<util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</property>
</bean>
필드 값에서 빈 속성 또는 생성자 인수 설정하기
FieldRetrievingFactoryBean은 정적 또는 비정적 필드 값을 검색하는 FactoryBean입니다. 일반적으로 공용 정적 최종 상수를 검색하는 데 사용되며, 다른 빈의 속성 값이나 생성자 인수를 설정하는 데 사용할 수 있습니다.
다음 예제는staticField속성을 사용하여 정적 필드를 노출하는 방법을 보여줍니다:
<bean id="myField"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>
다음 예제에서 볼 수 있듯이 정적 필드를 빈 이름으로 지정하는 편리한 사용 형태도 있습니다:
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
이것은 더 이상 빈 ID가 무엇인지 선택할 수 없다는 것을 의미하지만(따라서 이를 참조하는 다른 빈도 이 긴 이름을 사용해야 함), 다음 예제에서 볼 수 있듯이 이 양식은 정의하기가 매우 간결하고 빈 참조를 위해 ID를 지정할 필요가 없으므로 내부 빈으로 사용하기에 매우 편리합니다:
<bean id="..." class="...">
<property name="isolation">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
또한 다른 빈의 정적이 아닌(인스턴스) 필드에 액세스할 수도 있는데, 이는FieldRetrievingFactoryBean클래스에 대한 API 설명서에 설명된 대로 가능합니다.
열거형 값을 속성 또는 생성자 인수로 빈에 주입하는 것은 Spring에서 쉽게 할 수 있습니다. 실제로 Spring 내부(또는 FieldRetrievingFactoryBean과 같은 클래스)에 대해 아무것도 할 필요도, 알 필요도 없습니다. 다음 열거 예제는 열거형 값을 주입하는 것이 얼마나 쉬운지 보여줍니다:
package jakarta.persistence;
public enum PersistenceContextType {
TRANSACTION,
EXTENDED
}
이제 PersistenceContextType 유형의 다음 세터와 해당 빈 정의를 고려해 보겠습니다:
package example;
public class Client {
private PersistenceContextType persistenceContextType;
public void setPersistenceContextType(PersistenceContextType type) {
this.persistenceContextType = type;
}
}
<bean class="example.Client">
<property name="persistenceContextType" value="TRANSACTION"/>
</bean>
<util:property-path/> 사용
다음 예제를 살펴보겠습니다:
<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
<property name="age" value="10"/>
<property name="spouse">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="11"/>
</bean>
</property>
</bean>
<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<bean id="testBean.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
앞의 구성은 Spring FactoryBean 구현(PropertyPathFactoryBean)을 사용하여 testBean 빈의 age 속성과 동일한 값을 갖는 testBean .age라는 ( int 유형의) 빈을 생성합니다.
이제 <util:property-path/> 요소를 추가하는 다음 예제를 살펴보겠습니다:
<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
<property name="age" value="10"/>
<property name="spouse">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="11"/>
</bean>
</property>
</bean>
<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<util:property-path id="name" path="testBean.age"/>
<property-path/> 요소의 경로 속성 값은beanName.beanProperty의 형식을 따릅니다. 이 경우,testBean이라는 빈의 연령 속성을 선택합니다. 해당 연령 속성의 값은 10입니다.
<util:property-path/>를 사용하여 빈 속성 또는 생성자 인수 설정하기
PropertyPathFactoryBean은 지정된 대상 객체의 속성 경로를 평가하는 FactoryBean입니다. 대상 객체는 직접 지정하거나 빈 이름으로 지정할 수 있습니다. 그런 다음 다른 빈 정의에서 이 값을 속성 값 또는 생성자 인수로 사용할 수 있습니다.
다음 예는 다른 빈에 대해 이름별로 사용되는 경로를 보여줍니다:
<!-- target bean to be referenced by name -->
<bean id="person" class="org.springframework.beans.TestBean" scope="prototype">
<property name="age" value="10"/>
<property name="spouse">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="11"/>
</bean>
</property>
</bean>
<!-- results in 11, which is the value of property 'spouse.age' of bean 'person' -->
<bean id="theAge"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetBeanName" value="person"/>
<property name="propertyPath" value="spouse.age"/>
</bean>
다음 예제에서는 내부 빈에 대해 경로가 평가됩니다:
<!-- results in 12, which is the value of property 'age' of the inner bean -->
<bean id="theAge"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetObject">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="12"/>
</bean>
</property>
<property name="propertyPath" value="age"/>
</bean>
빈 이름이 속성 경로인 바로 가기 형식도 있습니다. 다음 예는 바로 가기 형식을 보여줍니다:
<!-- results in 10, which is the value of property 'age' of bean 'person' -->
<bean id="person.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
이 형식은 빈 이름에 선택의 여지가 없다는 것을 의미합니다. 이 빈에 대한 모든 참조도 경로인 동일한 ID를 사용해야 합니다. 내부 빈으로 사용하는 경우 다음 예제에서 볼 수 있듯이 전혀 참조할 필요가 없습니다:
<bean id="..." class="...">
<property name="age">
<bean id="person.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
</property>
</bean>
실제 정의에서 결과 유형을 구체적으로 설정할 수 있습니다. 대부분의 사용 사례에는 필요하지 않지만 때때로 유용할 수 있습니다. 이 기능에 대한 자세한 내용은 자바독을 참조하세요.
<util:properties/> 사용
다음 예제를 살펴보세요:
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:com/foo/jdbc-production.properties"/>
</bean>
앞의 구성은 제공된 리소스 위치에서 로드된 값으로 java.util.Properties 인스턴스를 인스턴스화하기 위해 Spring FactoryBean 구현(PropertiesFactoryBean)을 사용합니다(예: 제공된 리소스 위치에서 로드된 값).
다음 예제에서는 util:properties 요소를 사용하여 보다 간결하게 표현합니다:
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
<util:list/> 사용
다음 예제를 살펴보세요:
<!-- creates a java.util.List instance with values loaded from the supplied 'sourceList' -->
<bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</list>
</property>
</bean>
앞의 구성은 Spring FactoryBean 구현(ListFactoryBean)을 사용하여 java.util.List 인스턴스를 생성하고 제공된 sourceList에서 가져온 값으로 초기화합니다.
다음 예제에서는 <util:list/> 요소를 사용하여 보다 간결하게 표현합니다:
<!-- creates a java.util.List instance with the supplied values -->
<util:list id="emails">
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</util:list>
<util:list/> 요소의 list-class 속성을 사용하여 인스턴스화되고 채워지는 List의 정확한 유형을 명시적으로 제어할 수도 있습니다. 예를 들어 java.util.LinkedList를 인스턴스화해야 하는 경우 다음과 같은 구성을 사용할 수 있습니다:
<util:list id="emails" list-class="java.util.LinkedList">
<value>jackshaftoe@vagabond.org</value>
<value>eliza@thinkingmanscrumpet.org</value>
<value>vanhoek@pirate.org</value>
<value>d'Arcachon@nemesis.org</value>
</util:list>
목록 클래스 속성이 제공되지 않으면 컨테이너는 목록 구현을 선택합니다.
<util:map/> 사용
다음 예제를 살펴보세요:
<!-- creates a java.util.Map instance with values loaded from the supplied 'sourceMap' -->
<bean id="emails" class="org.springframework.beans.factory.config.MapFactoryBean">
<property name="sourceMap">
<map>
<entry key="pechorin" value="pechorin@hero.org"/>
<entry key="raskolnikov" value="raskolnikov@slums.org"/>
<entry key="stavrogin" value="stavrogin@gov.org"/>
<entry key="porfiry" value="porfiry@gov.org"/>
</map>
</property>
</bean>
앞의 구성은 Spring FactoryBean 구현(MapFactoryBean)을 사용하여 제공된 'sourceMap'에서 가져온 키-값 쌍으로 초기화된 java.util.Map 인스턴스를 생성합니다.
다음 예시에서는 <util:map/> 요소를 사용하여 보다 간결하게 표현합니다:
<!-- creates a java.util.Map instance with the supplied key-value pairs -->
<util:map id="emails">
<entry key="pechorin" value="pechorin@hero.org"/>
<entry key="raskolnikov" value="raskolnikov@slums.org"/>
<entry key="stavrogin" value="stavrogin@gov.org"/>
<entry key="porfiry" value="porfiry@gov.org"/>
</util:map>
또한 <util:map/> 요소의 'map-class' 속성을 사용하여 인스턴스화되고 채워지는 Map의 정확한 유형을 명시적으로 제어할 수도 있습니다. 예를 들어 java.util.TreeMap을 인스턴스화해야 하는 경우 다음과 같은 구성을 사용할 수 있습니다:
<util:map id="emails" map-class="java.util.TreeMap">
<entry key="pechorin" value="pechorin@hero.org"/>
<entry key="raskolnikov" value="raskolnikov@slums.org"/>
<entry key="stavrogin" value="stavrogin@gov.org"/>
<entry key="porfiry" value="porfiry@gov.org"/>
</util:map>
'map-class' 속성이 제공되지 않으면 컨테이너는 Map 구현을 선택합니다.
<util:set/> 사용
다음 예제를 살펴보세요:
<!-- creates a java.util.Set instance with values loaded from the supplied 'sourceSet' -->
<bean id="emails" class="org.springframework.beans.factory.config.SetFactoryBean">
<property name="sourceSet">
<set>
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</set>
</property>
</bean>
앞의 구성은 Spring FactoryBean 구현(SetFactoryBean)을 사용하여 제공된 sourceSet에서 가져온 값으로 초기화된 java.util.Set 인스턴스를 생성합니다.
다음 예제에서는 <util:set/> 요소를 사용하여 보다 간결하게 표현합니다:
<!-- creates a java.util.Set instance with the supplied values -->
<util:set id="emails">
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</util:set>
또한 <util:set/> 요소의 set-class 속성을 사용하여 인스턴스화되고 채워지는 Set의 정확한 유형을 명시적으로 제어할 수도 있습니다. 예를 들어 java.util.TreeSet을 인스턴스화해야 하는 경우 다음과 같은 구성을 사용할 수 있습니다:
<util:set id="emails" set-class="java.util.TreeSet">
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</util:set>
Set-class 속성이 제공되지 않으면 컨테이너는 Set 구현을 선택합니다.
The aop Schema
Aop 태그는 Spring의 자체 프록시 기반 AOP 프레임워크와 Spring과 AspectJ AOP 프레임워크의 통합을 포함하여 Spring의 모든 AOP를 구성하는 것을 다룹니다. 이러한 태그는 Spring을 사용한 객체지향 프로그래밍이라는 장에서 포괄적으로 다루고 있습니다.
완전성을 위해, aop 스키마의 태그를 사용하려면 Spring XML 구성 파일의 맨 위에 다음과 같은 서문이 있어야 합니다(스니펫의 텍스트가 올바른 스키마를 참조하여 aop 네임스페이스의 태그를 사용할 수 있도록 함):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
</beans>
The context Schema
컨텍스트 태그는 배관과 관련된 애플리케이션 컨텍스트 구성, 즉 일반적으로 최종 사용자에게 중요한 빈이 아니라 BeanfactoryPostProcessors와 같이 Spring에서 많은 "잡다한" 작업을 수행하는 빈을 처리합니다. 다음 스니펫은 컨텍스트 네임스페이스의 요소를 사용할 수 있도록 올바른 스키마를 참조합니다:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- bean definitions here -->
</beans>
<property-placeholder/> 사용
이 요소는 지정된 속성 파일( Spring 리소스 위치)에 대해 확인되는 ${...} 플레이스홀더의 대체를 활성화합니다. 이 요소는 PropertySourcesPlaceholderConfigurer를설정하는 편리한 메커니즘입니다. 특정PropertySourcesPlaceholderConfigurer 설정을 더 자세히 제어해야 하는 경우 직접 빈으로 명시적으로 정의할 수 있습니다.
특정 애플리케이션에 필요한 속성을 가진 이러한 요소는 해당 애플리케이션에 대해 하나만 정의해야 합니다. 고유한 플레이스홀더 구문(${...})을 사용하는 한 여러 프로퍼티 플레이스홀더를 구성할 수 있습니다.
대체에 사용되는 프로퍼티의 소스를 모듈화해야 하는 경우 여러 개의 프로퍼티 자리 표시자를 만들면 안 됩니다. 그 대신 각 모듈이프로퍼티 소스를 환경에 기여해야 합니다. 또는 사용할 프로퍼티를 수집하는 고유한PropertySourcesPlaceholderConfigurer 빈을 만들 수도 있습니다.
|
<annotation-config/> 사용
이 요소는 Spring 인프라를 활성화하여 빈 클래스에서 주석을 감지합니다:
- Spring의 @Configuration 모델
- 자동화된/@Inject, @Value 및 @Lookup
- JSR-250의 @Resource, @PostConstruct 및 @PreDestroy (사용 가능한 경우)
- JAX-WS의 @WebServiceRef 및 EJB 3의 @EJB (사용 가능한 경우)
- JPA의 @PersistenceContext 및 @PersistenceUnit (사용 가능한 경우)
- Spring의 @EventListener
또는 해당 어노테이션에 대해 개별 BeanPostProcessor를명시적으로 활성화하도록 선택할 수 있습니다.
이 요소는 Spring의@Transactional 어노테이션 처리를 활성화하지 않으며, 해당 용도로 <tx:annotation-driven/>요소를 사용할 수 있습니다. 마찬가지로 Spring의캐싱 어노테이션도 명시적으로활성화해야 합니다 |
<component-scan/> 사용
이 요소는 어노테이션 기반 컨테이너 구성섹션에 자세히 설명되어 있습니다.
<load-time-weaver/> 사용
이 요소는 Spring 프레임워크에서 AspectJ를 사용한 로드 시간위버 섹션에 자세히 설명되어 있습니다.
<spring-configured/> 사용
이 요소는 스프링으로 도메인 객체를 종속성 주입하기 위해 AspectJ를 사용하기섹션에 자세히 설명되어 있습니다.
<mbean-export/> 사용
이 요소는 어노테이션 기반 MBean 내보내기 구성섹션에 자세히 설명되어 있습니다.
The Beans Schema
마지막으로 콩 스키마에 있는 요소들이 있습니다. 이러한 요소는 프레임워크의 초창기부터 Spring에 존재해 왔습니다. 종속성과 구성에대해서는 이 장 전체에서 매우 포괄적으로 다루고 있기 때문에 여기서는 빈 스키마의 다양한 요소에 대한 예시를 보여주지 않습니다(실제로 해당 장 전체에서).
<bean/> XML 정의에 키-값 쌍을 0개 이상 추가할 수 있습니다. 이 추가 메타데이터로 무엇을 할지는 전적으로 사용자 정의 로직에 달려 있습니다(따라서 일반적으로 XML 스키마 작성이라는 부록에 설명된 대로 사용자 정의 요소를 직접 작성하는 경우에만 사용할 수 있습니다).
다음 예는 주변 <bean/>의맥락에서 <meta/> 요소를 보여줍니다(해석할 로직이 없으면 메타데이터는 사실상 그대로 쓸모가 없습니다).
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="foo" class="x.y.Foo">
<meta key="cacheName" value="foo"/>
<property name="name" value="Rick"/>
</bean>
</beans>
다음은 메타 요소의 예시입니다 |
앞의 예시의 경우, 빈 정의를 소비하고 제공된 메타데이터를 사용하는 캐싱 인프라를 설정하는 로직이 있다고 가정할 수 있습니다.
XML Schema Authoring
버전 2.0부터 Spring은 빈을 정의하고 구성하기 위해 기본 Spring XML 형식에 스키마 기반 확장을 추가하는 메커니즘을 제공했습니다. 이 섹션에서는 사용자 정의 XML 빈 정의 파서를 작성하고 이러한 파서를 Spring IoC 컨테이너에 통합하는 방법을 다룹니다.
스키마 인식 XML 편집기를 사용하는 구성 파일을 쉽게 작성할 수 있도록 Spring의 확장 가능한 XML 구성 메커니즘은 XML 스키마를 기반으로 합니다. 표준 Spring 배포와 함께 제공되는 Spring의 현재 XML 구성 확장에 익숙하지 않은 경우 먼저 XML 스키마에 대한 이전 섹션을 읽어보시기 바랍니다.
새 XML 구성 확장을 만들려면 다음과 같이 하세요:
통합 예제에서는 java.text 패키지에서SimpleDateFormat 유형의 객체를 구성할 수 있는 XML 확장(사용자 정의 XML 요소)을 생성합니다. 이 작업이 완료되면 다음과 같이 SimpleDateFormat 유형의 빈 정의를 정의할 수 있습니다:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
(이 부록의 뒷부분에 훨씬 더 자세한 예제가 포함되어 있습니다. 이 첫 번째 간단한 예제의 의도는 사용자 정의 확장을 만드는 기본 단계를 안내하는 것입니다
Authoring the Schema
Spring의 IoC 컨테이너와 함께 사용하기 위한 XML 구성 확장을 생성하는 것은 확장을 설명하는 XML 스키마를 작성하는 것으로 시작됩니다. 이 예제에서는 다음 스키마를 사용하여 SimpleDateFormat 객체를 구성합니다:
<!-- myns.xsd (inside package org/springframework/samples/xml) -->
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.mycompany.example/schema/myns"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="dateformat">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="lenient" type="xsd:boolean"/>
<xsd:attribute name="pattern" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
표시된 줄에는 식별 가능한 모든 태그에 대한 확장 기반이 포함되어 있습니다(컨테이너에서 빈 식별자로 사용할 수 있는 id 속성이 있음을 의미). Spring에서 제공하는빈 네임스페이스를 가져왔기 때문에 이 속성을 사용할 수 있습니다. |
앞의 스키마를 사용하면 다음 예제에서 볼 수 있듯이 <myns:dateformat/> 요소를 사용하여 XML 애플리케이션 컨텍스트 파일에서 직접 SimpleDateFormat 객체를 구성할 수 있습니다:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
인프라 클래스를 생성한 후 앞의 XML 스니펫은 기본적으로 다음 XML 스니펫과 동일하다는 점에 유의하세요:
<bean id="dateFormat" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm"/>
<property name="lenient" value="true"/>
</bean>
앞의 두 코드조각 중 두 번째 코드조각은 컨테이너에 몇 가지 프로퍼티가 설정된 빈을 생성합니다(SimpleDateFormat 유형의 dateFormat이라는 이름으로 식별됨).
구성 형식을 만드는 스키마 기반 접근 방식을 사용하면 스키마 인식 XML 편집기가 있는 IDE와 긴밀하게 통합할 수 있습니다. 제대로 작성된 스키마를 사용하면 자동 완성 기능을 사용하여 사용자가 열거형에 정의된 여러 구성 옵션 중에서 선택할 수 있도록 할 수 있습니다 |
Coding a NamespaceHandler
스키마 외에도 Spring이 구성 파일을 구문 분석하는 동안 이 특정 네임스페이스의 모든 요소를 구문 분석하기 위한 NamespaceHandler가 필요합니다. 이 예제에서는NamespaceHandler가 myns:dateformat요소의 구문 분석을 처리해야 합니다.
NamespaceHandler 인터페이스에는 세 가지 메서드가 있습니다:
- init(): 핸들러가 사용되기 전에 Spring이 호출하는 메서드로, NamespaceHandler를 초기화할 수 있습니다.
- BeanDefinition parse(엘리먼트, 파서컨텍스트): Spring이 최상위 요소(빈 정의 또는 다른 네임스페이스 내에 중첩되지 않은)를 발견할 때 호출됩니다. 이 메서드는 자체적으로 빈 정의를 등록하거나 빈 정의를 반환하거나 둘 다를 반환할 수 있습니다.
- BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext): Spring이 다른 네임스페이스의 속성 또는 중첩된 요소를 만날 때 호출됩니다. 하나 이상의 빈 정의의 장식은 (예를 들어)Spring이 지원하는 범위와 함께 사용됩니다. 먼저 장식을 사용하지 않고 간단한 예제를 강조 표시한 다음, 다소 고급 예제에서 장식을 표시합니다.
전체 네임스페이스에 대해 자체 NamespaceHandler를 코딩할 수 있지만(따라서 네임스페이스의 모든 요소를 구문 분석하는 코드를 제공할 수 있음), Spring XML 구성 파일의 각 최상위 XML 요소가 단일 빈 정의를 생성하는 경우가 많습니다(단일 <myns:dateformat/>요소가 단일 SimpleDateFormat 빈 정의를 생성하는 우리의 경우와 같이). Spring에는 이 시나리오를 지원하는 여러 편의 클래스가 있습니다. 다음 예제에서는 NamespaceHandlerSupport 클래스를 사용합니다:
package org.springframework.samples.xml;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
}
}
이 클래스에는 실제로 구문 분석 로직이 많지 않다는 것을 알 수 있습니다. 실제로 NamespaceHandlerSupport 클래스에는 위임이라는 개념이 내장되어 있습니다. 이 클래스는 이름 공간에서 요소를 구문 분석해야 할 때 위임하는 BeanDefinitionParser인스턴스를 얼마든지 등록할 수 있도록 지원합니다. 이러한 깔끔한 관심사 분리를 통해 NamespaceHandler는 네임스페이스에 있는 모든 사용자 정의 요소의 구문 분석 오케스트레이션을 처리하는 동시에 XML 구문 분석의 단순 작업은 BeanDefinitionParsers에 위임할 수 있습니다. 즉, 다음 단계에서 볼 수 있듯이 각 BeanDefinitionParser에는 단일 사용자 정의 요소를 파싱하기 위한 로직만 포함됩니다.
Using BeanDefinitionParser
NamespaceHandler가 특정 빈 정의 구문 분석기( 이 경우날짜 형식) 에 매핑된 유형의 XML 요소를 발견하면 BeanDefinitionParser가 사용됩니다. 다시 말해, BeanDefinitionParser는 스키마에 정의된 하나의 고유한 최상위 XML 요소를 구문 분석하는 역할을 담당합니다. 파서에서는 다음 예제에서 볼 수 있듯이 사용자 정의 XML 콘텐츠를 구문 분석할 수 있도록 XML 요소(따라서 하위 요소에도 액세스)에 액세스할 수 있습니다:
package org.springframework.samples.xml;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import java.text.SimpleDateFormat;
public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected Class getBeanClass(Element element) {
return SimpleDateFormat.class;
}
protected void doParse(Element element, BeanDefinitionBuilder bean) {
// this will never be null since the schema explicitly requires that a value be supplied
String pattern = element.getAttribute("pattern");
bean.addConstructorArgValue(pattern);
// this however is an optional property
String lenient = element.getAttribute("lenient");
if (StringUtils.hasText(lenient)) {
bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
}
}
}
우리는 Spring에서 제공하는 AbstractSingleBeanDefinitionParser를 사용하여 단일 BeanDefinition을 생성하는 많은 기본 작업을 처리합니다. | |
단일 BeanDefinition이 나타내는 유형을 AbstractSingleBeanDefinitionParser 수퍼클래스에 제공합니다. |
이 간단한 사례에서는 이 작업만 하면 됩니다. 단일BeanDefinition의 생성은 빈 정의의 고유 식별자를 추출하고 설정하는 것과 마찬가지로 AbstractSingleBeanDefinitionParser 슈퍼클래스에 의해 처리됩니다.
Registering the Handler and the Schema
코딩이 완료되었습니다. 이제 남은 작업은 Spring XML 구문 분석 인프라가 사용자 정의 요소를 인식하도록 하는 것입니다. 이를 위해 사용자 정의네임스페이스 핸들러와 사용자 정의 XSD 파일을 두 개의 특수 목적 속성 파일에 등록합니다. 이러한 속성 파일은 모두 애플리케이션의 META-INF 디렉터리에 배치되며, 예를 들어 JAR 파일에 바이너리 클래스와 함께 배포할 수 있습니다. Spring XML 구문 분석 인프라는 이러한 특수 속성 파일을 사용하여 새 확장자를 자동으로 선택하며, 그 형식은 다음 두 섹션에서 자세히 설명합니다.
META-INF/spring.handlers 작성하기
Spring.handlers라는 속성 파일에는 XML 스키마 URI와 네임스페이스 핸들러 클래스의 매핑이 포함되어 있습니다. 이 예제에서는 다음과 같이 작성해야 합니다:
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
( : 문자는 Java 속성 형식에서 유효한 구분 기호이므로 URI의: 문자는 백슬래시로 이스케이프 처리해야 합니다.)
키-값 쌍의 첫 번째 부분(키)은 사용자 정의 네임스페이스 확장에 연결된 URI이며 사용자 정의 XSD 스키마에 지정된 대로 targetNamespace속성 값과 정확히 일치해야 합니다.
'META-INF/spring.schemas' 작성하기
Spring.schemas라는 속성 파일에는 XML 스키마 위치(스키마 선언과 함께 스키마를 xsi:schemaLocation 속성의 일부로 사용하는 XML 파일에서 참조)를 클래스 경로 리소스에 매핑하는 내용이 포함되어 있습니다. 이 파일은 Spring이 스키마 파일을 검색하기 위해 인터넷 액세스가 필요한 기본 EntityResolver를 반드시 사용해야 하는 것을 방지하기 위해 필요합니다. 이 속성 파일에서 매핑을 지정하면 Spring은 클래스 경로에서 스키마(이 경우 org.springframework.samples.xml 패키지의myns.xsd )를 검색합니다. 다음 스니펫은 사용자 정의 스키마에 추가해야 하는 행을 보여줍니다:
http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
( : 문자는 이스케이프 처리해야 함을 기억하세요.)
클래스 경로의 NamespaceHandler 및 BeanDefinitionParser 클래스와 함께 XSD 파일을 바로 배포하는 것이 좋습니다.
Using a Custom Extension in Your Spring XML Configuration
직접 구현한 사용자 정의 확장을 사용하는 것은 Spring에서 제공하는 "사용자 정의" 확장 중 하나를 사용하는 것과 다르지 않습니다. 다음 예제에서는 이전 단계에서 개발한 사용자 정의 <dateformat/> 요소를 Spring XML 구성 파일에 사용합니다:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:myns="http://www.mycompany.example/schema/myns"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">
<!-- as a top-level bean -->
<myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
<bean id="jobDetailTemplate" abstract="true">
<property name="dateFormat">
<!-- as an inner bean -->
<myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
</property>
</bean>
</beans>
사용자 정의 빈. |
More Detailed Examples
이 섹션에서는 사용자 정의 XML 확장에 대한 몇 가지 자세한 예제를 소개합니다.
사용자 정의 요소 내에 사용자 정의 요소 중첩하기
이 섹션에 제시된 예제에서는 다음 구성의 대상을 충족하는 데 필요한 다양한 아티팩트를 작성하는 방법을 보여줍니다:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:foo="http://www.foo.example/schema/component"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">
<foo:component id="bionic-family" name="Bionic-1">
<foo:component name="Mother-1">
<foo:component name="Karate-1"/>
<foo:component name="Sport-1"/>
</foo:component>
<foo:component name="Rock-1"/>
</foo:component>
</beans>
앞의 구성은 사용자 지정 확장을 서로 중첩합니다. <foo:component/> 요소에 의해 실제로 구성되는 클래스는 다음 예제에서 볼 수 있는 Component클래스입니다. Component 클래스가 구성 요소 속성에 대한 세터 메서드를 노출하지 않는 것을 주목하세요. 따라서 세터 주입을 사용하여 Component 클래스에 대한 빈 정의를 구성하기가 어렵거나 오히려 불가능합니다. 다음 목록은 Component 클래스를 보여줍니다:
package com.foo;
import java.util.ArrayList;
import java.util.List;
public class Component {
private String name;
private List<Component> components = new ArrayList<Component> ();
// there is no setter method for the 'components'
public void addComponent(Component component) {
this.components.add(component);
}
public List<Component> getComponents() {
return components;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
이 문제에 대한 일반적인 해결책은 구성 요소 속성에 대한 세터 속성을 노출하는 사용자 지정 FactoryBean을 만드는 것입니다. 다음 목록은 이러한 사용자 정의FactoryBean을 보여줍니다:
package com.foo;
import org.springframework.beans.factory.FactoryBean;
import java.util.List;
public class ComponentFactoryBean implements FactoryBean<Component> {
private Component parent;
private List<Component> children;
public void setParent(Component parent) {
this.parent = parent;
}
public void setChildren(List<Component> children) {
this.children = children;
}
public Component getObject() throws Exception {
if (this.children != null && this.children.size() > 0) {
for (Component child : children) {
this.parent.addComponent(child);
}
}
return this.parent;
}
public Class<Component> getObjectType() {
return Component.class;
}
public boolean isSingleton() {
return true;
}
}
이 방법은 훌륭하게 작동하지만 최종 사용자에게 많은 Spring 배관을 노출합니다. 우리가 할 일은 이 모든 Spring 배관을 숨기는 사용자 정의 확장을 작성하는 것입니다. 앞서 설명한 단계를 따르면 다음 목록과 같이 사용자 정의 태그의 구조를 정의하는 XSD 스키마를 만드는 것부터 시작합니다:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/component"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.example/schema/component"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="component">
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="component"/>
</xsd:choice>
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="name" use="required" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
다시 앞서 설명한 프로세스에 따라 사용자 정의 NamespaceHandler를 생성합니다:
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
}
}
다음은 커스텀 BeanDefinitionParser입니다. 컴포넌트팩토리빈을 설명하는 BeanDefinition을 생성한다는 것을 기억하세요. 다음 목록은 커스텀 BeanDefinitionParser 구현을 보여줍니다:
package com.foo;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import java.util.List;
public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
return parseComponentElement(element);
}
private static AbstractBeanDefinition parseComponentElement(Element element) {
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
factory.addPropertyValue("parent", parseComponent(element));
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
if (childElements != null && childElements.size() > 0) {
parseChildComponents(childElements, factory);
}
return factory.getBeanDefinition();
}
private static BeanDefinition parseComponent(Element element) {
BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
component.addPropertyValue("name", element.getAttribute("name"));
return component.getBeanDefinition();
}
private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
ManagedList<BeanDefinition> children = new ManagedList<>(childElements.size());
for (Element element : childElements) {
children.add(parseComponentElement(element));
}
factory.addPropertyValue("children", children);
}
}
마지막으로, 다양한 아티팩트를 Spring XML 인프라에 등록하기 위해 다음과 같이 META-INF/spring.handlers 및 META-INF/spring.schemas 파일을 수정해야 합니다:
'META-INF/spring.handlers'의 # http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
'META-INF/spring.schemas'의 # http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
"일반" 요소의 사용자 정의 어트리뷰트
사용자 정의 파서와 관련 아티팩트를 직접 작성하는 것은 어렵지 않습니다. 하지만 때로는 이것이 옳은 일이 아닐 수도 있습니다. 이미 존재하는 빈 정의에 메타데이터를 추가해야 하는 시나리오를 생각해 보세요. 이 경우 전체 사용자 지정 확장을 직접 작성하고 싶지는 않을 것입니다. 오히려 기존 빈 정의 요소에 추가 속성을 추가하기만 하면 됩니다.
다른 예로, 클러스터된JCache에 액세스하는 (알 수 없는) 서비스 객체에 대한 빈 정의를 정의하고, 명명된 JCache 인스턴스가 주변 클러스터 내에서 열심히 시작되도록 하고 싶다고 가정해 보겠습니다. 다음 목록은 이러한 정의를 보여줍니다:
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
jcache:cache-name="checking.account">
<!-- other dependencies here... -->
</bean>
그런 다음'jcache:cache-name' 속성이 파싱될 때 또 다른 BeanDefinition을 생성할 수 있습니다. 그러면 이 BeanDefinition이 명명된 JCache를 초기화합니다. 또한'checkingAccountService' 에 대한 기존 BeanDefinition을 수정하여 이 새로운 JCache 초기화 BeanDefinition에 종속성을 갖도록 할 수 있습니다. 다음 목록은 JCacheInitializer를 보여줍니다:
package com.foo;
public class JCacheInitializer {
private final String name;
public JCacheInitializer(String name) {
this.name = name;
}
public void initialize() {
// lots of JCache API calls to initialize the named cache...
}
}
이제 사용자 정의 확장으로 넘어가겠습니다. 먼저, 다음과 같이 사용자 정의 속성을 설명하는 XSD 스키마를 작성해야 합니다:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/jcache"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.example/schema/jcache"
elementFormDefault="qualified">
<xsd:attribute name="cache-name" type="xsd:string"/>
</xsd:schema>
다음으로, 다음과 같이 연관된 NamespaceHandler를 생성해야 합니다:
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class JCacheNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
new JCacheInitializingBeanDefinitionDecorator());
}
}
다음으로 파서를 생성해야 합니다. 이 경우 XML 어트리뷰트를 구문 분석할 것이므로 BeanDefinitionParser가 아닌 BeanDefinitionDecorator를 작성합니다. 다음 목록은 BeanDefinitionDecorator 구현을 보여줍니다:
package com.foo;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
ParserContext ctx) {
String initializerBeanName = registerJCacheInitializer(source, ctx);
createDependencyOnJCacheInitializer(holder, initializerBeanName);
return holder;
}
private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
String initializerBeanName) {
AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
String[] dependsOn = definition.getDependsOn();
if (dependsOn == null) {
dependsOn = new String[]{initializerBeanName};
} else {
List dependencies = new ArrayList(Arrays.asList(dependsOn));
dependencies.add(initializerBeanName);
dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
}
definition.setDependsOn(dependsOn);
}
private String registerJCacheInitializer(Node source, ParserContext ctx) {
String cacheName = ((Attr) source).getValue();
String beanName = cacheName + "-initializer";
if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
initializer.addConstructorArg(cacheName);
ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
}
return beanName;
}
}
마지막으로, 다음과 같이 META-INF/spring.handlers 및 META-INF/spring.schemas 파일을 수정하여 다양한 아티팩트를 Spring XML 인프라에 등록해야 합니다:
'META-INF/spring.handlers'의 # http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
'META-INF/spring.schemas'의 # http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd
애플리케이션 시작 단계
부록의 이 부분에는 코어 컨테이너가 계측되는 기존 StartupSteps가 나열되어 있습니다.
각 시작 단계의 이름과 세부 정보는 공개 계약의 일부가 아니며 변경될 수 있으며, 이는 코어 컨테이너의 구현 세부 사항으로 간주되며 동작 변경에 따라 변경될 수 있습니다 |
표 1. 코어 컨테이너에 정의된 애플리케이션 시작 단계이름설명태그
spring.beans.instantiate | 빈과 그 종속성의 인스턴스화. | bean이름은 빈의 이름, bean타입은 주입 지점에서 필요한 타입입니다. |
spring.beans.smart-initialize | 스마트 초기화싱글톤 빈의 초기화. | bean빈의 이름입니다. |
spring.context.annotated-bean-reader.create | AnnotatedBeanDefinitionReader 생성. | |
spring.context.base-packages.scan | 베이스 패키지 스캔. | 스캔할 기본 패키지의패키지 배열. |
spring.context.beans.post-process | 빈 사후 처리 단계. | |
spring.context.bean-factory.post-process | BeanFactoryPostProcessor 빈의 호출. | postProcessor 현재 포스트 프로세서. |
spring.context.beandef-registry.post-process | BeanDefinitionRegistryPostProcessor 빈의 호출. | postProcessor 현재 포스트 프로세서. |
spring.context.component-classes.register | AnnotationConfigApplicationContext#register를 통한 컴포넌트 클래스 등록. | 등록을 위해 주어진클래스의 클래스 배열. |
spring.context.config-classes.enhance | CGLIB 프록시를 사용한 구성 클래스 향상. | classCount 향상된 클래스의 개수. |
spring.context.config-classes.parse | 구성 클래스 구문 분석 단계로 구성 클래스 포스트프로세서. | classCount 처리된 클래스의 개수. |
spring.context.refresh | 애플리케이션 컨텍스트 새로 고침 단계. |
'언어 > Spring Docs 번역' 카테고리의 다른 글
Testing (2) (0) | 2024.07.07 |
---|---|
Testing (1) (0) | 2024.07.07 |
Core Technologies / Spring AOP APIs (0) | 2024.07.07 |
Core Technologies / Aspect Oriented Programming with Spring (0) | 2024.07.07 |
Core Technologies / Spring Expression Language (SpEL) (0) | 2024.07.07 |