撸一撸Spring Framework-IoC-ApplicationContext

   撸一撸Spring Framework-IoC系列文章目录

从UML关系上看,ApplicationContext与BeanFactory既有继承关系、又有关联关系

BeanFactory篇中我们说过,很多ApplicationContext的实现中,都引用了一个DefaultListableBeanFactory对象,并将bean管理操作都委托给它。以下是从GenericApplicationContext及其父类中摘录的一段代码,主要是为了让你感受下它与DefaultListableBeanFactory的关系

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {

	private final DefaultListableBeanFactory beanFactory;

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

	public GenericApplicationContext(DefaultListableBeanFactory beanFactory) {
		Assert.notNull(beanFactory, "BeanFactory must not be null");
		this.beanFactory = beanFactory;
	}

    @Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
	}

    @Override
	public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
		return this.beanFactory.getBeanDefinition(beanName);
	}

    //来自父类
	public Object getBean(String name) throws BeansException {
		assertBeanFactoryActive();
		return getBeanFactory().getBean(name);
	}

    //来自父类
    public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException {
		assertBeanFactoryActive();
		return getBeanFactory().getBeansOfType(type);
	}

    //来自父类
	public boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
		assertBeanFactoryActive();
		return getBeanFactory().isSingleton(name);
	}
}
    

所以可以说ApplicationContext建立在BeanFactory的基础上,此外它还提供了很多企业级功能特性。从服务对象的角度理解,BeanFactory服务于Spring内部,ApplicationContext服务于开发者,几乎所有应用场合我们都会直接使用ApplicationContext,而非BeanFactory

我们把ApplicationContext的祖先类中有关BeanFactory的部分去掉,剩下的就是它提供的企业级功能特性

EnvironmentCapable:只有一个getEnvironment()方法,返回一个Environment对象,Environment内容比较多,后面会开单章,这里简要介绍下,Environment集成了两个关键特性,配置管理和profiles管理(本质上还是配置相关的东西)

profiles用于决定一个类是否会被装载到spring容器中,在类上添加@Profile注解后,容器在装载beanDefinition时,会判断类上指定的profiles是否为active profiles,是则装载、否则不装载(springboot中的application-{profile}.properties机制也是基于这个原理)。适用于多环境差异化的场景

对应用来说,配置管理无疑是很重要的,包括配置的增、删、改、查,比如通过@PropertySources和@PropertySource引入properties,通过@Value将配置项注入到bean的属性中,通过environment.getPropertySources().replace方法mock系统环境变量,这都属于配置管理的范畴。jvm参数、系统环境变量、servlet上下文参数、用户自定义的properties文件等各类配置,在spring中都会对应一个PropertySource对象,这些PropertySource对象组成了一个PropertySources对象,被关联在Environment中,对外提供服务

ApplicationEventPublisher:提供事件发布的能力,包括spring内部事件及用户自定义事件。spring在容器启停的一些关键阶段,会发布相应事件,以便于应用程序监听到事件(结合ApplicationListener一起使用)并做相应处理,比如容器启动的最终阶段会发布ContextRefreshedEvent、容器停止时会发布ContextClosedEvent,很多应用会监听它们,做一些初始化、资源释放、优雅关闭工作。此外,通过这套机制自定义事件发布、监听也是非常简单的,相当于帮你省去了实现观察者模式的工作量

ResourcePatternResolver:提供了资源加载能力(加载单个资源、或以通配符的方式加载所有满足条件的资源),在资源管理利器篇中有详细说明

MessageSource:提供i18n风格消息访问的能力

再来看看ApplicationContext的子孙类们

ConfigurableApplicationContext:提供了配置ApplicationContext的能力,便于用户扩展,比如addBeanFactoryPostProcessor、addApplicationListener、setApplicationStartup、setParent,这些方法见名知意,就不多解释了

AbstractApplicationContext:作为ApplicationContext的抽象实现,用模板方法模式实现了很多通用逻辑,包括:

  • 从beanFactory体系继承来的所有方法(统统委托给子类持有的BeanFactory引用实现,这个引用通常是DefaultListableBeanFactory),
  • 上述的企业级特性对应的方法,如publishEvent(通过ApplicationEventMulticaster以广播的形式发布事件)、getResources(使用PathMatchingResourcePatternResolver,支持加载满足通配符的任意个资源)
  • 容器启动方法refresh,非常关键,包含的内容很多,后续会开单章说明

AbstractApplicationContext子类分为两个派系,既是否refreshable,refreshable表示支持重复调用refresh(容器启动)方法,每次refresh都会优雅销毁老的BeanFactory,然后用一个全新的BeanFactory作为替代

//摘录自AbstractRefreshableApplicationContext
@Override
protected final void refreshBeanFactory() throws BeansException {
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		beanFactory.setSerializationId(getId());
		customizeBeanFactory(beanFactory);
		loadBeanDefinitions(beanFactory);
		this.beanFactory = beanFactory;
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}
//摘录自GenericApplicationContext
@Override
protected final void refreshBeanFactory() throws IllegalStateException {
	//通过状态位控制只允许refresh一次
    if (!this.refreshed.compareAndSet(false, true)) {
		throw new IllegalStateException(
				"GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
	}
	this.beanFactory.setSerializationId(getId());
}

抛开是否refreshable来说,GenericXmlApplicationContext、FileSystemXmlApplicationContext、ClassPathXmlApplicationContext这三个实现比较类似,都使用XmlBeanDefinitionReader从xml文件加载beanDefinition,FileSystemXmlApplicationContext、ClassPathXmlApplicationContext的区别在于分别使用FileSystemResource、ClassPathResource表示xml资源,而GenericXmlApplicationContext则更灵活,你可以直接通过Resource来构造它、也可以通过xml路径来构造,不论xml来源何处,只要它可以被加载为Resource即可(关于Resource的详细内容,请参考Resource篇)

public static void main(String[] args) throws IOException {
	ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("D:\\code\\spring\\spring-introduction\\src\\main\\resources\\bean.xml");
	System.out.println("applicationContext1.getBean===>" + applicationContext1.getBean("user"));

	ApplicationContext applicationContext2 = new ClassPathXmlApplicationContext("bean.xml");
	System.out.println("applicationContext2.getBean===>" + applicationContext2.getBean("user"));

	ApplicationContext applicationContext3 = new GenericXmlApplicationContext("classpath:bean.xml");
	System.out.println("applicationContext3.getBean===>" + applicationContext3.getBean("user"));

	ApplicationContext applicationContext4 = new GenericXmlApplicationContext("file:D:\\code\\spring\\spring-introduction\\src\\main\\resources\\bean.xml");
	System.out.println("applicationContext4.getBean===>" + applicationContext4.getBean("user"));

	Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:bean.xml");
	//通过Resource构造GenericXmlApplicationContext
	ApplicationContext applicationContext5 = new GenericXmlApplicationContext(resources);
	System.out.println("applicationContext5.getBean===>" + applicationContext5.getBean("user"));
}
##resources/demo.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
   <bean id="user" class="com.example.spring.entity.User" >
      <property name="id" value="001"/>
      <property name="name" value="zhang san"/>
   </bean>
</beans>

AnnotationConfigApplicationContext:使用component class(通常是注解了@Configuration的类,也支持@Component以及JSR-330标准中的@Inject注解)作为输入,来加载beanDefinition

@Configuration
public class AnnotationApplicationContextDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationApplicationContextDemo.class);
        System.out.println(applicationContext.getBean("userName"));
    }

    @Bean
    public String userName() {
        return "bobo";
    }
}

实际开发场景下,java config类通常不会只包含@Bean注解(尤其是项目的主配置类),往往还包含@ComponentScan、@PropertySource、@Import等注解,除了这些java config类上的注解外,还有诸如@Component、@Autowired、@Value、@Resource、@Inject、@PostConstruct、@PreDestroy、@EventListener等用于普通类的注解,针对这些注解的处理逻辑,是容器启动过程中的核心,搞清楚这些对我们理解IOC容器很有帮助

AnnotationConfigApplicationContext实例化时会创建并维护一个AnnotatedBeanDefinitionReader实例,后者在实例化时,会调用AnnotationConfigUtils#registerAnnotationConfigProcessors方法注册与各种注解处理相关的PostProcessor(包括BeanFactoryPostProcessor和BeanPostProcessor),上述注解的处理逻辑就由这些PostProcessor负责,比如ConfigurationClassPostProcessor负责@Bean、@ComponentScan、@PropertySource、@Import这些与java config类相关的注解,AutowiredAnnotationBeanPostProcessor负责@Autowired、@Value、@Inject这些依赖注入相关的注解。在后续关于容器生命周期的文章中,会进一步解析这其中的过程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值