Spring创建 BeanFactory 的方式

按照Bean的配置方式手动创建可以分为两种:

  • 使用XMl配置的Bean

    这种方式使用xml配置文件配置Bean的信息并且设置扫描的路径,扫描到的包可以使用注解进行配置Bean信息,一般来说手动创建BeanFactory容器的实现类为ClassPathXmlApplicationContextSystemFileXmlApplicationContext,设置xml的路径即可创建出IOC容器。

    例如:

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
    User user = context.getBean(User.class);
  • 使用注解配置的Bean

    这种方式不使用xml配置文件,全部基于注解方式配置Bean的信息,比如使用@Component@Configuration进行Bean的配置,实现类为AnnotationConfigApplicationContext 设置扫描的包,然后调用refresh方法进行IOC容器的创建。

    例如:

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.scan("com.redwinter.test");
    context.refresh();

但是一般来说开发中都是使用web容器进行IOC容器的创建的,比如tomcat容器、jetty容器、undertow容器、netty容器,在Spring中有一个BeanFactory的实现类:GenericApplicationContext,他的子类有一个叫GenericWebApplicationContext,在Spring Boot中,就是通过实现这个类完成Web容器的创建+IOC容器的创建的。在Spring Boot中有个类叫ServletWebServerApplicationContext就是继承了GenericWebApplicationContext这个类,然后ServletWebServerApplicationContext中有个属性叫webServer,这个是一个接口,这个接口对应的实现就是Web容器的实现:

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
// web 容器,实现类有TomcatWebServer、JettyWebServer、NettyWebServer、UndertowWebServer
private volatile WebServer webServer;
// .... 去掉其他代码
}

本文介绍使用XML配置文件手动创建IOC容器的方式

Spring 使用Xml启动IOC容器

根据上一篇文章 https://www.cnblogs.com/redwinter/p/16151489.htmlSpring Bean IOC 的创建流程种的第一个方法AbstractApplicationContext#prepareRefresh前戏准备工作继续解读AbstractApplicationContext#refresh方法中的第二方法 AbstractApplicationContext#obtainFreshBeanFactory获取BeanFactory,这个方法会创建一个DefaultListableBeanFactory 默认的可列出Bean的工厂。

AbstractApplicationContext#obtainFreshBeanFactory中主要是刷新BeanFactory,源码如下:

@Override
protected final void refreshBeanFactory() throws BeansException {
// 如果有BeanFactory 就销毁掉并关闭
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 直接new一个BeanFactory 实现出来 DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 根据上一步创建BeanFactory创建的Id进行获取
beanFactory.setSerializationId(getId());
// 定制化BanFactory ,比如设置allowBeanDefinitionOverriding 和allowCircularReferences 的属性
customizeBeanFactory(beanFactory);
// 加载BeanDefinitions 从xml 和注解定义的Bean
// 从configLocations -> String[] -> String -> Resource[] -> Resource -> InputStream -> Document -> 解析成一个一个的BeanDefinition 对象
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
  • 首先判断是否已经有BeanFactory了,如果有就销毁掉并且关闭工厂
  • 直接创建一个BeanFactory,默认就是使用new DefaultListableBeanFactory,不过在创建的过程中可能会默认初始化一些属性,比如:allowBeanDefinitionOverridingallowCircularReferences 允许Bean覆盖和解决循环依赖的问题,还有就是BeanFactory的序列化id等属性。
  • 设置序列化id
  • 定制BeanFactory,这里是一个扩展点,你可以对BeanFactory进行定制
  • 加载BeanDefinition,这里从XML配置文件中去加载,这里面的逻辑非常的复杂繁琐
  • 将创建的BeanFactory设置出去

定制个性化的BeanFactory

customizeBeanFactory(beanFactory);这个方法中,spring设置了两个属性,一个是设置是否可以覆盖Bean,一个是否允许循环依赖,源码如下:

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 可以定制设置是否允许Bean覆盖
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 可以定制设置是否允许循环依赖
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}

spring提供了这个扩展点,那么我们就可以定制BeanFactory,比如我们新建一个类继承ClassPathXmlApplicationContext,然后重写customizeBeanFactory这个方法:

/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext { public MyClassPathXmlApplicationContext(String... configLocation) throws BeansException {
super(configLocation);
} @Override
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 扩展点 设置不去处理循环依赖或者beanDefinition覆盖
super.setAllowBeanDefinitionOverriding(true);
// 设置不允许循环依赖
super.setAllowCircularReferences(false);
// 调用父类的方法
super.customizeBeanFactory(beanFactory);
} }

创建两个类,并且设置为循环依赖:

/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
@Service
public class PersonService { @Autowired
private UserService userService; public void test() {
System.out.println(userService);
}
} /**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
@Service
public class UserService {
@Autowired
private PersonService personService; public void test(){
System.out.println(personService);
}
}

创建之后然后使用自定义的MyClassPathXmlApplicationContext类进行启动:

/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class BeanCreate { @Test
public void classPathXml() {
// ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
ClassPathXmlApplicationContext context = new MyClassPathXmlApplicationContext("classpath:spring-test.xml");
UserService userService = context.getBean(UserService.class);
userService.test();
}
}

启动之后发现报错了:

四月 19, 2022 1:26:55 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'personService': Unsatisfied dependency expressed through field 'userService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'personService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'personService': Requested bean is currently in creation: Is there an unresolvable circular reference?

如果设置为true,那么启动不会报错了并且输出了:

com.redwinter.test.service.PersonService@6fc6f14e

BeanDefinition 的加载

在刷新BeanFactory的方法中,有个方法叫loadBeanDefinitions,这个方法就是进行BeanDefinition的加载的,他的大致流程是这样的:

BeanDefinition加载的过程中,有个关键点可以让我们自定义标签进行BeanDefinition的加载和解析,在设置解析器的时候,Spring是这样设置解析器的:

public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
// 创建dtd解析器
this.dtdResolver = new BeansDtdResolver();
// 创建schema 解析器
// 在Debug的时候,这里会调用toString方法,然后去调用getSchemaMappings 方法,将schemaMappings 设置属性进去
this.schemaResolver = new PluggableSchemaResolver(classLoader);
}

Spring中一般解析XML文件的时候都是从网上下载对应的标签解析,比如Spring配置文件中的https://www.springframework.org/schema/beans/spring-beans-3.1.xsd ,但是一般来说都是不需要进行下载的,Spring提供了本地文件的xsd文件,这些xsd文件就配置在META-INF/spring.shames文件中进行配置,由于文件中内容比较多我就不复制出来了。

Spring进行xml解析之前会创建一个namespace的处理器的解析器:

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
// 创建默认的namespace处理器解析器,加载spring.handlers中配置的处理器
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}

这里创建的namespace处理器就是放在META-INF/spring.handlers文件中,比如util标签、context标签的都是在这个文件中配置的处理器,对于util标签的namespace处理器如下:

public class UtilNamespaceHandler extends NamespaceHandlerSupport {

	private static final String SCOPE_ATTRIBUTE = "scope";

	@Override
public void init() {
// 注册constant标签的解析器
registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
// 注册property-path标签的解析器
registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
// 注册list标签的解析器
registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
// 注册set标签的解析器
registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
// 注册map标签的解析器
registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
// 注册properties标签的解析器
registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
}
// ....省略其他代码
}

这些处理器加载完之后就会进行BeanDefinition的解析:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
// 如果节点是默认的命名空间则使用默认的解析
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
// 定制的namespace标签
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
} 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节点
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 解析beans节点
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}

解析完之后就会调用注册,将解析到的BeanDefinition放在beanDefinitionMapbeanDefinitionNames集合中,最终完成了BeanDefinition的加载过程。

现在开发基本都是使用Spring Boot,是全注解方式,这种BeanDefinition的加载实际上就是指定了一个包的扫描,然后扫描这些包下标记了@Configuration、@Component、@Service、@Controller等注解的类。感兴趣的可以去看下AnnotationConfigApplicationContext这个类是如何扫描的。

这就是Spring BeanFactory的创建过程,并且包括了BeanDefinition的加载过程,接下来我们进行自定义标签,让spring进行解析。

Spring 源码(3)Spring BeanFactory 是怎么创建的?的更多相关文章

  1. Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md

    写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...

  2. Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签

    写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...

  3. Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作

    写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...

  4. Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件

    写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设 ...

  5. Spring源码学习之BeanFactory体系结构

    一.BeanFactory BeanFactory是Spring IOC容器的鼻祖,是IOC容器的基础接口,所有的容器都是从它这里继承实现而来.可见其地位.BeanFactory提供了最基本的IOC容 ...

  6. Spring源码阅读-spring启动

    web.xml web.xml中的spring容器配置 <listener> <listener-class>org.springframework.web.context.C ...

  7. Spring源码分析(十八)创建bean

    本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.创建bean的实例 1. autowireConstructor 2 ...

  8. Spring源码解读Spring IOC原理

    一.什么是Ioc/DI? IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等. 先从我们自己设计这样一个视角来考虑: 所谓控制反转,就是把原先我们代码里面需要实现的对象创建.依赖的代码,反 ...

  9. 初探Spring源码之Spring Bean的生命周期

    写在前面的话: 学无止境,写博客纯粹是一种乐趣而已,把自己理解的东西分享出去,不意味全是对的,欢迎指正! Spring 容器初始化过程做了什么? AnnotationConfigApplication ...

  10. Spring源码:Spring IoC容器加载过程(1)

    Spring源码版本:4.3.23.RELEASE 一.加载过程概览 Spring容器加载过程可以在org.springframework.context.support.AbstractApplic ...

随机推荐

  1. Mybatis入门实例解析

    写在前面:本文全程根据Mybatis官网进行入门讲解.毫无疑问,官方文档是学习这门技术最权威的资料,与此同时我们也知道官方文档对待入门小白基本上不太友好,没有入门demo.开篇就是小白们不懂的内容.有 ...

  2. Struts2里面有什么隐式对象?

    Struts 2.1 的隐式对象 (这些隐式对象都是Map类型) parameters 用于访问请求参数 request 用于访问HttpServletRequest的属性 session 用于访问H ...

  3. sleep()和wait()的区别?notify()和notifyAll()的区别?start()和run()的区别?

    sleep()和wait()的区别? 这两个方法来自不同的类分别是Thread和Object sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法.wait,not ...

  4. 转:怎样理解OOP?OOP又是什么?

    本文转载至:https://blog.csdn.net/q34323201/article/details/80198271. OOP面向对象编程.OOP思想中很重要的有五点,类,对象,还有面向对象的 ...

  5. Java 中,Maven 和 ANT 有什么区别?

    虽然两者都是构建工具,都用于创建 Java 应用,但是 Maven 做的事情更多, 在基于"约定优于配置"的概念下,提供标准的 Java 项目结构,同时能为应用自 动管理依赖(应用 ...

  6. sleep 方法和 wait 方法有什么区别?

    这个问题常问,sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点 在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器, wait 方法会放弃这个对象的 ...

  7. 什么是HTML 5?

    HTML 5是HTML的新标准,其主要目标是无需任何额外的插件如Flash.Silverlight等,就可以传输所有内容.它囊括了动画.视频.丰富的图形用户界面等. HTML5是由万维网联盟(W3C) ...

  8. AOP——基于AspectJ的注解来实现AOP操作

    1.使用注解方式实现AOP操作 第一步:创建对象 <!-- 创建对象 --> <bean id="book" class="com.bjxb.aop.B ...

  9. ACM - 图论 - P3385 负环

    P3385 负环 题目描述 给定一个 \(n\) 个点的有向图,请求出图中是否存在从顶点 \(1\) 出发能到达的负环. 负环的定义是:一条边权之和为负数的回路. 输入格式 本题单测试点有多组测试数据 ...

  10. 提交Form表单,submit之前做js判断处理

    效果: 在点击提交按钮时,首先进行js判断, 如果不符合条件,则alert出提示信息,并return false. 主要点就在于给form表单添加一个onsubmit事件. 在onsubmit事件中定 ...