注册Bean方式之@Import源码分析

You need not put all your @Configuration into a single class. The @Import annotation can be used to import additional configuration classes. Alternatively, you can use @ComponentScan to automatically pick up all Spring components, including @Configuration classes.

通过Java注册Bean的几种方式:

  1. @Configuration
  2. @Import
  3. @ComponentScan
  4. @ImportResource

源码:org.springframework.context.annotation.ConfigurationClassParser#processImports

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
收集Import类

比如对于以下类

/**
 * Configuration to import the {@link BootstrapImportSelector} configuration.
 *
 * @author Spencer Gibb
 */
@Configuration
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

通过getImports返回的就是BootstrapImportSelector.class,当然实际操作层面要考虑这个类上面也可能存在非@Import注解上也可能存在@Import注解,比如各种@Enable
比如说一个很普通的Spring Boot的启动类

@SpringBootApplication
public class BeanDemoStartMain {

    public static void main(String[] args) {
        SpringApplication.run(BeanDemoStartMain.class);
    }

}

其中包含的import如下所示:
在这里插入图片描述

这种情况下通过递归进行解决。因此,对于@Import注解直接获取注解属性,而对于非@Import注解需要遍历元数据查找是否存在包含@Import注解(也叫注解的注解)。

另外收集之后不是直接返回这个类,而是进行了包装。包装的类型的为org.springframework.context.annotation.ConfigurationClassParser.SourceClass。这样可以保存一些注解元数据信息,而不仅仅是类型。

/**
 * Returns {@code @Import} class, considering all meta-annotations.
 */
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
	Set<SourceClass> imports = new LinkedHashSet<>();
	Set<SourceClass> visited = new LinkedHashSet<>();
	collectImports(sourceClass, imports, visited);
	return imports;
}

递归收集一个类上面待Import的类,除了当前类上main的@Import注解,其他的注解比如EnableConfigurationProperties,也包含了@Import注解,所以处理起来必须进行遍历
在这里插入图片描述

/**
 * Recursively collect all declared {@code @Import} values. Unlike most
 * meta-annotations it is valid to have several {@code @Import}s declared with
 * different values; the usual process of returning values from the first
 * meta-annotation on a class is not sufficient.
 * <p>For example, it is common for a {@code @Configuration} class to declare direct
 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
 * annotation.
 * @param sourceClass the class to search
 * @param imports the imports collected so far
 * @param visited used to track visited classes to prevent infinite recursion
 * @throws IOException if there is any problem reading metadata from the named class
 */
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
		throws IOException {

	if (visited.add(sourceClass)) {
		for (SourceClass annotation : sourceClass.getAnnotations()) {
			String annName = annotation.getMetadata().getClassName();
			// 首先递归处理其他可能包含@Import的注解
			if (!annName.equals(Import.class.getName())) {
				collectImports(annotation, imports, visited);
			}
		}
		// 获取Import注解的值,比如上面的BootstrapImportSelector
		imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
	}
}
处理Import类

configClass:BootstrapImportSelectorConfiguration,包含@Configuration的那个类
SourceClass :BootstrapImportSelectorConfiguration,可能是configClass,也可能是内部类
importCandidates:通过以上步骤收集到的需要进行Import的类
checkForCircularImports:默认情况下为true 可能存在A import B,B import C,C import A这种情况,这里是不支持这种情况的

  • 首先要考虑解决链式循环引入的问题
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
		Collection<SourceClass> 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) {
			   // 遍历处理每一个candidate
			   ...
			}
		}
		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();
		}
	}
}
  • 对于Import的类有两种类型ImportSelectorImportBeanDefinitionRegistrar,如果不是以上两种类型,按照@Configuration class进行处理

  • ImportSelector类型

if (candidate.isAssignable(ImportSelector.class)) {
	// Candidate class is an ImportSelector -> delegate to it to determine imports
	// 从包装类中获取类类型
	Class<?> candidateClass = candidate.loadClass();
	// 实例化为一个ImportSelector类
	ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
	// 对于ImportSelector是用于注册其他类,需要依靠一些工具类,需要在注册其他类之前首先赋值
	ParserStrategyUtils.invokeAwareMethods(
			selector, this.environment, this.resourceLoader, this.registry);
	if (selector instanceof DeferredImportSelector) {
		this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
	}
	else {
	    // 如果非DeferredImportSelector 则现在就获取用户定义的需要进行import的类(selectImports)
		String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
		Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
		processImports(configClass, currentSourceClass, importSourceClasses, 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 =
			BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
	ParserStrategyUtils.invokeAwareMethods(
			registrar, this.environment, this.resourceLoader, this.registry);
	configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}

区别于类型ImportSelector(直接查找对应的Import类,然后进行各种解析添加到org.springframework.context.annotation.ConfigurationClassParser#configurationClasses)属性当中,此处直接添加到org.springframework.context.annotation.ConfigurationClass#importBeanDefinitionRegistrars属性当中。然后再添加到configurationClasses属性当中。
在这里插入图片描述

  • 按照@Configuration class进行处理

(这种情况是用户自定义的ImportSelector实现类的selectImports方法中需要import的那个类,可能不是ImportSelectorImportBeanDefinitionRegistrar类型,但是包含@Configuration注解,比如BootstrapImportSelector返回的就是这样一些BootstrapConfiguration类)

else {
	// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
	// process it as an @Configuration class
	// A import B -> 注册为 B —> A
	this.importStack.registerImport(
			currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
	processConfigurationClass(candidate.asConfigClass(configClass));
}

将当前被导入的类作为一个ConfigurationClass, importedBy是那个最初引起导入的类。比如A导致B被引入,则此处B将作为一个ConfigurationClass被处理,而A作为importedBy作为参数。对于不是通过@Import导入的配置类,这个参数则是空的。比如上面提到的BootstrapImportSelectorConfiguration.
在这里插入图片描述

public ConfigurationClass asConfigClass(ConfigurationClass importedBy) {
	if (this.source instanceof Class) {
		return new ConfigurationClass((Class<?>) this.source, importedBy);
	}
	return new ConfigurationClass((MetadataReader) this.source, importedBy);
}
解决循环引入的问题

通过一个栈+map的数据结构(org.springframework.context.annotation.ConfigurationClassParser.ImportStack)解决
在这里插入图片描述
在这里插入图片描述

private boolean isChainedImportOnStack(ConfigurationClass configClass) {
	if (this.importStack.contains(configClass)) {
		String configClassName = configClass.getMetadata().getClassName();
		AnnotationMetadata importingClass = this.importStack.getImportingClassFor(configClassName);
		while (importingClass != null) {
			if (configClassName.equals(importingClass.getClassName())) {
				return true;
			}
			importingClass = this.importStack.getImportingClassFor(importingClass.getClassName());
		}
	}
	return false;
}

然后处理每个configClass的时候都会先入栈,然后如果这个configClass引入了其他的@Configuration class,还需要注册到map当中。这里的importingClass是最初的那个configClass类(比如类BootstrapImportSelectorConfiguration),而importedClass是那个被导入的类(比如用户selectImport方法导入的PropertySourceBootstrapConfiguration类)。这里是将被导入的那个类作为key的。比如A(importingClass)导致B被Import(importedClass),map中保存数据为B->A,对应的栈里面保存的是A

private final MultiValueMap<String, AnnotationMetadata> imports = new LinkedMultiValueMap<>();

public void registerImport(AnnotationMetadata importingClass, String importedClass) {
	this.imports.add(importedClass, importingClass);
}
SourceClass包装类

通过包装可以保存元数据信息

/**
 * Simple wrapper that allows annotated source classes to be dealt with
 * in a uniform manner, regardless of how they are loaded.
 */
private class SourceClass implements Ordered {

	private final Object source;  // Class or MetadataReader

	private final AnnotationMetadata metadata;
}		
public Class<?> loadClass() throws ClassNotFoundException {
	if (this.source instanceof Class) {
		return (Class<?>) this.source;
	}
	String className = ((MetadataReader) this.source).getClassMetadata().getClassName();
	return ClassUtils.forName(className, resourceLoader.getClassLoader());
}
导入工具类

因为对于ImportSelector这些类是用来真实Import其他类的,有时候需要一些容器环境信息,比如类加载器、资源加载器、bean工厂等。就比如BootstrapImportSelector就需要Environment信息,根据当前环境中的参数配置决定是否真实需要Import某个类。
org.springframework.context.annotation.ParserStrategyUtils#invokeAwareMethods

/**
 * Invoke {@link BeanClassLoaderAware}, {@link BeanFactoryAware},
 * {@link EnvironmentAware}, and {@link ResourceLoaderAware} contracts
 * if implemented by the given object.
 */
public static void invokeAwareMethods(Object parserStrategyBean, Environment environment,
		ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {

	if (parserStrategyBean instanceof Aware) {
		if (parserStrategyBean instanceof BeanClassLoaderAware) {
			ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
					((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
			if (classLoader != null) {
				((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader);
			}
		}
		if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
			((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry);
		}
		if (parserStrategyBean instanceof EnvironmentAware) {
			((EnvironmentAware) parserStrategyBean).setEnvironment(environment);
		}
		if (parserStrategyBean instanceof ResourceLoaderAware) {
			((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader);
		}
	}
}
DeferredImportSelector解决bean注入的一个顺序问题

这种类型的类通过DeferredImportSelectorHandler特殊处理,为啥呢?因为通过Import注入的类可能是需要在一定的条件下才会进行注入的。比如BootstrapImportSelector就是用于读取spring.factories文件中对应key值为BootstrapConfiguration的配置类。比如以下这个配置类

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
	public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
	}
}

就存在@ConditionalOnMissingBean这样的注解,需要在全局条件下(所有待注册的Bean都注册完了的情况下才可以)

A variation of ImportSelector that runs after all @Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are @Conditional.
Implementations can also extend the org.springframework.core.Ordered interface or use the org.springframework.core.annotation.Order annotation to indicate a precedence against other DeferredImportSelectors.
Implementations may also provide an import group which can provide additional sorting and filtering logic across different selectors.

  1. 会在所有的@Configurationbeans都处理之后再进行导入,主要场景就是@Conditional,比如一个bean要是在不存在当前类型bean的时候才导入org.springframework.boot.autoconfigure.condition.ConditionalOnBean
  2. 这个类的实现者可以实现Ordered接口或者添加Order注解来实现优先级问题
  3. 进行分组以提供额外的排序或者过滤逻辑
private final DeferredImportSelectorHandler deferredImportSelectorHandler = new DeferredImportSelectorHandler();

if (selector instanceof DeferredImportSelector) {
	this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}

org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#handle

/**
 * Handle the specified {@link DeferredImportSelector}. If deferred import
 * selectors are being collected, this registers this instance to the list. If
 * they are being processed, the {@link DeferredImportSelector} is also processed
 * immediately according to its {@link DeferredImportSelector.Group}.
 * @param configClass the source configuration class
 * @param importSelector the selector to handle
 */
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
	// 首先进行包装
	DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
			configClass, importSelector);
	// 这个deferredImportSelectors是一个包装类的列表,初始化为空的列表,但是不可能为null,这段代码不知道有啥用 		
	if (this.deferredImportSelectors == null) {
		DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
		handler.register(holder);
		handler.processGroupImports();
	}
	else {
		// 添加到包装类的列表中后续再处理
		this.deferredImportSelectors.add(holder);
	}
}

在所有的configCandidates都进行parse之后就会进行DeferredImportSelector的处理了。通过以上的步骤,这些类已经添加到了deferredImportSelectors列表当中,下面就是从列表中取出来进行处理了。
在这里插入图片描述

在这里插入图片描述

org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#process

public void process() {
	List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
	this.deferredImportSelectors = null;
	try {
		if (deferredImports != null) {
			DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
			// 进行排序
			deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
			// 注册到DeferredImportSelectorGroupingHandler中
			deferredImports.forEach(handler::register);
			// 处理分组结果
			handler.processGroupImports();
		}
	}
	finally {
		this.deferredImportSelectors = new ArrayList<>();
	}
}

注册到DeferredImportSelectorGroupingHandler,一个LinkedHashMap,一个普通HashMap

private class DeferredImportSelectorGroupingHandler {

	private final Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();

	private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();

	public void register(DeferredImportSelectorHolder deferredImport) {
		// 获取分组 默认的DeferredImportSelector接口实现为null 也就是自己作为一组
		Class<? extends Group> group = deferredImport.getImportSelector()
				.getImportGroup();
		// 默认情况下(group==null)则使用默认的分组		
		DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
				(group != null ? group : deferredImport),
				key -> new DeferredImportSelectorGrouping(createGroup(group)));
		// 将当前对象加入到分组成员当中		
		grouping.add(deferredImport);
		// 添加到容器中
		this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
				deferredImport.getConfigurationClass());
	}
}

创建组并包装为DeferredImportSelectorGrouping对象

private Group createGroup(@Nullable Class<? extends Group> type) {
	// 采用默认的分组 DefaultDeferredImportSelectorGroup
	Class<? extends Group> effectiveType = (type != null ? type
			: DefaultDeferredImportSelectorGroup.class);
	// 进行实例化		
	Group group = BeanUtils.instantiateClass(effectiveType);
	// 设置一些关键环境参数和工具类
	ParserStrategyUtils.invokeAwareMethods(group,
			ConfigurationClassParser.this.environment,
			ConfigurationClassParser.this.resourceLoader,
			ConfigurationClassParser.this.registry);
	return group;
}

在这里插入图片描述

DeferredImportSelectorGrouping对象包含组信息和组员信息

private static class DeferredImportSelectorGrouping {
	// 组	
	private final DeferredImportSelector.Group group;
	// 组员
	private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();

}

在这里插入图片描述
进行分组Imports的处理
org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports

public void processGroupImports() {
	// 根据分组 获取Imports 一次进行处理
	for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
		grouping.getImports().forEach(entry -> {

这里的getImports

/**
 * Return the imports defined by the group.
 * @return each import with its associated configuration class
 */
public Iterable<Group.Entry> getImports() {
	for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
		this.group.process(deferredImport.getConfigurationClass().getMetadata(),
				deferredImport.getImportSelector());
	}
	return this.group.selectImports();
}

org.springframework.context.annotation.ConfigurationClassParser.DefaultDeferredImportSelectorGroup#process
此时才会真实调用到对应实现类的逻辑,可以看出前面做了很多的铺垫,最后才会真实执行自己定义的selectImports方法。

@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
	// 执行自己定义的selectImports方法并把需要import类添加到Group对象的imports列表当中
	for (String importClassName : selector.selectImports(metadata)) {
		this.imports.add(new Entry(metadata, importClassName));
	}
}

比如BootstrapImportSelector执行selectImports方法之后
在这里插入图片描述
依次处理上面获取到的需要Import的类

			// 首先获取到configurationClass
			ConfigurationClass configurationClass = this.configurationClasses.get(
					entry.getMetadata());
			try {
				// 进行import 此时不检查循环依赖了 这个方法就是一开始的那个方法
				processImports(configurationClass, asSourceClass(configurationClass),
						asSourceClasses(entry.getImportClassName()), false);
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
								configurationClass.getMetadata().getClassName() + "]", ex);
			}
		});
	}
}

这里DeferredImportSelector使用的是默认的org.springframework.context.annotation.DeferredImportSelector.Group,实现其实比较简单的,在process方法中解析元数据调用用户实现的selectImports方法获得需要进行注册的类信息存储到imports属性当中,而selectImports更是简单,直接返回这个imports属性。
其实以上的逻辑再整理一下, 无非就是通过DeferredImportSelectorGroupingselectImports,获取完成之后能就processImports(递归),但是它代理给了Group去做这件事,这个Group就是一个扩展点。用户在自己实现这个DeferredImportSelector的时候可以自己去定义这个Group实现类,然后针对特定的deferredImportSelector更细粒度的进行扩展,而不是仅仅返回一些需要注册的类信息。
比如org.springframework.boot.autoconfigure.AutoConfigurationImportSelector这个类,就覆盖了DeferredImportSelector接口中getImportGroup方法的默认实现。

@Override
public Class<? extends Group> getImportGroup() {
	return AutoConfigurationGroup.class;
}

在这里插入图片描述
在这里插入图片描述
读取spring.factories中的AutoConfigurationImportListener的实现类,然后发布AutoConfigurationImportEvent事件。

private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
	List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
	if (!listeners.isEmpty()) {
		AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
		for (AutoConfigurationImportListener listener : listeners) {
			invokeAwareMethods(listener);
			listener.onAutoConfigurationImportEvent(event);
		}
	}
}

protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
	return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}

在这里插入图片描述
在这里插入图片描述

主程序: ![图片说明](https://img-ask.csdn.net/upload/201607/11/1468227442_924549.png) 目录结构: ![图片说明](https://img-ask.csdn.net/upload/201607/11/1468227465_28252.png) applicationContext.xml: ![图片说明](https://img-ask.csdn.net/upload/201607/11/1468227487_415892.png) jar包: ![图片说明](https://img-ask.csdn.net/upload/201607/11/1468227505_842386.png) 报错: 七月 11, 2016 4:50:21 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4d405ef7: startup date [Mon Jul 11 16:50:21 CST 2016]; root of context hierarchy 七月 11, 2016 4:50:21 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [applicationContext.xml] Exception in thread "main" java.lang.IllegalArgumentException at org.springframework.asm.ClassReader.<init>(Unknown Source) at org.springframework.asm.ClassReader.<init>(Unknown Source) at org.springframework.asm.ClassReader.<init>(Unknown Source) at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:52) at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:80) at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:101) at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:76) at org.springframework.context.annotation.ConfigurationClassParser.getImports(ConfigurationClassParser.java:298) at org.springframework.context.annotation.ConfigurationClassParser.getImports(ConfigurationClassParser.java:300) at org.springframework.context.annotation.ConfigurationClassParser.getImports(ConfigurationClassParser.java:300) at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:230) at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:153) at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:130) at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:285) at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:223) at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:630) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:461) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83) at main.Test.main(Test.java:12)
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页