1. Движущие силы
В приложении Spring внедрение одного bean-компонента в другой bean-компонент очень распространено. Однако иногда желательно внедрить компонент в обычный объект. Например, мы можем захотеть получить ссылки на службы из объекта сущности.
К счастью, добиться этого не так сложно, как может показаться. В следующих разделах будет показано, как это сделать с помощью аннотации @Configurable
и AspectJ weaver.
2. Аннотация @Configurable
Эта аннотация позволяет экземплярам декорированного класса содержать ссылки на bean-компоненты Spring.
2.1. Определение и регистрация Spring Bean
Прежде чем рассматривать аннотацию @Configurable
, давайте настроим определение bean-компонента Spring:
@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:
@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. Тестирование
Теперь давайте проверим, что bean- компонент IdService
был успешно внедрен в PersonObject
:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AspectJConfig.class)
public class PersonUnitTest {
@Test
public void givenUnmanagedObjects_whenInjectingIdService_thenIdValueIsCorrectlySet() {
PersonObject personObject = new PersonObject("ForEach");
personObject.generateId();
assertEquals(1, personObject.getId());
assertEquals("ForEach", 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("ForEach");
assertEquals(2, personEntity.getId());
assertEquals("ForEach", personEntity.getName());
}
6. Предостережения
Хотя доступ к компонентам Spring из неуправляемого объекта удобен, часто это не рекомендуется.
Проблема в том, что неуправляемые объекты, включая сущности, обычно являются частью модели предметной области. Эти объекты должны нести данные только для повторного использования в разных службах.
Внедрение bean-компонентов в такие объекты может связать компоненты и объекты вместе, что усложнит поддержку и улучшение приложения.
7. Заключение
В этом руководстве рассматривается процесс внедрения bean-компонента Spring в неуправляемый объект. В нем также упоминается проблема проектирования, связанная с внедрением зависимостей в объекты.
Код реализации можно найти на GitHub .