本文节选自《Spring 5 外围原理》
1 IoC 与 DI 基本概念
IoC(Inversion of Control,管制反转)就是把原来代码里须要实现的对象创立、依赖,反转给容器来帮忙实现。咱们须要创立一个容器,同时须要一种形容来让容器晓得要创立的对象与对象的关系。这个形容最具体的体现就是咱们所看到的配置文件。
DI(Dependency Injection,依赖注入)就是指对象被动承受依赖类而不本人被动去找,换句话说,就是指对象不是从容器中查找它依赖的类,而是在容器实例化对象时被动将它依赖的类注入给它。
咱们先从本人设计的视角来思考。
(1)对象与对象的关系怎么示意?
能够用 XML、properties 等语义化配置文件示意。
(2)形容对象关系的文件寄存在哪里?
可能是 classpath、filesystem 或者 URL 网络资源、servletContext 等。
(3)不同的配置文件对对象的形容不一样,如规范的、自定义申明式的,如何对立?
在外部须要有一个对立的对于对象的定义,所有内部的形容都必须转化成对立的形容定义。
(4)如何对不同的配置文件进行解析?
须要对不同的配置文件语法采纳不同的解析器。
2 Spring 外围容器类图
2.1. BeanFactory
Spring 中 Bean 的创立是典型的工厂模式,这一系列的 Bean 工厂,即 IoC 容器,为开发者治理对象之间的依赖关系提供了很多便当和根底服务,在 Spring 中有许多 IoC 容器的实现供用户抉择,其互相关系如下图所示。
其中,BeanFactory 作为最顶层的一个接口类,定义了 IoC 容器的基本功能标准,BeanFactory 有三个重要的子类:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。然而从类图中咱们能够发现最终的默认实现类是 DefaultListableBeanFactory,它实现了所有的接口。那么为何要定义这么多层次的接口呢?查阅这些接口的源码和阐明发现,每个接口都有它的应用场合,次要是为了辨别在 Spring 外部操作过程中对象的传递和转化,对对象的数据拜访所做的限度。例如,ListableBeanFactory 接口示意这些 Bean 可列表化,而 HierarchicalBeanFactory 示意这些 Bean 是有继承关系的,也就是每个 Bean 可能有父 Bean。AutowireCapableBeanFactory 接口定义 Bean 的主动拆卸规定。这三个接口独特定义了 Bean 的汇合、Bean 之间的关系及 Bean 行为。最根本的 IoC 容器接口是 BeanFactory,来看一下它的源码:
public interface BeanFactory {
// 对 FactoryBean 的本义定义,因为如果应用 Bean 的名字检索 FactoryBean 失去的对象是工厂生成的对象
// 如果须要失去工厂自身,须要本义
String FACTORY_BEAN_PREFIX = "&";
// 依据 Bean 的名字,获取在 IoC 容器中失去的 Bean 实例
Object getBean(String name) throws BeansException;
// 依据 Bean 的名字和 Class 类型来失去 Bean 实例,减少了类型平安验证机制
<T> T getBean(String name, @Nullable 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 的检索,看看在 IoC 容器中是否有这个名字的 Bean
boolean containsBean(String name);
// 依据 Bean 的名字失去 Bean 实例,同时判断这个 Bean 是不是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws
NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws
NoSuchBeanDefinitionException;
// 失去 Bean 实例的 Class 类型
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
// 失去 Bean 的别名,如果依据别名检索,那么其原名也会被检索进去
String[] getAliases(String name);
}
在 BeanFactory 里只对 IoC 容器的根本行为做了定义,基本不关怀你的 Bean 是如何定义及怎么加载的。正如咱们只关怀能从工厂里失去什么产品,不关怀工厂是怎么生产这些产品的。
要晓得工厂是如何产生对象的,咱们须要看具体的 IoC 容器实现,Spring 提供了许多 IoC 容器实现,比方 GenericApplicationContext、ClasspathXmlApplicationContext 等。
ApplicationContext 是 Spring 提供的一个高级的 IoC 容器,它除了可能提供 IoC 容器的基本功能,还为用户提供了以下附加服务。
(1)反对信息源,能够实现国际化(实现 MessageSource 接口)。
(2)拜访资源(实现 ResourcePatternResolver 接口,前面章节会讲到)。
(3)反对利用事件(实现 ApplicationEventPublisher 接口)。
2.2. BeanDefinition
BeanDefinition 用于保留 Bean 的相干信息,包含属性、构造方法参数、依赖的 Bean 名称及是否单例、提早加载等,它相当于实例化 Bean 的原材料,Spring 就是依据 BeanDefinition 中的信息实例化 Bean。,其继承体系如下图所示。
2.3. BeanDefinitionReader
Bean 的解析过程非常复杂,性能被分得很细,因为这里须要被扩大的中央很多,必须保障足够的灵活性,以应答可能的变动。Bean 的解析次要就是对 Spring 配置文件的解析。这个解析过程次要通过 BeanDefinitionReader 来实现,看看 Spring 中 BeanDefinitionReader 的类结构图,如下图所示。
通过后面的剖析,咱们对 Spring 框架体系有了一个根本的宏观理解,心愿“小伙伴们”好好了解,最好在脑海中造成画面,为当前的学习打下良好的根底。
3 基于 Web 的 IoC 容器初体验
咱们还是从大家最相熟的 DispatcherServlet 开始,最先想到的应该是 DispatcherServlet 的 init() 办法。咱们在 DispatherServlet 中并没有找到 init() 办法,通过摸索,在其父类 HttpServletBean 中找到了,代码如下:
@Override
public final void init() throws ServletException {if (logger.isDebugEnabled()) {logger.debug("Initializing servlet'" + getServletName() + "'");
}
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(),
this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 定位资源
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 加载配置信息
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader,
getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error("Failed to set bean properties on servlet'" + getServletName() + "'", ex);
}
throw ex;
}
}
initServletBean();
if (logger.isDebugEnabled()) {logger.debug("Servlet'" + getServletName() + "'configured successfully");
}
}
在 init() 办法中,真正实现初始化容器动作的代码其实在 initServletBean() 办法中,咱们持续跟进:
@Override
protected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring FrameworkServlet'" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {this.logger.info("FrameworkServlet'" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();}
catch (ServletException ex) {this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet'" + getServletName() + "': initialization completed
in "+ elapsedTime +" ms");
}
}
在下面的代码中终于看到了似曾相识的代码 initWebApplicationContext(),持续跟进:
protected WebApplicationContext initWebApplicationContext() {
// 先从 ServletContext 中取得父容器 WebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// 申明子容器
WebApplicationContext wac = null;
// 建设父、子容器之间的关联关系
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 先去 ServletContext 中查找 Web 容器的援用是否存在,并创立好默认的空 IoC 容器
if (wac == null) {wac = findWebApplicationContext();
}
// 给上一步创立好的 IoC 容器赋值
if (wac == null) {wac = createWebApplicationContext(rootContext);
}
// 触发 onRefresh() 办法
if (!this.refreshEventReceived) {onRefresh(wac);
}
if (this.publishContext) {String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {this.logger.debug("Published WebApplicationContext of servlet'" + getServletName() +
"'as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
@Nullable
protected WebApplicationContext findWebApplicationContext() {String attrName = getContextAttribute();
if (attrName == null) {return null;}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {this.logger.debug("Servlet with name'" + getServletName() +
"'will try to create custom WebApplicationContext context of class'" +
contextClass.getName() + "'"+", using parent context ["+ parent +"]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Fatal initialization error in servlet with name'" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {if (this.contextId != null) {wac.setId(this.contextId);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(),
getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();}
从下面的代码能够看出,在 configAndRefreshWebApplicationContext() 办法中调用了 refresh() 办法,这是真正启动 IoC 容器的入口,前面会具体介绍。IoC 容器初始化当前,调用了 DispatcherServlet 的 onRefresh() 办法,在 onRefresh() 办法中又间接调用 initStrategies() 办法初始化 Spring MVC 的九大组件:
@Override
protected void onRefresh(ApplicationContext context) {initStrategies(context);
}
// 初始化策略
protected void initStrategies(ApplicationContext context) {
// 多文件上传的组件
initMultipartResolver(context);
// 初始化本地语言环境
initLocaleResolver(context);
// 初始化模板处理器
initThemeResolver(context);
// 初始化 handlerMapping
initHandlerMappings(context);
// 初始化参数适配器
initHandlerAdapters(context);
// 初始化异样拦截器
initHandlerExceptionResolvers(context);
// 初始化视图预处理器
initRequestToViewNameTranslator(context);
// 初始化视图转换器
initViewResolvers(context);
// 初始化 Flashmap 管理器
initFlashMapManager(context);
}
关注微信公众号『Tom 弹架构』回复“Spring”可获取残缺源码。
本文为“Tom 弹架构”原创,转载请注明出处。技术在于分享,我分享我高兴!如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源。关注微信公众号『Tom 弹架构』可获取更多技术干货!
原创不易,保持很酷,都看到这里了,小伙伴记得点赞、珍藏、在看,一键三连加关注!如果你感觉内容太干,能够分享转发给敌人滋润滋润!