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.Процесс выполнения функции приведенного выше кода не что иное, как это:
- Прочтите файл конфигурации Spring root.xml;
- Найдите конфигурацию соответствующего класса в соответствии с конфигурацией bean-компонента в root.xml и создайте ее экземпляр;
- Вызовите созданный объект для вывода результата.
Давайте посмотрим на исходный код 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, выполнив следующие три шага:
- Используя метод, унаследованный от AbstractBeanDefinitionReader, используйте ResourLoader для преобразования пути к файлу ресурсов (root.xml) в соответствующий файл ресурсов;
- Преобразование файлов ресурсов с помощью DocumentLoader и преобразование файлов ресурсов в файлы Ducument;
- Документ анализируется с помощью класса DefaultBeanDefinitionDocumentReader, и, наконец, анализируется проанализированный элемент.
Поняв вышеизложенные основы, давайте подробно проанализируем код в следующем начальном примере:
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));
Давайте взглянем на приведенную ниже диаграмму последовательности инициализации XmlBeanFactory, чтобы узнать больше о выполнении этого кода.Здесь видно, что тестовый класс 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 в основном объясняется здесь.