«1. Движущие силы

В приложении Spring внедрение одного bean-компонента в другой bean-компонент очень распространено. Однако иногда желательно внедрить компонент в обычный объект. Например, мы можем захотеть получить ссылки на службы из объекта сущности.

К счастью, добиться этого не так сложно, как может показаться. В следующих разделах будет показано, как это сделать с помощью аннотации @Configurable и AspectJ weaver.

2. Аннотация @Configurable

Эта аннотация позволяет экземплярам декорированного класса содержать ссылки на бины Spring.

2.1. Определение и регистрация Spring Bean

Перед рассмотрением аннотации @Configurable давайте настроим определение Spring bean:

@Service
public class IdService {
    private static int count;

    int generateId() {
        return ++count;
    }
}

Этот класс украшен аннотацией @Service; следовательно, его можно зарегистрировать в контексте Spring с помощью сканирования компонентов.

Вот простой класс конфигурации, обеспечивающий этот механизм:

@ComponentScan
public class AspectJConfig {
}

2.2. Использование @Configurable

В простейшей форме мы можем использовать @Configurable без какого-либо элемента:

@Configurable
public class PersonObject {
    private int id;
    private String name;

    public PersonObject(String name) {
        this.name = name;
    }

    // getters and other code shown in the next subsection
}

В этом случае аннотация @Configurable помечает класс PersonObject как подходящий для конфигурации, управляемой Spring.

2.3. Внедрение Spring Bean в неуправляемый объект

Мы можем внедрить IdService в PersonObject так же, как и в любой Spring bean:

@Configurable
public class PersonObject {
    @Autowired
    private IdService idService;

    // fields, constructor and getters - shown in the previous subsection

    void generateId() {
        this.id = idService.generateId();
    }
}

Однако аннотация полезна только в том случае, если она распознана и обработана обработчиком. Здесь в игру вступает AspectJ weaver. В частности, AnnotationBeanConfigurerAspect будет действовать при наличии @Configurable и выполнять необходимую обработку.

3. Включение AspectJ Weaving

3.1. Объявление плагина

Чтобы включить плетение AspectJ, нам сначала нужен плагин AspectJ Maven:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.11</version>
    <!-- configuration and executions -->
</plugin>

И он требует дополнительной настройки:

<configuration>
    <complianceLevel>1.8</complianceLevel>
    <Xlint>ignore</Xlint>
    <aspectLibraries>
        <aspectLibrary>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </aspectLibrary>
    </aspectLibraries>
</configuration>

Первый обязательный элемент — ComplianceLevel. Значение 1,8 устанавливает исходную и целевую версии JDK на 1,8. Если не указать явно, исходная версия будет 1.3, а целевая — 1.1. Эти значения явно устарели и недостаточны для современного Java-приложения.

Чтобы внедрить bean-компонент в неуправляемый объект, мы должны полагаться на класс AnnotationBeanConfigurerAspect, представленный в файле spring-aspects.jar. Поскольку это предварительно скомпилированный аспект, нам нужно будет добавить содержащий его артефакт в конфигурацию плагина.

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

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>

Мы можем найти последнюю версию spring-aspects на Maven Central.

3.2. Выполнение плагина

Чтобы указать плагину объединить все соответствующие классы, нам нужна следующая конфигурация выполнения:

<executions>
    <execution>
        <goals>
            <goal>compile</goal>
        </goals>
    </execution>
</executions>

Обратите внимание, что цель компиляции плагина по умолчанию привязана к фазе жизненного цикла компиляции.

3.2. Конфигурация компонента

Последним шагом для включения плетения AspectJ является добавление @EnableSpringConfigured в класс конфигурации:

@ComponentScan
@EnableSpringConfigured
public class AspectJConfig {
}

Дополнительная аннотация настраивает AnnotationBeanConfigurerAspect, который, в свою очередь, регистрирует экземпляры PersonObject в контейнере Spring IoC.

4. Тестирование

Теперь давайте проверим, что компонент IdService был успешно внедрен в PersonObject:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AspectJConfig.class)
public class PersonUnitTest {
    @Test
    public void givenUnmanagedObjects_whenInjectingIdService_thenIdValueIsCorrectlySet() {
        PersonObject personObject = new PersonObject("Baeldung");
        personObject.generateId();
        assertEquals(1, personObject.getId());
        assertEquals("Baeldung", personObject.getName());
    }
}

5. Внедрение компонента в объект JPA

С точки зрения контейнера Spring , сущность есть не что иное, как обычный объект. Таким образом, нет ничего особенного во внедрении bean-компонента Spring в сущность JPA.

Однако, поскольку внедрение в объекты JPA является типичным вариантом использования, давайте рассмотрим его более подробно.

5.1. Класс сущностей

Начнем со скелета класса сущностей:

@Entity
@Configurable(preConstruction = true)
public class PersonEntity {
    @Id
    private int id;
    private String name;

    public PersonEntity() {
    }

    // other code - shown in the next subsection
}

Обратите внимание на элемент preConstruction в аннотации @Configurable: он позволяет внедрить зависимость в объект до того, как он будет полностью построен.

5.2. Внедрение службы

Теперь мы можем внедрить IdService в PersonEntity, аналогично тому, что мы сделали с PersonObject:

// annotations
public class PersonEntity {
    @Autowired
    @Transient
    private IdService idService;

    // fields and no-arg constructor

    public PersonEntity(String name) {
        id = idService.generateId();
        this.name = name;
    }

    // getters
}

Аннотация @Transient используется, чтобы сообщить JPA, что idService — это поле, которое не должно сохраняться.

5.3. Обновление метода тестирования

Наконец, мы можем обновить метод тестирования, чтобы указать, что сервис может быть внедрен в объект:

@Test
public void givenUnmanagedObjects_whenInjectingIdService_thenIdValueIsCorrectlySet() {
    // existing statements

    PersonEntity personEntity = new PersonEntity("Baeldung");
    assertEquals(2, personEntity.getId());
    assertEquals("Baeldung", personEntity.getName());
}

6. Предостережения

«Хотя доступ к компонентам Spring из неуправляемого объекта удобен, часто это не рекомендуется.

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

Внедрение bean-компонентов в такие объекты может связать компоненты и объекты вместе, что усложнит поддержку и улучшение приложения.

7. Заключение

В этом руководстве рассматривается процесс внедрения bean-компонента Spring в неуправляемый объект. В нем также упоминается проблема проектирования, связанная с внедрением зависимостей в объекты.

Код реализации можно найти на GitHub.