0%

Spring核心容器简介-BeanFactory,ApplicationContext

在Spring中容器的实现类并不是唯一的,Spring框架中提供了多个容器的实现。主要分为两套体系:一套是早期的BeanFactory体系,还有一个就是ApplicationContext,也被成为服务上下文,它继承了BeanFactory,除了BeanFactory的功能外还提供了事务,AOP,国际化的消息源以及应用程序事务处理等企业级服务。

1.BeanFactory

首先我们来看一下BeanFacotry源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public interface BeanFactory {

/**
* 主要是用于FactoryBean的转义进行定义,因为当使用FactoryBean创建Bean时,使用名字进行检索获取的是FactoryBean所创建的Bean对象
* 而需要使用转义来获取FactoryBean本身。
*/
String FACTORY_BEAN_PREFIX = "&";


/**
* 根据bean的名称获取容器中的实例,这个bean可以是单例bean也可以是原型bean。
* 当无法获取指定名称的bean将会抛出NoSuchBeanDefinitionException异常。
*/
Object getBean(String name) throws BeansException;

/**
* 根据bean的名称和bean的类型来获取容器中的bean实例,这个bean可以是单例bean也可以是原型bean。
* 当获取的bean不是所需类型,则抛出BeanNotOfRequiredTypeException异常。可以简单理解成增加了安全验证机制。
*/
<T> T getBean(String name, Class<T> requiredType) throws BeansException;

/**
* 根据名称来获取bean,第二个参数args可以用来给bean进行赋值。复制的方式有两种,构造方法和工厂方法。
* 但是通过这种方式获取的bean必须为原型bean而不是单例bean。
*/
Object getBean(String name, Object... args) throws BeansException;

/**
* 根据bean类型来获取容器中的bean,当bean不唯一是抛出NoUniqueBeanDefinitionException异常。
* 当没有获得对应bean时抛出NoSuchBeanDefinitionException异常。
*/
<T> T getBean(Class<T> requiredType) throws BeansException;

/**
* 该方法与getBean(String name, Class<T> requiredType)方法类似
*/
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

/**
* 获取指定bean的提供者,可以简单理解为,假设一个bean是通过FactoryBean生成,此处将返回创建该bean的FactoryBean。
* 调用这个方法返回的是一个对象的实例。此接口通常用于封装一个泛型工厂,在每次调用的时候返回一些目标对象新的实例。
* ObjectFactory和FactoryBean是类似的,只不过FactoryBean通常被定义为BeanFactory中的服务提供者(SPI)实例,
* 而ObjectFactory通常是以API的形式提供给其他的bean。简单的来说,ObjectFactory一般是提供给开发者使用的,
* FactoryBean一般是提供给BeanFactory使用的。
*/
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

/**
* 同上
*/
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

/**
* 判断当前容器是否存在某种名称的bean
*/
boolean containsBean(String name);

/**
* 判断bean是否为单例bean
*/
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

/**
* 判断bean是否为原型bean
*/
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

/**
* 根据bean名称,判断是否有指定类型匹配
*/
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

/**
* 同上
*/
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

/**
* 获取bean的类型,对于FactoryBean,返回Factory.getObjectType()返回的类型。
*/
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;

/**
* 返回给定bean的别名(如果有的话),在getBean调用中,所有这些别名都指向同一个bean。
* 如果给定的名称是别名,则返回相应的原始bean名称和其他别名。
*/
String[] getAliases(String name);

}

2、ApplicationContext

ApplicationContext为BeanFactory的子类,它不仅包含BeanFactory的所有功能,还对其进行了扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

/**
* 返回当前应用程序上下文的唯一ID
*/
@Nullable
String getId();

/**
* 返回当前应用程序上下文所属应用程序的名称
*/
String getApplicationName();

/**
* 获取当前应用程序上下文的具象化类名
*/
String getDisplayName();

/**
* 获取当前应用上下文第一次加载时的时间戳
*/
long getStartupDate();

/**
* 获取父级应用程序上下文,如果没有则返回null
*/
@Nullable
ApplicationContext getParent();

/**
* 通过getAutowireCapableBeanFactory这个方法将 AutowireCapableBeanFactory这个接口暴露给外部使用,
* AutowireCapableBeanFactory这个接口一般在applicationContext的内部是较少使用的,
* 它的功能主要是为了装配applicationContext管理之外的Bean。
*/
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;

}

ApplicationContext本身实现的方法较少,也比较简单,但是他通过继承六个接口来实现了Bean相关的方法等常用功能。
ApplicationContext有多种实现类,其中最重要的两类是ConfigurableApplicationContext和WebApplicationContext。

2.1、ConfigurableApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {

/**
* 下面这些字符会被认为是用于分割上下文配置路径的分隔符
*/
String CONFIG_LOCATION_DELIMITERS = ",; \t\n";

/**
* 在BeanFactory中ConversionService对应的bean名称。如果没有实现该类的实例,则使用默认的转换规则。
*/
String CONVERSION_SERVICE_BEAN_NAME = "conversionService";

/**
* LoadTimeWaver类所对应的Bean在容器中的名字。如果提供了该实例,上下文会使用临时的 ClassLoader。
* 这样,LoadTimeWaver就可以使用bean确切的类型了
*/
String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver";

/**
* Environment在容器中的Bean名称
*/
String ENVIRONMENT_BEAN_NAME = "environment";

/**
* System系统变量在容器中对应的Bean名称
*/
String SYSTEM_PROPERTIES_BEAN_NAME = "systemProperties";

/**
* System环境变量在容器中对应的Bean名称
*/
String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment";


/**
* 给当前应用上下文设置唯一ID
*/
void setId(String id);

/**
* 为当前容器设置父容器
*/
void setParent(@Nullable ApplicationContext parent);

/**
* 设置当前容器的环境变量
*/
void setEnvironment(ConfigurableEnvironment environment);

/**
* 以ConfigurableEnvironment的形式返回当前容器的环境变量。
*/
@Override
ConfigurableEnvironment getEnvironment();

/**
* 为当前容器新增一个BeanFactoryPostProcessor。增加的BeanFactoryPostProcessor将在容器进行refresh操作后使用。
*/
void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);

/**
* 为当前容器新增一个ApplicationListener,增加的Listener将用于发布上下文时间。如初始化刷新容器,关闭容器。
*/
void addApplicationListener(ApplicationListener<?> listener);

/**
* 为当前容器新增一个ProtocolResolver,ProtocolResolver主要用于自定义协议的解析。
* 比如spring就有一个 “classpath:”开头的特定协议(但是spring并不是自定义ProtocolResolver 实现来完成这个功能的)
*/
void addProtocolResolver(ProtocolResolver resolver);

/**
* 加载或者刷新配置,可能是xml文件或者是properties文件,或者是连接数据源。
* 由于这是一个初始化方法,当前方法执行失败时,则已经创建的Bean也会销毁。
* 所以调用此方法时,要么所有的Bean都实例化成功,要么一个Bean都没有实例化
*/
void refresh() throws BeansException, IllegalStateException;

/**
* 向JVM注册一个关闭当前应用上下文的回调函数。当前方法可以多次调用,但是一个应用上下文只会有一个回调函数。
*/
void registerShutdownHook();

/**
* 关闭当前应用程序上下文,释放所占用对的资源和锁,并且销毁所有已经创建的单例Bean
* 该close()方法不会调用父类的close方法。父级应用上下文有自己的生命周期。
* 这个方法可以被多次调用而没有副作用:对已经关闭的上下文的后续close调用将被忽略。
*/
@Override
void close();

/**
* 判断当前应用上下文是否处于启动状态,即是否至少执行一次refresh()
*/
boolean isActive();

/**
* 获取当前应用上下文的BeanFactory容器。不要使用当前方法对BeanFactory生成的Bean进行后置处理,因为此时Bean已经初始化完成。
* 应该是用BeanFactoryPostProcessor来在Bean声称之前对其进行处理。在获取容器是应该保证容器是在启动状态,即在refresh()和close()之间。
*/
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

}

2.2、WebApplicationContext

WebApplicationCOntext是专门为Web应用所准备的,其允许从相对于Web根目录的路径中加载配置文件完成初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public interface WebApplicationContext extends ApplicationContext {

/**
* 整个Web应用上下文是作为属性放置在ServletContext中的,该常量就是应用上下文在ServletContext属性列表中的key
*/
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

/**
* request作用域,只要发出一个请求就会创建一个request,它的作用域:尽在当前请求中有效。
* 用处:常用于服务器间同一请求不同页面之间的参数传递,常应用于表单的控件值传递。
* 方法:request.setAttribute(); request.getAttribute(); request.removeAttribute(); request.getParameter().
*/
String SCOPE_REQUEST = "request";

/**
* 服务器会为每个会话创建一个session对象,所以session中的数据可供当前会话中所有servlet共享。
* 会话:用户打开浏览器会话开始,直到关闭浏览器会话才会结束。一次会话期间只会创建一个session对象。
* 用处:常用于web开发中的登陆验证界面(当用户登录成功后浏览器分配其一个session键值对)。
* 方法:session.setAttribute(); session.getAttribute(); session.removeAttribute();
*/
String SCOPE_SESSION = "session";

/**
* 作用范围:所有的用户都可以取得此信息,此信息在整个服务器上被保留。Application属性范围值,只要设置一次,则所有的网页窗口都可以取得数据。
* ServletContext在服务器启动时创建,在服务器关闭时销毁,一个JavaWeb应用只创建一个ServletContext对象,
* 所有的客户端在访问服务器时都共享同一个ServletContext对象;ServletContext对象一般用于在多个客户端间共享数据时使用;
*/
String SCOPE_APPLICATION = "application";

/**
* 在工厂中的bean名称
*/
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";

/**
* ServletContext 初始化参数名称
*/
String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";

/**
* 在工厂中 ServletContext 属性值环境bean的名称
*/
String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";


/**
* 用来获取 ServletContext 对象
*/
@Nullable
ServletContext getServletContext();

}

3、ApplicationContext和BeanFactory对比

1.ApplicationContext包含BeanFactory的所有特性,所以一般情况下我们都选择使用ApplicationContext,但是在一些限制情况下,如对于内存消耗要求比较严格的时候使用轻量的BeanFactory是更合理的选择。
2.在BeanFactory采用延迟加载的形式来注入Bean。只有使用到某个Bean是,才会对Bean进行加载实例化。而ApplicationContext则相反,它在容器启动时,一次性创建了所有Bean。这样我们就能在容器启动时发现Spring存在的配置错误。
3.BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

功能/特点 BeanFactory ApplicationContext
Bean加载方式 延迟加载 容器启动时加载所有Bean
Bean 实例化/装配
BeanPostProcessor 自动注册 没有
BeanFactoryPostProcessor 自动注册 没有
MessageSource 便捷访问(针对i18n) 没有
ApplicationEvent 发布 没有

3、ApplicationContext准备启动

ps:ContextLoaderListener简介https://www.cnblogs.com/xunyi/p/10363290.html

在容器启动时会调用ContextLoaderListener中的contextInitialized()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}

public ContextLoaderListener(WebApplicationContext context) {
super(context);
}

/**
* 初始化根web应用程序上下文
*/
@Override
public void contextInitialized(ServletContextEvent event) {
// 调用父类ContextLoader的方法进行web容器初始化
initWebApplicationContext(event.getServletContext());
}

/**
* 关闭根web应用程序上下文
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}

}

接下来我们查看父类的initWebApplicationListener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ContextLoader {

// ...

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// ..
try {
// 将上下文存储在本地实例变量中,以确保在ServletContext关闭时可用。
if (this.context == null) {
// 通过 createWebApplicationContext 方法创建应用上下文
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {

...
// 在该方法中调用上下文的 refresh 方法,refresh 就是启动上下文的入口
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
...
}
...
}
...

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {

...

wac.refresh();
}

...
}

使用SpringBoot启动ApplicationContext时
首先我们可以看SpringBoot的启动类

1
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

点击进入SpringApplication的run方法我们可以得知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
// ...
ConfigurableApplicationContext context = null;
try {
// ...
// 创建一个应用上下文
context = createApplicationContext();
// ...
// 进入refreshContext方法可以发现实际是调用refresh启动上下文方法
refreshContext(context);
// ...
}
catch (Throwable ex) {
// ...
}
// ...
return context;
}
}

通过对ContextLoaderListener和SpringApplication两个类的分析我们可以发现,实际最终他们都调用了refresh()方法,所以我们接下来对refresh()进行分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {

// 预刷新,做一些准备工作。记录了启动时间戳,标记为活动,非关闭状态,并处理配置文件中的占位符
prepareRefresh();

// 解析配置文件,创建BeanFactory,对Bean进行包装成BeanDefinition
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 对BeanFactory进行增强处理,比如添加BeanPostProcessor,手动注册部分bean等。
prepareBeanFactory(beanFactory);

try {
// 钩子方法,BeanFactory创建后,对BeanFactory的自定义操作。
postProcessBeanFactory(beanFactory);

// 重点:调用了postProcessBeanDefinitionRegistry(registry);
// springboot中很多激活自动配置的注解都是通过这里导入的。
invokeBeanFactoryPostProcessors(beanFactory);

// 重点:从beanFactory中获取所有的BeanPostProcessor,优先进行getBean操作,实例化
registerBeanPostProcessors(beanFactory);

// 国际化支持
initMessageSource();

// 初始化ApplicationEventMulticaster。 如果上下文中未定义,则使用SimpleApplicationEventMulticaster。
// 这里是监听器的支持,监听器时间多播。
initApplicationEventMulticaster();

// 钩子方法,springBoot中的嵌入式tomcat就是通过此方法实现的
onRefresh();

// 监听器注册
registerListeners();

// 重点方法:完成容器中bean的实例化,及代理的生成等操作。
finishBeanFactoryInitialization(beanFactory);

// 完成此上下文的刷新,调用LifecycleProcessor的onRefresh()方法并发布
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

总结: 容器是Spring中最为重要的概念之一,也是Spring大厦的基石。通过总结我们可以发现,Spring Bean的自动注入大大简化了操作,下一步我们将分析,Spring是如何通过Xml文件或者注解的方式将我们需要的Bean放入容器中的。