【Spring(十三)】ApplicationContext 容器体系 介绍

本文深入介绍了Spring容器体系,包括非Web、非注解容器如ClassPathXmlApplicationContext的实现原理,Web容器如XmlWebApplicationContext的工作机制,以及注解容器如AnnotationConfigApplicationContext的功能特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring容器集成体系很复杂,初看源码时很容易一头雾水,不过到目前为止,也看了不少Spring的源码了,这里对Spring容器体系做一个总结。这一篇介绍下高级容器部分。

我们先从基本的非web以及非注解的容器看起,比如说ClassPathXmlApplicationContext体系。

1.ApplicationContext

这是Spring高级容器的接口,继承了若干其他接口,这个接口定义清楚了Spring高级容器到底“高级”在哪了。除了具备低级容器的能力以外,还额外支持了国家化,消息机制,环境变量以及资源加载等特性。这些特性就不在这里意义展开了,可以翻看笔者其他博文。

2.AbstractApplicationContext

这是一个抽象类,直接实现了ApplicationContext的一些接口,诸如国际化、事件发布等功能,创建相应的代理类的实例变量。并且提供了高级容器的一些模板方法,子类只需要对特定部分提供实现即可,是一个骨架实现类。其中,最最重要的refresh方法就是在该类中是实现的。

3.AbstractRefreshableApplicationContext

该类继承了AbstractApplicationContext类,支持了多次refresh,这与其类名也是吻合的,refreshable,即可刷新的。那么是如何多次刷新的呢?核心代码如下:

	@Override
	protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

首先检测是否已经刷新了,如果是,就销毁之前的容器,然后重新创建容器。所以每一次refresh,都会创建行的内部容器。

该类并没有直接实现loadBeanDefinitions方法,bean定义的载入有是在子类中实现的,因为载入也是一个抽象的过程,可以是文件,可以是扫包或者是注解等等。

这里的refreshBeanFactory方法是在refresh的中的第二步调用的:

    @Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

            //省略
        }
    }

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		return getBeanFactory();
	}

4.AbstractRefreshableConfigApplicationContext

该类继承自上面的AbstractRefreshableApplicationContext,支持了设置配置文件的位置。核心代码:

public void setConfigLocation(String location) {
		setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
	}

	/**
	 * Set the config locations for this application context.
	 * <p>If not set, the implementation may use a default as appropriate.
	 */
	public void setConfigLocations(@Nullable String... locations) {
		if (locations != null) {
			Assert.noNullElements(locations, "Config locations must not be null");
			this.configLocations = new String[locations.length];
			for (int i = 0; i < locations.length; i++) {
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}

不同的容器类,配置文件的位置是不同的。

5.AbstractXmlApplicationContext

该类继承自AbstractRefreshableConfigApplicationContext类,是非web非注解容器的公共父类,主要实现了基于xml文件加载bean定义的逻辑,核心代码如下:

	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

6.ClassPathXmlApplicationContext与FileSystemXmlApplicationContext

这两个类都继承自上面的AbstractXmlApplicationContext,是非抽象类,可以直接使用。二者的区别是xml文件的加载位置不同,从类名就能看出。

因为核心功能在之前的类继承体系中已经都实现了,所以这两个类只是做了简单的扩展:

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}

二者的构造方法都是这样的,只是FileSystemXmlApplicationContext重新定义了location的解析方式:

	@Override
	protected Resource getResourceByPath(String path) {
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemResource(path);
	}

返回一个FileSystemResource。

默认的Resource是按照classpath解析的,所以ClassPathXmlApplicationContext不需要实现。

至此,高级容器中最最基础的两个容器的继承体系已经介绍完了。

 

下面看一下xml的web容器继承体系。

7.AbstractRefreshableWebApplicationContext

该类继承自AbstractRefreshableConfigApplicationContext,web环境下支持refresh的抽象类,refresh逻辑仍然沿用之前的逻辑,该类主要提供了基于ServletConfig的配置,例如DispatcherServlet中的init参数。

8.XmlWebApplicationContext

web环境下的基于xml的高级容器实现类,可以直接使用。

该类定义了beanDefinition的载入逻辑:

@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

其实和非web环境的载入逻辑一样。另外还定义了bean文件的位置:

	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}

这里load时会先取location:

	protected String[] getConfigLocations() {
		return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
	}
	protected String[] getDefaultConfigLocations() {
		if (getNamespace() != null) {
			return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
		}
		else {
			return new String[] {DEFAULT_CONFIG_LOCATION};
		}
	}

	/** Default config location for the root context. */
	public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

	/** Default prefix for building a config location for a namespace. */
	public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";

	/** Default suffix for building a config location for a namespace. */
	public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

这里分别可以支持web环境下的根容器和子容器的bean的加载,提供了默认的bean文件配置地址。

 

接着看下注解类的容器继承体系。

9.GenericApplicationContext

该类是注解类容器的父类,所以必须先看下这个类。GenericApplicationContext继承了AbstractApplicationContext接口,具备了高级容器的绝大多数能力。另外很关键的,它实现了BeanDefinitionRegistry接口,意味着它具备直接注册bean定义的能力。

看了java doc,前面介绍的容器类都限定了bean定义的形式,只能是xml文件,而这个类却不需要限定,可以支持任意的bean定义形式,这提供了灵活性,却也丧失了一定的便利性,因为,解析并注册bean定义的逻辑需要我们自己来写。

另外,该类还有一个特点,不是refreshable的,也就是不能refresh多次。它重写了之前的refreshBeanFactory方法:

	@Override
	protected final void refreshBeanFactory() throws IllegalStateException {
		if (!this.refreshed.compareAndSet(false, true)) {
			throw new IllegalStateException(
					"GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
		}
		this.beanFactory.setSerializationId(getId());
	}

可以看到,只能refresh一次,也仅仅是设置了一些状态,多次调用就会抛出一个异常。回顾之前的AbstractRefreshableApplicationContext容器,该方法的实现是每一次new一个新的内部容器并且销毁之前的,然后会调用一个抽象的loadbeandefinition方法。而GenericApplicationContext也并没有实现相应的loadbeandefinition方法。所以,该容器的使用模式是,先new出来,然后可以多次调用不同的reader来注册bean的定义,最后refresh。

java doc中提供了一个例子:

 GenericApplicationContext ctx = new GenericApplicationContext();
 XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
 xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
 PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
 propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
 ctx.refresh();
 
 MyBean myBean = (MyBean) ctx.getBean("myBean");

 

10.AnnotationConfigApplicationContext

该类是一个最基本的基于注解的高级容器实现类,可直接使用。它继承了上面的GenericApplicationContext类。该类主要提供了两种能力:

1)基于JavaConfig文件加载bean;

2)基于路径扫描并加载bean;

这两个能力分别由下面的两个组件实现:

// 基于java配置文件加载bean	
private final AnnotatedBeanDefinitionReader reader;
// 基于路径扫描bean
private final ClassPathBeanDefinitionScanner scanner;

但是,这两种方式都需要注解。1)需要搭配@Bean以及@ComponentScan注解;2)需要搭配@Component注解。

这也分别对应了两种构造函数:

	public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
		this();
		register(annotatedClasses);
		refresh();
	}
	public void register(Class<?>... annotatedClasses) {
		Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
		this.reader.register(annotatedClasses);
	}

这是加载java配置类的逻辑。主要是解析java配置类,并加载里面的bean。当然,最终都是通过一个PostProcessor来处理java的配置类的,具体在另一篇博文有介绍。

	public AnnotationConfigApplicationContext(String... basePackages) {
		this();
		scan(basePackages);
		refresh();
	}
	public void scan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		this.scanner.scan(basePackages);
	}

这是扫描bean的逻辑。具体实现在scaner内部,这里也不展开了,感兴趣的可以自行查阅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值