撸一撸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这些依赖注入相关的注解。在后续关于容器生命周期的文章中,会进一步解析这其中的过程