Spring 源码详解之Spring BeanFactory 是怎么创建的?

Spring创建 BeanFactory 的方式

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

  • 使用 XMl 配置的 Bean
    这种方式使用 xml 配置文件配置 Bean 的信息并且设置扫描的路径,扫描到的包可以使用注解进行配置 Bean 信息,一般来说手动创建 BeanFactory 容器的实现类为 ClassPathXmlApplicationContext 和 SystemFileXmlApplicationContext ,设置 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容器

根据上一篇文章 Spring 源码(2)Spring IOC 容器 前戏准备工作 - 玲丶蹊 - 博客园 Spring 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 ,不过在创建的过程中可能会默认初始化一些属性,比如: allowBeanDefinitionOverriding 和 allowCircularReferences 允许 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 放在 beanDefinitionMap 和 beanDefinitionNames 集合中,最终完成了 BeanDefinition 的加载过程。

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

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值