[Весна] BeanFactory подробно разбирает bean-компоненты

Java Spring
package bean;
public class TestBean {
    private String beanName = "beanName";

	public String getBeanName() {
		return beanName;
	}

	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}
}

Файл конфигурации Spring определяется следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

	<bean id="testBean" class="bean.TestBean">
</beans>

Следующее использует XmlBeanFactory для получения bean-компонента:

public class BeanTest {
	
	private static final java.util.logging.Logger logger = LoggerFactory.getLogger(BeanTest.class);
	
	@Test
	public void getBeanTest() {
		BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));
		TestBean bean = factory.getBean("testBean");
		logger.info(bean.getBeanName);
	}
}

Результатом этого модульного теста является выходной beanName, который является самой простой операцией получения bean-компонентов в Spring. Здесь я использую BeanFactory в качестве контейнера для получения bean-компонентов. В корпоративной разработке редко используется более полный ApplicationContext. Не обсуждая это во-первых, нижеследующее фокусируется на процессе использования BeanFactory для получения bean-компонентов.

Теперь давайте проанализируем приведенный выше тестовый код и посмотрим, что для нас сделал Spring.Процесс выполнения функции приведенного выше кода не что иное, как это:

  1. Прочтите файл конфигурации Spring root.xml;
  2. Найдите конфигурацию соответствующего класса в соответствии с конфигурацией bean-компонента в root.xml и создайте ее экземпляр;
  3. Вызовите созданный объект для вывода результата.

Давайте посмотрим на исходный код XmlBeanFactory:

public class XmlBeanFactory extends DefaultListableBeanFactory {

	  private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

	  public XmlBeanFactory(Resource resource) throws BeansException {
		  this(resource, null);
	  }

	  public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		  super(parentBeanFactory);
		  this.reader.loadBeanDefinitions(resource);
	  }
}

Из приведенного выше видно, что XmlBeanFactory наследует DefaultListableBeanFactory. DefaultListableBeanFactory — это реализация регистрации и загрузки bean-компонентов Spring по умолчанию. Это основная часть всей загрузки bean-компонентов. Разница между XmlBeanFactory и XmlBeanFactory заключается в том, что XmlBeanFactory использует пользовательское средство чтения XML XmlBeanDefinitionReader , который реализует Read с помощью собственного BeanDefinitionReader. Ключ к загрузке bean-компонентов XmlBeanFactory находится в XmlBeanDefinitionReader. Давайте посмотрим на исходный код XmlBeanDefinitionReader (перечислена только его часть):

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

	private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

	private ProblemReporter problemReporter = new FailFastProblemReporter();

	private ReaderEventListener eventListener = new EmptyReaderEventListener();

	private SourceExtractor sourceExtractor = new NullSourceExtractor();

	private NamespaceHandlerResolver namespaceHandlerResolver;

	private DocumentLoader documentLoader = new DefaultDocumentLoader();

	private EntityResolver entityResolver;

	private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
}

XmlBeanDefinitionReader наследуется от AbstractBeanDefinitionReader, ниже приведен исходный код AbstractBeanDefinitionReader (перечислена только часть):

public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {

	protected final Log logger = LogFactory.getLog(getClass());

	private final BeanDefinitionRegistry registry;

	private ResourceLoader resourceLoader;

	private ClassLoader beanClassLoader;

	private Environment environment;

	private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
}

XmlBeanDefinitionReader в основном загружает компоненты в файле конфигурации Spring, выполнив следующие три шага:

  1. Используя метод, унаследованный от AbstractBeanDefinitionReader, используйте ResourLoader для преобразования пути к файлу ресурсов (root.xml) в соответствующий файл ресурсов;
  2. Преобразование файлов ресурсов с помощью DocumentLoader и преобразование файлов ресурсов в файлы Ducument;
  3. Документ анализируется с помощью класса DefaultBeanDefinitionDocumentReader, и, наконец, анализируется проанализированный элемент.

Поняв вышеизложенные основы, давайте подробно проанализируем код в следующем начальном примере:

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));

Давайте взглянем на приведенную ниже диаграмму последовательности инициализации XmlBeanFactory, чтобы узнать больше о выполнении этого кода.xmlBeanFactory创建序列图.pngЗдесь видно, что тестовый класс BeanTest создает объект экземпляра файла ресурсов Resource, передавая файл конфигурации spring конструктору ClassPathResource, а затем создает нужный экземпляр XmlBeanFactory через этот файл ресурсов Resource. Как видно из метода построения в предыдущем исходном коде XmlBeanFactory,

public XmlBeanFactory(Resource resource) throws BeansException {
     this(resource, null);
}

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
     super(parentBeanFactory);
     this.reader.loadBeanDefinitions(resource);
}

this.reader.loadBeanDefinition(resource) — это реальная реализация загрузки ресурсов.Здесь выполняется загрузка данных XmlBeanDefinitionReader в диаграмму последовательности.

Затем следуйте методу this.reader.loadBeanDefinition(resource) (перечислены только ключевые части):

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
	
	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}
}

В методе loadBeanDefinition(resource) используйте класс EncodedResource для кодирования ресурса файла ресурсов, а затем продолжайте передавать метод loadBeanDefinitions и продолжайте следить за исходным кодом метода loadBeanDefinitions(new EncodedResource(resource)) :

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());
	}

    // 通过属性记录已加载的资源
	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
	if (currentResources == null) {
		currentResources = new HashSet<EncodedResource>(4);
		this.resourcesCurrentlyBeingLoaded.set(currentResources);
	}
	if (!currentResources.add(encodedResource)) {
		throw new BeanDefinitionStoreException(
				"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}
	try {
        // 从resource中获取对应的InputStream,用于下面构造InputSource
		InputStream inputStream = encodedResource.getResource().getInputStream();
		try {
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
            // 调用doLoadBeanDefinitions方法
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
		finally {
			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();
		}
	}
}

Продолжайте следить за методом doLoadBeanDefinitions(inputSource, encodedResource.getResource()), который является основным методом всего процесса загрузки компонента, в котором выполняется загрузка компонента.

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {
	try {
		Document doc = doLoadDocument(inputSource, resource);
		return registerBeanDefinitions(doc, resource);
	}
    /* 省略一堆catch */
}

Следите за исходным кодом doLoadDocument(inputSource, resource):

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
	return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
			getValidationModeForResource(resource), isNamespaceAware());
}

В методе doLoadDocument(inputSource, resource) упомянутый выше documentLoader используется для загрузки документа. Здесь DocumentLoader — это интерфейс. На самом деле вызывается метод loadDocument его класса реализации DefaultDocumentLoader, и следуйте исходному коду:

public class DefaultDocumentLoader implements DocumentLoader {

	@Override
	public 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);
		return builder.parse(inputSource);
	}
}

Из исходного кода видно, что сначала создается DocumentBuilderFactory, затем с ним создается DocumentBuilder, а затем анализируется inputSource для возврата объекта Document. После получения объекта Document вы можете подготовиться к регистрации информации о Bean.

После получения объекта Document в приведенном выше методе doLoadBeanDefinitions(inputSource, encodedResource.getResource()) реализуется следующий метод registerBeanDefinitions(doc, resource), см. исходный код:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	documentReader.setEnvironment(getEnvironment());
    // 还没注册bean前的BeanDefinition加载个数
	int countBefore = getRegistry().getBeanDefinitionCount();
    // 加载注册bean
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 本次加载注册的BeanDefinition个数
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

Документ здесь загружается и конвертируется методом loadDocument выше.Из вышеизложенного видно, что основная работа выполняется методом registerBeanDefinitions() BeanDefinitionDocumentReader, где BeanDefinitionDocumentReader — это интерфейс, а функция зарегистрированного компонента реализована в этом метод класса реализации по умолчанию DefaultBeanDefinitionDocumentReader. Следуйте его исходному коду:

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	this.readerContext = readerContext;
	logger.debug("Loading bean definitions");
	Element root = doc.getDocumentElement();
	doRegisterBeanDefinitions(root);
}

Здесь, после получения объекта Element с помощью doc.getDocumentElement() и передачи его методу doRegisterBeanDefinitions(), анализ XML-документа фактически выполняется.

protected void doRegisterBeanDefinitions(Element root) {
	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)) {
				return;
			}
		}
	}

	preProcessXml(root);
	parseBeanDefinitions(root, this.delegate);
	postProcessXml(root);

	this.delegate = parent;
}

Процесс обработки здесь очень ясен: сначала обрабатывается профиль, затем документ анализируется с помощью метода parseBeanDefinitions(), а затем исходный код метода parseBeanDefinitions():

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;
                // 下面对bean进行处理
				if (delegate.isDefaultNamespace(ele)) {
					parseDefaultElement(ele, delegate);
				}
				else {
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}

Элементы parseDefaultElement(ele, delegate) и delegate.parseCustomElement(ele) в приведенном выше блоке операторов if-else используются для анализа пространства имен по умолчанию и пользовательского пространства имен в файле конфигурации Spring. В XML-конфигурации Spring объявление bean-компонента по умолчанию определено выше:

<bean id="testBean" class="bean.TestBean">

Объявления пользовательских компонентов, такие как:

<tx:annotation-driven />

Весь процесс загрузки bean-компонентов XmlBeanFactory в основном объясняется здесь.