• Spring源码阅读(六)-ConfigurationClassPostProcessor


    说明

    ConfigurationClassPostProcessor.class主要解决的问题就是去处理spring定义的一些配置注解,例如@Configuration@Import@ComponentScan@Bean等。

    源码调用时机从https://www.cnblogs.com/LQBlog/p/13954302.html#autoid-5-0-0 <36>开始看

    类图

    可以发现间接继承org.springframework.beans.factory.config.BeanFactoryPostProcessor

    <1>

    org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors

    public static void invokeBeanFactoryPostProcessors(
                ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
    
            // Invoke BeanDefinitionRegistryPostProcessors first, if any.
            Set<String> processedBeans = new HashSet<>();
    
            if (beanFactory instanceof BeanDefinitionRegistry) {
                BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
                List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
    
                for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
                    //针对类型是BeanDefinitionRegistryPostProcessor 先调用postProcessBeanDefinitionRegistry
                    if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                        BeanDefinitionRegistryPostProcessor registryProcessor =
                                (BeanDefinitionRegistryPostProcessor) postProcessor;
    //<2> registryProcessor.postProcessBeanDefinitionRegistry(registry);
    //加入集合 后续调用BeanFactoryPostProcessor postProcessBeanFactory 方法 registryProcessors.add(registryProcessor); } else { //加入集合 后续调用BeanFactoryPostProcessor postProcessBeanFactory 方法 regularPostProcessors.add(postProcessor); } } // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the bean factory post-processors apply to them! // Separate between BeanDefinitionRegistryPostProcessors that implement // PriorityOrdered, Ordered, and the rest. List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>(); /** * 从容器获取BeanDefinitionRegistryPostProcessor 的beanNames * 这个接口是BeanFactoryPostProcessor子接口 * 代表实现就是ConfigurationClassPostProcessor */ String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { //需要实现了PriorityOrdered接口 if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { //获得bean并加入集合后续调用 currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } //执行先后排序 sortPostProcessors(currentRegistryProcessors, beanFactory); //因为BeanDefinitionRegistryPostProcessor 也是BeanFactoryPostProcessor 子接口加入集合 后面调用postProcessBeanFactory方法 registryProcessors.addAll(currentRegistryProcessors); //<2>调用BeanDefinitionRegistryPostProcessor接口 postProcessBeanDefinitionRegistry方法 invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); //清空 currentRegistryProcessors.clear(); // 再次获取未获取的BeanDefinitionRegistryPostProcessor 没有实现PriorityOrdered 但是确打了@Ordered 注解的 //这里说明PriorityOrdered接口优先级要大于Ordered postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } //执行先后排序 sortPostProcessors(currentRegistryProcessors, beanFactory); //因为BeanDefinitionRegistryPostProcessor 也是BeanFactoryPostProcessor 子接口加入集合 后面调用postProcessBeanFactory方法 registryProcessors.addAll(currentRegistryProcessors); //<2>调用BeanDefinitionRegistryPostProcessor接口 postProcessBeanDefinitionRegistry方法 invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); //清空 currentRegistryProcessors.clear(); // 最后调用未实现PriorityOrdered 又没有打@Ordered 注解的 优先级最低 boolean reiterate = true; while (reiterate) { reiterate = false; postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (!processedBeans.contains(ppName)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); reiterate = true; } } //执行先后排序 sortPostProcessors(currentRegistryProcessors, beanFactory); //因为BeanDefinitionRegistryPostProcessor 也是BeanFactoryPostProcessor 子接口加入集合 后面调用postProcessBeanFactory方法 registryProcessors.addAll(currentRegistryProcessors); //<2>调用BeanDefinitionRegistryPostProcessor接口 postProcessBeanDefinitionRegistry方法 invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); //清空 currentRegistryProcessors.clear(); } // 先执行BeanDefinitionRegistryPostProcessor(也实现也实现了BeanFactoryProcessor) 接口的postProcessBeanFactory invokeBeanFactoryPostProcessors(registryProcessors, beanFactory); //再执行非BeanDefinitionRegistryPostProcessor类型接口的postProcessBeanFactory invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory); } else { // 单纯执行beanFactoryPostProcessors的接口的postProcessBeanFactory invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory); } //----------------------------------------这下面主要是直接调用未在以上执行BeanFactoryPostProcessor的实现类------------------- String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); // Separate between BeanFactoryPostProcessors that implement PriorityOrdered, // Ordered, and the rest. List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>(); List<String> orderedPostProcessorNames = new ArrayList<>(); List<String> nonOrderedPostProcessorNames = new ArrayList<>(); for (String ppName : postProcessorNames) { //如果已经执行过了 直接跳过 if (processedBeans.contains(ppName)) { // skip - already processed in first phase above } //根据优先级排序执行 else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class)); } else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { orderedPostProcessorNames.add(ppName); } else { nonOrderedPostProcessorNames.add(ppName); } } // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered. sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    //<8> invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
    // Next, invoke the BeanFactoryPostProcessors that implement Ordered. List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size()); for (String postProcessorName : orderedPostProcessorNames) { orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); } sortPostProcessors(orderedPostProcessors, beanFactory); invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); // Finally, invoke all other BeanFactoryPostProcessors. List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size()); for (String postProcessorName : nonOrderedPostProcessorNames) { nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); } invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory); // Clear cached merged bean definitions since the post-processors might have // modified the original metadata, e.g. replacing placeholders in values... beanFactory.clearMetadataCache(); }

    <2>

        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
            //获得唯一id
            int registryId = System.identityHashCode(registry);
            //避免重复执行
            if (this.registriesPostProcessed.contains(registryId)) {
                throw new IllegalStateException(
                        "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
            }
            if (this.factoriesPostProcessed.contains(registryId)) {
                throw new IllegalStateException(
                        "postProcessBeanFactory already called on this post-processor against " + registry);
            }
            this.registriesPostProcessed.add(registryId);
            //<3>
            processConfigBeanDefinitions(registry);
        }

    <3>

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
            List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
            String[] candidateNames = registry.getBeanDefinitionNames();
    
            for (String beanName : candidateNames) {
                BeanDefinition beanDef = registry.getBeanDefinition(beanName);
                if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
                    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));
                }
            }
    
            // Return immediately if no @Configuration classes were found
            if (configCandidates.isEmpty()) {
                return;
            }
    
            // Sort by previously determined @Order value, if applicable
            configCandidates.sort((bd1, bd2) -> {
                int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return Integer.compare(i1, i2);
            });
    
            // Detect any custom bean name generation strategy supplied through the enclosing application context
            SingletonBeanRegistry sbr = null;
            if (registry instanceof SingletonBeanRegistry) {
                sbr = (SingletonBeanRegistry) registry;
                if (!this.localBeanNameGeneratorSet) {
                    BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
                            AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
                    if (generator != null) {
                        this.componentScanBeanNameGenerator = generator;
                        this.importBeanNameGenerator = generator;
                    }
                }
            }
    
            if (this.environment == null) {
                this.environment = new StandardEnvironment();
            }
    
            // 委托给ConfigurationClassParser 执行
            ConfigurationClassParser parser = new ConfigurationClassParser(
                    this.metadataReaderFactory, this.problemReporter, this.environment,
                    this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
            Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
            Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
            do {
                /**
                 * <4>解析后会保存所有需要导入的类元数据封装到configClasses
                 * 如Import解析  对应的configClass 打了@Bean方法则封装到ConfigClassBeanMethod
                 */
                parser.parse(candidates);
                parser.validate();
    
                Set<ConfigurationClass> 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());
                }
                //告诉reader后续加载这些类 从而实现了@Bean @Import的初始化
                this.reader.loadBeanDefinitions(configClasses);
                alreadyParsed.addAll(configClasses);
    
                candidates.clear();
                if (registry.getBeanDefinitionCount() > candidateNames.length) {
                    String[] newCandidateNames = registry.getBeanDefinitionNames();
                    Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                    Set<String> 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());
    
            // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
            if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
                sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
            }
    
            if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
                // Clear cache in externally provided MetadataReaderFactory; this is a no-op
                // for a shared cache since it'll be cleared by the ApplicationContext.
                ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
            }
        }

     <4>

    public void parse(Set<BeanDefinitionHolder> configCandidates) {
            for (BeanDefinitionHolder holder : configCandidates) {
                BeanDefinition bd = holder.getBeanDefinition();
                try {
                    //<5>针对不同的beanDefinition调用不同的parse方法
                    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);
                }
            }
    
            this.deferredImportSelectorHandler.process();
        }

    <5>

        protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
            //<6>
            processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
        }

    <6>

    protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
            if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
                return;
            }
    
            ConfigurationClass existingClass = this.configurationClasses.get(configClass);
            if (existingClass != null) {
                if (configClass.isImported()) {
                    if (existingClass.isImported()) {
                        existingClass.mergeImportedBy(configClass);
                    }
                    // Otherwise ignore new imported config class; existing non-imported class overrides it.
                    return;
                }
                else {
                    // Explicit bean definition found, probably replacing an import.
                    // Let's remove the old one and go with the new one.
                    this.configurationClasses.remove(configClass);
                    this.knownSuperclasses.values().removeIf(configClass::equals);
                }
            }
    
            // Recursively process the configuration class and its superclass hierarchy.
            SourceClass sourceClass = asSourceClass(configClass, filter);
            do {
                //<7>
                sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
            }
            while (sourceClass != null);
    
            this.configurationClasses.put(configClass, configClass);
        }

    <7>

    protected final SourceClass doProcessConfigurationClass(
                ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
                throws IOException {
    
            /**
             * 是否打了@Comment注解,注@Configuration 也打了@Comment注解
             * 这里主要是为了加载内部内 内部类也执行doProcessConfigurationClass
             */
            if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
                // Recursively process any member (nested) classes first
                processMemberClasses(configClass, sourceClass, filter);
            }
    
            // 如果打了PropertySources注解 则加载对应配置
            for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                    sourceClass.getMetadata(), PropertySources.class,
                    org.springframework.context.annotation.PropertySource.class)) {
                if (this.environment instanceof ConfigurableEnvironment) {
                    processPropertySource(propertySource);
                }
                else {
                    logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                            "]. Reason: Environment must implement ConfigurableEnvironment");
                }
            }
    
            //如果打了ComponentScans 则放入componentScanParser 后续扫描包加载到容器
            Set<AnnotationAttributes> 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<BeanDefinitionHolder> 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());
                        }
                    }
                }
            }
    
            // <8>执行@Import相关初始化
            processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    
            // 针对打了 @ImportResourc 导入对应的自营
            AnnotationAttributes importResource =
                    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
            if (importResource != null) {
                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);
                }
            }
    
            // 获得打了@BeanMhod的方法 加入beanMehod
            Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
            for (MethodMetadata methodMetadata : beanMethods) {
                configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
            }
    
            // 针对接口的默认实现 default方法打了@Bean的加入addBeanMethod
            processInterfaces(configClass, sourceClass);
    
            // Process superclass, if any
            if (sourceClass.getMetadata().hasSuperClass()) {
                String superclass = sourceClass.getMetadata().getSuperClassName();
                if (superclass != null && !superclass.startsWith("java") &&
                        !this.knownSuperclasses.containsKey(superclass)) {
                    this.knownSuperclasses.put(superclass, configClass);
                    // Superclass found, return its annotation metadata and recurse
                    return sourceClass.getSuperClass();
                }
            }
    
            // No superclass -> processing is complete
            return null;
        }

    <8>

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
                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)) {
                            // Candidate class is an ImportSelector -> delegate to it to determine imports
                            Class<?> candidateClass = candidate.loadClass();
                            ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                    this.environment, this.resourceLoader, this.registry);
                            Predicate<String> selectorFilter = selector.getExclusionFilter();
                            if (selectorFilter != null) {
                                exclusionFilter = exclusionFilter.or(selectorFilter);
                            }
                            if (selector instanceof DeferredImportSelector) {
                                this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                            }
                            else {
                                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                                Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                                processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                            }
                        }
                        //针对ImportBeanDefinitionRegistrar接口
                        else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                            // Candidate class is an ImportBeanDefinitionRegistrar ->
                            // delegate to it to register additional bean definitions
                            Class<?> candidateClass = candidate.loadClass();
                            ImportBeanDefinitionRegistrar registrar =
                                    ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                            this.environment, this.resourceLoader, this.registry);
                            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                        }
                        else {//普通类
                            // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                            // process it as an @Configuration class
                            this.importStack.registerImport(
                                    currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                            processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                        }
                    }
                }
                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();
                }
            }
        }

     <9>

    @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            int factoryId = System.identityHashCode(beanFactory);
            if (this.factoriesPostProcessed.contains(factoryId)) {
                throw new IllegalStateException(
                        "postProcessBeanFactory already called on this post-processor against " + beanFactory);
            }
            this.factoriesPostProcessed.add(factoryId);
            if (!this.registriesPostProcessed.contains(factoryId)) {
                // BeanDefinitionRegistryPostProcessor hook apparently not supported...
                // Simply call processConfigurationClasses lazily at this point then.
                processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
            }
    
            //判断是否对@Configuration类进行cglit代理
            enhanceConfigurationClasses(beanFactory);
            //主要是处理ImportAware的回调的BeanPostProcessor
            beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
        }
  • 相关阅读:
    gulp之压缩合并MD5清空替换加前缀以及自动编译自动刷新浏览器大全
    HTML5之文件API
    Angular2之路由学习笔记
    nodejs之主机不能访问到虚拟机的web服务器
    学习CSS3动画(animation)
    jQuery之ajax错误调试分析
    Angular2之管道学习笔记
    css3之3D魔方动画(小白版)
    关于二维网格导入autodyn的问题
    两个橡胶球自由落体撞击弹性板
  • 原文地址:https://www.cnblogs.com/LQBlog/p/15485225.html
Copyright © 2020-2023  润新知