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内部,这里也不展开了,感兴趣的可以自行查阅。