Spring @ Import 주해 원본 분석
19505 단어 Spring 소스 코드 분석
Spring 3.0 전에 Bean 을 만 들 면 xml 프로필 과 특정 가방 아래 의 클래스 를 스 캔 하여 Spring IOC 용기 에 클래스 를 주입 할 수 있 습 니 다.그리고 Spring 3.0 이후 자바 Config 방식 을 제공 했다. 즉, IOC 용기 에 있 는 Bean 의 메타 정 보 를 자바 코드 로 설명 하 는 것 이다.저 희 는 @ Configuration 과 @ Bean 이라는 두 주석 을 결합 하여 xml 파일 에 설 치 된 bean 을 자바 코드 로 설명 할 수 있 습 니 다.
@ Import 주 해 는 @ Bean 주해 기능 을 제공 하 는 동시에 xml 프로필 에 라벨 이 여러 개의 분 산 된 xml 파일 을 구성 하 는 기능 도 있 습 니 다. 물론 여 기 는 여러 개의 분 산 된 @ Configuration 을 구성 합 니 다.
먼저 @ Import 주해 의 원본 코드 를 보십시오.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class>[] value();
}
원본 코드 에서 볼 수 있 듯 이 @ Import 는
Configuration
, ImportSelector
, ImportBeanDefinitionRegistrar
에 맞 춰 사용 할 수 있 으 며, 아래 의 or 는 Import 를 일반적인 Bean 으로 사용 할 수도 있다 고 밝 혔 습 니 다. @ Import 는 클래스 위 에 만 사용 할 수 있 고 방법 위 에 올 릴 수 없습니다.구체 적 인 사용법 을 살 펴 보 겠 습 니 다. 일반 사용 방법
이런 방식 으로 종 류 를 Spring IOC 용기 에 직접 넣 을 수 있다.
@Configuration
@Import(value={UserServiceImpl.class})
public class Config {
}
그러나 이런 방식 은 몇 가지 문제 가 있다. 그것 은 바로 클래스 의 무 참 구조 방법 으로 만 bean 을 만 들 수 있 고 매개 변수 가 있 는 구조 방법 에 대해 서 는 무력 하 다 는 것 이다.
결합
ImportBeanDefinitionRegistrar
인터페이스ImportBeanDefinitionRegistrar
인터페이스의 소스 코드 는 다음 과 같다.public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
이 인 터 페 이 스 를 볼 수 있 는 유일한 방법 은 두 개의 매개 변수 가 있다.
public class UserServiceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {
BeanDefinitionBuilder userService = BeanDefinitionBuilder.rootBeanDefinition(UserServiceImpl.class);
registry.registerBeanDefinition("userService", userService.getBeanDefinition());
}
}
우리 가 이 방법 에서 특수 조작 을 하 는 것 을 볼 수 있다. 일반적인 방식 에 비해 많이 유연 해 졌 다.
이어서 저희 가 @ Import 주해 로 도 입 된 곳 은 UserServiceBeanDefinition Registrar 도입 으로 만 수정 하면 됩 니 다.
@Configuration
@Import(value={UserServiceBeanDefinitionRegistrar.class})
public class Config {
}
결합
ImportSelector
인터페이스비교 실현
ImportBeanDefinitionRegistrar
인터페이스 이후 빈 용 기 를 직접 조작 하 는 것 에 있어 서 사용 ImportSelector
은 더욱 우아 할 것 이 며, 주입 류 의 전체 제한 이름 으로 돌아 가면 된다.ImportSelector 인터페이스의 원본 코드 는 다음 과 같 습 니 다.
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
public class UserServiceImportSelect implements ImportSelector{
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{UserServiceImpl.class.getName()};
}
}
@Configuration()
@Import(value={UserServiceImportSelect.class})
public class Config {
}
세 가지 방법 을 비교 해 보면 마지막 에 이런 게 가장 우아 한 방법 이에 요.
소스 코드 분석
우선 세 번 째 우아 한 방식 으로 출발 하여 Call Hierarchy 를 사용 하여 ImportSelector 인터페이스의 selectImports 방법 으로 체인 관 계 를 호출 합 니 다.
1
이전에 Spring 소스 코드 해석 문장 을 본 학생 들 은 모두 알 고 있 었 다.
refresh
방법 은 용기 상하 문 을 초기 화 하 는 데 쓰 였 다.이 호출 체인 을 따라 내 려 오 면 중간 에 있 는 클래스 ConfigurationClassPostProcessor
가 있 습 니 다. 클래스 이름 에 따라 우 리 는 이 클래스 가 설정 류 (즉, 레이 블 @Configuration
를 처리 해 야 한 다 는 것 을 알 수 있 습 니 다.그럼 여기 서부 터 볼 게 요.
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List configCandidates = new ArrayList();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " beanDef);
}
}
//
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
//
if (configCandidates.isEmpty()) {
return;
}
// Order
Collections.sort(configCandidates, new Comparator() {
@Override
public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
});
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
//
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set candidates = new LinkedHashSet(configCandidates);
Set alreadyParsed = new HashSet(configCandidates.size());
do {
ConfigurationClassParser parse ,
parser.parse(candidates);
parser.validate();
Set configClasses = new LinkedHashSet(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set oldCandidateNames = new HashSet(Arrays.asList(candidateNames));
Set alreadyParsedClasses = new HashSet();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
if (sbr != null) {
if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
이제
ConfigurationClassParser
류 에 들 어가 야 합 니 다.public void parse(Set configCandidates) {
this.deferredImportSelectors = new LinkedList();
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" bd.getBeanClassName() "]", ex);
}
}
processDeferredImportSelectors();
}
// BeanDefinition , processConfigurationClass
protected final void parse(String className, String beanName) throws IOException {
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName));
}
protected final void parse(Class> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
설정 류 는 세 가지 형식의 존재 일 수 있 습 니 다. 이 세 가지 형식의 Bean 은 조작 에 있어 서 일부 가 다 르 지만 대부분 똑 같 기 때문에 Spring 은 이런 모델 로 처리 합 니 다.디자인 이 참 좋다 는 감탄 을 하지 않 을 수 없 었 어 요.
이어서 아래 를 보다
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
// Configuration import
// , import , 。 ,
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
return;
}
else {
this.configurationClasses.remove(configClass);
for (Iterator it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
if (configClass.equals(it.next())) {
it.remove();
}
}
}
}
SourceClass sourceClass = asSourceClass(configClass);
do {
//
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
//
processMemberClasses(configClass, sourceClass);
// @PropertySource
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" sourceClass.getMetadata().getClassName()
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// @ComponentScan
Set componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Import ,
processImports(configClass, sourceClass, getImports(sourceClass), true);
// @ImportResource
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getStringArray("locations");
Class extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// @Bean
Set beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
//
processInterfaces(configClass, sourceClass);
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
return null;
}
이곳 을 보 니 갑자기 신대륙 이 발 견 된 것 같 군요. 우리 가 자주 보 는
@Bean
, @ImportResource
, @Import
, @ComponentScan
, @PropertySource
모두 이곳 에서 처 리 했 군요.우리 의 중점 은 역시
@Import
에 두 어야 한다. 다른 주석 에 관심 이 있 는 몇 명의 학우 들 은 스스로 연구 해 볼 수 있다.private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection importCandidates, boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// ImportSelector
if (candidate.isAssignable(ImportSelector.class)) {
Class> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
// ImportBeanDefinitionRegistrar
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// import Configuration
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class ["
configClass.getMetadata().getClassName() "]", ex);
}
finally {
this.importStack.pop();
}
}
}