Spring @ Import 주해 원본 분석

간단 한 소개
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);
     
}

이 인 터 페 이 스 를 볼 수 있 는 유일한 방법 은 두 개의 매개 변수 가 있다.
  • AnnotationMetadata: 이 매개 변 수 를 통 해 클래스 의 메타 데이터 정 보 를 얻 을 수 있 습 니 다
  • BeanDefinitionRegistry: 이 매개 변 수 를 통 해 IOC 용 기 를 조작 할 수 있 습 니 다
  • 우 리 는 하나의 종 류 를 사용 하여 이 인 터 페 이 스 를 실현 할 수 있다.
    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();
            }
        }
    }

    좋은 웹페이지 즐겨찾기