Статьи

Spring Static Application Context

Вступление

У меня был интересный разговор на днях о пользовательских предметно-ориентированных языках, и мы случайно поговорили о функции Spring, которую я использовал ранее, но, кажется, не очень широко известен: статический контекст приложения. Этот пост иллюстрирует основной пример, который я написал, который представляет статический контекст приложения и показывает, как он может быть полезен. Это также интересная тема, поскольку она показывает некоторые хорошо спроектированные внутренние компоненты среды Spring.

Большинство применений Spring начинаются с XML или аннотаций и заканчиваются экземпляром контекста приложения. За кулисами Spring усердно работал над созданием экземпляров объектов, внедрением свойств, вызовом слушателей с учетом контекста и т. В Spring есть набор классов, помогающих этому процессу, так как Spring должен хранить все данные конфигурации о bean-компонентах до того, как будут созданы экземпляры bean-компонентов. (Это потому, что компоненты могут быть определены в любом порядке, и Spring не имеет исчерпывающего набора зависимостей, пока не будут определены все компоненты.)

Spring Static Application Context

Spring предлагает класс под названием,  StaticApplicationContext который предоставляет программный доступ из Java ко всему процессу настройки и регистрации. Это означает, что мы можем определить весь контекст приложения из чистого кода Java без использования аннотаций XML или Java или каких-либо других приемов. Javadoc для  StaticApplicationContext это  здесь , но пример приходит.

Почему мы можем использовать это? Как говорит Javadoc, это в основном полезно для тестирования. Spring использует его для собственного тестирования, но я нашел его полезным для тестирования приложений, которые используют Spring или другие инфраструктуры управления зависимостями. Часто для модульного тестирования мы хотим внедрить в класс объекты, отличные от используемых в производстве (например, фиктивные объекты или объекты, которые имитируют удаленный вызов, базу данных или обмен сообщениями). Конечно, мы можем просто сохранить отдельный файл конфигурации Spring XML для тестирования, но очень приятно иметь всю нашу конфигурацию прямо там, в классе модульного тестирования Java, поскольку она упрощает поддержку.

пример

Я добавил пример в мой   репозиторий intro-to-java на GitHub. Я создал StaticContext класс, который предоставляет базовый язык Java (DSL) для бинов Spring. Это просто для удобства использования из модульного теста. DSL включает в себя только самые основные возможности Spring: зарегистрировать bean-компонент, задать свойства и зависимости проводов.

package org.anvard.introtojava.spring;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.StaticApplicationContext;

public class StaticContext {

    public class BeanContext {
        private String name;
        private Class<?> beanClass;
        private ConstructorArgumentValues args;
        private MutablePropertyValues props;
        
        private BeanContext(String name, Class<?> beanClass) {
            this.name = name;
            this.beanClass = beanClass;
            this.args = new ConstructorArgumentValues();
            this.props = new MutablePropertyValues();
        }
        public BeanContext arg(Object arg) {
            args.addGenericArgumentValue(arg);
            return this;
        }
        public BeanContext arg(int index, Object arg) {
            args.addIndexedArgumentValue(index, arg);
            return this;
        }
        public BeanContext prop(String name, Object value) {
            props.add(name, value);
            return this;
        }
        public BeanContext ref(String name, String beanRef) {
            props.add(name, new RuntimeBeanReference(beanRef));
            return this;
        }
        public void build() {
            RootBeanDefinition def = 
              new RootBeanDefinition(beanClass, args, props);
            ctx.registerBeanDefinition(name, def);
        }
    }
    
    private StaticApplicationContext ctx;
    
    private StaticContext() {
        this.ctx = new StaticApplicationContext();
    }
    
    public static StaticContext create() {
        return new StaticContext();
    }
    
    public ApplicationContext build() {
        ctx.refresh();
        return ctx;
    }
    
    public BeanContext bean(String name, Class<?> beanClass) {
        return new BeanContext(name, beanClass);
    }
    
}

Этот класс использует несколько классов, которые обычно являются внутренними для Spring:

  • StaticApplicationContext: Содержит определения bean-компонентов и предоставляет обычные методы Java для регистрации bean-компонентов.
  • ConstructorArgumentValues: Умный список аргументов конструктора бина. Может содержать как аргументы проводного типа, так и индексированные конструкторы.
  • MutablePropertyValues: Умный список свойств бина. Может содержать обычные объекты и ссылки на другие компоненты Spring.
  • RuntimeBeanReference: Ссылка по имени на компонент в контексте. Используется для связывания компонентов вместе, потому что это позволяет Spring задерживать разрешение зависимости до момента ее создания.

StaticContext Класс использует шаблон строитель и обеспечивает метод построения цепочки. Это делает для более чистого использования из нашего кода модульного тестирования. Вот самый простой пример:

@Test
public void basicBean() {
    StaticContext sc = create();
    sc.bean("basic", InnerBean.class).prop("prop1", "abc").
      prop("prop2", "def").build();
    ApplicationContext ctx = sc.build();
    assertNotNull(ctx);
    InnerBean bean = (InnerBean) ctx.getBean("basic");
    assertNotNull(bean);
    assertEquals("abc", bean.getProp1());
    assertEquals("def", bean.getProp2());
}

Немного более реалистичный пример, который включает в себя сборку bean-компонентов вместе, не намного сложнее:

@Test
public void innerBean() {
    StaticContext sc = create();
    sc.bean("outer", OuterBean.class).prop("prop1", "xyz").
      ref("inner", "inner").build();
    sc.bean("inner", InnerBean.class).prop("prop1", "ghi").
      prop("prop2", "jkl").build();
    ApplicationContext ctx = sc.build();
    assertNotNull(ctx);
    InnerBean inner = (InnerBean) ctx.getBean("inner");
    assertNotNull(inner);
    assertEquals("ghi", inner.getProp1());
    assertEquals("jkl", inner.getProp2());
    OuterBean outer = (OuterBean) ctx.getBean(OuterBean.class);
    assertNotNull(outer);
    assertEquals("xyz", outer.getProp1());
    assertEquals(inner, outer.getInner());
}

Обратите внимание, что после создания контекста мы можем использовать его как любой другой контекст приложения Spring, включая выборку bean-компонентов по имени или типу. Также обратите внимание, что два контекста, которые мы создали здесь, совершенно разные, что важно для модульного тестирования.

Вывод

Как и мой пост о пользовательском Spring XML, статический контекст приложения — это особая функция, которая не предназначена для обычных пользователей Spring. Но я нашел это удобным при модульном тестировании, и он дает интересный взгляд на то, как работает Spring.