【Spring】ApplicationContext 三 AnnotationConfigApplicationContext

前言

基于之前对 ApplicationContext 相关接口和 AbstractApplicationContext 的相关解读,本章节了解对比下最常用的两个实现:

  • AnnotationConfigApplicationContext,非 web 环境使用
  • AnnotationConfigWebApplicationContextweb 环境使用

版本

Spring 5.3.x

GenericApplicationContext

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry

Generic:通用的,容器的通用实现,基于 AbstractApplicationContext,本类基本实现了所有容器相关功能

1

可以看到,它是一个 BeanDefinitionRegistry,因此不难推测: BeanFactory 注册 BeanDefinition 的能力是从这个类暴露出去的,对应方法的实现如下:

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

	// ...
	

相关方法全权委托给 beanFactory

2

同时,伴随此类的创建,其内部的 BeanFactory 也完成了初始化:

	public GenericApplicationContext() {
		this.beanFactory = new DefaultListableBeanFactory();
	}

可以看到,类型为 BeanFactory 的最终实现 DefaultListableBeanFactory

3

上一章节了解到,refresh 方法最后阶段会基于容器中注册的 BeanDefinition 来创建对应的 bean实例,我们可以借助其他 BeanDefinitionReader 来收集、注册对应的 BeanDefinition,如下示例扫描指定 xml 文件的 BeanDefinition

	@Test
    public void test() {
        
        GenericApplicationContext ctx = new GenericApplicationContext();
        
        // 扫描指定 xml
        XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
        xmlReader.loadBeanDefinitions(new ClassPathResource("demo.xml"));
        
        // 容器启动
        ctx.refresh();
    }

AnnotationConfigApplicationContext

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry
  • GenericApplicationContext 的子类,基于注解配置的容器
  • 抛开远古时期 xml 配置的时代,基于注解的容器配置已成主流,其中我们最常使用的就是此实现类

1

	private final AnnotatedBeanDefinitionReader reader;

	private final ClassPathBeanDefinitionScanner scanner;

核心成员属性,实现基于配置类、路径扫描来解析 BeanDefinition

  • AnnotatedBeanDefinitionReader:基于注解驱动配置类的扫描,对应的组件被注册成 AnnotatedGenericBeanDefinition
  • ClassPathBeanDefinitionScanner:基于路径配置类的扫描,此类用途更为广泛一点,对应的组件被注册为 ScannedGenericBeanDefinition
  • 相关链接

【源码】Spring —— AnnotatedBeanDefinitionReader 解读

【源码】Spring —— ClassPathBeanDefinitionScanner 解读

2

	// 成员初始化
	public AnnotationConfigApplicationContext() {
		StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
		this.reader = new AnnotatedBeanDefinitionReader(this);
		createAnnotatedBeanDefReader.end();
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

	// 扫描配置类
	public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
		this();
		register(componentClasses);
		refresh();
	}

	// 路径扫描
	public AnnotationConfigApplicationContext(String... basePackages) {
		this();
		scan(basePackages);
		refresh();
	}

常用构造方法:

  • 无参构造:初始化 AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner
  • 指定配置类扫描,基于 register 方法,最终委托 AnnotatedBeanDefinitionReader
  • 指定路径扫描,基于 scan 方法,最终委托 ClassPathBeanDefinitionScanner

demo

public class AnnotationConfigApplicationContextDemo {

    public static class A {}

    @Configuration
    static class Config {

        @Bean
        public A a() {
            return new A();
        }
    }
    
    @Test
    public void test() {
        
        // 指定配置类就不用手动 refresh
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext(Config.class);
        context.getBean(A.class);
    }
}

最眼熟的一段代码了

AnnotationConfigWebApplicationContext

public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWebApplicationContext
		implements AnnotationConfigRegistry
  • 它是一个 AbstractRefreshableWebApplicationContext,因此具有 Refreshable(可刷新) 的能力
  • 它是一个 WebApplicationContext,因此主要用在 web 环境
  • 不同于 AnnotationConfigApplicationContext,它不是一个 BeanDefinitionRegistry,因此不暴露注册 BeanDefinition 的方法

1 AbstractRefreshableApplicationContext#refreshBeanFactory

	@Override
	protected final void refreshBeanFactory() throws BeansException {
		// 销毁之前的 BeanFactory
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}

		// 创建新的 BeanFactory 并重新加载 BeanDefinition
		// 及支持容器的热刷新
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
			// ...
		}
	}
  • 作为一个 AbstractRefreshableWebApplicationContext,其父类 AbstractRefreshableApplicationContext 复写了 refreshBeanFactory
  • 该方法会销毁并创建新的 BeanFactory 实例,主要是用于重新读取 BeanDefinition
  • 具体读取 BeanDefinition 的方法 loadBeanDefinitions 由子类实现

2 AbstractRefreshableWebApplicationContext#postProcessBeanFactory

	@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		// 处理并忽略依赖注入 ServletContextAware ServletConfigAware
		beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
		beanFactory.ignoreDependencyInterface(ServletContextAware.class);
		beanFactory.ignoreDependencyInterface(ServletConfigAware.class);

		// 1.注册 request session application 三个 web 作用域
		// 2.注册 ServletRequest ServletResponse HttpSession WebRequest 的 bean 实例
		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);

		// 注册 web 环境的一些单例
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
	}
  • 作为一个 WebApplicationContext,其父类 AbstractRefreshableWebApplicationContext 实现了方法 postProcessBeanFactory
  • 主要是一些 web 组件的处理,比如注册一些 Scope Bean组件

3 AnnotationConfigWebApplicationContext#loadBeanDefinitions

	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {

		/**
		 * 还是委托 AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner
		 * 		来加载 BeanDefinition 的
		 */
		AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
		ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);

		// 创建并注册 BeanNameGenerator:beanName 生成器
		BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
		if (beanNameGenerator != null) {
			reader.setBeanNameGenerator(beanNameGenerator);
			scanner.setBeanNameGenerator(beanNameGenerator);
			beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
		}

		// Scope 元数据解析器,主要用来处理 BD 的 Scope 相关属性
		ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
		if (scopeMetadataResolver != null) {
			reader.setScopeMetadataResolver(scopeMetadataResolver);
			scanner.setScopeMetadataResolver(scopeMetadataResolver);
		}

		// 处理 componentClasses,即配置类
		if (!this.componentClasses.isEmpty()) {
			reader.register(ClassUtils.toClassArray(this.componentClasses));
		}

		// 处理 basePackages,即 @ComponentScan 路径扫描
		if (!this.basePackages.isEmpty()) {
			scanner.scan(StringUtils.toStringArray(this.basePackages));
		}

		// 加载通过 setConfigLocations 设置的配置文件
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				try {
					Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
					reader.register(clazz);
				}
				catch (ClassNotFoundException ex) {
					// ...
				}
			}
		}
	}
  • 作为 AbstractRefreshableWebApplicationContext 的子类,实现了 loadBeanDefinitions 方法
  • loadBeanDefinitions 方法会重新读取 BeanDefinition,从而实现配置 热更新,这也意味着我们可以执行多次 refresh 方法

demo

public class AnnotationConfigWebApplicationContextDemo {

    @AllArgsConstructor
    public static class A {
        public String name;
    }

    @Configuration
    static class Config1 {

        @Bean
        public A a() {
            return new A("1");
        }
    }

    @Configuration
    static class Config2 {

        @Bean
        public A a() {
            return new A("2");
        }
    }

    @Test
    public void test() {
        AnnotationConfigWebApplicationContext context
                = new AnnotationConfigWebApplicationContext();

        context.register(Config1.class);
        context.refresh();
        System.out.println(context.getBean(A.class).name);

        // 配置覆盖
        context.register(Config2.class);
        // 多次执行 refresh
        context.refresh();
        System.out.println(context.getBean(A.class).name);
    }
}

这里只是体现了 refreshable 的能力,而对于 WebApplicationContext 的能力,可以结合 springmvc 了解

总结

最后简单的对比下 AnnotationConfigApplicationContextAnnotationConfigWebApplicationContext

相同点

  1. 它们都是对应分支的最终实现类,也就是能力最强、最常使用的实现
  2. 它们加载 BeanDefinition 的行为都是委托 AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner 完成的

不同点

  1. AnnotationConfigApplicationContext 用于非 web 环境,而 AnnotationConfigWebApplicationContext 用于 web 环境
  2. AnnotationConfigApplicationContextGenericApplicationContext 的子类,因此暴露了 BeanDefinitionRegistry 的相关方法,尽管最终都是委托到 DefaultListableBeanFactory 上,而 AnnotationConfigApplicationContext 没有相关方法
  3. AnnotationConfigWebApplicationContext 是一个 refreshable(可刷新) 的容器实现,因此可以多次调用 refresh 方法,它会重新加载 BeanDefinition 来实现 热更新,而 AnnotationConfigApplicationContext 只能调用一次 refresh 方法

上一篇:【Spring】ApplicationContext 二 AbstractApplicationContext

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值