在Spring框架中最重要的是Spring IoC容器,它是Spring框架的核心。本文将从更高的角度来解析Sping IoC容器,了解其是如何设计的。了解一样东西最好的办法是从其核心本质出发,只要把握住了这样一个核心,其他的一些东西也迎刃而解了。这是一个很好的开端,我们一起开始吧...
org.springframework.context.ApplicationContext接口代表Spring IoC容器,主要负责bean的实例化、配置、装配,简而言之,Spring IoC容器是管理这些bean的(这里所说的bean指的是组成你的应用程序中的对象,并且这些对象被Spring所管理)。容器如何知道哪些对象要进行实例化、配置和装配的呢?是通过读取配置文件元数据来达到这个效果的,配置文件元数据是用xml配置、Java注解和Java代码配置来表示的。这使得作为程序员的我们,只需要向Spring容器提供配置元数据,Spring容器就能在我们的应用中实例化、配置和装配这些对象。org.springframework.beans和org.springframework.context包是Spring IoC容器的基础。Spring提供了很多Application接口的实现。在单独的应用中,创建ClassPathXmlApplicationContext和FileSystemXmlApplicationContext的实例是非常常用的做法。示例如下:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Hello hello = (Hello) ac.getBean("hello"); hello.sayHello();</pre>
然而在大部分的应用场景中,不需要实例化一个或者多个Spring IoC容器的实例。例如在web应用的场景下,只需要在web.xml中创建七行样板配置的代码如下:
<pre class="xml" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</paramvalue> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener></pre>
下面这张图从更高的视角展示了Spring是怎样工作的。你的应用程序中的类是和配置元数据组合在一起,以便在ApplicationContext创建和初始化之后,你拥有了一个完全配置的、可执行的系统。
ApplicationContext设计解析
为了方便对ApplicationContext接口的层次结构有一个大概的认识,下面使用IDEA来生成ApplicationContext的继承关系图。(选中ApplicationContext接口->右键->Diagrams->Show Diagrams...)
(温馨提示:点击图片可以查看高清大图)
从上图就能很清楚的看出ApplicationContext继承的接口分为五类:
先看一下在ApplicationContext中定义的方法:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
`String getId(); // 获取ApplicationContext的唯一id
String getApplicationName(); // 该上下文所属的已经部署了的应用的名字,默认为""
String getDisplayName(); // 友好的展示名字
long getStartupDate(); // 该上下文第一次加载的时间
ApplicationContext getParent(); // 父级ApplicationContext
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;`</pre>
前四个方法用于获取该ApplicationContext的一些基本信息,getAutowireCapableBeanFactory()用于暴露AutowireCapableBeanFactory的功能,这通常不是提供给用于代码使用的,除非你想要在应用上下文的外面初始化bean的实例,关于AutowireCapableBeanFactory后面会有更加详细的解析。
BeanFactory是Spring框架中比较重要的一个接口,下面列出了这个接口中的方法的定义:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
`// 获取bean
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
// 获取bean的提供者(对象工厂)
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
boolean containsBean(String name); // 是否包含指定名字的bean
boolean isSingleton(String name) throws NoSuchBeanDefinitionException; // 是否为单例
boolean isPrototype(String name) throws NoSuchBeanDefinitionException; // 是否为原型
// 指定名字的bean是否和指定的类型匹配
boolean isTypeMatch(String name, ResolvableType typeToMatch);
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
Class<?> getType(String name)
throws NoSuchBeanDefinitionException; // 获取指定名字的bean的类型
String[] getAliases(String name); // 获取指定名字的bean的所有别名`</pre>
这些方法大致可以分为三类:
或许你已经注意到了,有两个方法含有类型是ResolvableType的参数,那么ResolvableType是什么呢?假如说你要获取泛型类型的bean:MyBean<TheType>,根据Class<t style="margin: 0px; padding: 0px;">来获取,肯定是满足不了要求的,泛型在编译时会被擦除。使用ResolvableType就能满足此需求,代码如下:</t>
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
ResolvableType type = ResolvableType.forClassWithGenerics(MyType.class, TheType.class); ObjectProvider<MyType<TheType>> op = applicationContext.getBeanProvider(type); MyType<TheType> bean = op.getIfAvailable()</pre>
简单的来说,ResolvableType是对Java java.lang.reflect.Type的封装,并且提供了一些访问该类型的其他信息的方法(例如父类, 泛型参数,该类)。从成员变量、方法参数、方法返回类型、类来构建ResolvableType的实例。
ListableBeanFactory接口有能列出工厂中所有的bean的能力,下面给出该接口中的所有方法:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
`boolean containsBeanDefinition(String beanName); // 是否包含给定名字的bean的定义
int getBeanDefinitionCount(); // 工厂中bean的定义的数量
String[] getBeanDefinitionNames(); // 工厂中所有定义了的bean的名字
// 获取指定类型的bean的名字
String[] getBeanNamesForType(ResolvableType type);
String[] getBeanNamesForType(Class<?> type);
String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);
// 获取所有使用提供的注解进行标注的bean的名字
String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
// 查找指定bean中的所有指定的注解(会考虑接口和父类中的注解)
<A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
throws NoSuchBeanDefinitionException;
// 根据指定的类型来获取所有的bean
<T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException;
<T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
throws BeansException;
// 获取所有使用提供的注解进行标注了的bean
Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;`</pre>
上面的这些方法都不考虑祖先工厂中的bean,只会考虑在当前工厂中定义的bean。
HierarchicalBeanFactory
HierarchicalBeanFactory接口定义了BeanFactory之间的分层结构,ConfigurableBeanFactory中的setParentBeanFactory方法能设置父级的BeanFactory,下面列出了HierarchicalBeanFactory中定义的方法:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
BeanFactory getParentBeanFactory(); // 获取父级的BeanFactory // 本地的工厂是否包含指定名字的bean boolean containsLocalBean(String name);</pre>
这两个方法都比较直接明了,getParentBeanFactory方法用于获取父级BeanFactory。containsLocalBean
用于判断本地的工厂是否包含指定的bean,忽略在祖先工厂中定义的bean。
MessageSource
MessageSource主要用于消息的国际化,下面是该接口中的方法定义:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
// 获取消息 String getMessage(String code, Object[] args, String defaultMessage, Locale locale); String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException; String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;</pre>
以上的三个方法都是用于获取消息的,第一个方法提供了默认消息,第二个接口如果没有获取到指定的消息会抛出异常。第三个接口中的MessageSourceResolvable参数是对代码、参数值、默认值的一个封装。
ApplicationEventPublisher
ApplicationEventPublisher接口封装了事件发布功能,提供Spring中事件的机制。接口中的方法定义如下:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
// 发布事件 void publishEvent(ApplicationEvent event); void publishEvent(Object event);</pre>
第一个方法用于发布特定于应用程序事件。第二个方法能发布任意的事件,如果事件不是ApplicationEvent,那么会被包裹成PayloadApplicationEvent事件。
EnvironmentCapable
EnvironmentCapable提供了访问Environment的能力,该接口只有一个方法:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
Environment getEnvironment();</pre>
Environment表示当前正在运行的应用的环境变量,它分为两个部分:profiles和properties。它的父级接口PropertyResolver提供了property的访问能力。
ResourceLoader和ResourcePatternResolver
先来看一下ResourceLoader,该接口是用来加载资源(例如类路径或者文件系统中的资源)的策略接口。该接口中的方法如下:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
Resource getResource(String location); // 根据指定的位置获取资源 ClassLoader getClassLoader(); // 获取该资源加载器所使用的类加载器</pre>
该接口只有简单明了的两个方法,一个是用来获取指定位置的资源,一个用于获取资源加载器所使用的类加载器。Resource是从实际类型的底层资源(例如文件、类路径资源)进行抽象的资源描述符。先看下Resource中的方法:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
`boolean exists(); // 资源实际上是否存在
boolean isReadable(); // 资源是否可读
boolean isOpen(); // 检查资源是否为打开的流
boolean isFile(); // 资源是否为文件系统上的一个文件
URL getURL() throws IOException; // 获取url
URI getURI() throws IOException; // 获取URI
File getFile() throws IOException; // 获取文件
ReadableByteChannel readableChannel() throws IOException; // 获取ReadableByteChannel
long contentLength() throws IOException; // 资源的内容的长度
long lastModified() throws IOException; // 资源的最后修改时间
// 相对于当前的资源创建一个新的资源
Resource createRelative(String relativePath) throws IOException;
String getFilename(); // 获取资源的文件名
String getDescription(); // 获取资源的描述信息`</pre>
Resource的父级接口InputStreamSource,可以简单的理解为InputStream的来源,只有一个方法,如下:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
InputStream getInputStream() throws IOException; // 获取输入流</pre>
接下来在来看一下ResourcePatternResolver,该接口用于解析一个位置模式(例如Ant风格的路径模式),该接口只有一个方法,如下:
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
// 将给定的位置模式解析成资源对象 Resource[] getResources(String locationPattern) throws IOException;</pre>
Spring IoC容器设计复盘
假如让你设计IoC容器,你该如何去做呢?首先你应该要明确你设计的容器的功能和特性,然后根据这些功能和特性设计出合理的接口。下面只是粗略的分析一下:
本文思维导图