做婚恋网站赚钱吗手工制作大全简单漂亮
web/
2025/9/27 11:25:58/
文章来源:
做婚恋网站赚钱吗,手工制作大全简单漂亮,免费建站平台哪个好,网站服务器租用协议IOC 与 DI
IOC(Inversion of Control)控制反转#xff1a;所谓控制反转#xff0c;就是把原先我们代码里面需要实现的对象创建、依赖的代码#xff0c;反转给容器来帮忙实现。那么必然的我们需要创建一个容器#xff0c;同时需要一种描述来让容器知道需要创建的对象与对象…IOC 与 DI
IOC(Inversion of Control)控制反转所谓控制反转就是把原先我们代码里面需要实现的对象创建、依赖的代码反转给容器来帮忙实现。那么必然的我们需要创建一个容器同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们所看到的配置文件。DI(Dependency Injection)依赖注入就是指对象是被动接受依赖类而不是自己主动去找换句话说就是指对象不是从容器中查找它依赖的类而是在容器实例化对象的时候主动将它依赖的类注入给它。
1、对象和对象的关系怎么表示
可以用 xmlproperties 文件等语义化配置文件表示。
2、描述对象关系的文件存放在哪里
可能是 classpathfilesystem或者是 URL 网络资源servletContext 等。
3、不同的配置文件对对象的描述不一样如标准的自定义声明式的如何统一
在内部需要有一个统一的关于对象的定义所有外部的描述都必须转化成统一的描述定义。
4、如何对不同的配置文件进行解析
需要对不同的配置文件语法采用不同的解析器。Spring 核心容器类图
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 ClassT requiredType) throws BeansException;Object getBean(String name, Object... args) throws BeansException;T T getBean(ClassT requiredType) throws BeansException;T T getBean(ClassT requiredType, Object... args) throws BeansException;//提供对 bean 的检索看看是否在 IOC 容器有这个名字的 beanboolean containsBean(String name);//根据 bean 名字得到 bean 实例并同时判断这个 bean 是不是单例boolean isSingleton(String name) throws NoSuchBeanDefinitionException;boolean isPrototype(String name) throws NoSuchBeanDefinitionException;boolean isTypeMatch(String name, ResolvableType typeToMatch) throwsNoSuchBeanDefinitionException;boolean isTypeMatch(String name, Nullable Class? typeToMatch) throwsNoSuchBeanDefinitionException;//得到 bean 实例的 Class 类型NullableClass? getType(String name) throws NoSuchBeanDefinitionException;//得到 bean 的别名如果根据别名检索那么其原名也会被检索出来String[] getAliases(String name);
}在 BeanFactory 里只对 IOC 容器的基本行为作了定义根本不关心你的 Bean 是如何定义怎样加载的。正如我们只关心工厂里得到什么的产品对象至于工厂是怎么生产这些对象的这个基本的接口不关心。而要知道工厂是如何产生对象的我们需要看具体的 IOC 容器实现Spring 提供了许多 IOC 容器的 实 现 。 比 如 GenericApplicationContext ClasspathXmlApplicationContext 等 。ApplicationContext 是 Spring 提供的一个高级的 IOC 容器它除了能够提供 IOC 容器的基本功能外还为用户提供了以下的附加服务。从 ApplicationContext 接口的实现我们看出其特点
1、支持信息源可以实现国际化。实现 MessageSource 接口
2、访问资源。(实现 ResourcePatternResolver 接口后面章节会讲到)
3、支持应用事件。(实现 ApplicationEventPublisher 接口)2、BeanDefinition
SpringIOC 容器管理了我们定义的各种 Bean 对象及其相互的关系Bean 对象在 Spring 实现中是以 BeanDefinition 来描述的其继承体系如下3、BeanDefinitionReader
Bean 的解析过程非常复杂功能被分的很细因为这里需要被扩展的地方很多必须保证有足够的灵活性以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过BeanDefintionReader 来完成最后看看 Spring 中 BeanDefintionReader 的类结构图Web IOC 容器
我们还是从大家最熟悉的 DispatcherServlet 开始我们最先想到的还是 DispatcherServlet 的 init()方法。我们发现在 DispatherServlet 中并没有找到 init()方法。但是经过探索往上追索在其父类HttpServletBean 中找到了我们想要的 init()方法如下/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
Override
public final void init() throws ServletException {if (logger.isDebugEnabled()) {logger.debug(Initializing servlet getServletName() );}// Set bean properties from init parameters.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;}}// Let subclasses do whatever initialization they like.initServletBean();if (logger.isDebugEnabled()) {logger.debug(Servlet getServletName() configured successfully);}
}在 init()方法中真正完成初始化容器动作的逻辑其实在 initServletBean()方法中我们继续跟进initServletBean()中的代码在 FrameworkServlet 类中/**
* Overridden method of {link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlets WebApplicationContext.
*/
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);}
}在上面的代码中终于看到了我们似曾相识的代码 initWebAppplicationContext()继续跟进/**
* Initialize and publish the WebApplicationContext for this servlet.
* pDelegates to {link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* return the WebApplicationContext instance
* see #FrameworkServlet(WebApplicationContext)
* see #setContextClass
* see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {//先从 ServletContext 中获得父容器 WebAppliationContextWebApplicationContext rootContext WebApplicationContextUtils.getWebApplicationContext(getServletContext());//声明子容器WebApplicationContext wac null;//建立父、子容器之间的关联关系if (this.webApplicationContext ! null) {// A context instance was injected at construction time - use itwac this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {// The context has not yet been refreshed - provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() null) {// The context instance was injected without an explicit parent - set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}//先去 ServletContext 中查找 Web 容器的引用是否存在并创建好默认的空 IOC 容器if (wac null) {// No context instance was injected at construction time - see if one// has been registered in the servlet context. If one exists, it is assumed// that the parent context (if any) has already been set and that the// user has performed any initialization such as setting the context idwac findWebApplicationContext();}//给上一步创建好的 IOC 容器赋if (wac null) {// No context instance is defined for this servlet - create a local onewac createWebApplicationContext(rootContext);}//触发 onRefresh 方法if (!this.refreshEventReceived) {// Either the context is not a ConfigurableApplicationContext with refresh// support or the context injected at construction time had already been// refreshed - trigger initial onRefresh manually here.onRefresh(wac);}if (this.publishContext) {// Publish the context as a servlet context attribute.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;
}
/**
* Retrieve a {code WebApplicationContext} from the {code ServletContext}
* attribute with the {link #setContextAttribute configured name}. The
* {code WebApplicationContext} must have already been loaded and stored in the
* {code ServletContext} before this servlet gets initialized (or invoked).
* pSubclasses may override this method to provide a different
* {code WebApplicationContext} retrieval strategy.
* return the WebApplicationContext for this servlet, or {code null} if not found
* see #getContextAttribute()
*/
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;
}
/**
* Instantiate the WebApplicationContext for this servlet, either a default
* {link org.springframework.web.context.support.XmlWebApplicationContext}
* or a {link #setContextClass custom context class}, if set.
* pThis implementation expects custom contexts to implement the
* {link org.springframework.web.context.ConfigurableWebApplicationContext}
* interface. Can be overridden in subclasses.
* pDo not forget to register this servlet instance as application listener on the
* created context (for triggering its {link #onRefresh callback}, and to call
* {link org.springframework.context.ConfigurableApplicationContext#refresh()}
* before returning the context instance.
* param parent the parent ApplicationContext to use, or {code null} if none
* return the WebApplicationContext for this servlet
* see org.springframework.web.context.support.XmlWebApplicationContext
*/
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())) {
// The application context id is still set to its original default value
// - assign a more useful id based on available information
if (this.contextId ! null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
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()));
// The wac environments #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
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()方法初始化 SpringMVC 的九大组件/**
* This implementation calls {link #initStrategies}.
*/
Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* pMay be overridden in subclasses in order to initialize further strategy objects.
*/
//初始化策略
protected void initStrategies(ApplicationContext context) {
//多文件上传的组件
initMultipartResolver(context);
//初始化本地语言环境
initLocaleResolver(context);
//初始化模板处理器
initThemeResolver(context);
//handlerMapping
initHandlerMappings(context);
//初始化参数适配器
initHandlerAdapters(context);
//初始化异常拦截器
initHandlerExceptionResolvers(context);
//初始化视图预处理器
initRequestToViewNameTranslator(context);
//初始化视图转换器
initViewResolvers(context);
//
initFlashMapManager(context);
}基于 Xml 的 IOC 容器的初始化
IOC 容器的初始化包括 BeanDefinition 的 Resource 定位、加载和注册这三个基本的过程。我们以ApplicationContext 为例讲解ApplicationContext 系列容器也许是我们最熟悉的因为 Web 项目中使用的 XmlWebApplicationContext 就属于这个继承体系还有 ClasspathXmlApplicationContext等其继承体系如下图所示ApplicationContext 允许上下文嵌套通过保持父上下文可以维持一个上下文体系。对于 Bean 的查找可以在这个上下文体系中发生首先检查当前上下文其次是父上下文逐级向上这样为不同的 Spring应用提供了一个共享的 Bean 定义环境。
1、寻找入口
还有一个我们用的比较多的 ClassPathXmlApplicationContext通过 main()方法启动ApplicationContext app new ClassPathXmlApplicationContext(application.xml);先看其构造函数的调用public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[]{configLocation}, true, (ApplicationContext)null);
}其实际调用的构造函数为public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, Nullable
ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if(refresh) {
this.refresh();
}
}还 有 像 AnnotationConfigApplicationContext 、 FileSystemXmlApplicationContext 、XmlWebApplicationContext 等都继承自父容器 AbstractApplicationContext主要用到了装饰器模式和策略模式最终都是调用 refresh()方法。2、获得配置路径
通 过 分 析 ClassPathXmlApplicationContext 的 源 代 码 可 以 知 道 在 创 建ClassPathXmlApplicationContext 容器时构造方法做以下两项重要工作首先调用父类容器的构造方法(super(parent)方法)为容器设置好 Bean 资源加载器。然 后 再 调 用 父 类 AbstractRefreshableConfigApplicationContext 的setConfigLocations(configLocations)方法设置 Bean 配置信息的定位路径。通 过 追 踪 ClassPathXmlApplicationContext 的 继 承 体 系 发 现 其 父 类 的 父 类AbstractApplicationContext 中初始化 IOC 容器所做的主要源码如下public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
//静态初始化块在整个容器创建过程中只执行一次
static {
//为了避免应用程序在 Weblogic8.1 关闭时出现类加载异常加载问题加载 IOC 容
//器关闭事件(ContextClosedEvent)类
ContextClosedEvent.class.getName();
}
public AbstractApplicationContext() {
this.resourcePatternResolver getResourcePatternResolver();
}
public AbstractApplicationContext(Nullable ApplicationContext parent) {
this();
setParent(parent);
}
//获取一个 Spring Source 的加载器用于读入 Spring Bean 配置信息
protected ResourcePatternResolver getResourcePatternResolver() {
//AbstractApplicationContext 继承 DefaultResourceLoader因此也是一个资源加载器
//Spring 资源加载器其 getResource(String location)方法用于载入资源
return new PathMatchingResourcePatternResolver(this);
}
...
}AbstractApplicationContext 的默认构造方法中有调用 PathMatchingResourcePatternResolver 的构造方法创建 Spring 资源加载器public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, ResourceLoader must not be null);
//设置 Spring 的资源加载器
this.resourceLoader resourceLoader;
}在设置容器的资源加载器之后接下来 ClassPathXmlApplicationContext 执行 setConfigLocations()方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean配置信息的定位该方法的源码如下/*** Set the config locations for this application context in init-param style,* i.e. with distinct locations separated by commas, semicolons or whitespace.* pIf not set, the implementation may use a default as appropriate.*///处理单个资源文件路径为一个字符串的情况public void setConfigLocation(String location) {//String CONFIG_LOCATION_DELIMITERS ,; /t/n;//即多个资源文件路径之间用” ,; \t\n”分隔解析成数组形式setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));}
/*** Set the config locations for this application context.* pIf not set, the implementation may use a default as appropriate.*///解析Bean定义资源文件的路径处理多个资源文件字符串数组public void setConfigLocations(Nullable String... locations) {if (locations ! null) {Assert.noNullElements(locations, Config locations must not be null);this.configLocations new String[locations.length];for (int i 0; i locations.length; i) {// resolvePath为同一个类中将字符串解析为路径的方法this.configLocations[i] resolvePath(locations[i]).trim();}}else {this.configLocations null;}}通过这两个方法的源码我们可以看出我们既可以使用一个字符串来配置多个 Spring Bean 配置信息也可以使用字符串数组即下面两种方式都是可以的ClassPathResource res new ClassPathResource(a.xml,b.xml);多个资源文件路径之间可以是用” , ; \t\n”等分隔。ClassPathResource res new ClassPathResource(new String[]{a.xml,b.xml})至此SpringIOC 容器在初始化时将配置的 Bean 配置信息定位为 Spring 封装的 Resource。3、开始启动
SpringIOC 容器对 Bean 配置资源的载入是从 refresh()函数开始的refresh()是一个模板方法规定了IOC 容 器 的 启 动 流 程 有 些 逻 辑 要 交 给 其 子 类 去 实 现 。 它 对 Bean 配 置 资 源 进 行 载 入ClassPathXmlApplicationContext 通过调用其父类 AbstractApplicationContext 的 refresh()函数启动整个 IOC 容器对 Bean 定义的载入过程现在我们来详细看看 refresh()中的逻辑处理Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.//1、调用容器准备刷新的方法获取容器的当时时间同时给容器设置同步标识prepareRefresh();// Tell the subclass to refresh the internal bean factory.//2、告诉子类启动refreshBeanFactory()方法Bean定义资源文件的载入从//子类的refreshBeanFactory()方法启动ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory();// Prepare the bean factory for use in this context.//3、为BeanFactory配置容器特性例如类加载器、事件处理器等prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.//4、为容器的某些子类指定特殊的BeanPost事件处理器postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.//5、调用所有注册的BeanFactoryPostProcessor的BeaninvokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.//6、为BeanFactory注册BeanPost事件处理器.//BeanPostProcessor是Bean后置处理器用于监听容器触发的事件registerBeanPostProcessors(beanFactory);// Initialize message source for this context.//7、初始化信息源和国际化相关.initMessageSource();// Initialize event multicaster for this context.//8、初始化容器事件传播器.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.//9、调用子类的某些特殊Bean初始化方法onRefresh();// Check for listener beans and register them.//10、为事件传播器注册事件监听器.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.//11、初始化所有剩余的单例BeanfinishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.//12、初始化容器的生命周期事件处理器并发布容器的生命周期事件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.//13、销毁已创建的BeandestroyBeans();// Reset active flag.//14、取消refresh操作重置容器的同步标识。cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Springs core, since we// might not ever need metadata for singleton beans anymore...//15、重设公共缓存resetCommonCaches();}}}refresh()方法主要为 IOC 容器 Bean 的生命周期管理提供条件Spring IOC 容器载入 Bean 配置信息从 其 子 类 容 器 的 refreshBeanFactory() 方 法 启 动 所 以 整 个 refresh() 中“ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory();”这句以后代码的都是注册容器的信息源和生命周期事件我们前面说的载入就是从这句代码开始启动。
refresh()方法的主要作用是在创建 IOC 容器前如果已经有容器存在则需要把已有的容器销毁和关闭以保证在 refresh 之后使用的是新建立起来的 IOC 容器。它类似于对 IOC 容器的重启在新建立好的容器中对容器进行初始化对 Bean 配置资源进行载入。4、创建容器
obtainFreshBeanFactory()方法调用子类容器的 refreshBeanFactory()方法启动容器载入 Bean 配置信息的过程代码如下/*** Tell the subclass to refresh the internal bean factory.* return the fresh BeanFactory instance* see #refreshBeanFactory()* see #getBeanFactory()*/protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {//这里使用了委派设计模式父类定义了抽象的refreshBeanFactory()方法具体实现调用子类容器的refreshBeanFactory()方法refreshBeanFactory();ConfigurableListableBeanFactory beanFactory getBeanFactory();if (logger.isDebugEnabled()) {logger.debug(Bean factory for getDisplayName() : beanFactory);}return beanFactory;}
AbstractApplicationContext 类中只抽象定义了 refreshBeanFactory()方法容器真正调用的是其子类 AbstractRefreshableApplicationContext 实现的 refreshBeanFactory()方法方法的源码如下/*** This implementation performs an actual refresh of this contexts underlying* bean factory, shutting down the previous bean factory (if any) and* initializing a fresh bean factory for the next phase of the contexts lifecycle.*/Overrideprotected final void refreshBeanFactory() throws BeansException {//如果已经有容器销毁容器中的bean关闭容器if (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {//创建IOC容器DefaultListableBeanFactory beanFactory createBeanFactory();beanFactory.setSerializationId(getId());//对IOC容器进行定制化如设置启动参数开启注解的自动装配等customizeBeanFactory(beanFactory);//调用载入Bean定义的方法主要这里又使用了一个委派模式在当前类中只定义了抽象的loadBeanDefinitions方法具体的实现调用子类容器loadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory beanFactory;}}catch (IOException ex) {throw new ApplicationContextException(I/O error parsing bean definition source for getDisplayName(), ex);}}
在这个方法中先判断 BeanFactory 是否存在如果存在则先销毁 beans 并关闭 beanFactory接着创建 DefaultListableBeanFactory并调用 loadBeanDefinitions(beanFactory)装载 bean 定义。5、载入配置路径
AbstractRefreshableApplicationContext 中只定义了抽象的 loadBeanDefinitions 方法容器真正调用的是其子类 AbstractXmlApplicationContext 对该方法的实现AbstractXmlApplicationContext的主要源码如下
loadBeanDefinitions() 方 法 同 样 是 抽 象 方 法 是 由 其 子 类 实 现 的 也 即 在AbstractXmlApplicationContext 中。public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {private boolean validating true;/*** Create a new AbstractXmlApplicationContext with no parent.*/public AbstractXmlApplicationContext() {}/*** Create a new AbstractXmlApplicationContext with the given parent context.* param parent the parent context*/public AbstractXmlApplicationContext(Nullable ApplicationContext parent) {super(parent);}/*** Set whether to use XML validation. Default is {code true}.*/public void setValidating(boolean validating) {this.validating validating;}/*** Loads the bean definitions via an XmlBeanDefinitionReader.* see org.springframework.beans.factory.xml.XmlBeanDefinitionReader* see #initBeanDefinitionReader* see #loadBeanDefinitions*///实现父类抽象的载入Bean定义方法Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// Create a new XmlBeanDefinitionReader for the given BeanFactory.//创建XmlBeanDefinitionReader即创建Bean读取器并通过回调设置到容器中去容 器使用该读取器读取Bean定义资源XmlBeanDefinitionReader beanDefinitionReader new XmlBeanDefinitionReader(beanFactory);// Configure the bean definition reader with this contexts// resource loading environment.//为Bean读取器设置Spring资源加载器AbstractXmlApplicationContext的//祖先父类AbstractApplicationContext继承DefaultResourceLoader因此容器本身也是一个资源加载器beanDefinitionReader.setEnvironment(this.getEnvironment());beanDefinitionReader.setResourceLoader(this);//为Bean读取器设置SAX xml解析器beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));// Allow a subclass to provide custom initialization of the reader,// then proceed with actually loading the bean definitions.//当Bean读取器读取Bean定义的Xml资源文件时启用Xml的校验机制initBeanDefinitionReader(beanDefinitionReader);//Bean读取器真正实现加载的方法loadBeanDefinitions(beanDefinitionReader);}/*** Initialize the bean definition reader used for loading the bean* definitions of this context. Default implementation is empty.* pCan be overridden in subclasses, e.g. for turning off XML validation* or using a different XmlBeanDefinitionParser implementation.* param reader the bean definition reader used by this context* see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass*/protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {reader.setValidating(this.validating);}/*** Load the bean definitions with the given XmlBeanDefinitionReader.* pThe lifecycle of the bean factory is handled by the {link #refreshBeanFactory}* method; hence this method is just supposed to load and/or register bean definitions.* param reader the XmlBeanDefinitionReader to use* throws BeansException in case of bean registration errors* throws IOException if the required XML document isnt found* see #refreshBeanFactory* see #getConfigLocations* see #getResources* see #getResourcePatternResolver*///Xml Bean读取器加载Bean定义资源protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {//获取Bean定义资源的定位Resource[] configResources getConfigResources();if (configResources ! null) {//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位//的Bean定义资源reader.loadBeanDefinitions(configResources);}//如果子类中获取的Bean定义资源定位为空则获取FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源String[] configLocations getConfigLocations();if (configLocations ! null) {//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位//的Bean定义资源reader.loadBeanDefinitions(configLocations);}}/*** Return an array of Resource objects, referring to the XML bean definition* files that this context should be built with.* pThe default implementation returns {code null}. Subclasses can override* this to provide pre-built Resource objects rather than location Strings.* return an array of Resource objects, or {code null} if none* see #getConfigLocations()*///这里又使用了一个委托模式调用子类的获取Bean定义资源定位的方法//该方法在ClassPathXmlApplicationContext中进行实现对于我们//举例分析源码的FileSystemXmlApplicationContext没有使用该方法Nullableprotected Resource[] getConfigResources() {return null;}}以 XmlBean 读取器的其中一种策略 XmlBeanDefinitionReader 为例。XmlBeanDefinitionReader 调用其父类AbstractBeanDefinitionReader的 reader.loadBeanDefinitions()方法读取Bean配置资源。由于我们使用 ClassPathXmlApplicationContext 作为例子分析因此 getConfigResources 的返回值为 null因此程序执行 reader.loadBeanDefinitions(configLocations)分支。6、分配路径处理策略
在 XmlBeanDefinitionReader 的抽象父类 AbstractBeanDefinitionReader 中定义了载入过程。AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源码如下//重载方法调用下面的loadBeanDefinitions(String, SetResource);方法Overridepublic int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {return loadBeanDefinitions(location, null);}/*** Load bean definitions from the specified resource location.* pThe location can also be a location pattern, provided that the* ResourceLoader of this bean definition reader is a ResourcePatternResolver.* param location the resource location, to be loaded with the ResourceLoader* (or ResourcePatternResolver) of this bean definition reader* param actualResources a Set to be filled with the actual Resource objects* that have been resolved during the loading process. May be {code null}* to indicate that the caller is not interested in those Resource objects.* return the number of bean definitions found* throws BeanDefinitionStoreException in case of loading or parsing errors* see #getResourceLoader()* see #loadBeanDefinitions(org.springframework.core.io.Resource)* see #loadBeanDefinitions(org.springframework.core.io.Resource[])*/public int loadBeanDefinitions(String location, Nullable SetResource actualResources) throws BeanDefinitionStoreException {//获取在IoC容器初始化过程中设置的资源加载器ResourceLoader resourceLoader getResourceLoader();if (resourceLoader null) {throw new BeanDefinitionStoreException(Cannot import bean definitions from location [ location ]: no ResourceLoader available);}if (resourceLoader instanceof ResourcePatternResolver) {// Resource pattern matching available.try {//将指定位置的Bean定义资源文件解析为Spring IOC容器封装的资源//加载多个指定位置的Bean定义资源文件Resource[] resources ((ResourcePatternResolver) resourceLoader).getResources(location);//委派调用其子类XmlBeanDefinitionReader的方法实现加载功能int loadCount loadBeanDefinitions(resources);if (actualResources ! null) {for (Resource resource : resources) {actualResources.add(resource);}}if (logger.isDebugEnabled()) {logger.debug(Loaded loadCount bean definitions from location pattern [ location ]);}return loadCount;}catch (IOException ex) {throw new BeanDefinitionStoreException(Could not resolve bean definition resource pattern [ location ], ex);}}else {// Can only load single resources by absolute URL.//将指定位置的Bean定义资源文件解析为Spring IOC容器封装的资源//加载单个指定位置的Bean定义资源文件Resource resource resourceLoader.getResource(location);//委派调用其子类XmlBeanDefinitionReader的方法实现加载功能int loadCount loadBeanDefinitions(resource);if (actualResources ! null) {actualResources.add(resource);}if (logger.isDebugEnabled()) {logger.debug(Loaded loadCount bean definitions from location [ location ]);}return loadCount;}}//重载方法调用loadBeanDefinitions(String);Overridepublic int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {Assert.notNull(locations, Location array must not be null);int counter 0;for (String location : locations) {counter loadBeanDefinitions(location);}return counter;}AbstractRefreshableConfigApplicationContext 的 loadBeanDefinitions(Resource...resources) 方法实际上是调用 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法。
从对 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源码分析可以看出该方法就做了两件事
首先调用资源加载器的获取资源方法 resourceLoader.getResource(location)获取到要加载的资源。
其次真正执行加载功能是其子类 XmlBeanDefinitionReader 的 loadBeanDefinitions()方法。在loadBeanDefinitions()方法中调用了 AbstractApplicationContext 的 getResources()方法跟进去之后发现 getResources()方法其实定义在 ResourcePatternResolver 中此时我们有必要来看一下ResourcePatternResolver 的全类图从上面可以看到 ResourceLoader 与 ApplicationContext 的继承关系可以看出其实际调用的是DefaultResourceLoader 中 的 getSource() 方 法 定 位 Resource 因 为ClassPathXmlApplicationContext 本身就是 DefaultResourceLoader 的实现类所以此时又回到了ClassPathXmlApplicationContext 中来。
7、解析配置文件路径
XmlBeanDefinitionReader 通 过 调 用 ClassPathXmlApplicationContext 的 父 类DefaultResourceLoader 的 getResource()方法获取要加载的资源其源码如下//获取Resource的具体实现方法Overridepublic Resource getResource(String location) {Assert.notNull(location, Location must not be null);for (ProtocolResolver protocolResolver : this.protocolResolvers) {Resource resource protocolResolver.resolve(location, this);if (resource ! null) {return resource;}}//如果是类路径的方式那需要使用ClassPathResource 来得到bean 文件的资源对象if (location.startsWith(/)) {return getResourceByPath(location);}else if (location.startsWith(CLASSPATH_URL_PREFIX)) {return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());}else {try {// Try to parse the location as a URL...// 如果是URL 方式使用UrlResource 作为bean 文件的资源对象URL url new URL(location);return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));}catch (MalformedURLException ex) {// No URL - resolve as resource path.//如果既不是classpath标识又不是URL标识的Resource定位则调用//容器本身的getResourceByPath方法获取Resourcereturn getResourceByPath(location);}}}DefaultResourceLoader 提供了 getResourceByPath()方法的实现就是为了处理既不是 classpath标识又不是 URL 标识的 Resource 定位这种情况。/*** Return a Resource handle for the resource at the given path.* pThe default implementation supports class path locations. This should* be appropriate for standalone implementations but can be overridden,* e.g. for implementations targeted at a Servlet container.* param path the path to the resource* return the corresponding Resource handle* see ClassPathResource* see org.springframework.context.support.FileSystemXmlApplicationContext#getResourceByPath* see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath*/protected Resource getResourceByPath(String path) {return new ClassPathContextResource(path, getClassLoader());}
在 ClassPathResource 中完成了对整个路径的解析。这样就可以从类路径上对 IOC 配置文件进行加载当然我们可以按照这个逻辑从任何地方加载在 Spring 中我们看到它提供的各种资源抽象比如ClassPathResource、URLResource、FileSystemResource 等来供我们使用。上面我们看到的是定位Resource 的一个过程而这只是加载过程的一部分。例如 FileSystemXmlApplication 容器就重写了getResourceByPath()方法/*** Resolve resource paths as file system paths.* pNote: Even if a given path starts with a slash, it will get* interpreted as relative to the current VM working directory.* This is consistent with the semantics in a Servlet container.* param path path to the resource* return Resource handle* see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath*/Overrideprotected Resource getResourceByPath(String path) {if (path.startsWith(/)) {path path.substring(1);}//这里使用文件系统资源对象来定义bean 文件return new FileSystemResource(path);}}通过子类的覆盖巧妙地完成了将类路径变为文件路径的转换。8、开始读取配置内容
继续回到 XmlBeanDefinitionReader 的 loadBeanDefinitions(Resource …)方法看到代表 bean 文件的资源定义以后的载入过程。/*** Load bean definitions from the specified XML file.* param resource the resource descriptor for the XML file* return the number of bean definitions found* throws BeanDefinitionStoreException in case of loading or parsing errors*///XmlBeanDefinitionReader加载资源的入口方法Overridepublic int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {//将读入的XML资源进行特殊编码处理return loadBeanDefinitions(new EncodedResource(resource));}
/*** Load bean definitions from the specified XML file.* param encodedResource the resource descriptor for the XML file,* allowing to specify an encoding to use for parsing the file* return the number of bean definitions found* throws BeanDefinitionStoreException in case of loading or parsing errors*///这里是载入XML形式Bean定义资源文件方法public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {Assert.notNull(encodedResource, EncodedResource must not be null);if (logger.isInfoEnabled()) {logger.info(Loading XML bean definitions from encodedResource.getResource());}SetEncodedResource currentResources this.resourcesCurrentlyBeingLoaded.get();if (currentResources null) {currentResources new HashSet(4);this.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException(Detected cyclic loading of encodedResource - check your import definitions!);}try {//将资源文件转为InputStream的IO流InputStream inputStream encodedResource.getResource().getInputStream();try {//从InputStream中得到XML的解析源InputSource inputSource new InputSource(inputStream);if (encodedResource.getEncoding() ! null) {inputSource.setEncoding(encodedResource.getEncoding());}//这里是具体的读取过程return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {//关闭从Resource中得到的IO流inputStream.close();}}catch (IOException ex) {throw new BeanDefinitionStoreException(IOException parsing XML document from encodedResource.getResource(), ex);}finally {currentResources.remove(encodedResource);if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.remove();}}}
/*** Actually load bean definitions from the specified XML file.* param inputSource the SAX InputSource to read from* param resource the resource descriptor for the XML file* return the number of bean definitions found* throws BeanDefinitionStoreException in case of loading or parsing errors* see #doLoadDocument* see #registerBeanDefinitions*///从特定XML文件中实际载入Bean定义资源的方法protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {//将XML文件转换为DOM对象解析过程由documentLoader实现Document doc doLoadDocument(inputSource, resource);//这里是启动对Bean定义解析的详细过程该解析过程会用到Spring的Bean配置规则return registerBeanDefinitions(doc, resource);}catch (BeanDefinitionStoreException ex) {throw ex;}catch (SAXParseException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),Line ex.getLineNumber() in XML document from resource is invalid, ex);}catch (SAXException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),XML document from resource is invalid, ex);}catch (ParserConfigurationException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),Parser configuration exception parsing XML from resource, ex);}catch (IOException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),IOException parsing XML document from resource, ex);}catch (Throwable ex) {throw new BeanDefinitionStoreException(resource.getDescription(),Unexpected exception parsing XML document from resource, ex);}}通过源码分析载入 Bean 配置信息的最后一步是将 Bean 配置信息转换为 Document 对象该过程由documentLoader()方法实现。9、准备文档对象
DocumentLoader 将 Bean 配置资源转换成 Document 对象的源码如下/*** Load the {link Document} at the supplied {link InputSource} using the standard JAXP-configured* XML parser.*///使用标准的JAXP将载入的Bean定义资源转换成document对象Overridepublic Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {//创建文件解析器工厂DocumentBuilderFactory factory createDocumentBuilderFactory(validationMode, namespaceAware);if (logger.isDebugEnabled()) {logger.debug(Using JAXP provider [ factory.getClass().getName() ]);}//创建文档解析器DocumentBuilder builder createDocumentBuilder(factory, entityResolver, errorHandler);//解析Spring的Bean定义资源return builder.parse(inputSource);}/*** Create the {link DocumentBuilderFactory} instance.* param validationMode the type of validation: {link XmlValidationModeDetector#VALIDATION_DTD DTD}* or {link XmlValidationModeDetector#VALIDATION_XSD XSD})* param namespaceAware whether the returned factory is to provide support for XML namespaces* return the JAXP DocumentBuilderFactory* throws ParserConfigurationException if we failed to build a proper DocumentBuilderFactory*/protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)throws ParserConfigurationException {//创建文档解析工厂DocumentBuilderFactory factory DocumentBuilderFactory.newInstance();factory.setNamespaceAware(namespaceAware);//设置解析XML的校验if (validationMode ! XmlValidationModeDetector.VALIDATION_NONE) {factory.setValidating(true);if (validationMode XmlValidationModeDetector.VALIDATION_XSD) {// Enforce namespace aware for XSD...factory.setNamespaceAware(true);try {factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);}catch (IllegalArgumentException ex) {ParserConfigurationException pcex new ParserConfigurationException(Unable to validate using XSD: Your JAXP provider [ factory ] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? Upgrade to Apache Xerces (or Java 1.5) for full XSD support.);pcex.initCause(ex);throw pcex;}}}return factory;}
上面的解析过程是调用 JavaEE 标准的 JAXP 标准进行处理。至此 Spring IOC 容器根据定位的 Bean 配置信息将其加载读入并转换成为 Document 对象过程完成。接下来我们要继续分析 Spring IOC 容器将载入的 Bean 配置信息转换为 Document 对象之后是如何将其解析为 Spring IOC 管理的 Bean 对象并将其注册到容器中的。10、分配解析策略
XmlBeanDefinitionReader 类中的 doLoadBeanDefinition()方法是从特定 XML 文件中实际载入Bean 配置资源的方法该方法在载入 Bean 配置资源之后将其转换为 Document 对象接下来调用registerBeanDefinitions() 启 动 Spring IOC 容 器 对 Bean 定 义 的 解 析 过 程 registerBeanDefinitions()方法源码如下/*** Register the bean definitions contained in the given DOM document.* Called by {code loadBeanDefinitions}.* pCreates a new instance of the parser class and invokes* {code registerBeanDefinitions} on it.* param doc the DOM document* param resource the resource descriptor (for context information)* return the number of bean definitions found* throws BeanDefinitionStoreException in case of parsing errors* see #loadBeanDefinitions* see #setDocumentReaderClass* see BeanDefinitionDocumentReader#registerBeanDefinitions*///按照Spring的Bean语义要求将Bean定义资源解析并转换为容器内部数据结构public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {//得到BeanDefinitionDocumentReader来对xml格式的BeanDefinition解析BeanDefinitionDocumentReader documentReader createBeanDefinitionDocumentReader();//获得容器中注册的Bean数量int countBefore getRegistry().getBeanDefinitionCount();//解析过程入口这里使用了委派模式BeanDefinitionDocumentReader只是个接口,//具体的解析实现过程有实现类DefaultBeanDefinitionDocumentReader完成documentReader.registerBeanDefinitions(doc, createReaderContext(resource));//统计解析的Bean数量return getRegistry().getBeanDefinitionCount() - countBefore;}Bean 配置资源的载入解析分为以下两个过程
首先通过调用 XML 解析器将 Bean 配置信息转换得到 Document 对象但是这些 Document 对象并没有按照 Spring 的 Bean 规则进行解析。这一步是载入的过程
其次在完成通用的 XML 解析之后按照 Spring Bean 的定义规则对 Document 对象进行解析其解 析 过 程 是 在 接 口 BeanDefinitionDocumentReader 的 实 现 类DefaultBeanDefinitionDocumentReader 中实现。11、将配置载入内存
BeanDefinitionDocumentReader 接 口 通 过 registerBeanDefinitions() 方 法 调 用 其 实 现 类DefaultBeanDefinitionDocumentReader 对 Document 对象进行解析解析的代码如下/*** This implementation parses bean definitions according to the spring-beans XSD* (or DTD, historically).* pOpens a DOM Document; then initializes the default settings* specified at the {code beans/} level; then parses the contained bean definitions.*///根据Spring DTD对Bean的定义规则解析Bean定义Document对象Overridepublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {//获得XML描述符this.readerContext readerContext;logger.debug(Loading bean definitions);//获得Document的根元素Element root doc.getDocumentElement();doRegisterBeanDefinitions(root);}/*** Register each bean definition within the given root {code beans/} element.*/protected void doRegisterBeanDefinitions(Element root) {// Any nested beans elements will cause recursion in this method. In// order to propagate and preserve beans default-* attributes correctly,// keep track of the current (parent) delegate, which may be null. Create// the new (child) delegate with a reference to the parent for fallback purposes,// then ultimately reset this.delegate back to its original (parent) reference.// this behavior emulates a stack of delegates without actually necessitating one.//具体的解析过程由BeanDefinitionParserDelegate实现//BeanDefinitionParserDelegate中定义了Spring Bean定义XML文件的各种元素BeanDefinitionParserDelegate parent this.delegate;this.delegate createDelegate(getReaderContext(), root, parent);if (this.delegate.isDefaultNamespace(root)) {String profileSpec root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isInfoEnabled()) {logger.info(Skipped XML bean definition file due to specified profiles [ profileSpec ] not matching: getReaderContext().getResource());}return;}}}//在解析Bean定义之前进行自定义的解析增强解析过程的可扩展性preProcessXml(root);//从Document的根元素开始进行Bean定义的Document对象parseBeanDefinitions(root, this.delegate);//在解析Bean定义之后进行自定义的解析增加解析过程的可扩展性postProcessXml(root);this.delegate parent;}//创建BeanDefinitionParserDelegate用于完成真正的解析过程protected BeanDefinitionParserDelegate createDelegate(XmlReaderContext readerContext, Element root, Nullable BeanDefinitionParserDelegate parentDelegate) {BeanDefinitionParserDelegate delegate new BeanDefinitionParserDelegate(readerContext);//BeanDefinitionParserDelegate初始化Document根元素delegate.initDefaults(root, parentDelegate);return delegate;}/*** Parse the elements at the root level in the document:* import, alias, bean.* param root the DOM root element of the document*///使用Spring的Bean规则从Document的根元素开始进行Bean定义的Document对象protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {//Bean定义的Document对象使用了Spring默认的XML命名空间if (delegate.isDefaultNamespace(root)) {//获取Bean定义的Document对象根元素的所有子节点NodeList nl root.getChildNodes();for (int i 0; i nl.getLength(); i) {Node node nl.item(i);//获得Document节点是XML元素节点if (node instanceof Element) {Element ele (Element) node;//Bean定义的Document的元素节点使用的是Spring默认的XML命名空间if (delegate.isDefaultNamespace(ele)) {//使用Spring的Bean规则解析元素节点parseDefaultElement(ele, delegate);}else {//没有使用Spring默认的XML命名空间则使用用户自定义的解//析规则解析元素节点delegate.parseCustomElement(ele);}}}}else {//Document的根节点没有使用Spring默认的命名空间则使用用户自定义的//解析规则解析Document根节点delegate.parseCustomElement(root);}}//使用Spring的Bean规则解析Document元素节点private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {//如果元素节点是Import导入元素进行导入解析if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {importBeanDefinitionResource(ele);}//如果元素节点是Alias别名元素进行别名解析else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {processAliasRegistration(ele);}//元素节点既不是导入元素也不是别名元素即普通的Bean元素//按照Spring的Bean规则解析元素else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// recursedoRegisterBeanDefinitions(ele);}}/*** Parse an import element and load the bean definitions* from the given resource into the bean factory.*///解析Import导入元素从给定的导入路径加载Bean定义资源到Spring IoC容器中protected void importBeanDefinitionResource(Element ele) {//获取给定的导入元素的location属性String location ele.getAttribute(RESOURCE_ATTRIBUTE);//如果导入元素的location属性值为空则没有导入任何资源直接返回if (!StringUtils.hasText(location)) {getReaderContext().error(Resource location must not be empty, ele);return;}// Resolve system properties: e.g. ${user.dir}//使用系统变量值解析location属性值location getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);SetResource actualResources new LinkedHashSet(4);// Discover whether the location is an absolute or relative URI//标识给定的导入元素的location是否是绝对路径boolean absoluteLocation false;try {absoluteLocation ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();}catch (URISyntaxException ex) {// cannot convert to an URI, considering the location relative// unless it is the well-known Spring prefix classpath*://给定的导入元素的location不是绝对路径}// Absolute or relative?//给定的导入元素的location是绝对路径if (absoluteLocation) {try {//使用资源读入器加载给定路径的Bean定义资源int importCount getReaderContext().getReader().loadBeanDefinitions(location, actualResources);if (logger.isDebugEnabled()) {logger.debug(Imported importCount bean definitions from URL location [ location ]);}}catch (BeanDefinitionStoreException ex) {getReaderContext().error(Failed to import bean definitions from URL location [ location ], ele, ex);}}else {// No URL - considering resource location as relative to the current file.//给定的导入元素的location是相对路径try {int importCount;//将给定导入元素的location封装为相对路径资源Resource relativeResource getReaderContext().getResource().createRelative(location);//封装的相对路径资源存在if (relativeResource.exists()) {//使用资源读入器加载Bean定义资源importCount getReaderContext().getReader().loadBeanDefinitions(relativeResource);actualResources.add(relativeResource);}//封装的相对路径资源不存在else {//获取Spring IOC容器资源读入器的基本路径String baseLocation getReaderContext().getResource().getURL().toString();//根据Spring IOC容器资源读入器的基本路径加载给定导入路径的资源importCount getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);}if (logger.isDebugEnabled()) {logger.debug(Imported importCount bean definitions from relative location [ location ]);}}catch (IOException ex) {getReaderContext().error(Failed to resolve current resource location, ele, ex);}catch (BeanDefinitionStoreException ex) {getReaderContext().error(Failed to import bean definitions from relative location [ location ],ele, ex);}}Resource[] actResArray actualResources.toArray(new Resource[actualResources.size()]);//在解析完Import元素之后发送容器导入其他资源处理完成事件getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));}/*** Process the given alias element, registering the alias with the registry.*///解析Alias别名元素为Bean向Spring IoC容器注册别名protected void processAliasRegistration(Element ele) {//获取Alias别名元素中name的属性值String name ele.getAttribute(NAME_ATTRIBUTE);//获取Alias别名元素中alias的属性值String alias ele.getAttribute(ALIAS_ATTRIBUTE);boolean valid true;//alias别名元素的name属性值为空if (!StringUtils.hasText(name)) {getReaderContext().error(Name must not be empty, ele);valid false;}//alias别名元素的alias属性值为空if (!StringUtils.hasText(alias)) {getReaderContext().error(Alias must not be empty, ele);valid false;}if (valid) {try {//向容器的资源读入器注册别名getReaderContext().getRegistry().registerAlias(name, alias);}catch (Exception ex) {getReaderContext().error(Failed to register alias alias for bean with name name , ele, ex);}//在解析完Alias元素之后发送容器别名处理完成事件getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));}}/*** Process the given bean element, parsing the bean definition* and registering it with the registry.*///解析Bean定义资源Document对象的普通元素protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {BeanDefinitionHolder bdHolder delegate.parseBeanDefinitionElement(ele);// BeanDefinitionHolder是对BeanDefinition的封装即Bean定义的封装类//对Document对象中Bean元素的解析由BeanDefinitionParserDelegate实现// BeanDefinitionHolder bdHolder delegate.parseBeanDefinitionElement(ele);if (bdHolder ! null) {bdHolder delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// Register the final decorated instance.//向Spring IOC容器注册解析得到的Bean定义这是Bean定义向IOC容器注册的入口BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error(Failed to register bean definition with name bdHolder.getBeanName() , ele, ex);}// Send registration event.//在完成向Spring IOC容器注册解析得到的Bean定义之后发送注册事件getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}通过上述 Spring IOC 容器对载入的 Bean 定义 Document 解析可以看出我们使用 Spring 时在Spring 配置文件中可以使用import元素来导入 IOC 容器所需要的其他资源Spring IOC 容器在解析时会首先将指定导入的资源加载进容器中。使用ailas别名时Spring IOC 容器首先将别名元素所定义的别名注册到容器中。
对于既不是import元素又不是alias元素的元素即 Spring 配置文件中普通的bean元素的解析由 BeanDefinitionParserDelegate 类的 parseBeanDefinitionElement()方法来实现。这个解析的过程非常复杂我们在 mini 版本的时候就用 properties 文件代替了。12、载入元素
Bean 配置信息中的import和alias元素解析在 DefaultBeanDefinitionDocumentReader 中已经完成对 Bean 配置信息中使用最多的bean元素交由 BeanDefinitionParserDelegate 来解析其解析实现的源码如下/*** Parses the supplied {code bean} element. May return {code null}* if there were errors during parse. Errors are reported to the* {link org.springframework.beans.factory.parsing.ProblemReporter}.*///解析Bean元素的入口Nullablepublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {return parseBeanDefinitionElement(ele, null);}
/*** Parses the supplied {code bean} element. May return {code null}* if there were errors during parse. Errors are reported to the* {link org.springframework.beans.factory.parsing.ProblemReporter}.*///解析Bean定义资源文件中的Bean元素这个方法中主要处理Bean元素的idname和别名属性Nullablepublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele, Nullable BeanDefinition containingBean) {//获取Bean元素中的id属性值String id ele.getAttribute(ID_ATTRIBUTE);//获取Bean元素中的name属性值String nameAttr ele.getAttribute(NAME_ATTRIBUTE);//获取Bean元素中的alias属性值ListString aliases new ArrayList();//将Bean元素中的所有name属性值存放到别名中if (StringUtils.hasLength(nameAttr)) {String[] nameArr StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}String beanName id;//如果Bean元素中没有配置id属性时将别名中的第一个值赋值给beanNameif (!StringUtils.hasText(beanName) !aliases.isEmpty()) {beanName aliases.remove(0);if (logger.isDebugEnabled()) {logger.debug(No XML id specified - using beanName as bean name and aliases as aliases);}}//检查Bean元素所配置的id或者name的唯一性containingBean标识Bean//元素中是否包含子Bean元素if (containingBean null) {//检查Bean元素所配置的id、name或者别名是否重复checkNameUniqueness(beanName, aliases, ele);}//详细对Bean元素中配置的Bean定义进行解析的地方AbstractBeanDefinition beanDefinition parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition ! null) {if (!StringUtils.hasText(beanName)) {try {if (containingBean ! null) {//如果Bean元素中没有配置id、别名或者name且没有包含子元素//Bean元素为解析的Bean生成一个唯一beanName并注册beanName BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {//如果Bean元素中没有配置id、别名或者name且包含了子元素//Bean元素为解析的Bean使用别名向IOC容器注册beanName this.readerContext.generateBeanName(beanDefinition);// Register an alias for the plain bean class name, if still possible,// if the generator returned the class name plus a suffix.// This is expected for Spring 1.2/2.0 backwards compatibility.//为解析的Bean使用别名注册时为了向后兼容//Spring1.2/2.0给别名添加类名后缀String beanClassName beanDefinition.getBeanClassName();if (beanClassName ! null beanName.startsWith(beanClassName) beanName.length() beanClassName.length() !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}if (logger.isDebugEnabled()) {logger.debug(Neither XML id nor name specified - using generated bean name [ beanName ]);}}catch (Exception ex) {error(ex.getMessage(), ele);return null;}}String[] aliasesArray StringUtils.toStringArray(aliases);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}//当解析出错时返回nullreturn null;}/*** Validate that the specified bean name and aliases have not been used already* within the current level of beans element nesting.*/protected void checkNameUniqueness(String beanName, ListString aliases, Element beanElement) {String foundName null;if (StringUtils.hasText(beanName) this.usedNames.contains(beanName)) {foundName beanName;}if (foundName null) {foundName CollectionUtils.findFirstMatch(this.usedNames, aliases);}if (foundName ! null) {error(Bean name foundName is already used in this beans element, beanElement);}this.usedNames.add(beanName);this.usedNames.addAll(aliases);}/*** Parse the bean definition itself, without regard to name or aliases. May return* {code null} if problems occurred during the parsing of the bean definition.*///详细对Bean元素中配置的Bean定义其他属性进行解析//由于上面的方法中已经对Bean的id、name和别名等属性进行了处理//该方法中主要处理除这三个以外的其他属性数据Nullablepublic AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, Nullable BeanDefinition containingBean) {//记录解析的Beanthis.parseState.push(new BeanEntry(beanName));//这里只读取Bean元素中配置的class名字然后载入到BeanDefinition中去//只是记录配置的class名字不做实例化对象的实例化在依赖注入时完成String className null;//如果Bean元素中配置了parent属性则获取parent属性的值if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent null;if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent ele.getAttribute(PARENT_ATTRIBUTE);}try {//根据Bean元素配置的class名称和parent属性值创建BeanDefinition//为载入Bean定义信息做准备AbstractBeanDefinition bd createBeanDefinition(className, parent);//对当前的Bean元素中配置的一些属性进行解析和设置如配置的单态(singleton)属性等parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);//为Bean元素解析的Bean设置description信息bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));//对Bean元素的meta(元信息)属性解析parseMetaElements(ele, bd);//对Bean元素的lookup-method属性解析parseLookupOverrideSubElements(ele, bd.getMethodOverrides());//对Bean元素的replaced-method属性解析parseReplacedMethodSubElements(ele, bd.getMethodOverrides());//解析Bean元素的构造方法设置parseConstructorArgElements(ele, bd);//解析Bean元素的property设置parsePropertyElements(ele, bd);//解析Bean元素的qualifier属性parseQualifierElements(ele, bd);//为当前解析的Bean设置所需的资源和依赖对象bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));return bd;}catch (ClassNotFoundException ex) {error(Bean class [ className ] not found, ele, ex);}catch (NoClassDefFoundError err) {error(Class that bean class [ className ] depends on not found, ele, err);}catch (Throwable ex) {error(Unexpected failure during bean definition parsing, ele, ex);}finally {this.parseState.pop();}//解析Bean元素出错时返回nullreturn null;}只要使用过 Spring对 Spring 配置文件比较熟悉的人通过对上述源码的分析就会明白我们在 Spring配置文件中Bean元素的中配置的属性就是通过该方法解析和设置到 Bean 中去的。
注意在解析Bean元素过程中没有创建和实例化 Bean 对象只是创建了 Bean 对象的定义类BeanDefinition将Bean元素中的配置信息设置到 BeanDefinition 中作为记录当依赖注入时才使用这些记录信息创建和实例化具体的 Bean 对象。
上面方法中一些对一些配置如元信息(meta)、qualifier 等的解析我们在 Spring 中配置时使用的也不多我们在使用 Spring 的Bean元素时配置最多的是property属性因此我们下面继续分析源码了解 Bean 的属性在解析时是如何设置的。13、载入元素
BeanDefinitionParserDelegate 在解析Bean调用 parsePropertyElements()方法解析Bean元素中的property属性子元素解析源码如下/*** Parse property sub-elements of the given bean element.*///解析Bean元素中的property子元素public void parsePropertyElements(Element beanEle, BeanDefinition bd) {//获取Bean元素中所有的子元素NodeList nl beanEle.getChildNodes();for (int i 0; i nl.getLength(); i) {Node node nl.item(i);//如果子元素是property子元素则调用解析property子元素方法解析if (isCandidateElement(node) nodeNameEquals(node, PROPERTY_ELEMENT)) {parsePropertyElement((Element) node, bd);}}}
/*** Parse a property element.*///解析property元素public void parsePropertyElement(Element ele, BeanDefinition bd) {//获取property元素的名字String propertyName ele.getAttribute(NAME_ATTRIBUTE);if (!StringUtils.hasLength(propertyName)) {error(Tag property must have a name attribute, ele);return;}this.parseState.push(new PropertyEntry(propertyName));try {//如果一个Bean中已经有同名的property存在则不进行解析直接返回。//即如果在同一个Bean中配置同名的property则只有第一个起作用if (bd.getPropertyValues().contains(propertyName)) {error(Multiple property definitions for property propertyName , ele);return;}//解析获取property的值Object val parsePropertyValue(ele, bd, propertyName);//根据property的名字和值创建property实例PropertyValue pv new PropertyValue(propertyName, val);//解析property元素中的属性parseMetaElements(ele, pv);pv.setSource(extractSource(ele));bd.getPropertyValues().addPropertyValue(pv);}finally {this.parseState.pop();}}
/*** Get the value of a property element. May be a list etc.* Also used for constructor arguments, propertyName being null in this case.*///解析获取property值Nullablepublic Object parsePropertyValue(Element ele, BeanDefinition bd, Nullable String propertyName) {String elementName (propertyName ! null) ?property element for property propertyName :constructor-arg element;// Should only have one child element: ref, value, list, etc.//获取property的所有子元素只能是其中一种类型:ref,value,list,etc等NodeList nl ele.getChildNodes();Element subElement null;for (int i 0; i nl.getLength(); i) {Node node nl.item(i);//子元素不是description和meta属性if (node instanceof Element !nodeNameEquals(node, DESCRIPTION_ELEMENT) !nodeNameEquals(node, META_ELEMENT)) {// Child element is what were looking for.if (subElement ! null) {error(elementName must not contain more than one sub-element, ele);}else {//当前property元素包含有子元素subElement (Element) node;}}}//判断property的属性值是ref还是value不允许既是ref又是valueboolean hasRefAttribute ele.hasAttribute(REF_ATTRIBUTE);boolean hasValueAttribute ele.hasAttribute(VALUE_ATTRIBUTE);if ((hasRefAttribute hasValueAttribute) ||((hasRefAttribute || hasValueAttribute) subElement ! null)) {error(elementName is only allowed to contain either ref attribute OR value attribute OR sub-element, ele);}//如果属性是ref创建一个ref的数据对象RuntimeBeanReference//这个对象封装了ref信息if (hasRefAttribute) {String refName ele.getAttribute(REF_ATTRIBUTE);if (!StringUtils.hasText(refName)) {error(elementName contains empty ref attribute, ele);}//一个指向运行时所依赖对象的引用RuntimeBeanReference ref new RuntimeBeanReference(refName);//设置这个ref的数据对象是被当前的property对象所引用ref.setSource(extractSource(ele));return ref;}//如果属性是value创建一个value的数据对象TypedStringValue//这个对象封装了value信息else if (hasValueAttribute) {//一个持有String类型值的对象TypedStringValue valueHolder new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));//设置这个value数据对象是被当前的property对象所引用valueHolder.setSource(extractSource(ele));return valueHolder;}//如果当前property元素还有子元素else if (subElement ! null) {//解析property的子元素return parsePropertySubElement(subElement, bd);}else {// Neither child element nor ref or value attribute found.//propery属性中既不是ref也不是value属性解析出错返回nullerror(elementName must specify a ref or value, ele);return null;}}通过对上述源码的分析我们可以了解在 Spring 配置文件中Bean元素中property元素的相关配置是如何处理的
1、ref 被封装为指向依赖对象一个引用。
2、value 配置都会封装成一个字符串类型的对象。
3、ref 和 value 都通过“解析的数据类型属性值.setSource(extractSource(ele));”方法将属性值/引用与所引用的属性关联起来。
在方法的最后对于property元素的子元素通过 parsePropertySubElement ()方法解析我们继续分析该方法的源码了解其解析过程。14、载入的子元素
在 BeanDefinitionParserDelegate 类中的 parsePropertySubElement()方法对property中的子元素解析源码如下/*** Parse a value, ref or collection sub-element of a property or* constructor-arg element.* param ele subelement of property element; we dont know which yet* param defaultValueType the default type (class name) for any* {code value} tag that might be created*///解析property元素中ref,value或者集合等子元素Nullablepublic Object parsePropertySubElement(Element ele, Nullable BeanDefinition bd, Nullable String defaultValueType) {//如果property没有使用Spring默认的命名空间则使用用户自定义的规则解析内嵌元素if (!isDefaultNamespace(ele)) {return parseNestedCustomElement(ele, bd);}//如果子元素是bean则使用解析Bean元素的方法解析else if (nodeNameEquals(ele, BEAN_ELEMENT)) {BeanDefinitionHolder nestedBd parseBeanDefinitionElement(ele, bd);if (nestedBd ! null) {nestedBd decorateBeanDefinitionIfRequired(ele, nestedBd, bd);}return nestedBd;}//如果子元素是refref中只能有以下3个属性bean、local、parentelse if (nodeNameEquals(ele, REF_ELEMENT)) {// A generic reference to any name of any bean.//可以不再同一个Spring配置文件中具体请参考Spring对ref的配置规则String refName ele.getAttribute(BEAN_REF_ATTRIBUTE);boolean toParent false;if (!StringUtils.hasLength(refName)) {// A reference to the id of another bean in a parent context.//获取property元素中parent属性值引用父级容器中的BeanrefName ele.getAttribute(PARENT_REF_ATTRIBUTE);toParent true;if (!StringUtils.hasLength(refName)) {error(bean or parent is required for ref element, ele);return null;}}if (!StringUtils.hasText(refName)) {error(ref element contains empty target attribute, ele);return null;}//创建ref类型数据指向被引用的对象RuntimeBeanReference ref new RuntimeBeanReference(refName, toParent);//设置引用类型值是被当前子元素所引用ref.setSource(extractSource(ele));return ref;}//如果子元素是idref使用解析ref元素的方法解析else if (nodeNameEquals(ele, IDREF_ELEMENT)) {return parseIdRefElement(ele);}//如果子元素是value使用解析value元素的方法解析else if (nodeNameEquals(ele, VALUE_ELEMENT)) {return parseValueElement(ele, defaultValueType);}//如果子元素是null为property设置一个封装null值的字符串数据else if (nodeNameEquals(ele, NULL_ELEMENT)) {// Its a distinguished null value. Lets wrap it in a TypedStringValue// object in order to preserve the source location.TypedStringValue nullHolder new TypedStringValue(null);nullHolder.setSource(extractSource(ele));return nullHolder;}//如果子元素是array使用解析array集合子元素的方法解析else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {return parseArrayElement(ele, bd);}//如果子元素是list使用解析list集合子元素的方法解析else if (nodeNameEquals(ele, LIST_ELEMENT)) {return parseListElement(ele, bd);}//如果子元素是set使用解析set集合子元素的方法解析else if (nodeNameEquals(ele, SET_ELEMENT)) {return parseSetElement(ele, bd);}//如果子元素是map使用解析map集合子元素的方法解析else if (nodeNameEquals(ele, MAP_ELEMENT)) {return parseMapElement(ele, bd);}//如果子元素是props使用解析props集合子元素的方法解析else if (nodeNameEquals(ele, PROPS_ELEMENT)) {return parsePropsElement(ele);}//既不是ref又不是value也不是集合则子元素配置错误返回nullelse {error(Unknown property sub-element: [ ele.getNodeName() ], ele);return null;}}通过上述源码分析我们明白了在 Spring 配置文件中对property元素中配置的 array、list、set、map、prop 等各种集合子元素的都通过上述方法解析生成对应的数据对象比如 ManagedList、ManagedArray、ManagedSet 等这些 Managed 类是 Spring 对象 BeanDefiniton 的数据封装对集合数据类型的具体解析有各自的解析方法实现解析方法的命名非常规范一目了然我们对list集合元素的解析方法进行源码分析了解其实现过程。15、载入的子元素
在 BeanDefinitionParserDelegate 类中的 parseListElement()方法就是具体实现解析property元素中的list集合子元素源码如下/*** Parse a list element.*///解析list集合子元素public ListObject parseListElement(Element collectionEle, Nullable BeanDefinition bd) {//获取list元素中的value-type属性即获取集合元素的数据类型String defaultElementType collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE);//获取list集合元素中的所有子节点NodeList nl collectionEle.getChildNodes();//Spring中将List封装为ManagedListManagedListObject target new ManagedList(nl.getLength());target.setSource(extractSource(collectionEle));//设置集合目标数据类型target.setElementTypeName(defaultElementType);target.setMergeEnabled(parseMergeAttribute(collectionEle));//具体的list元素解析parseCollectionElements(nl, target, bd, defaultElementType);return target;}//具体解析list集合元素array、list和set都使用该方法解析protected void parseCollectionElements(NodeList elementNodes, CollectionObject target, Nullable BeanDefinition bd, String defaultElementType) {//遍历集合所有节点for (int i 0; i elementNodes.getLength(); i) {Node node elementNodes.item(i);//节点不是description节点if (node instanceof Element !nodeNameEquals(node, DESCRIPTION_ELEMENT)) {//将解析的元素加入集合中递归调用下一个子元素target.add(parsePropertySubElement((Element) node, bd, defaultElementType));}}}经过对 Spring Bean 配置信息转换的 Document 对象中的元素层层解析Spring IOC 现在已经将 XML形式定义的 Bean 配置信息转换为 Spring IOC 所识别的数据结构——BeanDefinition它是 Bean 配置信息中配置的 POJO 对象在 Spring IOC 容器中的映射我们可以通过 AbstractBeanDefinition 为入口看到了 IOC 容器进行索引、查询和操作。
通过 Spring IOC 容器对 Bean 配置资源的解析后IOC 容器大致完成了管理 Bean 对象的准备工作即初始化过程但是最为重要的依赖注入还没有发生现在在 IOC 容器中 BeanDefinition 存储的只是一些静态信息接下来需要向容器注册 Bean 定义信息才能全部完成 IOC 容器的初始化过程。16、分配注册策略
让我们继续跟踪程序的执行顺序接下来我们来分析 DefaultBeanDefinitionDocumentReader 对Bean 定 义转 换的 Document 对 象解 析的 流程 中 在其 parseDefaultElement() 方 法中 完成对Document 对 象 的 解 析 后 得 到 封 装 BeanDefinition 的 BeanDefinitionHold 对 象 然 后 调 用BeanDefinitionReaderUtils 的 registerBeanDefinition() 方 法 向 IOC 容 器 注 册 解 析 的 Bean BeanDefinitionReaderUtils 的注册的源码如下/*** Register the given bean definition with the given bean factory.* param definitionHolder the bean definition including name and aliases* param registry the bean factory to register with* throws BeanDefinitionStoreException if registration failed*///将解析的BeanDefinitionHold注册到容器中public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// Register bean definition under primary name.//获取解析的BeanDefinition的名称String beanName definitionHolder.getBeanName();//向IOC容器注册BeanDefinitionregistry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// Register aliases for bean name, if any.//如果解析的BeanDefinition有别名向容器为其注册别名String[] aliases definitionHolder.getAliases();if (aliases ! null) {for (String alias : aliases) {registry.registerAlias(beanName, alias);}}}当调用 BeanDefinitionReaderUtils 向 IOC 容器注册解析的 BeanDefinition 时真正完成注册功能的是 DefaultListableBeanFactory。17、向容器注册
DefaultListableBeanFactory 中 使 用 一 个 HashMap 的 集 合 对 象 存 放 IOC 容 器 中 注 册 解 析 的BeanDefinition向 IOC 容器注册的主要源码如下在这里插入代码片
//向IOC容器注册解析的BeanDefinitonOverridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, Bean name must not be empty);Assert.notNull(beanDefinition, BeanDefinition must not be null);//校验解析的BeanDefinitonif (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,Validation of bean definition failed, ex);}}BeanDefinition oldBeanDefinition;oldBeanDefinition this.beanDefinitionMap.get(beanName);if (oldBeanDefinition ! null) {if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,Cannot register bean definition [ beanDefinition ] for bean beanName : There is already [ oldBeanDefinition ] bound.);}else if (oldBeanDefinition.getRole() beanDefinition.getRole()) {// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTUREif (this.logger.isWarnEnabled()) {this.logger.warn(Overriding user-defined bean definition for bean beanName with a framework-generated bean definition: replacing [ oldBeanDefinition ] with [ beanDefinition ]);}}else if (!beanDefinition.equals(oldBeanDefinition)) {if (this.logger.isInfoEnabled()) {this.logger.info(Overriding bean definition for bean beanName with a different definition: replacing [ oldBeanDefinition ] with [ beanDefinition ]);}}else {if (this.logger.isDebugEnabled()) {this.logger.debug(Overriding bean definition for bean beanName with an equivalent definition: replacing [ oldBeanDefinition ] with [ beanDefinition ]);}}this.beanDefinitionMap.put(beanName, beanDefinition);}else {if (hasBeanCreationStarted()) {// Cannot modify startup-time collection elements anymore (for stable iteration)//注册的过程中需要线程同步以保证数据的一致性synchronized (this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);ListString updatedDefinitions new ArrayList(this.beanDefinitionNames.size() 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames updatedDefinitions;if (this.manualSingletonNames.contains(beanName)) {SetString updatedSingletons new LinkedHashSet(this.manualSingletonNames);updatedSingletons.remove(beanName);this.manualSingletonNames updatedSingletons;}}}else {// Still in startup registration phasethis.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);this.manualSingletonNames.remove(beanName);}this.frozenBeanDefinitionNames null;}//检查是否有同名的BeanDefinition已经在IOC容器中注册if (oldBeanDefinition ! null || containsSingleton(beanName)) {//重置所有已经注册过的BeanDefinition的缓存resetBeanDefinition(beanName);}}至此Bean 配置信息中配置的 Bean 被解析过后已经注册到 IOC 容器中被容器管理起来真正完成了 IOC 容器初始化所做的全部工作。现在 IOC 容器中已经建立了整个 Bean 的配置信息这些BeanDefinition 信息已经可以使用并且可以被检索IOC 容器的作用就是对这些注册的 Bean 定义信息进行处理和维护。这些的注册的 Bean 定义信息是 IOC 容器控制反转的基础正是有了这些注册的数据容器才可以进行依赖注入。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/82734.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!