本文节选自《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弹架构 』可获取更多技术干货!
原创不易,保持很酷,都看到这里了,小伙伴记得点赞、珍藏、在看,一键三连加关注!如果你感觉内容太干,能够分享转发给敌人滋润滋润!
发表回复