【Java】【系列篇】【Spring源码解析】【三】【体系】【ApplicationContext体系】

总概

ApplicationContext继承结构图

image-20221227141144610

ApplicationContext整体结构解析

1、非web应用(可配置执行期上下文)

如图:

image-20230105115753416

此时,再看结构图:

image-20230104155509018

2、web应用(web环境执行期上下文)

image-20230104164333163

顶层接口

ApplicationContext接口

结构图

image-20230105151330160

作用

  • BeanFactory:Spring 管理 Bean 的顶层接口,ApplicationContext 继承了 BeanFactory 的两个子类:HierarchicalBeanFactory 和 ListableBeanFactory。HierarchicalBeanFactory 是一个具有层级关系的 BeanFactory,拥有属性 parentBeanFactory。 ListableBeanFactory 实现了枚举方法可以列举出当前 BeanFactory 中所有的 bean 对象而不必根据 name 一个一个的获取。
  • ApplicationEventPublisher(事件发布):用于封装事件发布功能的接口,向事件监听器(Listener)发送事件消息。
  • ResourceLoader(让其有加载文件资源的能力):Spring 加载资源的顶层接口,用于加载资源文件。ApplicationContext 继承 ResourceLoader 的子类 ResourcePatternResolver,该接口是将 location 解析为 Resource 对象的策略接口。
  • MessageSource(消息,一般用于国际化的支持) :解析 message 的策略接口,用于支撑国际化等功能。
  • EnvironmentCapable (环境感知):用于获取 Environment 的接口。

关键词

执行期上下文

解析

image-20230105152428915

二级接口-ConfigurableApplicationContext接口

结构图

image-20230105142930440

作用

  1. 配置应用上下文的能力
  2. 启动/停止生命周期
  3. 关闭应用上下文,释放所有的资源和锁,这也包括摧毁所有缓存的单例的bean

关键词

Configurable:可配置的

解析

ApplicationContext接口本身是 read-only 的,所以子接口 ConfigurableApplicationContext就提供了如setID()、setParent()、setEnvironment()等方法,用来配置ApplicationContext。如图:
image-20230105141341384
我们可以看到,ApplicationContext里面都是get方法,所以ApplicationContext接口本身是 read-only 的。ConfigurableApplicationContextApplicationContext的基础上提供了配置应用上下文的能力。如图:
image-20230105142737895
而且ConfigurableApplicationContext不仅如此,他还继承了LifecycleCloseable
(1)Lifecycle定义了启动/停止生命周期等一些方法
image-20230105141815238
(2)Closeable接口用于关闭应用上下文,释放所有的资源和锁,这也包括摧毁所有缓存的单例的bean
image-20230105141946788

二级接口-WebApplicationContext接口

结构图

image-20230105145613101

作用

  1. 它允许从相对于Web根目录的路径中加载配置文件完成初始化工作。从WebApplicationContext中可以获取ServletContext引用,整个Web应用上下文对象将作为属性放置在ServletContext中,以便Web应用环境可以访问Spring上下文。
  2. WebApplicationContext还为Bean提供了三个新的作用域,request、session和application。其中两个参数HttpServletRequest:服务器从客户端拿去数据 ,HttpServletResponse:服务器向前台传送数据

关键词

web:web环境

解析

WebApplicationContext是实现ApplicationContext接口的子类。是专门为WEB应用准备的。
WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实例,也就是说它必须拥有Web容器的前提下才能完成启动的工作。WebApplicationContext提供了在web应用中的配置,接口提供了一个ServletContext getServletContext()用来获取ServletContext对象:

image-20230105150408193
注:有过Web开发经验的读者都知道可以在web.xml中配置自启动的Servlet或定义Web容器监听器(ServletContextListener),借助着两者中的任何一个,我们就可以启动Spring Web应用上下文的工作。

非web应用

三级抽象类-AbstractApplicationContext

JavaDoc

1.它是对ApplicationContext接口的抽象实现,不强制指定用于配置的存储类型(就是注解或者xml文件配置等的类型),只实现公共上下文功能。
2.它使用了模板设计模式,需要具体的子类去实现抽象方法
3.与纯BeanFactory不同,ApplicationContext用于检测在其内部bean工厂中定义的特殊bean:因此,他会自动注册在context中定义为bean的BeanFactoryPostProcessors、BeanPostProcessors和ApplicationListeners。
4.在context中MessageSource也可能作为bean被提供,其名字为"messageSource",否则,消息解析将会被委托给父类上下文。此外,在context中,对应用程序事件的广播由ApplicationEventMulticaster类型的"applicationEventMulticaster"的bean提供,否则,默认会使用SimpleApplicationEventMulticaster。
5.通过继承DefaultResourceLoader实现了资源的加载功能,主要作用:将非URL资源路径视为类路径资源(支持包含包路径的完整类路径资源名称),除非在子类重写getResourceByPath方法。

方法解析

ApplicationContext方法实现
省略
ConfigurableApplicationContext方法实现
在这里,实现了一个很重要的方法refresh(),并实现了里面绝大多数步骤的方法,但是也遗留了一些模板方法留给子类实现
BeanFactory方法实现
省略
ListableBeanFactory方法实现
省略
HierarchicalBeanFactory方法实现
@Override
@Nullable
public BeanFactory getParentBeanFactory() {
    return getParent();
}
@Override
public boolean containsLocalBean(String name) {
    return getBeanFactory().containsLocalBean(name);
}
//找到父的,若存在就返回 若存在父容器就存在父的BeanFactory
@Nullable
protected BeanFactory getInternalParentBeanFactory() {
    return (getParent() instanceof ConfigurableApplicationContext ?
            ((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent());
}
MessageSource方法实现
省略
ResourcePatternResolver方法实现
省略
Lifecycle方法实现
@Override
public void start() {
    getLifecycleProcessor().start();
    publishEvent(new ContextStartedEvent(this));
}

@Override
public void stop() {
    getLifecycleProcessor().stop();
    publishEvent(new ContextStoppedEvent(this));
}

@Override
public boolean isRunning() {
    return (this.lifecycleProcessor != null && this.lifecycleProcessor.isRunning());
}
自定义的抽象方法
// 子类必须实现这个方法:子类可以创建一个新的BeanFactory,或者直接指向自己一个已经hold的引用
// (GenericApplicationContext就是指向自己已经持有的)
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

// 销毁BeanFactory  this.beanFactory=null
protected abstract void closeBeanFactory();

// refreshBeanFactory这里肯定已经有工厂实例了,这里直接返回呗
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

-----------------------------------------------------------------------------------------

四级类-GenericApplicationContext

JavaDoc

1. 通用的ApplicationContext实现,它拥有一个单独的内部DefaultListableBeanFactory实例,并且不假设特定的bean定义格式。
2. 实现BeanDefinitionRegistry接口,以便允许对其应用任何bean definition reader(也可以不是XmlBeanDefinitionReader)。典型的用法是通过BeanDefinitionRegistry接口注册各种bean定义,然后调用AbstractApplicationContext。
3. 使用应用程序上下文语义(处理ApplicationContextAware,自动检测BeanFactoryPostProcessors等)初始化这些bean。
4. 与为每次刷新创建一个新的内部BeanFactory实例的其他ApplicationContext实现相反,此上下文的内部BeanFactory从一开始就可用,以便能够在其上注册bean定义。AbstractApplicationContext.refresh()只能被调用一次。

关键词

Generic:表现出它的通用

解析

在这里,GenericApplicationContext 持有一个DefaultListableBeanFactory实例

image-20230105160205617

GenericApplicationContext基本就是对DefaultListableBeanFactory 做了个简易的封装,几乎所有方法都是使用了DefaultListableBeanFactory的方法去实现
基于这个特性,GenericApplicationContext能够混合搭配从不同源头获取bean的定义信息,能加载各种配置文件,例如xml,properties等等,如图:

image-20230105162156221

最后在统一调用refresh()方法刷新,如下示例:

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");
..
注:详见官方文档

方法解析

ResourceLoader / ResourcePatternResolver方法实现
省略
AbstractApplicationContext模板方法的实现
@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());
}
@Override
protected void cancelRefresh(BeansException ex) {
    this.beanFactory.setSerializationId(null);
    super.cancelRefresh(ex);
}
@Override
protected final void closeBeanFactory() {
    this.beanFactory.setSerializationId(null);
}
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
    return this.beanFactory;
}
public final DefaultListableBeanFactory getDefaultListableBeanFactory() {
    return this.beanFactory;
}
@Override
public AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException {
    assertBeanFactoryActive();
    return this.beanFactory;
}
BeanDefinitionRegistry方法实现

可以看到注册BeanDefinition时,使用的是DefaultListableBeanFactory实例

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
    this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
}
@Override
public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
    this.beanFactory.removeBeanDefinition(beanName);
}
@Override
public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
    return this.beanFactory.getBeanDefinition(beanName);
}
@Override
public boolean isBeanNameInUse(String beanName) {
    return this.beanFactory.isBeanNameInUse(beanName);
}
@Override
public void registerAlias(String beanName, String alias) {
    this.beanFactory.registerAlias(beanName, alias);
}
@Override
public void removeAlias(String alias) {
    this.beanFactory.removeAlias(alias);
}
@Override
public boolean isAlias(String beanName) {
    return this.beanFactory.isAlias(beanName);
}
注册单个bean的方法
public <T> void registerBean(Class<T> beanClass, Object... constructorArgs) {
    registerBean(null, beanClass, constructorArgs);
}

public <T> void registerBean(@Nullable String beanName, Class<T> beanClass, Object... constructorArgs) {
    registerBean(beanName, beanClass, (Supplier<T>) null,
            bd -> {
                for (Object arg : constructorArgs) {
                    bd.getConstructorArgumentValues().addGenericArgumentValue(arg);
                }
            });
}
public final <T> void registerBean(Class<T> beanClass, BeanDefinitionCustomizer... customizers) {
    registerBean(null, beanClass, null, customizers);
}
public final <T> void registerBean(
        @Nullable String beanName, Class<T> beanClass, BeanDefinitionCustomizer... customizers) {

    registerBean(beanName, beanClass, null, customizers);
}
public final <T> void registerBean(Class<T> beanClass, Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
    registerBean(null, beanClass, supplier, customizers);
}
/**
 * Register a bean from the given bean class, using the given supplier for
 * obtaining a new instance (typically declared as a lambda expression or
 * method reference), optionally customizing its bean definition metadata
 * (again typically declared as a lambda expression).
 * <p>This method can be overridden to adapt the registration mechanism for
 * all {@code registerBean} methods (since they all delegate to this one).
 * @param beanName bean 名字。如果是 null 则会使用类名。
 * @param beanClass 要创建的 bean 类。
 * @param supplier 如果为 null,这会通过 beanClass 构造函数自动装配,否则会直接回调获取 bean 实例。
 * @param customizers 定制的意思。也就是说在 bean 还没有被注册之前可以通过该接口做定制化,例如:强制要求该 Bean 为单例或多例。
 */
public <T> void registerBean(@Nullable String beanName, Class<T> beanClass,@Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
    ClassDerivedBeanDefinition beanDefinition = new ClassDerivedBeanDefinition(beanClass);
    if (supplier != null) {
        beanDefinition.setInstanceSupplier(supplier);
    }
    for (BeanDefinitionCustomizer customizer : customizers) {
        // 如果你是使用某个构造方法来创建 Bean,这里的回调就会
        // 按照你的参数顺序将参数类型和值保存到当前的 BeanDefinition 中。
        //
        // 参数顺序指的是 constructorArgs 的顺序。
        // registerBean(@Nullable String beanName, Class<T> beanClass, Object... constructorArgs)
        customizer.customize(beanDefinition);
    }
    String nameToUse = (beanName != null ? beanName : beanClass.getName());
    // 该方法中会调用 DefaultListableBeanFactory#registerBeanDefinition 方法完成 Bean 注册。
    registerBeanDefinition(nameToUse, beanDefinition);
}

四级抽象类-AbstractRefreshableApplicationContext

JavaDoc

基础类 ApplicationContext,这个实现支持多次调用 refresh(),每次调用都创建一个新的内部 bean factory 实例。通常(但也不必然),例如一个 context 将凭借 配置地点被驱动,从而去加载bean definitions。
方法 loadBeanDefinitions 仅凭借子类实现,在每一次被刷新时调用。一个具体的实现应当去加载 bean definitions 到 DefaultListableBeanFactory 中
通常会委派到一个或多个 bean definition 加载器。

关键词

Refreshable:可重复刷新

解析

如果你追求定制化的ApplicationContext实现,可从AbstractRefreshableApplicationContext继承来实现从不同地方去加载Bean定义信息进来。
AbstractRefreshableApplicationContext实现了父类的方法refreshBeanFactory(),执行BeanFactory的“刷新”。
与GenericApplicationContext不同,AbstractRefreshableApplicationContext是可以多次刷新的。当然,每次刷新的时候,都会重新创建DefaultListableBeanFactory实例:
image-20230105170950248
当然,在这个方法里面加载BeanDefinition的方法,还需子类实现

image-20230105171731353

-----------------------------------------------------------------------------------------

五级类-AnnotationConfigApplicationContext

解析

AnnotationConfigApplicationContext继承自GenericApplicationContext,提供了注解配置(例如:Configuration、Component、inject等)和类路径扫描(scan方法)的支持,可以使用register(Class<?>... annotatedClasses)来注册一个一个的进行注册。实现了AnnotationConfigRegistry接口,来完成对注册配置的支持,只有两个方法:register和scan。内部使用AnnotatedBeanDefinitionReader来完成注解配置的解析,使用ClassPathBeanDefinitionScanner来完成类路径下的bean定义的扫描。

方法解析

构造器
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;

//AnnotatedBeanDefinitionReader是一个读取注解的Bean读取器,这里将this传了进去。
public AnnotationConfigApplicationContext() {
    // 创建读取器,传入 bean 定义信息的注册中心
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}
//从上面这个构造函数可以顺便提一句:如果你仅仅是这样ApplicationContext applicationContext = new AnnotationConfigApplicationContext()
// 容器是不会启动的(也就是不会执行refresh()的),这时候需要自己之后再手动启动容器

/**
 * Create a new AnnotationConfigApplicationContext with the given DefaultListableBeanFactory.
 * @param beanFactory the DefaultListableBeanFactory instance to use for this context
 */
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
    super(beanFactory);
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

/**
 * Create a new AnnotationConfigApplicationContext, deriving bean definitions
 * from the given component classes and automatically refreshing the context.
 * @param componentClasses one or more component classes &mdash; for example,
 * {@link Configuration @Configuration} classes
 */
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    // 调用空参构造
    this();
    // 注册主配置类,也就是 new AnnotationConfigApplicationContext(Config.class); 时传入的配置类
    register(componentClasses);
    // 刷新工厂十二大步
    refresh();
}

public AnnotationConfigApplicationContext(String... basePackages) {
    this();
    scan(basePackages);
    refresh();
}
AnnotationConfigRegistry方法实现
@Override
public void register(Class<?>... componentClasses) {
    Assert.notEmpty(componentClasses, "At least one component class must be specified");
    this.reader.register(componentClasses);
}

@Override
public void scan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    this.scanner.scan(basePackages);
}
将超类registerBean调用改编为AnnotatedBeanDefinitionReader
@Override
public <T> void registerBean(@Nullable String beanName, Class<T> beanClass,
        @Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
    this.reader.registerBean(beanClass, beanName, supplier, customizers);
}

五级类-GenericXmlApplicationContext

JavaDoc

内置XML支持,方便的应用程序上下文。这是ClassPathXmlApplicationcontext和FileSystemXmlApplicationContext的灵活替代方案,可以通过设置器进行配置,并最终调用AbstractApplicationcontext.refresh()来激活上下文。对于多个配置文件,后面文件中的bean定义将覆盖前面文件中定义的bean定义。可以利用这一点,通过附加到列表中的额外配置文件,有意地覆盖某些bean定义。

解析

如图:
image-20230106095250649
我们可以看到,GenericXmlApplicationContext和GenericApplicationContext的区别就在于,GenericApplicationContext可以自定义使用想要的BeanDefinitionReader,即可以混合搭配;而GenericXmlApplicationContext则内置了XmlBeanDefinitionReader,针对xml做了特异性处理,正如官方文档所说,是ClassPathXmlApplicationcontext和FileSystemXmlApplicationContext的灵活替代方案。

五级类-GenericGroovyApplicationContext

JavaDoc

GenericGroovyApplicationContext继承自GenericApplicationContext,实现了GroovyObject接口以便能够使用点的语法(.xx)取代getBean方法来获取bean。它主要用于Groovy bean的定义,与GenericXmlApplicationContext一样,它也能解析XML格式定义的bean。内部使用GroovyBeanDefinitionReader来完成groovy脚本和XML的解析。

解析

如图:
image-20230106095734301

和GenericXmlApplicationContext类似,针对Groovy语言做了特异性处理

五级类-ResourceAdapterApplicationContext

JavaDoc

ResourceAdapterApplicationContext继承自GenericApplicationContext,是为JCA(J2EE Connector Architecture)的ResourceAdapter设计的,主要用于传递BootstrapContext的实例给实现了BootstrapContextAware接口且由spring管理的bean。覆盖了postProcessBeanFactory方法来实现此功能。

解析

public class ResourceAdapterApplicationContext extends GenericApplicationContext {
	private final BootstrapContext bootstrapContext;
	/**
	 * Create a new ResourceAdapterApplicationContext for the given BootstrapContext.
	 * @param bootstrapContext the JCA BootstrapContext that the ResourceAdapter
	 * has been started with
	 */
	public ResourceAdapterApplicationContext(BootstrapContext bootstrapContext) {
		Assert.notNull(bootstrapContext, "BootstrapContext must not be null");
		this.bootstrapContext = bootstrapContext;
	}
	@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		beanFactory.addBeanPostProcessor(new BootstrapContextAwareProcessor(this.bootstrapContext));
		beanFactory.ignoreDependencyInterface(BootstrapContextAware.class);
		beanFactory.registerResolvableDependency(BootstrapContext.class, this.bootstrapContext);
		// JCA WorkManager resolved lazily - may not be available.
		beanFactory.registerResolvableDependency(WorkManager.class,
				(ObjectFactory<WorkManager>) this.bootstrapContext::getWorkManager);
	}
}

----------------------------------------------------------------------------------------

五级抽象类-AbstractRefreshableConfigApplicationContext

结构图

image-20230105172418426

作用

对指定的配置文件路径的公共的处理

解析

AbstractRefreshableConfigApplicationContext继承自AbstractRefreshableApplicationContext添加了对指定的配置文件路径的公共的处理,可以把他看作基于XML的应用上下文的基类。实现了如下的两个接口:

  • BeanNameAware用于设置上下文的bean的名称,只有一个方法:void setBeanName(String name)
  • InitializingBean用于上下文一切就绪后,如果还未刷新,那么就执行刷新操作,只有一个方法:void afterPropertiesSet()

image-20230105172652592

六级抽象类-AbstractXmlApplicationContext

JavaDoc

方便的ApplicationContext实现的基类,它从包含XmlBeanDefinitionReader的bean定义的XML文档中提取配置。
子类只需要实现getConfigResources和/或getConfigLocations方法。
此外,它们可能会覆盖getResourceByPath钩子,以特定于环境的方式解释相对路径,和/或getResourcePatternResolver来扩展模式解析

解析

AbstractXmlApplicationContext这个类主要实现了对xml配置文件的解析,主要是由两个函数loadBeanDefinitions(DefaultListableBeanFactory beanFactory)和loadBeanDefinitions(XmlBeanDefinitionReader reader)中实现。
(1)loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
在这里面使用了xml专属的读取器XmlBeanDefinitionReader

//主要是对spring注入配置文件的解析,主要使用到BeanDefinitionReader和BeanDefinitionDocumentReader两个接口相关的实现类  
@Override  
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {  
    //创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容 器使用该读取器读取Bean定义资源  
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);  
    //为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的    
    //祖先父类AbstractApplicationContext继承DefaultResourceLoader,因此,容器本身也是一个资源加载器   
    beanDefinitionReader.setEnvironment(this.getEnvironment());  
    beanDefinitionReader.setResourceLoader(this);  
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));  
    //当Bean读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制  
    initBeanDefinitionReader(beanDefinitionReader);  
    //Bean读取器真正实现加载的方法  
    loadBeanDefinitions(beanDefinitionReader);  
}  

(2)loadBeanDefinitions(XmlBeanDefinitionReader reader)

//Xml Bean读取器加载Bean定义资源   
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {  
    //获取Bean定义资源的定位  
    Resource[] configResources = getConfigResources();  
    if (configResources != null) {  
        //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位的Bean定义资源     
        reader.loadBeanDefinitions(configResources);  
    }  
    String[] configLocations = getConfigLocations();  
    if (configLocations != null) {  
        //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位的Bean定义资源      
        reader.loadBeanDefinitions(configLocations);  
    }  
}  

其他

//这里又使用了一个委托模式,调用子类的获取Bean定义资源定位的方法    
//getConfigResources() 用来获取程序启动读取的xml ,内部实现交给子类ClassPathXmlApplicationContext 来实现的
protected Resource[] getConfigResources() {  
    return null;  
}  

七级类-FileSystemXmlApplicationContext

解析

FileSystemXmlApplicationContext是独立的 XML application context,从文件系统或URL得到上下文定义文件。URL可以是绝对或者相对路径。普通路径将始终被解释为相对于当前VM工作目录,即使它们以斜杠/开头。原因:
image-20230105181035595

使用方式

1.没有盘符的是项目工作路径,即项目的根目录;
2.有盘符表示的是文件绝对路径.(若是绝对路径,“file:” 前缀可以缺省)
示例:
ApplicationContext ctx = newFileSystemXmlApplicationContext("spring-config.xml"); //当前路径加载单个配置文件
String[] locations = {"bean1.xml", "bean2.xml", "bean3.xml"};
ApplicationContext ctx = new FileSystemXmlApplicationContext(locations ); //同时加载多个配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:/project/bean.xml");//根据具体路径加载文件

使用注意

配置文件路径可以是一个具体的文件比如/myfiles/context.xml也可以是带有通配符的如/myfiles/*-context.xml,其可以通过getConfigLocations方法被覆盖。
在多个配置位置的情况下,后面的bean定义将覆盖前面加载的文件中定义的bean。这可以通过一个额外的XML文件故意覆盖某些bean定义。

方法解析

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
	public FileSystemXmlApplicationContext() {
	}
	public FileSystemXmlApplicationContext(ApplicationContext parent) {
		super(parent);
	}
	// 这个构造函数的configLocation包含的是BeanDefinition所在的文件路径
	// 从给定的xml 文件中加载BeanDefinition,然后自动刷新context
	public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}
	// 这个构造函数允许configLocations包含多个BeanDefinition的文件路径
	public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
		this(configLocations, true, null);
	}
 	// 这个构造函数在允许configLocation包含多个BeanDefinition的文件路径同时,还允许指定自己的双亲IOC容器
	public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
		this(configLocations, true, parent);
	}
	//这个构造函数在允许configLocation包含多个BeanDefinition的文件路径同时,还允许指定是否刷新context
	public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
		this(configLocations, refresh, null);
	}
	//  允许多个路径、允许指定是否刷新、允许指定自己的双亲IOC容器
	public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)throws BeansException {
		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}
	//将资源路径解析为文件系统路径 即使一个文件路径以/ 开头,它也会被视作为一个相对当前VM工作路径的相对路径
	@Override
	protected Resource getResourceByPath(String path) {
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemResource(path);
	}
}

七级类-ClassPathXmlApplicationContext

解析

ClassPathXmlApplicationContext 默认会去 classpath 路径下找。

使用方式

1. classpath:前缀是不需要的,默认就是指项目的classpath路径下面;
2. 如果要使用绝对路径,需要加上“file:”前缀表示这是绝对路径;

使用注意:

classPath 路径指的就是编译后的 classes 目录。可以理解读取resource目录下的配置文件

方法解析

@Nullable
private Resource[] configResources;
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
    throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}
public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent)throws BeansException {
    super(parent);
    Assert.notNull(paths, "Path array must not be null");
    Assert.notNull(clazz, "Class argument must not be null");
    this.configResources = new Resource[paths.length];
    for (int i = 0; i < paths.length; i++) {
        this.configResources[i] = new ClassPathResource(paths[i], clazz);
    }
    refresh();
}
@Override
@Nullable
protected Resource[] getConfigResources() {
    return this.configResources;
}

web应用

三级接口-ConfigurableWebApplicationContext

JavaDoc

由可配置的web应用程序上下文实现的接口。由ContextLoader和FrameworkServlet支持。
注意:这个接口的setter需要在调用继承自ConfigurableApplicationcontext的ConfigurableApplicationcontext refresh()方法之前调用。它们本身不会导致上下文的初始化。

描述

web应用上下文使用的接口,并没有定义太多的操作,主要和Servlet上下文及配置文件相关。
它一次性都继承了上面的两个二级接口,可以说是他俩的结合体

方法解析

public interface ConfigurableWebApplicationContext extends WebApplicationContext, ConfigurableApplicationContext {
	//引用上下文路径和/或servlet名称的ApplicationContext id的前缀。
	String APPLICATION_CONTEXT_ID_PREFIX = WebApplicationContext.class.getName() + ":";

	//工厂中的ServletConfig环境bean的名称。
	String SERVLET_CONFIG_BEAN_NAME = "servletConfig";
    //为这个web应用程序上下文设置ServletContext。不会导致上下文的初始化:需要在设置所有配置属性后调用refresh。
	void setServletContext(@Nullable ServletContext servletContext);
    //为这个web应用程序上下文设置ServletConfig。仅为属于特定Servlet的WebApplicationContext调用。
	void setServletConfig(@Nullable ServletConfig servletConfig);
	@Nullable
	ServletConfig getServletConfig();
    //设置此web应用程序上下文的名称空间,用于构建默认上下文配置位置。根web应用程序上下文没有名称空间。
	void setNamespace(@Nullable String namespace);
	@Nullable
	String getNamespace();

	// Spring配置文件的地方 比如默认位置为:/WEB-INF/applicationContext.xml
    //以init-param风格设置web应用程序上下文的配置位置,即使用逗号、分号或空格分隔不同的位置。如果没有设置,实现将根据需要对给定的名称空间或根web应用程序上下文使用默认值。
	void setConfigLocation(String configLocation);
	void setConfigLocations(String... configLocations);
	@Nullable
	String[] getConfigLocations();
}

四级类-GenericWebApplicationContext

JavaDoc

GenericApplicationcontext的子类,适用于web环境。
实现ConfigurablewebApplicationcontext,但不用于web中的声明性设置。xml。相反,它是为编程设置而设计的,例如用于构建嵌套上下文或在WebApplicationInitializers中使用。
如果您打算实现一个从配置文件读取bean定义的WebApplicationContext,请考虑从AbstractRefreshableWebApplicationContext派生,在loadBeanDefinitions方法的实现中读取bean定义。
将资源路径解释为servlet上下文资源,即web应用程序根目录下的路径。绝对路径,例如web应用根目录外的文件,可以通过“file:”URs访问,由AbstractApplicationContext实现。除了由AbstractApplicationContext检测到的特殊bean之外,该类还在上下文中检测一个名为“ThemeSource”的ThemeSource bean。

解析

如图:
image-20230106100830636
我们可以看到里面有ServletContext,这是针对web做出的处理

四级抽象类-AbstractRefreshableWebApplicationContext

描述

GenericWebApplicationContext继承自AbstractRefreshableConfigApplicationContext,实现了ConfigurableWebApplicationContext和ThemeSource接口,主要是用于web环境下。在web程序启动的时候,提供一个configLocations属性,通过ConfigurableWebApplicationContext接口来进行填充。子类化这个接口是很简单的,所有你所需要做的事情就是实现loadBeanDefinitions方法,来实现你自己的bean定义的加载逻辑。

方法解析

@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    //注册ServletContextAwareProcessor 这样任意Bean都可以很方便的获取到ServletContext了  同时忽略另外两个,
    // 因为ServletContextAwareProcessor 都把事情都做了
    beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
    beanFactory.ignoreDependencyInterface(ServletContextAware.class);
    beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
    //注册web环境,包括request、session、golableSession、application
    WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
    //注册servletContext、contextParamters、contextAttributes  、servletConfig单例bean
    WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}

五级类-XmlWebApplicationContext

JavaDoc

webApplicationcontext实现,它从XML文档中获取配置,由XmlBeanDefinitionReader理解。这本质上相当于web的GenericmlApplicationcontext环境。
默认情况下,根上下文的配置将取自“/WEB-INF/applicationContext.xml”,命名空间为“test-servlet”的上下文的配置将取自“/WEB-INF/test-servlet.xml”(类似于servlet-name为“test”的DispatcherServlet实例)。
配置位置默认值可以通过ContextLoader的contextConfigLocation" context-param和FrameworkServlet的servlet init-param来覆盖。配置位置既可以表示具体文件,如/WEB-INF/context.xml”,也可以表示Ant风格的模式,如“/WEB-INF/*-context.xml”(模式细节参见PathMatcher javadoc)。
注意:如果有多个配置位置,后面的bean定义将覆盖前面加载文件中定义的bean定义。可以利用这一点,通过额外的ML文件故意覆盖某些bean定义。
对于以不同bean定义格式读取的WebApplicationContext,创建AbstractRefreshablewebApplicationContext的类似子类。可以指定这样的上下文实现作为 ContextLoader 的 “contextClass” context-param 或 FrameworkServlet 的 “contextClass” init-param。

解析

XmlWebApplicationContext继承自AbstractRefreshableWebApplicationContext,接受能被XmlBeanDefinitionReader所理解的XML文档配置。对于根上下文,默认的配置文件路径是/WEB-INF/applicationContext.xml,对于命名空间为test-servlet的上下文,默认的配置文件路径是/WEB-INF/test-servlet.xml(就像servlet-name为test的DispatcherServlet实例)。

默认的配置文件路径处理的代码如下:

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};
    }
}

和其他的上下文一样,bean定义的加载也是在void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中,使用的是XmlBeanDefinitionReader

方法解析

public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {

	/** 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";

	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

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

	@Override
	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};
		}
	}
}

五级类-GroovyWebApplicationContext

解析

GroovyWebApplicationContext继承自AbstractRefreshableWebApplicationContext,实现了GroovyObject接口,接受能被GroovyBeanDefinitionReader所理解的groovy bean定义脚本和XML文档配置。对于web环境,基本上是和GenericGroovyApplicationContext是等价的。对于根上下文,默认的配置文件路径是/WEB-INF/applicationContext.groovy,对于命名空间为test-servlet的上下文,默认的配置文件路径是/WEB-INF/test-servlet.xml(就像servlet-name为test的DispatcherServlet实例)。

默认的配置文件路径处理的代码如下:

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};
    }
}

和其他的上下文一样,bean定义的加载也是在void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中,使用的是GroovyBeanDefinitionReader

五级类-AnnotationConfigWebApplicationContext

JavaDoc

WebApplicationcontext实现,它接受组件类作为输入——特别是@configuration注释的类,还包括使用javax.inject注释的普通@Component类和JSR-330兼容类。允许一个一个地注册类(指定类名作为配置位置)以及类路径扫描(指定基本包作为配置位置)。这本质上相当于web环境中的AnnotationconfigApplicationcontext。要使用这个应用程序上下文,ContextLoader的“contextClass”上下文参数和/或FrameworkServlet的“contextClass”初始化参数必须设置为该类的全限定名。

解析

AnnotationConfigWebApplicationContext继承自AbstractRefreshableWebApplicationContext,接受注解的类作为输入(特殊的@Configuration注解类,一般的@Component注解类,与JSR-330兼容的javax.inject注解)。允许一个一个的注入,同样也能使用类路径扫描。对于web环境,基本上是和AnnotationConfigApplicationContext等价的。使用AnnotatedBeanDefinitionReader来对注解的bean进行处理,使用ClassPathBeanDefinitionScanner来对类路径下的bean进行扫描。

方法解析

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
    // 初始化这个脚手架 其实就是直接new出实例。
    AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
    ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);

    // 生成Bean的名称的生成器,如果自己没有setBeanNameGenerator(可以自定义),这里目前为null
    BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
    if (beanNameGenerator != null) {
        reader.setBeanNameGenerator(beanNameGenerator);
        scanner.setBeanNameGenerator(beanNameGenerator);
        //若我们注册了beanName生成器,那么就会注册进容器里面
        beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
    }

    //这是给reader和scanner注册scope的解析器  此处为null
    ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
    if (scopeMetadataResolver != null) {
        reader.setScopeMetadataResolver(scopeMetadataResolver);
        scanner.setScopeMetadataResolver(scopeMetadataResolver);
    }

    // 此处注意了:annotatedClasses和basePackages一般是选其一(当然看到此处,他们是可以并存的)
    //我们可以自己指定annotatedClasses 配置文件,同时也可以交给下面扫描
    if (!this.componentClasses.isEmpty()) {
        // 这里会把所有的配置文件输出=======info日志  请注意观察控制台
        if (logger.isDebugEnabled()) {
            logger.debug("Registering component classes: [" +
                    StringUtils.collectionToCommaDelimitedString(this.componentClasses) + "]");
        }
        // 若是指明的Bean,就交给reader去处理,至于怎么处理,见上篇博文的doRegisterBean去怎么解析每一个Config Bean的
        reader.register(ClassUtils.toClassArray(this.componentClasses));
    }

    // 也可以是包扫描的方式,扫描配置文件的Bean
    if (!this.basePackages.isEmpty()) {
        // 输出对应的info日志
        if (logger.isDebugEnabled()) {
            logger.debug("Scanning base packages: [" +
                    StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]");
        }
        // 这里重要了,scan方法具体做了什么事,上篇博文也有详细的介绍,请参阅
        scanner.scan(StringUtils.toStringArray(this.basePackages));
    }
    // 此处的意思是,也可以以全类名的形式注册。比如可以调用setConfigLocations设置(这在xml配置中使用较多) 可以是全类名,也可以是包路径
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            try {
                Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
                if (logger.isTraceEnabled()) {
                    logger.trace("Registering [" + configLocation + "]");
                }
                reader.register(clazz);
            }
            catch (ClassNotFoundException ex) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Could not load class for config location [" + configLocation +
                            "] - trying package scan. " + ex);
                }
                int count = scanner.scan(configLocation);
                if (count == 0 && logger.isDebugEnabled()) {
                    logger.debug("No component classes found for specified class/package [" + configLocation + "]");
                }
            }
        }
    }
}

引用

https://docs.spring.io/spring-framework/docs/5.2.x/javadoc-api/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.html
https://www.cnblogs.com/coder-qi/p/11192054.html#configurablewebapplicationcontext

备用资料

FileSystemXmlApplicationContext,从磁盘路径查找 XML 配置文件,创建容器(旧)
XmlWebApplicationContext,传统 SSM 整合时,基于 XML 配置文件的容器(旧)
AnnotationConfigWebApplicationContext,传统 SSM 整合时,基于 java 配置类的容器(旧)
AnnotationConfigApplicationContext,Spring boot 中非 web 环境容器(新)
StaticApplicationContext:它能让你编程注册。而不是从配置文件中读取,一般用于测试环境(忽略)

1、ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径之下。
2、FileSystemXmlApplicationContext:可以加载磁盘中任意路径下的配置文件,要求具有访问权限。
3、AnnotationConfigApplicationContext:用于读取注解创建容器。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值