Spring Framework, part 2 - DmitryGontarenko/usefultricks GitHub Wiki

Использование коллекций для внедрения

Spring позволяет внедрять коллекцию объектов в бин, рассмотрим на примере (стиль XML):
Создадим необходимые поля и экземпляр бина:

public class CollectionInjection {
    private Map<String, Object> map;
    private Properties properties;
    private Set set;
    private List list;

    public static void main(String[] args) {
        GenericXmlApplicationContext context = new GenericXmlApplicationContext();
        context.load("classpath:ch3/annotation/application-context.xml");
        context.refresh();

        CollectionInjection injection = 
                (CollectionInjection) context.getBean("InjectCollection");
        injection.displayInfo(); // метод производит вывод всех значений коллекций на экран
    }
...

Теперь конфигурацию:

 <bean id="adventureBook" class="com.accenture.xmlspring.ch3.xml.AdventureBook"/>
  <bean id="injectCollection" 
        class="com.accenture.xmlspring.ch3.xml.CollectionInjection">
        <property name="map">
            <map>
                <entry key="name">
                    <value>John</value>
                </entry>
                <entry key="age">
                    <value>28</value>
                </entry>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="firstName">Chris</prop>
                <prop key="age">32</prop>
            </props>
        </property>
        <property name="set">
            <set>
                <value>Hello World!</value>
                <ref bean="adventureBook"/>
            </set>
        </property>
        <property name="list">
            <list>
                <value>Hello World!</value>
                <ref bean="adventureBook"/>
            </list>
        </property>
    </bean>

Теперь коллекции содержат все заданные выше значения.

Для внедрения так же могут быть использованы аннотации. Для этого необходимо установить аннотацию @Resource для внедряемых полей:

@Service("InjectCollection")
public class CollectionInjection {
    @Resource(name = "map")
    private Map<String, Object> map;
    @Resource(name = "properties")
    private Properties properties;
    @Resource(name = "set")
    private Set set;
    @Resource(name = "list")
    private List list;
...

Создадим конфигурацию:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/util
            http://www.springframework.org/schema/util/spring-util.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="com.accenture.xmlspring.ch3.annotation"/>

    <util:map id="map">
        <entry key="name">
            <value>John</value>
        </entry>
        <entry key="age">
            <value>28</value>
        </entry>
    </util:map>
    <util:list id="list">
        <value>Hello World!</value>
        <ref bean="adventureBook"/>
    </util:list>
</beans>

Теперь мы имеет такой же результат, как и при внедрении через XML-конфигурацию.

Именование бинов

Каждый бин имеет как минимум одно уникальное имя внутри контейнера. Имена присваиваются по такой схеме: id > name > className.

    <bean id="name1" name="name2 name3,name4;name5" class="java.lang.String"/>
    <alias name="name1" alias="name6"/>

Бин может именоваться по id, множеству name или имени класса (если id и name не указаны), а так же с помощью alias (псевдонимов). К бину, который определен на примере выше, можно обратиться по всем шести именам.

BeanNameAware

Реализация интерфейса BeanNameAware, который имеет единственный метод setBeanName(String) позволяет получить имя своего бина. Spring вызывает метод setBeanName() после завершения конфигурирования бина, но перед методами инициализации или уничтожения.

public class Car implements BeanNameAware {
    private String name;
    
    @Override
    public void setBeanName(String beanName) {
        name = beanName;
    }
...

Lookup method injection

Внедрение через метод поиска - предназначено для использования в ситуацих, когда нужно работать с двумя бинами, имеющими разные жизненные циклы.

Method Replacement

Замена метода - одна из форм внедрения. Использую MR, можно заменить реализацию любого метода бина, без необходимости изменять исходный код этого бина.
Это достигается созданием подкласса для класса бина динамическим образом. Используется библиотека CGLIB и перенаправляется обращение к методу, подлежащему замене, другому бину, который реализует интерфейс MethodReplacement. Давайте рассмотрми на примере.
Создаздим Java-класс (бин), содержащий метод, который будет изменен:

public class ReplacementTarget {
    public String formatMessage(String message) { return "original: " + message; }
}

Что бы заменить метод, реализуем интерфейс MethodReplacer:

public class FormatMethodReplacer implements MethodReplacer {
    @Override
    public Object reimplement(Object arg0, Method method, Object[] args) {
        String message = (String) args[0];
        return "replaced: " + message;
    }
}

Конфигурирование замены метода:

    <bean id="formatMethodReplacement"
          class="com.accenture.xmlspring.ch3.replace.FormatMethodReplacer"/>

    <bean id="replacementTarget"
          class="com.accenture.xmlspring.ch3.replace.ReplacementTarget">
        <replaced-method name="formatMessage" replacer="formatMethodReplacement">
            <arg-type>String</arg-type>
        </replaced-method>
    </bean>

    <bean id="standardTarget"
          class="com.accenture.xmlspring.ch3.replace.ReplacementTarget"/>

Теперь извлечем бины standardTarget и replacementTarget из ApplicationContext, и посмотрим на результат вызова метода formatMessage у обоих бинов:

        ReplacementTarget replacementTarget = (ReplacementTarget) context.getBean("replacementTarget");
        ReplacementTarget standardTarget = (ReplacementTarget) context.getBean("standardTarget");

        System.out.println(replacementTarget.formatMessage("Hello!")); // replaced: Hello!
        System.out.println(standardTarget.formatMessage("Hello!")); // original: Hello!

Автосвязывание бина

Если вы не хотите явно указывать в конфигурационном файле связь бинов между собов, то можно предоставить Spring возможность связывать бины автоматически, что бы включить эту возможность, в атрибуте autowire для бина понадобиться указать режим автосвязывание.
Spring поддерживаем следующие режимы: byName, byType, constructor, default и no (автосвязывание отключено, по умолчанию).

Наследование бинов

Spring позволяет бинам наследовать настройки свойств от другого бина в том же самом контексте. При необходимости значения дочернего бина можно переопределить.
Создаздим Java-класс, который имеет несколько полей:

public class InheritApplication {
    private String name;
    private int age;
    private String city;
...

Настроим конфигурационный файл:

<beans xmlns:p="http://www.springframework.org/schema/p" ... >
    <bean id="inheritParentAbstract"
          class="com.accenture.xmlspring.ch3.inherit.InheritApplication"
          p:city="Oklahoma" abstract="true"/>

    <bean id="inheritParent"
          class="com.accenture.xmlspring.ch3.inherit.InheritApplication"
          parent="inheritParentAbstract" p:name="Chris Winter" p:age="32"/>

    <bean id="inheritChild"
          class="com.accenture.xmlspring.ch3.inherit.InheritApplication"
          parent="inheritParent" p:age="33"/>
</beans>

В бине inheritChild мы переопредлили поле age.
Атрибут abstract дескриптора <bean> делат такой бин недоступным для поиска в ApplicationContext, следовательно обратиться к нему мы не сможем.

Обратимся к созданным бинам и выведем результат на экран:

        InheritApplication parent = (InheritApplication) context.getBean("inheritParent");
        InheritApplication child = (InheritApplication) context.getBean("inheritChild");

        System.out.println("Parent: " + parent); // Parent: name='Chris Winter', age=32, city='Oklahoma
        System.out.println("Child " + child); // Child name='Chris Winter', age=33, city='Oklahoma

Жизненный цикл бина

Ранее было показано, как управлять жизненным циклом бина с помощью методов init и destroy, реализовать которые можно быть через собственный метод + XML-конфигурацию или же через аннотации @PostConstruct и @PreDestroy.
Но управлять ЖЦ можно так же с помощью реализации интерфейсов InitializingBean или DisposableBean.
В интерфейсе InitializingBean определен единственный метод afterPropertiesSet(), который служит той же цели, что и метод init(). Разница лишь в том, что при переопределении этого метода, указывать аргумент init-method в XML-конфигурации или аннотацию в Java-классе не требуется. Spring известно, какой метод инициализировать, поэтому потребность в дополнительной конфигурации отпадает.
К одному и тому же бину можно применять все механизмы управлением ЖЦ. В этом случае порядок распознавания такой: метод, аннотированный @PostConstruct, затем метод InitializingBean.afterPropertiesSet() и, наконец, собственный метод, указанный в XML-конфигурации.
Так же существует интерфейс DisposableBean, который выполняет роль destroy метода. Интерфейс имеет единственный метод destroy() и механизм его работы такой же, как и у интерфейса InitializingBean.

Profiles

Настройка профиля позволяет конфигурировать в Spring только тот ApplicationContext, который определен активным профилем. Рассмотрим на примере.
Представим, что у нас есть два конфигурационных файла - kindergarten-config.xml и hightschool-config.xml, у который в дискрипторе <beans> указан атрибут profile="kindergarten" и profile="hightschool". Эти конфигурации описывают два класса, которые реализуют один и тот же интерфейс.
Загрузим оба наших файла в контекст:

var context = new GenericXmlApplicationContext();
context.load("classpath:ch4/*-config.xml");
context.refresh();

Теперь, что бы вызвать определенную реализацию, нам нужно указать профиль, это можно сделать двумя способами:

  • использовать VR аргумент - -Dspring.profiles.active="hightschool"
  • после создания объекта контекста вызвать метод - context.getEnvironment().setActiveProfiles("hightschool");

Так же можно зарегистрировать классы, которые должны быть доступны посредством профилей, проставив над ними аннотацию @Profile(String)

⚠️ **GitHub.com Fallback** ⚠️