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

mac2022-06-30  97

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

最新回复(0)